firecore_battle/host.rs
1//! Basic battle host
2
3use alloc::vec::Vec;
4use core::{cell::RefMut, fmt::Display, hash::Hash};
5
6use log::warn;
7use rand::Rng;
8use serde::{Deserialize, Serialize};
9
10use pokedex::{
11 item::Item,
12 moves::Move,
13 pokemon::{Health, Pokemon},
14 Dex,
15};
16
17use crate::{
18 data::*,
19 endpoint::ReceiveError,
20 engine::{BattleEngine, MoveResult, ItemResult},
21 message::{ClientMessage, EndMessage, FailedAction, ServerMessage, StartableAction},
22 moves::{
23 damage::{ClientDamage, DamageResult},
24 BattleMove, ClientMove, ClientMoveAction,
25 },
26 player::ClientPlayerData,
27 pokemon::{Indexed, PokemonIdentifier},
28 prelude::CommandAction,
29};
30
31mod collections;
32pub mod moves;
33mod party;
34mod player;
35mod pokemon;
36// mod timer;
37// pub mod saved;
38
39use collections::BattleMap;
40use player::{BattlePlayer, PlayerData};
41use pokemon::HostPokemon;
42
43pub(crate) mod prelude {
44
45 pub use super::player::PlayerData;
46 pub use super::Battle;
47}
48
49/// A battle host.
50pub struct Battle<ID: core::fmt::Debug + Display + Clone + Ord + Hash + 'static, T: Clone> {
51 state: BattleState<ID>,
52 data: BattleData,
53 players: BattleMap<ID, BattlePlayer<ID, T>>,
54 // timer: Timer,
55}
56
57#[derive(Debug, Deserialize, Serialize)]
58enum BattleState<ID> {
59 Begin,
60 StartSelecting,
61 WaitSelecting,
62 QueueMoves,
63 WaitReplace,
64 End(Option<ID>),
65}
66
67impl<ID> Default for BattleState<ID> {
68 fn default() -> Self {
69 Self::Begin
70 }
71}
72
73impl<ID: core::fmt::Debug + Display + Clone + Ord + Hash + 'static, T: Clone> Battle<ID, T> {
74 pub fn new<R: Rng>(
75 data: BattleData,
76 random: &mut R,
77 active: usize,
78 pokedex: &Dex<Pokemon>,
79 movedex: &Dex<Move>,
80 itemdex: &Dex<Item>,
81 players: impl Iterator<Item = PlayerData<ID, T>>,
82 ) -> Self {
83 Self {
84 players: players
85 .map(|p| {
86 let p = p.init(random, active, pokedex, movedex, itemdex);
87 (p.id().clone(), p)
88 })
89 .collect(),
90 state: BattleState::default(),
91 data,
92 // timer: Default::default(),
93 }
94 }
95
96 pub fn begin(&mut self) {
97 for mut player in self.players.values_mut() {
98 let v = ClientPlayerData::new(self.data, &player, self.players.values());
99 player.send(ServerMessage::Begin(v));
100 }
101
102 self.state = BattleState::StartSelecting;
103 }
104
105 pub fn end(&mut self, winner: Option<ID>) {
106 for mut player in self.players.all_values_mut() {
107 if Some(player.id()) != winner.as_ref() {
108 let id = player.id().clone();
109 player.send(ServerMessage::PlayerEnd(id, EndMessage::Lose));
110 }
111 player.send(ServerMessage::GameEnd(winner.clone()))
112 }
113 self.state = BattleState::End(winner);
114 }
115
116 pub fn update<R: Rng + Clone + 'static, E: BattleEngine>(
117 &mut self,
118 random: &mut R,
119 engine: &E,
120 movedex: &Dex<Move>,
121 itemdex: &Dex<Item>,
122 ) {
123 // self.timer.update(delta);
124
125 for player in self.players.values_mut() {
126 self.receive(player, movedex);
127 }
128
129 if self.players.active() <= 1 {
130 let winner = self.players.keys().next().cloned();
131 self.end(winner);
132 return;
133 }
134
135 self.update_state(random, engine, movedex, itemdex);
136 }
137
138 fn update_state<R: Rng + Clone + 'static, E: BattleEngine>(
139 &mut self,
140 random: &mut R,
141 engine: &E,
142 movedex: &Dex<Move>,
143 itemdex: &Dex<Item>,
144 ) {
145 match self.state {
146 BattleState::Begin => self.begin(),
147 BattleState::StartSelecting => {
148 for mut player in self.players.values_mut() {
149 player.send(ServerMessage::Start(StartableAction::Selecting));
150 }
151 self.state = BattleState::WaitSelecting;
152 }
153 BattleState::WaitSelecting => {
154 match self.players.values().all(|p| p.party.ready_to_move()) {
155 true => self.state = BattleState::QueueMoves,
156 false => {
157 // if self.timer.wait(TimedAction::Selecting) {
158 // for mut player in self
159 // .players
160 // .values_mut()
161 // .filter(|p| !p.party.ready_to_move())
162 // {
163 // player.send(ServerMessage::Ping(TimedAction::Selecting));
164 // }
165 // }
166 }
167 }
168 }
169 BattleState::QueueMoves => {
170 let queue = moves::move_queue(&mut self.players, random);
171
172 let player_queue = self.run_queue(random, engine, movedex, itemdex, queue);
173
174 // end queue calculations
175
176 for mut player in self.players.values_mut() {
177 player.send(ServerMessage::Start(StartableAction::Turns(
178 player_queue.clone(),
179 )));
180 }
181
182 for mut player in self.players.values_mut() {
183 if player.party.all_fainted()
184 || player
185 .party
186 .pokemon
187 .iter()
188 .all(|p| p.moves.iter().all(|m| m.1 == 0))
189 {
190 self.remove_player(&mut player, EndMessage::Lose);
191 }
192 }
193
194 self.state = BattleState::WaitReplace;
195 self.update(random, engine, movedex, itemdex);
196 }
197 BattleState::WaitReplace => {
198 match self.players.values().all(|p| !p.party.needs_replace()) {
199 true => self.state = BattleState::StartSelecting,
200 false => {
201 // if self.timer.wait(TimedAction::Replace) {
202 // for mut player in self
203 // .players
204 // .values_mut()
205 // .filter(|p| p.party.needs_replace())
206 // {
207 // player.send(ServerMessage::Ping(TimedAction::Replace));
208 // }
209 // }
210 }
211 }
212 }
213 BattleState::End(..) => (),
214 }
215 }
216
217 pub fn remove(&mut self, id: &ID, reason: EndMessage) {
218 if let Some(mut player) = self.players.get_mut(id) {
219 self.remove_player(&mut player, reason);
220 }
221 }
222
223 /// Remember to drop the player that is in use before calling this!
224 fn remove_player(&self, player: &mut BattlePlayer<ID, T>, reason: EndMessage) {
225 match self.players.deactivate(player.id()) {
226 true => {
227 let id = player.id().clone();
228 for mut player in self.players.all_values_mut() {
229 player.send(ServerMessage::PlayerEnd(id.clone(), reason));
230 }
231
232 player.send(ServerMessage::PlayerEnd(player.id().clone(), reason));
233 }
234 false => {
235 log::error!("Cannot remove player!");
236 }
237 }
238 }
239
240 pub fn faint(&mut self, pokemon: PokemonIdentifier<ID>) {
241 if let Some(mut team) = self.players.get_mut(pokemon.team()) {
242 if let Some(pokemon1) = team.party.pokemon.get_mut(pokemon.index()) {
243 pokemon1.hp = 0;
244 drop(team);
245 for mut player in self.players.all_values_mut() {
246 player.send(ServerMessage::Command(CommandAction::Faint(
247 pokemon.clone(),
248 )));
249 }
250 }
251 }
252 }
253
254 pub fn finished(&self) -> bool {
255 matches!(self.state, BattleState::End(..))
256 }
257
258 pub fn data(&self) -> &BattleData {
259 &self.data
260 }
261
262 pub fn winner(&self) -> Option<&ID> {
263 match &self.state {
264 BattleState::End(winner) => winner.as_ref(),
265 _ => None,
266 }
267 }
268
269 fn receive(&self, mut player: RefMut<BattlePlayer<ID, T>>, movedex: &Dex<Move>) {
270 loop {
271 match player.receive() {
272 Ok(message) => match message {
273 ClientMessage::Move(active, bmove) => {
274 if match &bmove {
275 BattleMove::Move(index, ..) => {
276 // maybe add check for incorrect targeting here?
277 if !player
278 .party
279 .active(active)
280 .map(|p| p.moves.len() > *index)
281 .unwrap_or_default()
282 {
283 player.send(ServerMessage::Fail(FailedAction::Move(active)));
284 false
285 } else {
286 true
287 }
288 }
289 BattleMove::Switch(to) => {
290 if player.party.active_contains(*to) {
291 player.send(ServerMessage::Fail(FailedAction::Switch(active)));
292 false
293 } else {
294 true
295 }
296 }
297 BattleMove::UseItem(..) => true,
298 } {
299 match player.party.active.get_mut(active).and_then(Option::as_mut) {
300 Some(pokemon) => pokemon.queued_move = Some(bmove),
301 None => warn!(
302 "Party {} could not add move #{} to pokemon #{}",
303 player.party.name(),
304 bmove,
305 active
306 ),
307 }
308 }
309 }
310 ClientMessage::ReplaceFaint(active, index) => {
311 if match player.party.active_contains(index) {
312 false => match player.party.pokemon.get(index) {
313 Some(pokemon) => match pokemon.fainted() {
314 false => {
315 player.party.active[active] = Some(index.into());
316 let unknown = player.party.know(index);
317 for mut other in self.players.values_mut() {
318 if let Some(pokemon) = unknown.as_ref() {
319 other.send(ServerMessage::AddRemote(Indexed(
320 PokemonIdentifier(
321 player.party.id().clone(),
322 index,
323 ),
324 pokemon.clone(),
325 )));
326 }
327 other.send(ServerMessage::Replace(Indexed(
328 PokemonIdentifier(
329 player.party.id().clone(),
330 active,
331 ),
332 index,
333 )));
334 }
335 false
336 }
337 true => true,
338 },
339 None => true,
340 },
341 true => true,
342 } {
343 player.send(ServerMessage::Fail(FailedAction::Replace(active)));
344 }
345 }
346 ClientMessage::Forfeit => match self.data.type_ {
347 BattleType::Wild => {
348 for mut other in self.players.values_mut() {
349 self.remove_player(&mut other, EndMessage::Lose)
350 }
351 }
352 _ => {
353 self.remove_player(&mut player, EndMessage::Lose);
354 }
355 },
356 ClientMessage::LearnMove(pokemon, id, index) => {
357 if let Some(pokemon) = player.party.pokemon.get_mut(pokemon) {
358 if pokemon.learnable_moves.remove(&id) {
359 if let Some(m) = movedex.try_get(&id) {
360 pokemon.moves.add(index, m.clone());
361 }
362 }
363 }
364 }
365 },
366 Err(err) => match err {
367 Some(err) => match err {
368 ReceiveError::Disconnected => {
369 self.remove_player(&mut player, EndMessage::Lose)
370 }
371 },
372 None => break,
373 },
374 }
375 }
376 }
377
378 fn run_queue<R: Rng + Clone + 'static, E: BattleEngine>(
379 &mut self,
380 random: &mut R,
381 engine: &E,
382 movedex: &Dex<Move>,
383 itemdex: &Dex<Item>,
384 queue: Vec<Indexed<ID, BattleMove<ID>>>,
385 ) -> Vec<Indexed<ID, ClientMove<ID>>> {
386 let mut player_queue = Vec::with_capacity(queue.len());
387
388 for Indexed(user_id, bmove) in queue {
389 match bmove {
390 BattleMove::Move(move_index, targets) => {
391 let turn = match self.players.get(user_id.team()) {
392 Some(user) => match user.party.active(user_id.index()) {
393 Some(pokemon) => {
394 // match Some(pokemon.moves.get(0).unwrap().0)
395 match pokemon.moves.get(move_index) {
396 Some(m) => {
397 if m.pp() != 0 {
398 let used_move = &*m.0;
399 match engine.execute_move(
400 random,
401 used_move,
402 Indexed(user_id.clone(), pokemon),
403 targets,
404 &self.players,
405 ) {
406 Ok(turn) => Some((used_move.id, turn)),
407 Err(err) => {
408 warn!("Cannot execute move {} for user {}'s pokemon {} with error {}", used_move.name, user.name(), pokemon.name(), err);
409 None
410 }
411 }
412 } else {
413 log::debug!("{}'s {} does not have enough PP to use their move!", user.name(), pokemon.name());
414 None
415 }
416 }
417 None => {
418 warn!(
419 "Could not get move #{} for {}'s {}",
420 move_index,
421 user.name(),
422 pokemon.name()
423 );
424 None
425 }
426 }
427 }
428 None => None,
429 },
430 None => {
431 warn!("Cannot get user {}!", user_id.team());
432 None
433 }
434 };
435
436 if let Some((used_move, output)) = turn {
437 let mut actions = Vec::with_capacity(output.len());
438
439 for Indexed(target_id, action) in output {
440 match self.players.get_mut(target_id.team()) {
441 Some(mut player) => {
442 match player.party.active_mut(target_id.index()) {
443 Some(target) => {
444 /// calculates hp and adds it to actions
445 fn on_damage<ID>(
446 location: PokemonIdentifier<ID>,
447 pokemon: &mut HostPokemon,
448 actions: &mut Vec<Indexed<ID, ClientMoveAction>>,
449 result: DamageResult<Health>,
450 ) {
451 pokemon.hp =
452 pokemon.hp.saturating_sub(result.damage);
453 actions.push(Indexed(
454 location,
455 ClientMoveAction::SetHP(ClientDamage::Result(
456 DamageResult {
457 damage: pokemon.percent_hp(),
458 effective: result.effective,
459 crit: result.crit,
460 },
461 )),
462 ));
463 }
464
465 let t_id = target_id.clone();
466
467 match action {
468 MoveResult::Damage(result) => on_damage(
469 target_id,
470 target,
471 &mut actions,
472 result,
473 ),
474 MoveResult::Ailment(ailment) => {
475 target.ailment = ailment;
476 actions.push(Indexed(
477 target_id,
478 ClientMoveAction::Ailment(ailment),
479 ));
480 }
481 MoveResult::Heal(health) => {
482 let hp = health.unsigned_abs();
483 target.hp = match health.is_positive() {
484 true => target.hp + hp.min(target.max_hp()),
485 false => target.hp.saturating_sub(hp),
486 };
487 actions.push(Indexed(
488 target_id,
489 ClientMoveAction::SetHP(
490 ClientDamage::Number(
491 target.percent_hp(),
492 ),
493 ),
494 ));
495 }
496 MoveResult::Stat(stat, stage) => {
497 target.stages.change_stage(stat, stage);
498 actions.push(Indexed(
499 target_id,
500 ClientMoveAction::AddStat(stat, stage),
501 ));
502 }
503 MoveResult::Cancel(reason) => {
504 actions.push(Indexed(
505 target_id,
506 ClientMoveAction::Cancel(reason),
507 ))
508 }
509 MoveResult::Miss => {
510 actions.push(Indexed(
511 target_id,
512 ClientMoveAction::Miss,
513 ));
514 continue;
515 }
516 MoveResult::Error => actions.push(Indexed(
517 target_id,
518 ClientMoveAction::Error,
519 )),
520 MoveResult::Custom => todo!("Implement custom move results"),
521 }
522
523 if target.fainted() {
524 let experience =
525 target.battle_exp_from(&self.data.type_);
526
527 player.party.remove_active(t_id.index());
528
529 if player.id() != t_id.team() {
530 let mut user = self
531 .players
532 .get_mut(user_id.team())
533 .unwrap();
534
535 if user.settings.gains_exp {
536 let user = user
537 .party
538 .active_mut(user_id.index())
539 .unwrap();
540
541 user.learnable_moves.extend(
542 user.p.add_exp(movedex, experience),
543 );
544
545 actions.push(Indexed(
546 user_id.clone(),
547 ClientMoveAction::SetExp(
548 experience, user.level,
549 ),
550 ));
551 }
552 }
553 }
554 }
555 None => {}
556 }
557 }
558 None => {}
559 }
560 }
561
562 let mut user = self.players.get_mut(user_id.team()).unwrap();
563
564 if let Some(pokemon) = user.party.active_mut(user_id.index()) {
565 // decrement PP
566 let pp = &mut pokemon.moves.get_mut(move_index).unwrap().1;
567 *pp = pp.saturating_sub(1);
568
569 if pokemon.fainted() {
570 user.party.remove_active(user_id.index());
571 }
572 }
573
574 const TEMP_PP: u8 = 1;
575
576 player_queue.push(Indexed(
577 user_id,
578 ClientMove::Move(used_move, TEMP_PP, actions),
579 ));
580 }
581 }
582 BattleMove::UseItem(Indexed(target, id)) => match itemdex.try_get(&id) {
583 Some(item) => {
584 match engine.execute_item(
585 &self.data,
586 random,
587 &*item,
588 user_id.team(),
589 target.clone(),
590 &mut self.players,
591 ) {
592 Ok(results) => {
593 player_queue.push(Indexed(
594 user_id.clone(),
595 ClientMove::UseItem(Indexed(target, id)),
596 ));
597
598 let mut user = match self.players.get_mut(user_id.team()) {
599 Some(user) => user,
600 None => continue,
601 };
602
603 for result in results {
604 match result {
605 ItemResult::Catch(pokemon) => {
606 user.send(ServerMessage::Catch(pokemon))
607 }
608 }
609 }
610 }
611 Err(err) => warn!("Cannot execute item engine with error {}", err),
612 }
613 }
614 None => warn!("Could not get item with id {}", id.as_str()),
615 },
616 BattleMove::Switch(new) => match self.players.get_mut(user_id.team()) {
617 Some(mut user) => {
618 user.party.replace(user_id.index(), Some(new));
619
620 if let Some(unknown) = user
621 .party
622 .index(user_id.index())
623 .and_then(|index| user.party.know(index))
624 {
625 for mut other in self.players.values_mut() {
626 other.send(ServerMessage::AddRemote(Indexed(
627 PokemonIdentifier(user.party.id().clone(), new),
628 unknown.clone(),
629 )))
630 }
631 }
632 player_queue.push(Indexed(user_id, ClientMove::Switch(new)));
633 }
634 None => todo!(),
635 },
636 }
637 }
638 player_queue
639 }
640}