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}