Support for new error handling.
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2023-04-15 14:34:42 +02:00
parent 3058739ea0
commit feffb5f030
36 changed files with 466 additions and 274 deletions

View File

@@ -2,6 +2,8 @@ use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::Arc;
use anyhow::Result;
use anyhow_ext::anyhow;
use atomig::Atomic;
use parking_lot::RwLock;
@@ -18,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, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier};
use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier};
/// A pokemon battle, with any amount of sides and pokemon per side.
#[derive(Debug)]
@@ -169,11 +171,9 @@ impl Battle {
/// Get a Pokemon on the battlefield, on a specific side and an index on that side.
pub fn get_pokemon(&self, side: u8, index: u8) -> Option<Arc<Pokemon>> {
let side = self.sides.get(side as usize);
side?;
let pokemon_read_lock = side.unwrap().pokemon();
let pokemon_read_lock = side?.pokemon();
let pokemon = pokemon_read_lock.get(index as usize);
pokemon?;
pokemon.unwrap().clone()
pokemon?.clone()
}
/// Returns whether a slot on the battlefield can still be filled. If no party is responsible
@@ -190,10 +190,10 @@ impl Battle {
/// Validates whether the battle is still in a non-ended state. If the battle has ended, this
/// properly sets who has won etc.
pub fn validate_battle_state(&self) {
pub fn validate_battle_state(&self) -> Result<()> {
// If we've already ended, we dont need to run this.
if self.has_ended() {
return;
return Ok(());
}
let mut surviving_side_exists = false;
let mut winning_side = None;
@@ -205,13 +205,13 @@ impl Battle {
self.result.data_ptr().replace(BattleResult::Inconclusive);
}
self.has_ended.store(true, Ordering::SeqCst);
return;
return Ok(());
}
// If the side is not defeated
if !side.is_defeated() {
// More than 1 surviving side. Battle is not ended
if surviving_side_exists {
return;
return Ok(());
}
surviving_side_exists = true;
winning_side = Some(side_index as u8);
@@ -228,12 +228,13 @@ impl Battle {
else {
let _w = self.result.write();
unsafe {
self.result
.data_ptr()
.replace(BattleResult::Conclusive(winning_side.unwrap()));
self.result.data_ptr().replace(BattleResult::Conclusive(
winning_side.ok_or(anyhow!("Winning side was not set"))?,
));
}
}
self.has_ended.store(true, Ordering::SeqCst);
Ok(())
}
/// Checks whether a choice is actually possible.
@@ -260,7 +261,7 @@ impl Battle {
}
/// Try and set the choice for the battle. If the choice is not valid, this returns false.
pub fn try_set_choice(&self, choice: TurnChoice) -> PkmnResult<bool> {
pub fn try_set_choice(&self, choice: TurnChoice) -> Result<bool> {
if !self.can_use(&choice) {
return Ok(false);
}
@@ -268,17 +269,19 @@ impl Battle {
return Ok(false);
}
let side = choice.user().get_battle_side_index();
if side.is_none() {
return Ok(false);
match side {
Some(side) => {
self.sides[side as usize].set_choice(choice);
self.check_choices_set_and_run()?;
Ok(true)
}
None => Ok(false),
}
self.sides[side.unwrap() as usize].set_choice(choice);
self.check_choices_set_and_run()?;
Ok(true)
}
/// Checks to see whether all Pokemon on the field have set their choices. If so, we then run
/// the turn.
fn check_choices_set_and_run(&self) -> PkmnResult<()> {
fn check_choices_set_and_run(&self) -> Result<()> {
for side in &self.sides {
if !side.all_choices_set() {
return Ok(());
@@ -292,10 +295,9 @@ impl Battle {
for side in &self.sides {
let mut side_choices = side.choices().write();
for choice_opt in side_choices.deref_mut() {
if choice_opt.is_none() {
panic!("Choice was none, but all choices were set? Logic error.");
}
let mut choice = choice_opt.as_mut().unwrap();
let mut choice = choice_opt
.as_mut()
.ok_or(anyhow!("Choice was none, but all choices were set? Logic error."))?;
let c = choice.deref();
if let TurnChoice::Move(data) = c {
let mut change_priority = data.priority();
@@ -312,7 +314,7 @@ impl Battle {
script_hook!(change_speed, c, c, &mut speed);
*choice.speed_mut() = speed;
choice.set_random_value(self.random.get() as u32);
choice.set_random_value(self.random.get()? as u32);
choices.push(choice_opt.take());
}
// Drop the lock guard, as we need to write into it in reset_choices.
@@ -331,32 +333,41 @@ impl Battle {
self.event_hook.trigger(Event::EndTurn);
let end_time = chrono::Utc::now();
let time = end_time - start_time;
self.last_turn_time
.store(time.num_nanoseconds().unwrap() as u64, Ordering::SeqCst);
match time.num_nanoseconds() {
None => {}
Some(v) => {
self.last_turn_time.store(v as u64, Ordering::SeqCst);
}
}
Ok(())
}
/// Sets the current weather for the battle. If None is passed, this clears the weather.
pub fn set_weather(&self, weather: Option<StringKey>) {
pub fn set_weather(&self, weather: Option<StringKey>) -> Result<()> {
if let Some(weather) = weather {
let script = self
.library()
.load_script(self.into(), ScriptCategory::Weather, &weather)
.unwrap()
.unwrap_or_else(|| panic!("Couldn't find weather script by name {}", weather));
.load_script(self.into(), ScriptCategory::Weather, &weather)?
.ok_or(anyhow!("Couldn't find weather script by name {}", weather))?;
self.weather.set(script);
} else {
self.weather.clear();
}
Ok(())
}
/// Gets the current weather of the battle. If no weather is present, this returns None.
pub fn weather_name(&self) -> Option<StringKey> {
pub fn weather_name(&self) -> Result<Option<StringKey>> {
if let Some(script) = self.weather.get() {
let lock = script.read();
Some(lock.as_ref().unwrap().name().clone())
Ok(Some(
lock.as_ref()
.ok_or(anyhow!("Failed to get a lock on weather"))?
.name()
.clone(),
))
} else {
None
Ok(None)
}
}
}
@@ -366,7 +377,7 @@ impl VolatileScriptsOwner for Battle {
&self.volatile_scripts
}
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
fn load_volatile_script(&self, key: &StringKey) -> Result<Option<Arc<dyn Script>>> {
self.library.load_script(self.into(), ScriptCategory::Battle, key)
}
}

View File

@@ -1,3 +1,5 @@
use anyhow::Result;
use anyhow_ext::anyhow;
use std::fmt::{Debug, Formatter};
use std::sync::{Arc, Mutex};
@@ -32,16 +34,25 @@ impl BattleRandom {
}
/// Get a random 32 bit integer. Can be any value between min int and max int.
pub fn get(&self) -> i32 {
return self.get_rng().lock().unwrap().get();
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")),
}
}
/// Get a random 32 bit integer between 0 and max.
pub fn get_max(&self, max: i32) -> i32 {
return self.get_rng().lock().unwrap().get_max(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")),
}
}
/// Get a random 32 bit integer between min and max.
pub fn get_between(&self, min: i32, max: i32) -> i32 {
return self.get_rng().lock().unwrap().get_between(min, 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")),
}
}
/// Gets whether or not a move triggers its secondary effect. This takes its chance, and
@@ -53,7 +64,7 @@ impl BattleRandom {
executing_move: &ExecutingMove,
target: &Arc<Pokemon>,
hit_number: u8,
) -> bool {
) -> Result<bool> {
script_hook!(
change_effect_chance,
executing_move,
@@ -72,12 +83,15 @@ impl BattleRandom {
);
if chance < 100.0 {
if chance > 0.0 {
self.get_rng().lock().unwrap().get_float() < (chance / 100.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")),
}
} else {
false
Ok(false)
}
} else {
true
Ok(true)
}
}
}
@@ -92,6 +106,10 @@ impl Clone for BattleRandom {
fn clone(&self) -> Self {
Self {
identifier: Default::default(),
// As cloning when we can't get a lock on the randomness is completely impossible, we
// should unwrap here.
#[allow(clippy::unwrap_used)]
random: Mutex::new(self.random.lock().unwrap().clone()),
}
}

View File

@@ -2,6 +2,7 @@ use std::ops::Deref;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::Arc;
use anyhow::Result;
use parking_lot::lock_api::RwLockReadGuard;
use parking_lot::{RawRwLock, RwLock};
@@ -14,7 +15,7 @@ use crate::dynamic_data::script_handling::{ScriptSource, ScriptSourceData, Scrip
use crate::dynamic_data::Script;
use crate::dynamic_data::ScriptSet;
use crate::dynamic_data::VolatileScriptsOwner;
use crate::{script_hook, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier};
use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier};
/// A side on a battle.
#[derive(Debug)]
@@ -248,9 +249,9 @@ impl BattleSide {
}
/// Gets a random Pokemon on the given side.
pub fn get_random_creature_index(&self) -> u8 {
pub fn get_random_creature_index(&self) -> Result<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
Ok(self.battle().random().get_max(self.pokemon_per_side as i32)? as u8)
}
/// Swap two Pokemon on a single side around.
@@ -299,7 +300,7 @@ impl VolatileScriptsOwner for BattleSide {
&self.volatile_scripts
}
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
fn load_volatile_script(&self, key: &StringKey) -> Result<Option<Arc<dyn Script>>> {
self.battle()
.library()
.load_script(self.into(), crate::ScriptCategory::Side, key)

View File

@@ -2,6 +2,8 @@ use std::ops::Deref;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU8, Ordering};
use std::sync::Arc;
use anyhow::Result;
use anyhow_ext::bail;
use atomig::Atomic;
use parking_lot::RwLock;
@@ -11,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::{PkmnResult, PokemonError, ValueIdentifiable, ValueIdentifier};
use crate::{ValueIdentifiable, ValueIdentifier};
/// A hit data is the data for a single hit, on a single target.
#[derive(Default, Debug)]
@@ -164,7 +166,7 @@ impl ExecutingMove {
}
/// Gets a hit data for a target, with a specific index.
pub fn get_hit_data(&self, for_target: &Pokemon, hit: u8) -> PkmnResult<&HitData> {
pub fn get_hit_data(&self, for_target: &Pokemon, hit: u8) -> Result<&HitData> {
for (index, target) in self.targets.iter().enumerate() {
if let Some(target) = target {
if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) {
@@ -173,7 +175,7 @@ impl ExecutingMove {
}
}
}
Err(PokemonError::InvalidTargetRequested)
bail!("Invalid target requested");
}
/// Checks whether a Pokemon is a target for this move.
@@ -187,7 +189,7 @@ impl ExecutingMove {
}
/// Gets the index of the hits in this move where the hits for a specific target start.
pub(crate) fn get_index_of_target(&self, for_target: &Arc<Pokemon>) -> PkmnResult<usize> {
pub(crate) fn get_index_of_target(&self, for_target: &Arc<Pokemon>) -> Result<usize> {
for (index, target) in self.targets.iter().enumerate() {
if let Some(target) = target {
if std::ptr::eq(target.deref().deref(), for_target.deref().deref()) {
@@ -196,7 +198,7 @@ impl ExecutingMove {
}
}
}
Err(PokemonError::InvalidTargetRequested)
bail!("Invalid target requested");
}
/// Gets a hit based on its raw index.

View File

@@ -22,7 +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, PkmnResult, StringKey, ValueIdentifiable, ValueIdentifier};
use crate::{script_hook, StringKey, ValueIdentifiable, ValueIdentifier};
use anyhow::Result;
/// An individual Pokemon as we know and love them.
#[derive(Debug)]
@@ -129,12 +130,12 @@ impl Pokemon {
gender: Gender,
coloring: u8,
nature: &StringKey,
) -> Self {
) -> Result<Self> {
// Calculate experience from the level for the specified growth rate.
let experience = library
.static_data()
.growth_rates()
.calculate_experience(species.growth_rate(), level);
.calculate_experience(species.growth_rate(), level)?;
let weight = form.weight();
let height = form.height();
let nature = library
@@ -183,7 +184,7 @@ impl Pokemon {
let health = pokemon.flat_stats().hp();
pokemon.current_health = AtomicU32::new(health);
pokemon
Ok(pokemon)
}
/// The library data of the Pokemon.
@@ -617,12 +618,12 @@ impl Pokemon {
}
/// Damages the Pokemon by a certain amount of damage, from a damage source.
pub fn damage(&self, mut damage: u32, source: DamageSource) {
pub fn damage(&self, mut damage: u32, source: DamageSource) -> Result<()> {
if damage > self.current_health() {
damage = self.current_health();
}
if damage == 0 {
return;
return Ok(());
}
let new_health = self.current_health() - damage;
if let Some(battle_data) = &self.battle_data.read().deref() {
@@ -641,12 +642,13 @@ impl Pokemon {
self.current_health.store(new_health, Ordering::SeqCst);
if self.is_fainted() && damage > 0 {
self.on_faint(source);
self.on_faint(source)?;
}
Ok(())
}
/// Triggers when the Pokemon faints.
fn on_faint(&self, source: DamageSource) {
fn on_faint(&self, source: DamageSource) -> Result<()> {
let r = self.battle_data.read();
if let Some(battle_data) = r.deref() {
if let Some(battle) = battle_data.battle() {
@@ -659,9 +661,10 @@ impl Pokemon {
.mark_slot_as_unfillable(battle_data.index());
}
battle.validate_battle_state();
battle.validate_battle_state()?;
}
}
Ok(())
}
/// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not
@@ -803,7 +806,7 @@ impl VolatileScriptsOwner for Pokemon {
&self.volatile
}
fn load_volatile_script(&self, key: &StringKey) -> PkmnResult<Option<Arc<dyn Script>>> {
fn load_volatile_script(&self, key: &StringKey) -> Result<Option<Arc<dyn Script>>> {
self.library.load_script(self.into(), ScriptCategory::Pokemon, key)
}
}
@@ -866,7 +869,9 @@ pub mod test {
static_lib.expect_species().return_const(Box::new(species_lib));
let mut growth_rate_lib = MockGrowthRateLibrary::new();
growth_rate_lib.expect_calculate_experience().return_const(1000u32);
growth_rate_lib
.expect_calculate_experience()
.returning(|_, _| Ok(1000u32));
let mut nature_lib = MockNatureLibrary::new();
nature_lib.expect_get_nature().returning(|_| {
@@ -907,7 +912,8 @@ pub mod test {
Gender::Male,
0,
&"test_nature".into(),
);
)
.unwrap();
assert_eq!(pokemon.species().name(), &"test_species".into());
assert_eq!(pokemon.form().name(), &"default".into());
}

View File

@@ -6,6 +6,7 @@ use crate::dynamic_data::models::pokemon::Pokemon;
use crate::dynamic_data::DynamicLibrary;
use crate::static_data::{AbilityIndex, Gender};
use crate::{Random, StringKey};
use anyhow::Result;
/// This allows for the easy chain building of a Pokemon.
pub struct PokemonBuilder {
@@ -39,7 +40,7 @@ impl PokemonBuilder {
}
/// Finally turn the builder into an actual Pokemon.
pub fn build(self) -> Pokemon {
pub fn build(self) -> Result<Pokemon> {
let mut random = if let Some(seed) = self.random_seed {
Random::new(seed)
} else {
@@ -61,11 +62,11 @@ impl PokemonBuilder {
Gender::Male,
0,
&"hardy".into(),
);
)?;
for learned_move in self.learned_moves {
p.learn_move(&learned_move, MoveLearnMethod::Unknown);
}
p
Ok(p)
}
}