use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use std::sync::Arc; use anyhow::{anyhow, Result}; 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::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, PkmnError, StringKey, ValueIdentifiable, ValueIdentifier}; /// A side on a battle. #[derive(Debug)] pub struct BattleSide { /// A unique identifier so we know what value this is. identifier: ValueIdentifier, /// 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 { identifier: Default::default(), 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) -> Result<&Battle> { unsafe { self.battle.as_ref().ok_or(anyhow!("Battle was not set, but requested")) } } /// 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) -> Result { for (i, pokemon) in self.pokemon.read().iter().enumerate() { match pokemon { Some(pokemon) => { if !pokemon.is_usable() && self.battle()?.can_slot_be_filled(self.index, i as u8) { return Ok(false); } } None => { if self.battle()?.can_slot_be_filled(self.index, i as u8) { return Ok(false); } } } } Ok(true) } /// Sets a choice for a Pokemon on this side. pub(crate) fn set_choice(&self, choice: TurnChoice) -> Result<()> { 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()) { let len = self.choices.read().len(); self.choices .write() .get_mut(index) .ok_or(PkmnError::IndexOutOfBounds { index, len })? .replace(choice); self.choices_set.fetch_add(1, Ordering::SeqCst); return Ok(()); } } } Ok(()) } /// 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().get_mut(i).take(); } } /// Forcibly removes a Pokemon from the field. pub fn force_clear_pokemon(&mut self, index: u8) { self.pokemon.write().get_mut(index as usize).take(); } /// Switches out a spot on the field for a different Pokemon. pub fn set_pokemon(&self, index: u8, pokemon: Option>) -> Result<()> { { let mut write_lock = self.pokemon.write(); let old = write_lock.get_mut(index as usize).ok_or(PkmnError::IndexOutOfBounds { index: index as usize, len: self.pokemon_per_side as usize, })?; let old = match pokemon { Some(pokemon) => old.replace(pokemon), None => old.take(), }; if let Some(old_pokemon) = old { // We need to drop the lock before calling the hook, as it might try to access the // side again. drop(write_lock); script_hook!(on_remove, old_pokemon,); old_pokemon.set_on_battlefield(false); } } let pokemon = { let read_lock = self.pokemon.read(); &read_lock .get(index as usize) .ok_or(PkmnError::IndexOutOfBounds { index: index as usize, len: read_lock.len(), })? .clone() }; 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.clone()), }); script_hook!(on_switch_in, pokemon, pokemon); } else { self.battle()?.event_hook().trigger(Event::Switch { side_index: self.index, index, pokemon: None, }); } Ok(()) } /// 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) -> Result<()> { self.fillable_slots .get(index as usize) .ok_or(PkmnError::IndexOutOfBounds { index: index as usize, len: self.fillable_slots.len(), })? .store(false, Ordering::SeqCst); Ok(()) } /// Checks whether a slot is unfillable or not. pub fn is_slot_unfillable(&self, pokemon: Arc) -> Result { for (i, slot) in self.pokemon.read().iter().enumerate() { if let Some(p) = slot { if std::ptr::eq(p.deref().deref(), pokemon.deref()) { return Ok(self .fillable_slots .get(i) .ok_or(PkmnError::IndexOutOfBounds { index: i, len: self.fillable_slots.len(), })? .load(Ordering::Relaxed)); } } } Ok(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) -> Result { // TODO: Consider adding parameter to only get index for available creatures. Ok(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) -> Result { // If out of range, don't allow swapping. if a >= self.pokemon_per_side || b >= self.pokemon_per_side { return Ok(false); } // If the two indices are the same, don't allow swapping. if a == b { return Ok(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. let party_a = match party_a { Some(party) => party, None => return Ok(false), }; let party_b = match party_b { Some(party) => party, None => return Ok(false), }; // Don't allow swapping if different parties are responsible for the indices. if party_a.value_identifier() != party_b.value_identifier() { return Ok(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, }); Ok(true) } } impl VolatileScriptsOwner for BattleSide { fn volatile_scripts(&self) -> &Arc { &self.volatile_scripts } fn load_volatile_script(&self, key: &StringKey) -> Result>> { self.battle()? .library() .load_script(self.into(), crate::ScriptCategory::Side, key) } } impl ScriptSource for BattleSide { fn get_script_count(&self) -> Result { Ok(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) -> Result<()> { self.get_own_scripts(scripts); self.battle()?.collect_scripts(scripts) } } impl ValueIdentifiable for BattleSide { fn value_identifier(&self) -> ValueIdentifier { self.identifier } }