Loads of work to replace panics with results.
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
@@ -20,7 +20,7 @@ use crate::dynamic_data::VolatileScriptsOwner;
|
||||
use crate::dynamic_data::{is_valid_target, ScriptWrapper};
|
||||
use crate::dynamic_data::{ChoiceQueue, ScriptContainer};
|
||||
use crate::dynamic_data::{ScriptCategory, ScriptSource, ScriptSourceData};
|
||||
use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier};
|
||||
use crate::{script_hook, PkmnError, StringKey, ValueIdentifiable, ValueIdentifier};
|
||||
|
||||
/// A pokemon battle, with any amount of sides and pokemon per side.
|
||||
#[derive(Debug)]
|
||||
@@ -271,7 +271,13 @@ impl Battle {
|
||||
let side = choice.user().get_battle_side_index();
|
||||
match side {
|
||||
Some(side) => {
|
||||
self.sides[side as usize].set_choice(choice);
|
||||
self.sides
|
||||
.get(side as usize)
|
||||
.ok_or(PkmnError::IndexOutOfBounds {
|
||||
index: side as usize,
|
||||
len: self.sides.len(),
|
||||
})?
|
||||
.set_choice(choice)?;
|
||||
self.check_choices_set_and_run()?;
|
||||
Ok(true)
|
||||
}
|
||||
@@ -286,7 +292,7 @@ impl Battle {
|
||||
if !side.all_choices_set() {
|
||||
return Ok(());
|
||||
}
|
||||
if !side.all_slots_filled() {
|
||||
if !side.all_slots_filled()? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -361,10 +367,7 @@ impl Battle {
|
||||
if let Some(script) = self.weather.get() {
|
||||
let lock = script.read();
|
||||
Ok(Some(
|
||||
lock.as_ref()
|
||||
.ok_or(anyhow!("Failed to get a lock on weather"))?
|
||||
.name()
|
||||
.clone(),
|
||||
lock.as_ref().ok_or(PkmnError::UnableToAcquireLock)?.name().clone(),
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -383,8 +386,8 @@ impl VolatileScriptsOwner for Battle {
|
||||
}
|
||||
|
||||
impl ScriptSource for Battle {
|
||||
fn get_script_count(&self) -> usize {
|
||||
1
|
||||
fn get_script_count(&self) -> Result<usize> {
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
fn get_script_source_data(&self) -> &RwLock<ScriptSourceData> {
|
||||
@@ -396,8 +399,9 @@ impl ScriptSource for Battle {
|
||||
scripts.push((&self.volatile_scripts).into());
|
||||
}
|
||||
|
||||
fn collect_scripts(&self, scripts: &mut Vec<ScriptWrapper>) {
|
||||
fn collect_scripts(&self, scripts: &mut Vec<ScriptWrapper>) -> Result<()> {
|
||||
self.get_own_scripts(scripts);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use anyhow_ext::anyhow;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
@@ -7,7 +6,7 @@ use crate::dynamic_data::models::executing_move::ExecutingMove;
|
||||
use crate::dynamic_data::models::pokemon::Pokemon;
|
||||
use crate::dynamic_data::script_handling::ScriptSource;
|
||||
use crate::utils::Random;
|
||||
use crate::{script_hook, ValueIdentifiable, ValueIdentifier};
|
||||
use crate::{script_hook, PkmnError, ValueIdentifiable, ValueIdentifier};
|
||||
|
||||
/// The RNG for a battle.
|
||||
#[derive(Default)]
|
||||
@@ -37,21 +36,21 @@ impl BattleRandom {
|
||||
pub fn get(&self) -> Result<i32> {
|
||||
match self.get_rng().lock() {
|
||||
Ok(mut l) => Ok(l.get()),
|
||||
Err(_) => Err(anyhow!("Failed to get a RNG lock")),
|
||||
Err(_) => Err(PkmnError::UnableToAcquireLock.into()),
|
||||
}
|
||||
}
|
||||
/// Get a random 32 bit integer between 0 and max.
|
||||
pub fn get_max(&self, max: i32) -> Result<i32> {
|
||||
match self.get_rng().lock() {
|
||||
Ok(mut l) => Ok(l.get_max(max)),
|
||||
Err(_) => Err(anyhow!("Failed to get a RNG lock")),
|
||||
Err(_) => Err(PkmnError::UnableToAcquireLock.into()),
|
||||
}
|
||||
}
|
||||
/// Get a random 32 bit integer between min and max.
|
||||
pub fn get_between(&self, min: i32, max: i32) -> Result<i32> {
|
||||
match self.get_rng().lock() {
|
||||
Ok(mut l) => Ok(l.get_between(min, max)),
|
||||
Err(_) => Err(anyhow!("Failed to get a RNG lock")),
|
||||
Err(_) => Err(PkmnError::UnableToAcquireLock.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +84,7 @@ impl BattleRandom {
|
||||
if chance > 0.0 {
|
||||
match self.get_rng().lock() {
|
||||
Ok(mut l) => Ok(l.get_float() < (chance / 100.0)),
|
||||
Err(_) => Err(anyhow!("Failed to get a RNG lock")),
|
||||
Err(_) => Err(PkmnError::UnableToAcquireLock.into()),
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
|
||||
@@ -2,20 +2,19 @@ use std::ops::Deref;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
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::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, StringKey, ValueIdentifiable, ValueIdentifier};
|
||||
use crate::{script_hook, PkmnError, StringKey, ValueIdentifiable, ValueIdentifier};
|
||||
|
||||
/// A side on a battle.
|
||||
#[derive(Debug)]
|
||||
@@ -107,8 +106,8 @@ impl BattleSide {
|
||||
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() }
|
||||
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 {
|
||||
@@ -127,60 +126,93 @@ impl BattleSide {
|
||||
/// 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 {
|
||||
pub fn all_slots_filled(&self) -> Result<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;
|
||||
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()[i] = None;
|
||||
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()[index as usize] = None;
|
||||
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<Arc<Pokemon>>) {
|
||||
pub fn set_pokemon(&self, index: u8, pokemon: Option<Arc<Pokemon>>) -> Result<()> {
|
||||
{
|
||||
let old = &self.pokemon.read()[index as usize];
|
||||
if let Some(old_pokemon) = old {
|
||||
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.clone() {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
self.pokemon.write()[index as usize] = pokemon;
|
||||
let pokemon = &self.pokemon.read()[index as usize];
|
||||
|
||||
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();
|
||||
let battle = self.battle()?;
|
||||
for side in battle.sides() {
|
||||
if side.index() == self.index {
|
||||
continue;
|
||||
@@ -197,12 +229,13 @@ impl BattleSide {
|
||||
});
|
||||
script_hook!(on_switch_in, pokemon, pokemon);
|
||||
} else {
|
||||
self.battle().event_hook().trigger(Event::Switch {
|
||||
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.
|
||||
@@ -217,20 +250,34 @@ impl BattleSide {
|
||||
|
||||
/// 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);
|
||||
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<Pokemon>) -> bool {
|
||||
pub fn is_slot_unfillable(&self, pokemon: Arc<Pokemon>) -> Result<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);
|
||||
return Ok(self
|
||||
.fillable_slots
|
||||
.get(i)
|
||||
.ok_or(PkmnError::IndexOutOfBounds {
|
||||
index: i,
|
||||
len: self.fillable_slots.len(),
|
||||
})?
|
||||
.load(Ordering::Relaxed));
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
/// Checks whether the side has been defeated.
|
||||
@@ -251,24 +298,24 @@ impl BattleSide {
|
||||
/// Gets a random Pokemon on the given side.
|
||||
pub fn get_random_creature_index(&self) -> Result<u8> {
|
||||
// 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)
|
||||
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) -> bool {
|
||||
pub fn swap_positions(&mut self, a: u8, b: u8) -> Result<bool> {
|
||||
// If out of range, don't allow swapping.
|
||||
if a >= self.pokemon_per_side || b >= self.pokemon_per_side {
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
// If the two indices are the same, don't allow swapping.
|
||||
if a == b {
|
||||
return false;
|
||||
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() {
|
||||
for party in self.battle()?.parties() {
|
||||
if party.is_responsible_for_index(self.index, a) {
|
||||
party_a = Some(party);
|
||||
}
|
||||
@@ -277,21 +324,26 @@ impl BattleSide {
|
||||
}
|
||||
}
|
||||
// If either of the parties does not exist, fail.
|
||||
if party_a.is_none() || party_b.is_none() {
|
||||
return false;
|
||||
}
|
||||
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.unwrap() as *const BattleParty != party_b.unwrap() as *const BattleParty {
|
||||
return false;
|
||||
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 {
|
||||
self.battle()?.event_hook().trigger(Event::Swap {
|
||||
side_index: self.index,
|
||||
index_a: a,
|
||||
index_b: b,
|
||||
});
|
||||
true
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,15 +353,15 @@ impl VolatileScriptsOwner for BattleSide {
|
||||
}
|
||||
|
||||
fn load_volatile_script(&self, key: &StringKey) -> Result<Option<Arc<dyn Script>>> {
|
||||
self.battle()
|
||||
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_count(&self) -> Result<usize> {
|
||||
Ok(self.battle()?.get_script_count()? + 1)
|
||||
}
|
||||
|
||||
fn get_script_source_data(&self) -> &RwLock<ScriptSourceData> {
|
||||
@@ -320,9 +372,9 @@ impl ScriptSource for BattleSide {
|
||||
scripts.push((&self.volatile_scripts).into());
|
||||
}
|
||||
|
||||
fn collect_scripts(&self, scripts: &mut Vec<ScriptWrapper>) {
|
||||
fn collect_scripts(&self, scripts: &mut Vec<ScriptWrapper>) -> Result<()> {
|
||||
self.get_own_scripts(scripts);
|
||||
self.battle().collect_scripts(scripts);
|
||||
self.battle()?.collect_scripts(scripts)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, Scrip
|
||||
use crate::dynamic_data::ScriptContainer;
|
||||
use crate::dynamic_data::TargetList;
|
||||
use crate::static_data::{MoveData, TypeIdentifier};
|
||||
use crate::{ValueIdentifiable, ValueIdentifier};
|
||||
use crate::{PkmnError, ValueIdentifiable, ValueIdentifier};
|
||||
|
||||
/// A hit data is the data for a single hit, on a single target.
|
||||
#[derive(Default, Debug)]
|
||||
@@ -171,7 +171,14 @@ impl ExecutingMove {
|
||||
if let Some(target) = target {
|
||||
if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) {
|
||||
let i = index * self.number_of_hits as usize + hit as usize;
|
||||
return Ok(&self.hits[i]);
|
||||
return match self.hits.get(i) {
|
||||
Some(hit) => Ok(hit),
|
||||
None => Err(PkmnError::IndexOutOfBounds {
|
||||
index: i,
|
||||
len: self.hits.len(),
|
||||
}
|
||||
.into()),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,14 +209,21 @@ impl ExecutingMove {
|
||||
}
|
||||
|
||||
/// Gets a hit based on its raw index.
|
||||
pub(crate) fn get_hit_from_raw_index(&self, index: usize) -> &HitData {
|
||||
&self.hits[index]
|
||||
pub(crate) fn get_hit_from_raw_index(&self, index: usize) -> Result<&HitData> {
|
||||
match self.hits.get(index) {
|
||||
Some(hit) => Ok(hit),
|
||||
None => Err(PkmnError::IndexOutOfBounds {
|
||||
index,
|
||||
len: self.hits.len(),
|
||||
}
|
||||
.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptSource for ExecutingMove {
|
||||
fn get_script_count(&self) -> usize {
|
||||
1
|
||||
fn get_script_count(&self) -> Result<usize> {
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
fn get_script_source_data(&self) -> &RwLock<ScriptSourceData> {
|
||||
@@ -220,9 +234,10 @@ impl ScriptSource for ExecutingMove {
|
||||
scripts.push((&self.script).into());
|
||||
}
|
||||
|
||||
fn collect_scripts(&self, scripts: &mut Vec<ScriptWrapper>) {
|
||||
fn collect_scripts(&self, scripts: &mut Vec<ScriptWrapper>) -> Result<()> {
|
||||
self.get_own_scripts(scripts);
|
||||
self.user.get_own_scripts(scripts);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -80,7 +81,7 @@ impl LearnedMove {
|
||||
}
|
||||
|
||||
/// Restore the remaining PP by a certain amount. Will prevent it from going above max PP.
|
||||
pub fn restore_uses(&self, mut uses: u8) {
|
||||
pub fn restore_uses(&self, mut uses: u8) -> Result<()> {
|
||||
self.remaining_pp
|
||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| {
|
||||
if x + uses > self.max_pp {
|
||||
@@ -88,7 +89,8 @@ impl LearnedMove {
|
||||
}
|
||||
Some(x + uses)
|
||||
})
|
||||
.unwrap();
|
||||
.ok();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +112,7 @@ mod tests {
|
||||
let data: Arc<dyn MoveData> = Arc::new(mock);
|
||||
let learned_move = LearnedMove::new(data, MoveLearnMethod::Level);
|
||||
assert!(learned_move.try_use(15));
|
||||
learned_move.restore_uses(5);
|
||||
learned_move.restore_uses(5).unwrap();
|
||||
assert_eq!(20, learned_move.remaining_pp());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ use crate::static_data::TypeIdentifier;
|
||||
use crate::static_data::{Ability, Statistic};
|
||||
use crate::static_data::{ClampedStatisticSet, StatisticSet};
|
||||
use crate::utils::Random;
|
||||
use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier};
|
||||
use anyhow::Result;
|
||||
use crate::{script_hook, PkmnError, StringKey, ValueIdentifiable, ValueIdentifier};
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
/// An individual Pokemon as we know and love them.
|
||||
#[derive(Debug)]
|
||||
@@ -142,7 +142,7 @@ impl Pokemon {
|
||||
.static_data()
|
||||
.natures()
|
||||
.get_nature(nature)
|
||||
.unwrap_or_else(|| panic!("Unknown nature name was given: {}.", &nature));
|
||||
.ok_or(PkmnError::InvalidNatureName { nature: nature.clone() })?;
|
||||
let mut pokemon = Self {
|
||||
identifier: Default::default(),
|
||||
library,
|
||||
@@ -180,7 +180,7 @@ impl Pokemon {
|
||||
volatile: Default::default(),
|
||||
script_source_data: Default::default(),
|
||||
};
|
||||
pokemon.recalculate_flat_stats();
|
||||
pokemon.recalculate_flat_stats()?;
|
||||
let health = pokemon.flat_stats().hp();
|
||||
pokemon.current_health = AtomicU32::new(health);
|
||||
|
||||
@@ -257,20 +257,19 @@ impl Pokemon {
|
||||
self.held_item.write().take()
|
||||
}
|
||||
/// Makes the Pokemon uses its held item.
|
||||
pub fn consume_held_item(&self) -> bool {
|
||||
pub fn consume_held_item(&self) -> Result<bool> {
|
||||
if self.held_item.read().is_none() {
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
let script = self
|
||||
.library
|
||||
.load_item_script(self.held_item.read().as_ref().unwrap())
|
||||
.unwrap();
|
||||
.load_item_script(self.held_item.read().as_ref().ok_or(PkmnError::UnableToAcquireLock)?)?;
|
||||
if script.is_none() {
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// TODO: the entire item use part.
|
||||
todo!();
|
||||
bail!("Not implemented yet.")
|
||||
}
|
||||
|
||||
/// The remaining health points of the Pokemon.
|
||||
@@ -331,7 +330,7 @@ impl Pokemon {
|
||||
self.stat_boost.get_stat(stat)
|
||||
}
|
||||
/// Change a boosted stat by a certain amount.
|
||||
pub fn change_stat_boost(&self, stat: Statistic, mut diff_amount: i8, self_inflicted: bool) -> bool {
|
||||
pub fn change_stat_boost(&self, stat: Statistic, mut diff_amount: i8, self_inflicted: bool) -> Result<bool> {
|
||||
let mut prevent = false;
|
||||
script_hook!(
|
||||
prevent_stat_boost_change,
|
||||
@@ -343,7 +342,7 @@ impl Pokemon {
|
||||
&mut prevent
|
||||
);
|
||||
if prevent {
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
script_hook!(
|
||||
change_stat_boost_change,
|
||||
@@ -354,7 +353,7 @@ impl Pokemon {
|
||||
&mut diff_amount
|
||||
);
|
||||
if diff_amount == 0 {
|
||||
return false;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut changed = false;
|
||||
@@ -378,9 +377,9 @@ impl Pokemon {
|
||||
new_value,
|
||||
})
|
||||
}
|
||||
self.recalculate_boosted_stats();
|
||||
self.recalculate_boosted_stats()?;
|
||||
}
|
||||
changed
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
/// The [individual values](https://bulbapedia.bulbagarden.net/wiki/Individual_values) of the Pokemon.
|
||||
@@ -416,15 +415,21 @@ impl Pokemon {
|
||||
self.override_ability.is_some()
|
||||
}
|
||||
/// Returns the currently active ability.
|
||||
pub fn active_ability(&self) -> Arc<dyn Ability> {
|
||||
pub fn active_ability(&self) -> Result<Arc<dyn Ability>> {
|
||||
if let Some(v) = &self.override_ability {
|
||||
return v.clone();
|
||||
return Ok(v.clone());
|
||||
}
|
||||
self.library
|
||||
|
||||
let form = self.form();
|
||||
let ability = form.get_ability(self.ability_index);
|
||||
Ok(self
|
||||
.library
|
||||
.static_data()
|
||||
.abilities()
|
||||
.get(self.form().get_ability(self.ability_index))
|
||||
.unwrap()
|
||||
.get(ability)
|
||||
.ok_or(PkmnError::InvalidAbilityName {
|
||||
ability: ability.clone(),
|
||||
})?)
|
||||
}
|
||||
|
||||
/// The script for the status.
|
||||
@@ -450,22 +455,23 @@ impl Pokemon {
|
||||
/// Calculates the flat stats on the Pokemon. This should be called when for example the base
|
||||
/// stats, level, nature, IV, or EV changes. This has a side effect of recalculating the boosted
|
||||
/// stats, as those depend on the flat stats.
|
||||
pub fn recalculate_flat_stats(&self) {
|
||||
pub fn recalculate_flat_stats(&self) -> Result<()> {
|
||||
self.library
|
||||
.stat_calculator()
|
||||
.calculate_flat_stats(self, &self.flat_stats);
|
||||
self.recalculate_boosted_stats();
|
||||
.calculate_flat_stats(self, &self.flat_stats)?;
|
||||
self.recalculate_boosted_stats()?;
|
||||
Ok(())
|
||||
}
|
||||
/// Calculates the boosted stats on the Pokemon, _without_ recalculating the flat stats.
|
||||
/// This should be called when a stat boost changes.
|
||||
pub fn recalculate_boosted_stats(&self) {
|
||||
pub fn recalculate_boosted_stats(&self) -> Result<()> {
|
||||
self.library
|
||||
.stat_calculator()
|
||||
.calculate_boosted_stats(self, &self.boosted_stats);
|
||||
.calculate_boosted_stats(self, &self.boosted_stats)
|
||||
}
|
||||
|
||||
/// Change the species of the Pokemon.
|
||||
pub fn change_species(&self, species: Arc<dyn Species>, form: Arc<dyn Form>) {
|
||||
pub fn change_species(&self, species: Arc<dyn Species>, form: Arc<dyn Form>) -> Result<()> {
|
||||
*self.species.write() = species.clone();
|
||||
*self.form.write() = form.clone();
|
||||
|
||||
@@ -474,7 +480,16 @@ impl Pokemon {
|
||||
// If we're in battle, use the battle random for predictability
|
||||
let r = self.battle_data.read();
|
||||
if let Some(data) = r.deref() {
|
||||
let mut random = data.battle().unwrap().random().get_rng().lock().unwrap();
|
||||
let mut random = match data
|
||||
.battle()
|
||||
.ok_or(anyhow!("Battle not set"))?
|
||||
.random()
|
||||
.get_rng()
|
||||
.lock()
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(_) => return Err(PkmnError::UnableToAcquireLock.into()),
|
||||
};
|
||||
*self.gender.write() = species.get_random_gender(random.deref_mut());
|
||||
} else {
|
||||
// If we're not in battle, just use a new random.
|
||||
@@ -495,12 +510,13 @@ impl Pokemon {
|
||||
})
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Change the form of the Pokemon.
|
||||
pub fn change_form(&self, form: &Arc<dyn Form>) {
|
||||
pub fn change_form(&self, form: &Arc<dyn Form>) -> Result<()> {
|
||||
if self.form().value_identifier() == form.value_identifier() {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
*self.form.write() = form.clone();
|
||||
|
||||
@@ -514,21 +530,21 @@ impl Pokemon {
|
||||
self.weight.store(form.weight(), Ordering::SeqCst);
|
||||
self.height.store(form.height(), Ordering::SeqCst);
|
||||
|
||||
let ability = self.active_ability()?;
|
||||
let ability_script = self
|
||||
.library
|
||||
.load_script(self.into(), ScriptCategory::Ability, self.active_ability().name())
|
||||
.unwrap();
|
||||
.load_script(self.into(), ScriptCategory::Ability, ability.name())?;
|
||||
if let Some(ability_script) = ability_script {
|
||||
self.ability_script
|
||||
.set(ability_script)
|
||||
.as_ref()
|
||||
// Ensure the ability script gets initialized with the parameters for the ability.
|
||||
.on_initialize(&self.library, self.active_ability().parameters().to_vec())
|
||||
.on_initialize(&self.library, ability.parameters().to_vec())
|
||||
} else {
|
||||
self.ability_script.clear();
|
||||
}
|
||||
let old_health = self.max_health();
|
||||
self.recalculate_flat_stats();
|
||||
self.recalculate_flat_stats()?;
|
||||
let diff_health = (self.max_health() - old_health) as i32;
|
||||
if self.current_health() == 0 && (self.current_health() as i32) < -diff_health {
|
||||
self.current_health.store(0, Ordering::SeqCst);
|
||||
@@ -547,7 +563,8 @@ impl Pokemon {
|
||||
form: form.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Whether or not the Pokemon is useable in a battle.
|
||||
@@ -657,8 +674,14 @@ impl Pokemon {
|
||||
script_hook!(on_remove, self,);
|
||||
|
||||
if !battle.can_slot_be_filled(battle_data.battle_side_index(), battle_data.index()) {
|
||||
battle.sides()[battle_data.battle_side_index() as usize]
|
||||
.mark_slot_as_unfillable(battle_data.index());
|
||||
battle
|
||||
.sides()
|
||||
.get(battle_data.battle_side_index() as usize)
|
||||
.ok_or(PkmnError::IndexOutOfBounds {
|
||||
index: battle_data.battle_side_index() as usize,
|
||||
len: battle.sides().len(),
|
||||
})?
|
||||
.mark_slot_as_unfillable(battle_data.index())?;
|
||||
}
|
||||
|
||||
battle.validate_battle_state()?;
|
||||
@@ -695,14 +718,22 @@ impl Pokemon {
|
||||
}
|
||||
|
||||
/// Learn a move.
|
||||
pub fn learn_move(&self, move_name: &StringKey, learn_method: MoveLearnMethod) {
|
||||
pub fn learn_move(&self, move_name: &StringKey, learn_method: MoveLearnMethod) -> Result<()> {
|
||||
let mut learned_moves = self.learned_moves().write();
|
||||
let move_pos = learned_moves.iter().position(|a| a.is_none());
|
||||
if move_pos.is_none() {
|
||||
panic!("No more moves with an empty space found.");
|
||||
bail!("No more moves with an empty space found.");
|
||||
}
|
||||
let move_data = self.library.static_data().moves().get(move_name).unwrap();
|
||||
let move_data = self
|
||||
.library
|
||||
.static_data()
|
||||
.moves()
|
||||
.get(move_name)
|
||||
.ok_or(PkmnError::InvalidMoveName {
|
||||
move_name: move_name.clone(),
|
||||
})?;
|
||||
learned_moves[move_pos.unwrap()] = Some(Arc::new(LearnedMove::new(move_data, learn_method)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes the current non-volatile status from the Pokemon.
|
||||
@@ -711,7 +742,7 @@ impl Pokemon {
|
||||
}
|
||||
|
||||
/// Increases the level by a certain amount
|
||||
pub fn change_level_by(&self, amount: LevelInt) {
|
||||
pub fn change_level_by(&self, amount: LevelInt) -> Result<()> {
|
||||
self.level
|
||||
.fetch_update(Ordering::SeqCst, Ordering::Relaxed, |x| {
|
||||
let max_level = self.library().static_data().settings().maximum_level();
|
||||
@@ -721,8 +752,8 @@ impl Pokemon {
|
||||
Some(x + amount)
|
||||
}
|
||||
})
|
||||
.expect("Failed to change level.");
|
||||
self.recalculate_flat_stats();
|
||||
.ok();
|
||||
self.recalculate_flat_stats()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,14 +801,14 @@ impl PokemonBattleData {
|
||||
}
|
||||
|
||||
impl ScriptSource for Pokemon {
|
||||
fn get_script_count(&self) -> usize {
|
||||
fn get_script_count(&self) -> Result<usize> {
|
||||
let mut c = 3;
|
||||
if let Some(battle_data) = &self.battle_data.read().deref() {
|
||||
if let Some(battle) = battle_data.battle() {
|
||||
c += battle.sides()[battle_data.battle_side_index() as usize].get_script_count();
|
||||
c += battle.sides()[battle_data.battle_side_index() as usize].get_script_count()?;
|
||||
}
|
||||
}
|
||||
c
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
fn get_script_source_data(&self) -> &RwLock<ScriptSourceData> {
|
||||
@@ -791,13 +822,14 @@ impl ScriptSource for Pokemon {
|
||||
scripts.push((&self.volatile).into());
|
||||
}
|
||||
|
||||
fn collect_scripts(&self, scripts: &mut Vec<ScriptWrapper>) {
|
||||
fn collect_scripts(&self, scripts: &mut Vec<ScriptWrapper>) -> Result<()> {
|
||||
self.get_own_scripts(scripts);
|
||||
if let Some(battle_data) = &self.battle_data.read().deref() {
|
||||
if let Some(battle) = battle_data.battle() {
|
||||
battle.sides()[battle_data.battle_side_index() as usize].collect_scripts(scripts);
|
||||
battle.sides()[battle_data.battle_side_index() as usize].collect_scripts(scripts)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -883,8 +915,10 @@ pub mod test {
|
||||
static_lib.expect_natures().return_const(Box::new(nature_lib));
|
||||
|
||||
let mut stat_calculator = MockBattleStatCalculator::new();
|
||||
stat_calculator.expect_calculate_flat_stats().returning(|_, _| {});
|
||||
stat_calculator.expect_calculate_boosted_stats().returning(|_, _| {});
|
||||
stat_calculator.expect_calculate_flat_stats().returning(|_, _| Ok(()));
|
||||
stat_calculator
|
||||
.expect_calculate_boosted_stats()
|
||||
.returning(|_, _| Ok(()));
|
||||
|
||||
let mut lib = MockDynamicLibrary::new();
|
||||
lib.expect_static_data().return_const(Box::new(static_lib));
|
||||
|
||||
@@ -64,7 +64,7 @@ impl PokemonBuilder {
|
||||
&"hardy".into(),
|
||||
)?;
|
||||
for learned_move in self.learned_moves {
|
||||
p.learn_move(&learned_move, MoveLearnMethod::Unknown);
|
||||
p.learn_move(&learned_move, MoveLearnMethod::Unknown)?;
|
||||
}
|
||||
|
||||
Ok(p)
|
||||
|
||||
Reference in New Issue
Block a user