mud/src/mudlib/render/pov.py
Jared Miller 2a546a3171
Fix operator precedence in POV smart conjugation
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.
2026-02-14 01:00:37 -05:00

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)