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