File size: 8,152 Bytes
463f868
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
from typing import Any, Dict, List

from .ability_descriptions import (
    EFFECT_DESCRIPTIONS,
    EFFECT_DESCRIPTIONS_JP,
    TRIGGER_DESCRIPTIONS,
    TRIGGER_DESCRIPTIONS_JP,
)
from .ability_filter import format_filter_attr
from .ability_ir import SemanticAbility, SemanticCondition, SemanticCost, SemanticEffect
from .generated_enums import AbilityCostType, ConditionType, EffectType, TargetType, TriggerType


def build_semantic_form(ability: Any) -> Dict[str, Any]:
    semantic_effects: List[SemanticEffect] = []
    for eff in ability.effects:
        try:
            params_copy = eff.params.copy() if hasattr(eff.params, "copy") else dict(eff.params or {})
        except (AttributeError, TypeError, ValueError):
            params_copy = {}

        semantic_effects.append(
            SemanticEffect(
                effect_type=eff.effect_type.name if hasattr(eff.effect_type, "name") else str(eff.effect_type),
                value=eff.value,
                target=eff.target.name if hasattr(eff.target, "name") else str(eff.target),
                params=params_copy,
                is_optional=eff.is_optional,
            )
        )

    semantic_conditions: List[SemanticCondition] = []
    for cond in ability.conditions:
        try:
            params_upper = {k.upper(): v for k, v in cond.params.items() if isinstance(k, str)}
        except (AttributeError, TypeError):
            params_upper = {}

        comp_str = str(cond.params.get("comparison") or params_upper.get("COMPARISON") or "GE").upper() if cond.params else "GE"
        filter_summary = ""
        if hasattr(cond, "attr") and cond.attr:
            try:
                filter_summary = format_filter_attr(cond.attr)
            except (AttributeError, TypeError, ValueError):
                filter_summary = ""

        semantic_conditions.append(
            SemanticCondition(
                condition_type=cond.type.name if hasattr(cond.type, "name") else str(cond.type),
                value=cond.value if hasattr(cond, "value") else 0,
                comparison=comp_str,
                filter_summary=filter_summary,
                area=str(cond.params.get("area", "")).upper() if cond.params else "",
                is_negated=cond.is_negated if hasattr(cond, "is_negated") else False,
            )
        )

    semantic_costs: List[SemanticCost] = []
    for cost in ability.costs:
        semantic_costs.append(
            SemanticCost(
                cost_type=cost.type.name if hasattr(cost.type, "name") else str(cost.type),
                value=cost.value,
                is_optional=cost.is_optional,
            )
        )

    instructions_summary = ""
    if ability.instructions:
        parts = []
        from .ability import Condition, Cost, Effect

        for instr in ability.instructions:
            if isinstance(instr, Effect):
                parts.append(f"Effect({instr.effect_type.name})")
            elif isinstance(instr, Condition):
                parts.append(f"Condition({instr.type.name})")
            elif isinstance(instr, Cost):
                parts.append(f"Cost({instr.type.name})")
        instructions_summary = " -> ".join(parts)

    semantic_form = SemanticAbility(
        trigger=ability.trigger.name if hasattr(ability.trigger, "name") else str(ability.trigger),
        effects=semantic_effects,
        conditions=semantic_conditions,
        costs=semantic_costs,
        is_once_per_turn=ability.is_once_per_turn,
        description=ability.pseudocode or ability.raw_text,
        instructions_summary=instructions_summary,
    )

    ability.semantic_form = semantic_form.to_dict()
    return ability.semantic_form


