1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
use serde::{Deserialize, Serialize};
use std::{
cell::RefCell,
fmt::Display,
ops::RangeInclusive,
rc::{Rc, Weak},
};
use crate::{
effects::{effect::EntityName, stats::Statistics},
error::SAPTestError,
pets::pet::Pet,
shop::store::ShopState,
FoodName,
};
use super::actions::Action;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
/// Possible equality conditions to check.
pub enum EqualityCondition {
/// Is same pet.
IsSelf,
/// Is this tier.
Tier(usize),
/// Has same name.
Name(EntityName),
/// Is this level.
Level(usize),
/// Has this [`Action`].
Action(Box<Action>),
/// Triggered by this [`Status`].
Trigger(Status),
/// Is frozen. Only available for shops.
Frozen,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
/// Conditions a `Team` is in.
pub enum TeamCondition {
/// Previous team fight was win.
PreviousWon,
/// Previous team fight was draw.
PreviousDraw,
/// Previous team fight was loss.
PreviousLoss,
/// Has this many open slots.
OpenSpaceEqual(usize),
/// Has this many pets on team.
NumberPetsEqual(usize),
/// Has this many or more pets on team.
NumberPetsGreaterEqual(usize),
}
impl TryFrom<&TeamCondition> for Status {
type Error = SAPTestError;
fn try_from(value: &TeamCondition) -> Result<Self, Self::Error> {
match value {
TeamCondition::PreviousWon => Ok(Status::WinBattle),
TeamCondition::PreviousDraw => Ok(Status::DrawBattle),
TeamCondition::PreviousLoss => Ok(Status::LoseBattle),
_ => Err(SAPTestError::InvalidTeamAction {
subject: "Convert TeamCondition Failure".to_string(),
reason: "Team Condition must match a possible battle status (ex. Win or Lose)"
.to_string(),
}),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
/// Conditions a `Shop` is in.
pub enum ShopCondition {
/// Shop is in this state.
InState(ShopState),
/// Gold is equal to this amount.
GoldEqual(usize),
/// Gold is greater than or equal to this amount.
GoldGreaterEqual(usize),
}
/// Conditions to select [`Pet`]s or [`ShopItem`](crate::shop::store::ShopItem) by.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub enum ItemCondition {
/// Is the healthiest (highest health) pet.
Healthiest,
/// Is the illest (lowest health) pet.
Illest,
/// Is the strongest (highest attack) pet.
Strongest,
/// Is the weakest (lowest attack) pet.
Weakest,
/// Is highest tier pet.
HighestTier,
/// Is lowest tier pet.
LowestTier,
/// Multiple conditions.
Multiple(Vec<ItemCondition>),
/// Multiple conditions. All must be met to be included.
MultipleAll(Vec<ItemCondition>),
/// Has the quality.
Equal(EqualityCondition),
/// Doesn't have this quality.
NotEqual(EqualityCondition),
/// All alive pets.
None,
}
/// Positions to select pets by.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
pub enum Position {
/// Some number of [`Pet`]s based on a given [`ItemCondition`].
/// * 3rd argument will shuffle any found pets.
N(ItemCondition, usize, bool),
/// Any [`Pet`] that matches a given [`ItemCondition`].
Any(ItemCondition),
/// All [`Pet`]s that match a given [`ItemCondition`].
All(ItemCondition),
/// Position of self.
OnSelf,
/// Pet affected in [`Outcome`] trigger.
TriggerAffected,
/// Pet causing in [`Outcome`] trigger.
TriggerAfflicting,
/// First pet on [`Team`](crate::teams::team::Team).
First,
/// Last pet on [`Team`](crate::teams::team::Team).
Last,
/// Opposite team's pet at the current pet index.
Opposite,
/// Pets ahead of current pet.
Ahead,
/// A specified range on a [`Team`](crate::teams::team::Team).
Range(RangeInclusive<isize>),
/// A [`Pet`] relative to current [`Pet`].
/// * Note: Empty slots are taken into consideration.
Relative(isize),
/// Nearest pet(s) ahead or behind from current [`Pet`].
/// * Negative values check pets behind.
/// * Positive values check pets ahead.
Nearest(isize),
/// Multiple [`Position`]s.
Multiple(Vec<Position>),
/// All [`Pet`]'s adjacent to current index.
Adjacent,
#[default]
/// No position.
None,
}
/// Target team for an effect.
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
pub enum Target {
/// Friend team.
Friend,
/// Enemy team.
Enemy,
/// Shop.
Shop,
/// Either `Friend` or `Enemy` team.
/// * Ex. [Badger](crate::pets::names::PetName::Badger)
Either,
#[default]
/// No target.
None,
}
/// The outcome of any [`Pet`] action. Serve as [`Effect`](crate::Effect) triggers in battle.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Outcome {
/// Status of a [`Pet`].
pub status: Status,
#[serde(skip)]
/// The affected pet.
pub(crate) affected_pet: Option<Weak<RefCell<Pet>>>,
/// The affected team.
pub affected_team: Target,
#[serde(skip)]
/// The pet causing the status_update.
pub(crate) afflicting_pet: Option<Weak<RefCell<Pet>>>,
/// The team causing the status update.
pub afflicting_team: Target,
/// General position on `affected_team`.
pub position: Position,
/// Difference in [`Statistics`] after status update from initial state.
pub(crate) stat_diff: Option<Statistics>,
}
impl PartialEq for Outcome {
fn eq(&self, other: &Self) -> bool {
let same_affected_pet = if let (Some(pet), Some(other_pet)) =
(self.affected_pet.as_ref(), other.affected_pet.as_ref())
{
pet.ptr_eq(other_pet)
} else {
self.affected_pet.is_none() && other.affected_pet.is_none()
};
same_affected_pet
&& self.status == other.status
&& self.position == other.position
&& self.affected_team == other.affected_team
&& self.afflicting_team == other.afflicting_team
}
}
impl Default for Outcome {
fn default() -> Self {
Self {
status: Status::None,
affected_pet: Default::default(),
affected_team: Target::None,
afflicting_pet: Default::default(),
afflicting_team: Target::None,
position: Position::None,
stat_diff: Default::default(),
}
}
}
impl Display for Outcome {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[Status: {:?}, Position: {:?}, Affected: {:?}, From: {:?}]",
self.status, self.position, self.affected_pet, self.afflicting_pet
)
}
}
impl Outcome {
/// Attach the affected pet to this trigger.
/// # Example.
/// ```
/// use std::{rc::Rc, cell::RefCell};
/// use saptest::{Pet, PetName, effects::trigger::TRIGGER_SELF_FAINT};
///
/// let ant = Rc::new(RefCell::new(Pet::try_from(PetName::Ant).unwrap()));
/// let mut faint_trigger = TRIGGER_SELF_FAINT.clone();
/// // Set affected pet to be ant.
/// faint_trigger.set_affected(&ant);
///
/// let affected_pet = faint_trigger.get_affected().unwrap();
/// assert!(affected_pet.ptr_eq(&Rc::downgrade(&ant)));
/// ```
pub fn set_affected(&mut self, pet: &Rc<RefCell<Pet>>) -> &mut Self {
self.affected_pet = Some(Rc::downgrade(pet));
self
}
/// Attach the afflicting pet to this trigger.
/// # Example.
/// ```
/// use std::{rc::Rc, cell::RefCell};
/// use saptest::{Pet, PetName, effects::trigger::TRIGGER_SELF_FAINT};
///
/// let ant = Rc::new(RefCell::new(Pet::try_from(PetName::Ant).unwrap()));
/// let mosquito = Rc::new(RefCell::new(Pet::try_from(PetName::Mosquito).unwrap()));
/// let mut faint_trigger = TRIGGER_SELF_FAINT.clone();
/// // Set affected pet to be ant and afflicting pet to be mosquito.
/// faint_trigger.set_affected(&ant).set_afflicting(&mosquito);
///
/// let afflicting_pet = faint_trigger.get_afflicting().unwrap();
/// assert!(afflicting_pet.ptr_eq(&Rc::downgrade(&mosquito)));
/// ```
pub fn set_afflicting(&mut self, pet: &Rc<RefCell<Pet>>) -> &mut Self {
self.afflicting_pet = Some(Rc::downgrade(pet));
self
}
/// Get the affected pet of a trigger.
/// # Example
/// ```
/// use saptest::effects::trigger::TRIGGER_START_BATTLE;
/// // No single affected pet as affects every pet.
/// assert!(TRIGGER_START_BATTLE.get_affected().is_none())
/// ```
pub fn get_affected(&self) -> Option<Weak<RefCell<Pet>>> {
self.affected_pet.as_ref().cloned()
}
/// Get the afflicting pet of a trigger.
/// # Example
/// ```
/// use saptest::effects::trigger::TRIGGER_START_BATTLE;
/// // No single afflicting pet as no pet causes the start of battle.
/// assert!(TRIGGER_START_BATTLE.get_afflicting().is_none())
/// ```
pub fn get_afflicting(&self) -> Option<Weak<RefCell<Pet>>> {
self.afflicting_pet.as_ref().cloned()
}
}
/// Status of [`Entity`](super::effect::Entity).
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub enum Status {
/// Start of Turn.
StartTurn,
/// End of Turn.
EndTurn,
/// Shop tier upgraded.
ShopTierUpgrade,
/// Start of Battle.
StartOfBattle,
/// After start of battle, prior to first battle.
BeforeFirstBattle,
/// Won the Battle.
WinBattle,
/// Loss the battle.
LoseBattle,
/// Drew
DrawBattle,
/// Before pet attacks.
BeforeAttack,
/// Pet is attacking.
Attack,
/// Any damage calculation
AnyDmgCalc,
/// Indirect dmg attack calculation.
IndirectAttackDmgCalc,
/// Direct dmg attack calculation.
AttackDmgCalc,
/// Pet levels up.
Levelup,
/// Food bought.
BuyFood,
/// Food eaten.
AteFood,
/// Specific food eaten.
AteSpecificFood(FoodName),
/// Pet bought.
BuyPet,
/// Pet sold.
Sell,
/// Shop rolled.
Roll,
/// Pet hurt.
Hurt,
/// Pet fainted.
Faint,
/// Pet knocked out during an attack.
/// * After [`attack`](crate::pets::combat::PetCombat::attack) or [`indirect_attack`](crate::pets::combat::PetCombat::indirect_attack)
KnockOut,
/// Pet summoned.
Summoned,
/// Pet pushed.
Pushed,
/// No status change.
None,
}