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)]
190pub struct FightTemplate {
191 #[schemars(schema_with = "id_schema")]
192 pub id: FightTemplateId,
193
194 #[schemars(title = "Название боя")]
195 pub title: i18n::I18nString,
196
197 #[schemars(title = "Мощь боя")]
198 pub power: Option<u64>,
199
200 #[schemars(title = "Все существа в бою")]
203 pub fight_entities: Vec<FightEntity>,
204
205 #[schemars(title = "Максимальная длительность боя в тиках")]
206 pub max_duration_ticks: u64,
207
208 #[schemars(title = "Целевая ширина экрана (в клетках)")]
209 pub target_width_cells: u64,
210
211 #[schemars(title = "Fx на старте боя")]
212 pub starting_fx: String,
214
215 #[schemars(title = "Тип боя")]
216 pub fight_type: FightType,
217
218 #[schemars(title = "Количество волн в бою")]
219 pub waves_amount: i64,
220
221 #[schemars(
222 title = "Волны боя (типизированные)",
223 description = "Типизированные данные волн боя."
224 )]
225 #[serde(default)]
226 pub prepare_fight_waves: Option<PrepareFightWaves>,
227
228 #[schemars(
229 title = "Нативная функция начала боя",
230 description = "Имя нативной функции категории fight_start, выполняющей начало боя.",
231 schema_with = "fight_start_ref_schema"
232 )]
233 #[serde(default)]
234 pub start_behavior: Option<String>,
235
236 #[schemars(title = "Задник", schema_with = "asset_background_schema")]
237 pub background: String,
238
239 #[schemars(
240 title = "Время ожидания перед стартом следующего боя в тиках",
241 description = "Это момент анимации перебежки персонажа между битвами"
242 )]
243 pub start_fight_delay_ticks: Option<u64>,
244
245 #[schemars(
246 title = "Время ожидания перед стартом уже начавшегося боя в случае победы",
247 description = "Это момент, когда враги выбегают на экран"
248 )]
249 pub prepare_fight_win_duration_ticks: Option<u64>,
250
251 #[schemars(
252 title = "Время ожидания перед стартом уже начавшегося боя в случае поражения",
253 description = "Это момент, когда враги выбегают на экран"
254 )]
255 pub prepare_fight_lose_duration_ticks: Option<u64>,
256
257 #[schemars(title = "Время ожидания перед окончанием боя")]
258 pub end_fight_delay_ticks: Option<u64>,
259
260 #[schemars(
261 title = "Бандл награды за победу в бою",
262 schema_with = "option_bundle_id_schema"
263 )]
264 pub bundle_reward_id: Option<BundleId>,
265
266 #[schemars(title = "Останавливать ли бой после победы")]
267 pub stop_on_win: bool,
268
269 #[schemars(title = "Останавливать ли бой после поражения")]
270 pub stop_on_lose: bool,
271
272 #[schemars(title = "Показывать ли VS экран в начале боя")]
273 pub show_vs_screen: bool,
274
275 #[schemars(title = "Показывать ли стейджи")]
276 pub show_stages: Option<bool>,
277
278 #[schemars(
279 title = "Является ли подземельем",
280 description = "Бой подземелья (fight_template_is_dungeon): включает dungeon-таланты в init_fight."
281 )]
282 #[serde(default)]
283 pub is_dungeon: bool,
284
285 #[schemars(
286 title = "Является ли боссфайтом",
287 description = "Босс-бой (fight_template_is_bossfight): включает boss-таланты в init_fight."
288 )]
289 #[serde(default)]
290 pub is_bossfight: bool,
291}
292
293#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
294pub struct ActiveDungeon {
295 pub id: DungeonTemplateId,
296 pub difficulty: i64,
297}
298
299#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
306pub struct PendingWaveSpawn {
307 pub id: uuid::Uuid,
308 pub entity_template_id: EntityTemplateId,
309 pub position: Coordinates,
310 pub attributes: crate::entity::EntityAttributes,
311 pub has_big_hp_bar: bool,
312}
313
314#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Tsify, JsonSchema)]
315pub struct ActiveFight {
316 pub id: uuid::Uuid,
317 pub fight_id: FightTemplateId,
318 pub current_wave: i64,
319 pub player_id: EntityId,
320 pub party_player_id: Option<EntityId>,
321 pub entities: Vec<Entity>,
322 pub fight_stopped: bool,
323 pub fight_ended: bool,
324 pub max_duration_ticks: u64,
325 pub dungeon: Option<ActiveDungeon>,
326 pub paused: bool,
327 #[serde(default)]
331 pub pending_wave_spawns: Vec<PendingWaveSpawn>,
332 #[serde(default)]
338 pub summoned_entity_ids: Vec<EntityId>,
339}
340
341impl ActiveFight {
342 pub fn get_player(&self) -> Option<&Entity> {
343 self.entities
344 .iter()
345 .find(|entity| entity.id == self.player_id)
346 }
347
348 pub fn get_player_mut(&mut self) -> Option<&mut Entity> {
349 self.entities
350 .iter_mut()
351 .find(|entity| entity.id == self.player_id)
352 }
353
354 pub fn get_party_player(&self) -> Option<&Entity> {
355 let party_id = self.party_player_id?;
356 self.entities.iter().find(|entity| entity.id == party_id)
357 }
358
359 pub fn get_party_player_mut(&mut self) -> Option<&mut Entity> {
360 let party_id = self.party_player_id?;
361 self.entities
362 .iter_mut()
363 .find(|entity| entity.id == party_id)
364 }
365
366 pub fn get_enemies_amount(&self) -> usize {
367 self.entities
368 .iter()
369 .filter(|entity| entity.team == EntityTeam::Enemy)
370 .count()
371 }
372}