def reconstruct_text(ability: Any, lang: str = "en") -> str:
    parts: List[str] = []
    is_jp = lang == "jp"
    effect_descriptions = EFFECT_DESCRIPTIONS_JP if is_jp else EFFECT_DESCRIPTIONS
    trigger_descriptions = TRIGGER_DESCRIPTIONS_JP if is_jp else TRIGGER_DESCRIPTIONS

    trigger_name = getattr(ability.trigger, "name", str(ability.trigger))
    parts.append(trigger_descriptions.get(ability.trigger, f"[{trigger_name}]"))

    for cost in ability.costs:
        if is_jp:
            if cost.type == AbilityCostType.ENERGY:
                parts.append(f"(コスト: エネルギー{cost.value})")
            elif cost.type == AbilityCostType.TAP_SELF:
                parts.append("(コスト: 自分をレスト)")
            elif cost.type == AbilityCostType.DISCARD_HAND:
                parts.append(f"(コスト: 手札を{cost.value}枚捨てる)")
            elif cost.type == AbilityCostType.SACRIFICE_SELF:
                parts.append("(コスト: 自分を退場)")
            else:
                parts.append(f"(コスト: {cost.type.name} {cost.value})")
        else:
            if cost.type == AbilityCostType.ENERGY:
                parts.append(f"(Cost: Pay {cost.value} Energy)")
            elif cost.type == AbilityCostType.TAP_SELF:
                parts.append("(Cost: Rest Self)")
            elif cost.type == AbilityCostType.DISCARD_HAND:
                parts.append(f"(Cost: Discard {cost.value} from hand)")
            elif cost.type == AbilityCostType.SACRIFICE_SELF:
                parts.append("(Cost: Sacrifice Self)")
            else:
                parts.append(f"(Cost: {cost.type.name} {cost.value})")

    for cond in ability.conditions:
        neg = "NOT " if getattr(cond, "is_negated", False) and not is_jp else ""
        cond_desc = f"{neg}{cond.type.name}"
        if cond.params.get("group") is not None:
            cond_desc += f"({cond.params['group']})"
        if cond.params.get("unit") is not None:
            cond_desc += f" ({cond.params['unit']})"
        if cond.params.get("zone"):
            cond_desc += f" (in {cond.params['zone']})" if not is_jp else f" ({cond.params['zone']})"
        if cond.type == ConditionType.HAS_KEYWORD:
            cond_desc += f" ({cond.params.get('keyword', '?')})"
        parts.append(cond_desc)

    for eff in ability.effects:
        template = effect_descriptions.get(eff.effect_type, getattr(eff.effect_type, "name", str(eff.effect_type)))
        context = eff.params.copy()
        context["value"] = eff.value
        try:
            desc = template.format(**context)
        except KeyError:
            desc = template

        if eff.params.get("per_energy"):
            desc += " per Energy" if not is_jp else " / エネルギーごと"
        if eff.params.get("per_member"):
            desc += " per Member" if not is_jp else " / メンバーごと"
        if eff.params.get("per_live"):
            desc += " per Live" if not is_jp else " / ライブごと"
        if eff.target == TargetType.MEMBER_SELECT:
            desc += " (Choose member)" if not is_jp else " (対象を選ぶ)"
        if eff.target in (TargetType.OPPONENT, getattr(TargetType, "OPPONENT_HAND", TargetType.OPPONENT)):
            if ("opponent" not in desc.lower() and not is_jp) or (is_jp and "相手" not in desc):
                desc += " (Opponent)" if not is_jp else " (相手)"
        parts.append(desc)

        if eff.modal_options:
            for index, option in enumerate(eff.modal_options, start=1):
                option_descs = []
                for sub_eff in option:
                    option_template = effect_descriptions.get(sub_eff.effect_type, sub_eff.effect_type.name)
                    option_context = sub_eff.params.copy()
                    option_context["value"] = sub_eff.value
                    try:
                        option_descs.append(option_template.format(**option_context))
                    except KeyError:
                        option_descs.append(option_template)
                label = f"Option {index}" if not is_jp else f"選択肢{index}"
                parts.append(f"[{label}: {' + '.join(option_descs)}]")

    return " ".join(parts)


__all__ = ["build_semantic_form", "reconstruct_text"]