1use crate::bundles::BundleId;
2use crate::dungeons::DungeonTemplateId;
3use crate::entity::{Coordinates, Entity, EntityId};
4use crate::game::EntityTemplateId;
5
6use crate::prelude::*;
7
8use strum_macros::{Display, EnumString};
9
10#[derive(
11 Clone,
12 Debug,
13 Default,
14 Serialize,
15 Deserialize,
16 PartialEq,
17 Eq,
18 Tsify,
19 EnumString,
20 Display,
21 JsonSchema,
22)]
23#[tsify(from_wasm_abi, into_wasm_abi)]
24pub enum EntityTeam {
25 #[default]
26 Ally,
27 Enemy,
28}
29
30#[derive(
31 Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, Display, JsonSchema,
32)]
33#[tsify(from_wasm_abi, into_wasm_abi)]
34pub enum EntityType {
35 #[schemars(title = "ПВЕ юнит")]
36 PVEEntity {
37 #[schemars(title = "ID юнита", schema_with = "entity_link_id_schema")]
38 entity_template_id: EntityTemplateId,
39 },
40 #[default]
41 #[schemars(title = "ПВП юнит")]
42 PVPEntity,
43}
44
45#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
46pub struct FightEntity {
47 #[schemars(title = "Тип юнита")]
48 pub entity_type: EntityType,
49 #[schemars(title = "Координаты юнита")]
50 pub position: Coordinates,
51 #[schemars(title = "Нужна ли отрисовка большого хп бара")]
52 pub has_big_hp_bar: bool,
53 #[schemars(title = "Команда юнита")]
54 pub team: EntityTeam,
55}
56
57#[derive(
58 Clone,
59 Debug,
60 Default,
61 Serialize,
62 Deserialize,
63 PartialEq,
64 Eq,
65 Tsify,
66 EnumString,
67 Display,
68 JsonSchema,
69)]
70#[tsify(from_wasm_abi, into_wasm_abi)]
71pub enum FightType {
72 #[default]
73 CampaignFight,
74 CampaignBossFight,
75 ArenaPVP,
76 VassalPVP,
77 SingleFight,
78}
79
80#[declare]
81pub type FightTemplateId = Uuid;
82
83#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Tsify, JsonSchema)]
88pub struct PrepareFightWaves {
89 #[schemars(title = "Относительные силы шаблонов сущностей")]
90 pub entities: Vec<PrepareFightEntityPower>,
91 #[schemars(title = "Волны спавна")]
92 pub waves: Vec<Vec<PrepareFightSpawn>>,
93 #[schemars(title = "Бюджет времени боя (сек), для нормализации HP мобов")]
94 pub time: f64,
95 #[serde(default)]
96 #[schemars(title = "Опорная сила волны (base_power для spawn_wave)")]
97 pub power: f64,
98 #[schemars(
103 title = "Streaming: сколько мобов волны на поле одновременно",
104 description = "Задано — волна выходит потоком: столько мобов на поле, остальные доливаются по мере смертей. Пусто — вся волна выходит сразу (как раньше)."
105 )]
106 pub stream_active_count: Option<u64>,
107
108 #[schemars(
114 title = "Призыв босса: порог HP (доля max HP)",
115 description = "Если задано — ПОСЛЕДНЯЯ волна шаблона становится волной призыва: она выходит, когда HP босса падает ниже этой доли (0.3 = 30%). Босс убит раньше — волна пропускается. Пусто — обычные волны."
116 )]
117 pub summon_wave_at_hp_fraction: Option<f64>,
118
119 #[schemars(
128 title = "Бюджет наград/веса боя (как будто мобов N)",
129 description = "Мигратор пишет исходное число мобов боя. Каждый моб получает wave_share = бюджет / фактическое число мобов, чтобы дропы/заряд ульты пета/контратаки не зависели от числа мобов. Пусто — вес 1.0 (как раньше)."
130 )]
131 pub reward_mob_budget: Option<u64>,
132
133 #[schemars(
139 title = "Множитель урона врагов этого боя",
140 description = "Масштабирует только бюджет урона по игроку (0.8 = враги бьют на 20% слабее). HP мобов, длительность боя и награды не меняются. Пусто — 1.0."
141 )]
142 pub enemy_damage_mult: Option<f64>,
143}
144
145#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Tsify, JsonSchema)]
146pub struct PrepareFightEntityPower {
147 #[serde(default)]
150 #[schemars(schema_with = "option_entity_link_id_schema")]
151 pub entity_id: Option<String>,
152 #[serde(default)]
153 pub power: Option<f64>,
154}
155
156#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Tsify, JsonSchema)]
157pub struct PrepareFightSpawn {
158 #[schemars(schema_with = "entity_link_id_schema")]
159 pub entity_id: String,
160 #[serde(default)]
161 pub delay: Option<f64>,
162 #[serde(default)]
163 pub position: Option<Coordinates>,
164 #[serde(default)]
173 #[schemars(
174 title = "Слоты: кулдаун выхода (сек от старта волны)",
175 description = "Слотовая модель: когда юнит выходит из-за края экрана. Пока сервер на старой модели — поле неактивно."
176 )]
177 pub cooldown_seconds: Option<f64>,
178 #[serde(default)]
179 #[schemars(
180 title = "Слоты: ряд посадки (0..2)",
181 description = "Слотовая модель: ряд, в котором юнит займёт слот. Пока сервер на старой модели — поле неактивно."
182 )]
183 pub row: Option<i64>,
184}
185
186#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Tsify, JsonSchema, i18n::Translate)]
190pub struct FightTemplate {
191 #[schemars(schema_with = "id_schema")]
192 pub id: FightTemplateId,
193
194 #[schemars(title = "Название боя")]
195 #[translate]
196 pub title: i18n::I18nString,
197
198 #[schemars(title = "Мощь боя")]
199 pub power: Option<u64>,
200
201 #[schemars(title = "Все существа в бою")]
204 pub fight_entities: Vec<FightEntity>,
205
206 #[schemars(title = "Максимальная длительность боя в тиках")]
207 pub max_duration_ticks: u64,
208
209 #[schemars(title = "Целевая ширина экрана (в клетках)")]
210 pub target_width_cells: u64,
211
212 #[schemars(title = "Fx на старте боя")]
213 pub starting_fx: String,
215
216 #[schemars(title = "Тип боя")]
217 pub fight_type: FightType,
218
219 #[schemars(title = "Количество волн в бою")]
220 pub waves_amount: i64,
221
222 #[schemars(
223 title = "Волны боя (типизированные)",
224 description = "Типизированные данные волн боя."
225 )]
226 #[serde(default)]
227 pub prepare_fight_waves: Option<PrepareFightWaves>,
228
229 #[schemars(
230 title = "Нативная функция начала боя",
231 description = "Имя нативной функции категории fight_start, выполняющей начало боя.",
232 schema_with = "fight_start_ref_schema"
233 )]
234 #[serde(default)]
235 pub start_behavior: Option<String>,
236
237 #[schemars(title = "Задник", schema_with = "asset_background_schema")]
238 pub background: String,
239
240 #[schemars(
241 title = "Время ожидания перед стартом следующего боя в тиках",
242 description = "Это момент анимации перебежки персонажа между битвами"
243 )]
244 pub start_fight_delay_ticks: Option<u64>,
245
246 #[schemars(
247 title = "Время ожидания перед стартом уже начавшегося боя в случае победы",
248 description = "Это момент, когда враги выбегают на экран"
249 )]
250 pub prepare_fight_win_duration_ticks: Option<u64>,
251
252 #[schemars(
253 title = "Время ожидания перед стартом уже начавшегося боя в случае поражения",
254 description = "Это момент, когда враги выбегают на экран"
255 )]
256 pub prepare_fight_lose_duration_ticks: Option<u64>,
257
258 #[schemars(title = "Время ожидания перед окончанием боя")]
259 pub end_fight_delay_ticks: Option<u64>,
260
261 #[schemars(
262 title = "Бандл награды за победу в бою",
263 schema_with = "option_bundle_id_schema"
264 )]
265 pub bundle_reward_id: Option<BundleId>,
266
267 #[schemars(title = "Останавливать ли бой после победы")]
268 pub stop_on_win: bool,
269
270 #[schemars(title = "Останавливать ли бой после поражения")]
271 pub stop_on_lose: bool,
272
273 #[schemars(title = "Показывать ли VS экран в начале боя")]
274 pub show_vs_screen: bool,
275
276 #[schemars(title = "Показывать ли стейджи")]
277 pub show_stages: Option<bool>,
278
279 #[schemars(
280 title = "Является ли подземельем",
281 description = "Бой подземелья (fight_template_is_dungeon): включает dungeon-таланты в init_fight."
282 )]
283 #[serde(default)]
284 pub is_dungeon: bool,
285
286 #[schemars(
287 title = "Является ли боссфайтом",
288 description = "Босс-бой (fight_template_is_bossfight): включает boss-таланты в init_fight."
289 )]
290 #[serde(default)]
291 pub is_bossfight: bool,
292}
293
294#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
295pub struct ActiveDungeon {
296 pub id: DungeonTemplateId,
297 pub difficulty: i64,
298}
299
300#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
307pub struct PendingWaveSpawn {
308 pub id: uuid::Uuid,
309 pub entity_template_id: EntityTemplateId,
310 pub position: Coordinates,
311 pub attributes: crate::entity::EntityAttributes,
312 pub has_big_hp_bar: bool,
313}
314
315#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
316pub struct ActiveFight {
317 pub id: uuid::Uuid,
318 pub fight_id: FightTemplateId,
319 pub current_wave: i64,
320 pub player_id: EntityId,
321 pub party_player_id: Option<EntityId>,
322 pub entities: Vec<Entity>,
323 pub fight_stopped: bool,
324 pub fight_ended: bool,
325 pub max_duration_ticks: u64,
326 pub dungeon: Option<ActiveDungeon>,
327 pub paused: bool,
328 #[serde(default)]
332 pub pending_wave_spawns: Vec<PendingWaveSpawn>,
333 #[serde(default)]
339 pub summoned_entity_ids: Vec<EntityId>,
340}
341
342impl ActiveFight {
343 pub fn get_player(&self) -> Option<&Entity> {
344 self.entities
345 .iter()
346 .find(|entity| entity.id == self.player_id)
347 }
348
349 pub fn get_player_mut(&mut self) -> Option<&mut Entity> {
350 self.entities
351 .iter_mut()
352 .find(|entity| entity.id == self.player_id)
353 }
354
355 pub fn get_party_player(&self) -> Option<&Entity> {
356 let party_id = self.party_player_id?;
357 self.entities.iter().find(|entity| entity.id == party_id)
358 }
359
360 pub fn get_party_player_mut(&mut self) -> Option<&mut Entity> {
361 let party_id = self.party_player_id?;
362 self.entities
363 .iter_mut()
364 .find(|entity| entity.id == party_id)
365 }
366
367 pub fn get_enemies_amount(&self) -> usize {
368 self.entities
369 .iter()
370 .filter(|entity| entity.team == EntityTeam::Enemy)
371 .count()
372 }
373}