The {s} conjugation check had incorrect operator precedence that
evaluated the ch/sh suffix check independently of the prev_text
existence check. This could lead to confusing logic flow even
though it didn't crash due to len() handling empty strings safely.
Fixed by wrapping both suffix conditions in parentheses so they're
both guarded by the prev_text truthiness check.
105 lines
3.3 KiB
Python
105 lines
3.3 KiB
Python
"""POV template engine for combat messages."""
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
|
|
def render_pov(
|
|
template: str,
|
|
viewer: Any | None,
|
|
attacker: Any | None,
|
|
defender: Any | None,
|
|
) -> str:
|
|
"""Render a combat message template from a specific viewer's POV.
|
|
|
|
Args:
|
|
template: Message template with {attacker}, {defender}, and
|
|
contextual tags like {s}, {es}, {y|ies}, {his}, {him}
|
|
viewer: Entity viewing the message (determines "You" vs name)
|
|
attacker: Attacking entity
|
|
defender: Defending entity
|
|
|
|
Returns:
|
|
Rendered message with appropriate perspective substitutions
|
|
|
|
Contextual tags apply to the most recently mentioned entity:
|
|
- {s} → "" for 2nd person, "s"/"es" for 3rd (smart conjugation)
|
|
- {es} → "" for 2nd person, "es" for 3rd person
|
|
- {y|ies} → left form for 2nd person, right form for 3rd person
|
|
- {his} → "your" for 2nd person, "his" for 3rd person
|
|
- {him} → "you" for 2nd person, "him" for 3rd person
|
|
"""
|
|
if not template:
|
|
return template
|
|
|
|
# Track whether the last entity mentioned was "You" (2nd person)
|
|
last_was_you = False
|
|
|
|
# Pattern to match all tags
|
|
tag_pattern = re.compile(r"\{(attacker|defender|s|es|his|him|[^}]*\|[^}]*)\}")
|
|
|
|
# Process template character by character, building result
|
|
result = []
|
|
pos = 0
|
|
|
|
for match in tag_pattern.finditer(template):
|
|
# Add text before this tag
|
|
result.append(template[pos : match.start()])
|
|
|
|
tag = match.group(0)
|
|
tag_name = match.group(1)
|
|
|
|
# Entity tags
|
|
if tag_name in ("attacker", "defender"):
|
|
entity = attacker if tag_name == "attacker" else defender
|
|
if entity is viewer:
|
|
last_was_you = True
|
|
result.append("You")
|
|
else:
|
|
last_was_you = False
|
|
result.append(entity.name if entity else "")
|
|
|
|
# Contextual verb conjugation tags
|
|
elif tag == "{s}":
|
|
if last_was_you:
|
|
result.append("")
|
|
else:
|
|
# Check previous character for smart conjugation
|
|
prev_text = "".join(result)
|
|
if prev_text and (
|
|
prev_text[-1:] in "sxz"
|
|
or (len(prev_text) >= 2 and prev_text[-2:] in ("ch", "sh"))
|
|
):
|
|
result.append("es")
|
|
else:
|
|
result.append("s")
|
|
|
|
elif tag == "{es}":
|
|
result.append("" if last_was_you else "es")
|
|
|
|
elif tag == "{his}":
|
|
result.append("your" if last_was_you else "his")
|
|
|
|
elif tag == "{him}":
|
|
# Check if "self" follows to form reflexive pronoun
|
|
remaining = template[match.end() :]
|
|
if remaining.startswith("self"):
|
|
result.append("your" if last_was_you else "him")
|
|
else:
|
|
result.append("you" if last_was_you else "him")
|
|
|
|
elif "|" in tag:
|
|
# Handle {a|b} pattern
|
|
content = tag[1:-1] # Strip { and }
|
|
left, right = content.split("|", 1)
|
|
result.append(left if last_was_you else right)
|
|
|
|
else:
|
|
result.append(tag)
|
|
|
|
pos = match.end()
|
|
|
|
# Add remaining text after last tag
|
|
result.append(template[pos:])
|
|
|
|
return "".join(result)
|