use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::Arc; use parking_lot::lock_api::RwLockReadGuard; use parking_lot::{RawRwLock, RwLock}; use crate::dynamic_data::choices::TurnChoice; use crate::dynamic_data::event_hooks::Event; use crate::dynamic_data::models::battle::Battle; use crate::dynamic_data::models::battle_party::BattleParty; use crate::dynamic_data::models::pokemon::Pokemon; use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, ScriptWrapper}; use crate::dynamic_data::Script; use crate::dynamic_data::ScriptSet; use crate::dynamic_data::VolatileScriptsOwner; use crate::{script_hook, PkmnResult, StringKey}; /// A side on a battle. #[derive(Debug)] #[cfg_attr(feature = "wasm", derive(unique_type_id_derive::UniqueTypeId))] pub struct BattleSide { /// The index of the side on the battle. index: u8, /// The number of Pokemon that can be on the side. pokemon_per_side: u8, /// A list of pokemon currently on the battlefield. pokemon: RwLock>>>, /// The currently set choices for all Pokemon on the battlefield. Cleared when the turn starts. choices: RwLock>>, /// The slots on the side that can still be filled. Once all slots are set to false, this side /// has lost the battle. fillable_slots: Vec, /// The number of choices that are set. choices_set: AtomicU8, /// A reference to the battle we're part of. battle: *mut Battle, /// Whether or not this side has fled. has_fled_battle: bool, /// The volatile scripts that are attached to the side. volatile_scripts: Arc, /// Data required for this to be a script source. script_source_data: RwLock, } impl BattleSide { /// Instantiates a battle side. pub fn new(index: u8, pokemon_per_side: u8) -> Self { let mut pokemon = Vec::with_capacity(pokemon_per_side as usize); let mut choices = Vec::with_capacity(pokemon_per_side as usize); let mut fillable_slots = Vec::with_capacity(pokemon_per_side as usize); for _i in 0..pokemon_per_side { pokemon.push(None); choices.push(None); fillable_slots.push(AtomicBool::new(false)); } let choices = RwLock::new(choices); let pokemon = RwLock::new(pokemon); Self { index, pokemon_per_side, pokemon, choices, fillable_slots, choices_set: AtomicU8::new(0), battle: std::ptr::null_mut::(), has_fled_battle: false, volatile_scripts: Default::default(), script_source_data: Default::default(), } } /// Set the battle this side belongs to. pub(crate) fn set_battle(&mut self, battle: *mut Battle) { self.battle = battle; } /// The index of the side on the battle. pub fn index(&self) -> u8 { self.index } /// The number of Pokemon that can be on the side. pub fn pokemon_per_side(&self) -> u8 { self.pokemon_per_side } /// A list of pokemon currently on the battlefield. pub fn pokemon(&self) -> RwLockReadGuard<'_, RawRwLock, Vec>>> { self.pokemon.read() } /// The currently set choices for all Pokemon on the battlefield. Cleared when the turn starts. pub fn choices(&self) -> &RwLock>> { &self.choices } /// The slots on the side that can still be filled. Once all slots are set to false, this side /// has lost the battle. pub fn fillable_slots(&self) -> &Vec { &self.fillable_slots } /// The number of choices that are set. pub fn choices_set(&self) -> u8 { self.choices_set.load(Ordering::SeqCst) } /// A reference to the battle we're part of. pub fn battle(&self) -> &Battle { unsafe { self.battle.as_ref().unwrap() } } /// Whether or not this side has fled. pub fn has_fled_battle(&self) -> bool { self.has_fled_battle } /// The volatile scripts that are attached to the side. pub fn volatile_scripts(&self) -> &Arc { &self.volatile_scripts } /// Whether every Pokemon on this side has its choices pub fn all_choices_set(&self) -> bool { self.choices_set() == self.pokemon_per_side } /// Returns true if there are slots that need to be filled with a new pokemon, that have parties /// responsible for them. Returns false if all slots are filled with usable pokemon, or slots are /// empty, but can't be filled by any party anymore. pub fn all_slots_filled(&self) -> bool { for (i, pokemon) in self.pokemon.read().iter().enumerate() { if (!pokemon.is_none() || !pokemon.as_ref().unwrap().is_usable()) && self.battle().can_slot_be_filled(self.index, i as u8) { return false; } } true } /// Sets a choice for a Pokemon on this side. pub(crate) fn set_choice(&self, choice: TurnChoice) { for (index, pokemon_slot) in self.pokemon.read().iter().enumerate() { if let Some(pokemon) = pokemon_slot { if std::ptr::eq(pokemon.deref(), choice.user().deref()) { self.choices.write()[index] = Some(choice); self.choices_set.fetch_add(1, Ordering::SeqCst); return; } } } } /// Resets all choices on this side. pub fn reset_choices(&self) { let len = self.choices.read().len(); for i in 0..len { self.choices.write()[i] = None; } } /// Forcibly removes a Pokemon from the field. pub fn force_clear_pokemon(&mut self, index: u8) { self.pokemon.write()[index as usize] = None; } /// Switches out a spot on the field for a different Pokemon. pub fn set_pokemon(&self, index: u8, pokemon: Option>) { { let old = &self.pokemon.read()[index as usize]; if let Some(old_pokemon) = old { script_hook!(on_remove, old_pokemon,); old_pokemon.set_on_battlefield(false); } } self.pokemon.write()[index as usize] = pokemon; let pokemon = &self.pokemon.read()[index as usize]; if let Some(pokemon) = pokemon { pokemon.set_battle_data(self.battle, self.index); pokemon.set_on_battlefield(true); pokemon.set_battle_index(index); let battle = self.battle(); for side in battle.sides() { if side.index() == self.index { continue; } for opponent in side.pokemon().iter().flatten() { opponent.mark_opponent_as_seen(Arc::downgrade(pokemon)); pokemon.mark_opponent_as_seen(Arc::downgrade(opponent)); } } battle.event_hook().trigger(Event::Switch { side_index: self.index, index, pokemon: Some(pokemon), }); script_hook!(on_switch_in, pokemon, pokemon); } else { self.battle().event_hook().trigger(Event::Switch { side_index: self.index, index, pokemon: None, }); } } /// Checks whether a Pokemon is on the field in this side. pub fn is_pokemon_on_side(&self, pokemon: Arc) -> bool { for p in self.pokemon.read().iter().flatten() { if std::ptr::eq(p.deref().deref(), pokemon.deref()) { return true; } } false } /// Marks a slot as unfillable. This happens when no parties are able to fill the slot anymore. /// If this happens, the slot can not be used again. pub(crate) fn mark_slot_as_unfillable(&self, index: u8) { self.fillable_slots[index as usize].store(false, Ordering::SeqCst); } /// Checks whether a slot is unfillable or not. pub fn is_slot_unfillable(&self, pokemon: Arc) -> bool { for (i, slot) in self.pokemon.read().iter().enumerate() { if let Some(p) = slot { if std::ptr::eq(p.deref().deref(), pokemon.deref()) { return self.fillable_slots[i].load(Ordering::Relaxed); } } } false } /// Checks whether the side has been defeated. pub fn is_defeated(&self) -> bool { for fillable_slot in &self.fillable_slots { if fillable_slot.load(Ordering::Relaxed) { return false; } } true } /// Mark the side as fled. pub fn mark_as_fled(&mut self) { self.has_fled_battle = true; } /// Gets a random Pokemon on the given side. pub fn get_random_creature_index(&self) -> u8 { // TODO: Consider adding parameter to only get index for available creatures. self.battle().random().get_max(self.pokemon_per_side as i32) as u8 } /// Swap two Pokemon on a single side around. pub fn swap_positions(&mut self, a: u8, b: u8) -> bool { // If out of range, don't allow swapping. if a >= self.pokemon_per_side || b >= self.pokemon_per_side { return false; } // If the two indices are the same, don't allow swapping. if a == b { return false; } // Fetch parties for the two indices. let mut party_a = None; let mut party_b = None; for party in self.battle().parties() { if party.is_responsible_for_index(self.index, a) { party_a = Some(party); } if party.is_responsible_for_index(self.index, b) { party_b = Some(party); } } // If either of the parties does not exist, fail. if party_a.is_none() || party_b.is_none() { return false; } // Don't allow swapping if different parties are responsible for the indices. if party_a.unwrap() as *const BattleParty != party_b.unwrap() as *const BattleParty { return false; } self.pokemon.write().swap(a as usize, b as usize); self.battle().event_hook().trigger(Event::Swap { side_index: self.index, index_a: a, index_b: b, }); true } } impl VolatileScriptsOwner for BattleSide { fn volatile_scripts(&self) -> &Arc { &self.volatile_scripts } fn load_volatile_script(&self, key: &StringKey) -> PkmnResult>> { self.battle() .library() .load_script(self.into(), crate::ScriptCategory::Side, key) } } impl ScriptSource for BattleSide { fn get_script_count(&self) -> usize { self.battle().get_script_count() + 1 } fn get_script_source_data(&self) -> &RwLock { &self.script_source_data } fn get_own_scripts(&self, scripts: &mut Vec) { scripts.push((&self.volatile_scripts).into()); } fn collect_scripts(&self, scripts: &mut Vec) { self.get_own_scripts(scripts); self.battle().collect_scripts(scripts); } }