overlord_event_system/behaviors/combat/
fight_start.rs

1//! Native ports for the `fight_start` category — a fight template's
2//! `start_behavior` (run via `run_event`, returning `Vec<OverlordEvent>`).
3//!
4//! The deployed `start_behavior`s are dominated by a single shape:
5//! `ctx.init_fight(State, "<self template id>")` — covered by
6//! [`init_fight_self`]. A small tail additionally pushes a `static` attribute
7//! increment on the player (and, when present, the party player) — covered by
8//! [`init_fight_self_static_buff`].
9//!
10//! Unlike the combat `event`/`start_cast_ability` categories these scripts do
11//! NOT consume the authoritative RNG (`init_fight` is RNG-free and the shipped
12//! bodies never touch `Random`).
13
14use essences::combat_origin::CombatEventOrigin;
15use essences::fight_breakdown::CombatSource;
16use essences::fighting::ActiveFight;
17
18use crate::behaviors::{BehaviorKind, BehaviorMeta, BehaviorRegistry};
19use crate::event::OverlordEvent;
20use crate::mechanics::content_lookups::ContentLookups;
21use crate::mechanics::fight::{self, NativeSink};
22use crate::state::OverlordState;
23
24/// Inputs available to a `fight_start` native fn — the fight `start_behavior`
25/// scope (`Fight`, `State`) plus the lookups `init_fight` needs. The
26/// fight template id is `fight.fight_id` (the deployed scripts always pass the
27/// template's own `$.id` to `init_fight`).
28pub struct FightStartCtx<'a> {
29    pub fight: &'a ActiveFight,
30    pub state: &'a OverlordState,
31    pub lookups: &'a ContentLookups,
32}
33
34/// Signature of a `fight_start` native fn.
35pub type FightStartFn = fn(&FightStartCtx) -> anyhow::Result<Vec<OverlordEvent>>;
36
37/// Port of `ctx.init_fight(State, "<self template id>")` — the dominant fight
38/// `start_behavior`. Delegates to the already-native [`fight::init_fight`]
39/// with `fight.fight_id` as the template id (identical to passing `$.id`).
40pub fn init_fight_self(ctx: &FightStartCtx) -> anyhow::Result<Vec<OverlordEvent>> {
41    let mut sink = NativeSink::default();
42    fight::init_fight(
43        &mut sink,
44        ctx.lookups,
45        ctx.fight,
46        ctx.state,
47        ctx.fight.fight_id,
48    )
49    .map_err(|e| anyhow::anyhow!("init_fight: {e}"))?;
50    Ok(sink.events)
51}
52
53/// Port of the static-buff variant: the `init_fight` call followed by
54/// `Result.push(OverlordEventEntityIncrAttribute(Fight.player_id, "static", 1))`
55/// and the same for `Fight.party_player_id` when it is set. Event order matches
56/// static).
57pub fn init_fight_self_static_buff(ctx: &FightStartCtx) -> anyhow::Result<Vec<OverlordEvent>> {
58    let mut events = init_fight_self(ctx)?;
59    events.push(OverlordEvent::EntityIncrAttribute {
60        entity_id: ctx.fight.player_id,
61        attribute: "static".to_string(),
62        delta: 1,
63    });
64    if let Some(party_player_id) = ctx.fight.party_player_id {
65        events.push(OverlordEvent::EntityIncrAttribute {
66            entity_id: party_player_id,
67            attribute: "static".to_string(),
68            delta: 1,
69        });
70    }
71    Ok(events)
72}
73
74/// No-op fight start: the equivalent of an empty `start_behavior` (the program
75/// produces no `Result` events). Used by test fight templates whose
76/// `start_behavior` is `""`.
77pub fn noop(_ctx: &FightStartCtx) -> anyhow::Result<Vec<OverlordEvent>> {
78    Ok(vec![])
79}
80
81/// Port of the test fight `start_behavior`
82/// `Result.push(OverlordEventDamage(Fight.entities[0].id, unsigned(5), CustomEventData()))`
83/// — damages the first fight entity by 5. RNG-free; used by
84/// `test_fighting::test_start_fight_script`.
85pub fn damage_first_entity_5(ctx: &FightStartCtx) -> anyhow::Result<Vec<OverlordEvent>> {
86    let Some(entity) = ctx.fight.entities.first() else {
87        return Ok(vec![]);
88    };
89    Ok(vec![OverlordEvent::Damage {
90        by_entity_id: None,
91        entity_id: entity.id,
92        damage: 5,
93        damage_data: crate::event::CustomEventData::default(),
94        origin: CombatEventOrigin::Core,
95        source: CombatSource::Other,
96    }])
97}
98
99/// Port of the test fight `start_behavior`
100/// `Result.push(OverlordEventEntityApplyEffect(Fight.player_id, uuid("39f135d2-...")));`
101/// — applies the spawn-on-death effect to the player. RNG-free; used by
102/// `test_fighting::test_spawn_entity`.
103pub fn apply_spawn_on_death_to_player(ctx: &FightStartCtx) -> anyhow::Result<Vec<OverlordEvent>> {
104    Ok(vec![OverlordEvent::EntityApplyEffect {
105        entity_id: ctx.fight.player_id,
106        effect_id: uuid::Uuid::parse_str("39f135d2-b930-42a7-abfa-f1ec49f3cc00")?,
107        origin: CombatEventOrigin::Core,
108    }])
109}
110
111/// Register this category's native fns.
112pub fn register(registry: &mut BehaviorRegistry) {
113    registry.register_fight_start(
114        BehaviorMeta {
115            name: "noop".to_string(),
116            category: BehaviorKind::FightStart,
117            title: "Пустой start_behavior".to_string(),
118            description: "Эквивалент пустого start_behavior — не создаёт событий.".to_string(),
119        },
120        noop,
121    );
122    registry.register_fight_start(
123        BehaviorMeta {
124            name: "damage_first_entity_5".to_string(),
125            category: BehaviorKind::FightStart,
126            title: "Урон 5 по первой сущности (тест)".to_string(),
127            description: "Damage(Fight.entities[0], 5) — порт test fight start_behavior."
128                .to_string(),
129        },
130        damage_first_entity_5,
131    );
132    registry.register_fight_start(
133        BehaviorMeta {
134            name: "apply_spawn_on_death_to_player".to_string(),
135            category: BehaviorKind::FightStart,
136            title: "Эффект spawn-on-death на игрока (тест)".to_string(),
137            description: "ApplyEffect(Fight.player_id, 39f135d2) — порт test fight start_behavior."
138                .to_string(),
139        },
140        apply_spawn_on_death_to_player,
141    );
142    registry.register_fight_start(
143        BehaviorMeta {
144            name: "init_fight_self".to_string(),
145            category: BehaviorKind::FightStart,
146            title: "Инициализация боя".to_string(),
147            description: "Порт start_behavior `ctx.init_fight(State, <self id>)` — \
148                стартовые события боя через init_fight (без RNG)."
149                .to_string(),
150        },
151        init_fight_self,
152    );
153    registry.register_fight_start(
154        BehaviorMeta {
155            name: "init_fight_self_static_buff".to_string(),
156            category: BehaviorKind::FightStart,
157            title: "Инициализация боя + static-бафф".to_string(),
158            description: "Порт init_fight + push EntityIncrAttribute(player, \"static\", 1) \
159                и того же для party_player_id, если он есть."
160                .to_string(),
161        },
162        init_fight_self_static_buff,
163    );
164}