sb-core: constants module (WI-2, issue #2)
This commit is contained in:
parent
337e9bcf2c
commit
093012af20
6 changed files with 223 additions and 45 deletions
|
|
@ -1,3 +1,4 @@
|
|||
use crate::constants;
|
||||
use crate::spells;
|
||||
use sb_shared::{Character, CharacterStats, Class, Race};
|
||||
|
||||
|
|
@ -186,11 +187,11 @@ pub const CLASSES: &[ClassInfo] = &[
|
|||
];
|
||||
|
||||
pub fn stat_bonus(stat: i64) -> i64 {
|
||||
(stat - 20) / 2
|
||||
(stat - constants::STAT_BONUS_NEUTRAL) / constants::STAT_BONUS_STEP
|
||||
}
|
||||
|
||||
fn roll_stat(rng: &mut impl rand::Rng, modifier: i64) -> i64 {
|
||||
let base: i64 = rng.random_range(6..=16);
|
||||
let base: i64 = rng.random_range(constants::STAT_ROLL_MIN..=constants::STAT_ROLL_MAX);
|
||||
base + modifier
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +222,7 @@ pub fn calc_max_energy(class: &Class) -> i32 {
|
|||
.iter()
|
||||
.find(|c| &c.class == class)
|
||||
.expect("invalid class");
|
||||
30 + info.base_hp / 3
|
||||
constants::BASE_MAX_ENERGY + info.base_hp / constants::MAX_ENERGY_PER_BASE_HP_DIVISOR
|
||||
}
|
||||
|
||||
pub fn calc_max_nerve(class: &Class) -> i32 {
|
||||
|
|
@ -229,19 +230,19 @@ pub fn calc_max_nerve(class: &Class) -> i32 {
|
|||
.iter()
|
||||
.find(|c| &c.class == class)
|
||||
.expect("invalid class");
|
||||
100 + info.base_hp / 2
|
||||
constants::BASE_MAX_NERVE + info.base_hp / constants::MAX_NERVE_PER_BASE_HP_DIVISOR
|
||||
}
|
||||
|
||||
pub fn energy_regen_rate() -> i64 {
|
||||
180000
|
||||
constants::ENERGY_REGEN_INTERVAL_MS
|
||||
}
|
||||
|
||||
pub fn nerve_regen_rate() -> i64 {
|
||||
300000
|
||||
constants::NERVE_REGEN_INTERVAL_MS
|
||||
}
|
||||
|
||||
pub fn hp_regen_rate() -> i64 {
|
||||
300000
|
||||
constants::HP_REGEN_INTERVAL_MS
|
||||
}
|
||||
|
||||
pub fn apply_regen(char: &mut Character, now_ms: i64) {
|
||||
|
|
@ -259,15 +260,16 @@ pub fn apply_regen(char: &mut Character, now_ms: i64) {
|
|||
char.nerve = (char.nerve + nerve_ticks).min(char.max_nerve);
|
||||
}
|
||||
if hp_ticks > 0 && char.hp > 0 && char.hp < char.max_hp {
|
||||
let regen_per_tick = (char.max_hp as f64 * 0.05).ceil() as i32;
|
||||
let regen_per_tick =
|
||||
(char.max_hp as f64 * constants::HP_REGEN_FRACTION_PER_TICK).ceil() as i32;
|
||||
char.hp = (char.hp + regen_per_tick * hp_ticks).min(char.max_hp);
|
||||
}
|
||||
char.last_regen = now_ms;
|
||||
}
|
||||
|
||||
pub fn training_cost(stat: i64) -> i32 {
|
||||
let scaled = (stat as f64 / 10.0).floor() as i32;
|
||||
(5.0_f64 * 1.3_f64.powi(scaled)) as i32
|
||||
let scaled = (stat as f64 / constants::TRAINING_STAT_SCALER).floor() as i32;
|
||||
(constants::TRAINING_BASE_COST * constants::TRAINING_GROWTH_FACTOR.powi(scaled)) as i32
|
||||
}
|
||||
|
||||
pub fn create_character(
|
||||
|
|
@ -299,7 +301,7 @@ pub fn create_character(
|
|||
name,
|
||||
race,
|
||||
class,
|
||||
level: 1,
|
||||
level: constants::STARTING_LEVEL,
|
||||
xp: 0,
|
||||
stats,
|
||||
hp: max_hp,
|
||||
|
|
@ -308,7 +310,7 @@ pub fn create_character(
|
|||
max_energy,
|
||||
nerve: max_nerve,
|
||||
max_nerve,
|
||||
gold: 50,
|
||||
gold: constants::STARTING_GOLD,
|
||||
last_regen: 0,
|
||||
equipment: [
|
||||
("head".into(), None),
|
||||
|
|
@ -342,11 +344,11 @@ pub fn xp_for_level(level: i32) -> i64 {
|
|||
return 0;
|
||||
}
|
||||
let n = (level - 1) as f64;
|
||||
XP_FACTOR * (100.0 * n * n) as i64
|
||||
XP_FACTOR * (constants::XP_CURVE_COEFFICIENT * n * n) as i64
|
||||
}
|
||||
|
||||
pub fn level_up_cost(level: i32) -> i32 {
|
||||
500 * level
|
||||
constants::LEVEL_UP_GOLD_PER_LEVEL * level
|
||||
}
|
||||
|
||||
pub fn level_up(char: &mut Character) -> Result<i32, &'static str> {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::constants;
|
||||
use rand::Rng;
|
||||
use sb_shared::{AttackType, CombatLog, CombatRound, CritEntry, CritResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -32,7 +33,8 @@ impl CombatStats {
|
|||
|
||||
pub fn defensive_bonus(&self) -> i32 {
|
||||
let dex_bonus = super::character::stat_bonus(self.dexterity) as i32;
|
||||
let con_bonus = (super::character::stat_bonus(self.constitution) as i32) / 2;
|
||||
let con_bonus = (super::character::stat_bonus(self.constitution) as i32)
|
||||
/ constants::DEFENSIVE_CON_BONUS_DIVISOR;
|
||||
dex_bonus + con_bonus + self.db_base
|
||||
}
|
||||
}
|
||||
|
|
@ -52,21 +54,27 @@ pub fn crit_damage_cap(attacker_level: i32, defender_level: i32, defender_hp: i3
|
|||
return None;
|
||||
}
|
||||
let cap_pct = match diff {
|
||||
1..=9 => return None,
|
||||
10..=19 => 0.60,
|
||||
20..=49 => 0.40,
|
||||
50..=99 => 0.15,
|
||||
_ => 0.05,
|
||||
1..=constants::CRIT_CAP_DISABLED_MAX_LEVEL_GAP => return None,
|
||||
constants::CRIT_CAP_TIER1_MIN_GAP..=constants::CRIT_CAP_TIER1_MAX_GAP => {
|
||||
constants::CRIT_CAP_TIER1_RATIO
|
||||
}
|
||||
constants::CRIT_CAP_TIER2_MIN_GAP..=constants::CRIT_CAP_TIER2_MAX_GAP => {
|
||||
constants::CRIT_CAP_TIER2_RATIO
|
||||
}
|
||||
constants::CRIT_CAP_TIER3_MIN_GAP..=constants::CRIT_CAP_TIER3_MAX_GAP => {
|
||||
constants::CRIT_CAP_TIER3_RATIO
|
||||
}
|
||||
_ => constants::CRIT_CAP_TIER4_RATIO,
|
||||
};
|
||||
Some((defender_hp as f64 * cap_pct) as i32)
|
||||
}
|
||||
|
||||
pub fn open_ended_roll(rng: &mut impl Rng) -> i32 {
|
||||
let roll: i32 = rng.random_range(1..=100);
|
||||
let roll: i32 = rng.random_range(constants::ATTACK_ROLL_MIN..=constants::ATTACK_ROLL_MAX);
|
||||
let mut total = roll;
|
||||
if roll >= 96 {
|
||||
if roll >= constants::OPEN_END_HIGH_THRESHOLD {
|
||||
total += open_ended_roll(rng);
|
||||
} else if roll <= 5 {
|
||||
} else if roll <= constants::OPEN_END_LOW_THRESHOLD {
|
||||
total -= open_ended_roll(rng);
|
||||
}
|
||||
total
|
||||
|
|
@ -120,13 +128,13 @@ pub fn resolve_attack(
|
|||
}
|
||||
|
||||
pub fn crit_severity(result: i32) -> Option<String> {
|
||||
if result >= 151 {
|
||||
if result >= constants::CRIT_SEVERITY_D_MIN {
|
||||
Some("D".into())
|
||||
} else if result >= 101 {
|
||||
} else if result >= constants::CRIT_SEVERITY_C_MIN {
|
||||
Some("C".into())
|
||||
} else if result >= 51 {
|
||||
} else if result >= constants::CRIT_SEVERITY_B_MIN {
|
||||
Some("B".into())
|
||||
} else if result >= 21 {
|
||||
} else if result >= constants::CRIT_SEVERITY_A_MIN {
|
||||
Some("A".into())
|
||||
} else {
|
||||
None
|
||||
|
|
@ -246,7 +254,7 @@ pub fn resolve_combat_full(
|
|||
}
|
||||
|
||||
if song_effect == Some("heal") && a_hp > 0 && a_hp < attacker.hp {
|
||||
let heal_amt = (attacker.hp as f64 * 0.03).ceil() as i32;
|
||||
let heal_amt = (attacker.hp as f64 * constants::BARD_SONG_HEAL_FRACTION).ceil() as i32;
|
||||
a_hp = (a_hp + heal_amt).min(attacker.hp);
|
||||
}
|
||||
|
||||
|
|
@ -265,10 +273,10 @@ fn apply_song_to_attacker(stats: &CombatStats, song_effect: Option<&str>) -> Com
|
|||
let mut s = stats.clone();
|
||||
match song_effect {
|
||||
Some("accuracy") => {
|
||||
s.ob_base += 15;
|
||||
s.ob_base += constants::SONG_ACCURACY_OB_BONUS;
|
||||
}
|
||||
Some("damage") => {
|
||||
s.ob_base += 20;
|
||||
s.ob_base += constants::SONG_DAMAGE_OB_BONUS;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -279,10 +287,10 @@ fn apply_song_to_defender(stats: &CombatStats, song_effect: Option<&str>) -> Com
|
|||
let mut s = stats.clone();
|
||||
match song_effect {
|
||||
Some("defense") | Some("magic_resist") => {
|
||||
s.db_base += 10;
|
||||
s.db_base += constants::SONG_DEFENSE_DB_BONUS;
|
||||
}
|
||||
Some("weaken") => {
|
||||
s.ob_base = (s.ob_base as f64 * 0.7) as i32;
|
||||
s.ob_base = (s.ob_base as f64 * constants::SONG_WEAKEN_OB_FACTOR) as i32;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
|||
131
crates/sb-core/src/constants.rs
Normal file
131
crates/sb-core/src/constants.rs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
//! Game-balance constants — the single audit home for tuning scalars.
|
||||
//! Static data tables (`RACES`, `CLASSES`, `BARD_SONGS`, `TRAINING_GROUNDS`)
|
||||
//! stay in their modules; only tuning scalars live here.
|
||||
|
||||
pub use crate::character::{MAX_LEVEL, XP_FACTOR};
|
||||
|
||||
/// d100 attack roll bounds (origin: combat.rs:66).
|
||||
pub const ATTACK_ROLL_MIN: i32 = 1;
|
||||
/// d100 attack roll bounds (origin: combat.rs:66).
|
||||
pub const ATTACK_ROLL_MAX: i32 = 100;
|
||||
|
||||
/// Open-ended roll: re-roll upward at or above this value (origin: combat.rs:68).
|
||||
pub const OPEN_END_HIGH_THRESHOLD: i32 = 96;
|
||||
/// Open-ended roll: re-roll downward at or below this value (origin: combat.rs:70).
|
||||
pub const OPEN_END_LOW_THRESHOLD: i32 = 5;
|
||||
|
||||
/// Crit severity A floor (origin: combat.rs:129).
|
||||
pub const CRIT_SEVERITY_A_MIN: i32 = 21;
|
||||
/// Crit severity B floor (origin: combat.rs:127).
|
||||
pub const CRIT_SEVERITY_B_MIN: i32 = 51;
|
||||
/// Crit severity C floor (origin: combat.rs:125).
|
||||
pub const CRIT_SEVERITY_C_MIN: i32 = 101;
|
||||
/// Crit severity D floor (origin: combat.rs:123).
|
||||
pub const CRIT_SEVERITY_D_MIN: i32 = 151;
|
||||
|
||||
/// Level gaps up to this value apply no crit cap (origin: combat.rs:56).
|
||||
pub const CRIT_CAP_DISABLED_MAX_LEVEL_GAP: i32 = 9;
|
||||
/// Crit cap tier 1 level-gap range (origin: combat.rs:57).
|
||||
pub const CRIT_CAP_TIER1_MIN_GAP: i32 = 10;
|
||||
/// Crit cap tier 1 level-gap range (origin: combat.rs:57).
|
||||
pub const CRIT_CAP_TIER1_MAX_GAP: i32 = 19;
|
||||
/// Crit cap tier 2 level-gap range (origin: combat.rs:58).
|
||||
pub const CRIT_CAP_TIER2_MIN_GAP: i32 = 20;
|
||||
/// Crit cap tier 2 level-gap range (origin: combat.rs:58).
|
||||
pub const CRIT_CAP_TIER2_MAX_GAP: i32 = 49;
|
||||
/// Crit cap tier 3 level-gap range (origin: combat.rs:59).
|
||||
pub const CRIT_CAP_TIER3_MIN_GAP: i32 = 50;
|
||||
/// Crit cap tier 3 level-gap range (origin: combat.rs:59).
|
||||
pub const CRIT_CAP_TIER3_MAX_GAP: i32 = 99;
|
||||
/// Crit cap tier 4 applies from this gap upward (origin: combat.rs:60).
|
||||
pub const CRIT_CAP_TIER4_MIN_GAP: i32 = 100;
|
||||
|
||||
/// Crit damage cap ratio for level gaps 10–19 (origin: combat.rs:57).
|
||||
pub const CRIT_CAP_TIER1_RATIO: f64 = 0.60;
|
||||
/// Crit damage cap ratio for level gaps 20–49 (origin: combat.rs:58).
|
||||
pub const CRIT_CAP_TIER2_RATIO: f64 = 0.40;
|
||||
/// Crit damage cap ratio for level gaps 50–99 (origin: combat.rs:59).
|
||||
pub const CRIT_CAP_TIER3_RATIO: f64 = 0.15;
|
||||
/// Crit damage cap ratio for level gaps 100 and above (origin: combat.rs:60).
|
||||
pub const CRIT_CAP_TIER4_RATIO: f64 = 0.05;
|
||||
|
||||
/// Constitution contributes half its stat bonus to defense (origin: combat.rs:36).
|
||||
pub const DEFENSIVE_CON_BONUS_DIVISOR: i32 = 2;
|
||||
|
||||
/// Accuracy song offensive-bonus bonus (origin: combat.rs:246).
|
||||
pub const SONG_ACCURACY_OB_BONUS: i32 = 15;
|
||||
/// Damage song offensive-bonus bonus (origin: combat.rs:247).
|
||||
pub const SONG_DAMAGE_OB_BONUS: i32 = 20;
|
||||
/// Defense song defensive-bonus bonus (origin: combat.rs:256).
|
||||
pub const SONG_DEFENSE_DB_BONUS: i32 = 10;
|
||||
/// Weaken song reduces the foe's offensive bonus to this fraction (origin: combat.rs:257).
|
||||
pub const SONG_WEAKEN_OB_FACTOR: f64 = 0.7;
|
||||
|
||||
/// Bard heal song: fraction of max HP restored per round (origin: combat.rs:230).
|
||||
pub const BARD_SONG_HEAL_FRACTION: f64 = 0.03;
|
||||
|
||||
/// Stat bonus curve: bonus is zero at this stat value (origin: character.rs:59).
|
||||
pub const STAT_BONUS_NEUTRAL: i64 = 20;
|
||||
/// Stat bonus curve: bonus step size per 2 stat points (origin: character.rs:59).
|
||||
pub const STAT_BONUS_STEP: i64 = 2;
|
||||
|
||||
/// Character creation stat roll bounds (origin: character.rs:63).
|
||||
pub const STAT_ROLL_MIN: i64 = 6;
|
||||
/// Character creation stat roll bounds (origin: character.rs:63).
|
||||
pub const STAT_ROLL_MAX: i64 = 16;
|
||||
|
||||
/// Base maximum energy before the class contribution (origin: character.rs:85).
|
||||
pub const BASE_MAX_ENERGY: i32 = 30;
|
||||
/// Energy gains one point per this many base HP (origin: character.rs:85).
|
||||
pub const MAX_ENERGY_PER_BASE_HP_DIVISOR: i32 = 3;
|
||||
|
||||
/// Base maximum nerve before the class contribution (origin: character.rs:90).
|
||||
pub const BASE_MAX_NERVE: i32 = 100;
|
||||
/// Nerve gains one point per this many base HP (origin: character.rs:90).
|
||||
pub const MAX_NERVE_PER_BASE_HP_DIVISOR: i32 = 2;
|
||||
|
||||
/// Energy regen tick interval in milliseconds (origin: character.rs:94).
|
||||
pub const ENERGY_REGEN_INTERVAL_MS: i64 = 180_000;
|
||||
/// Nerve regen tick interval in milliseconds (origin: character.rs:98).
|
||||
pub const NERVE_REGEN_INTERVAL_MS: i64 = 300_000;
|
||||
/// HP regen tick interval in milliseconds (origin: character.rs:102).
|
||||
pub const HP_REGEN_INTERVAL_MS: i64 = 300_000;
|
||||
|
||||
/// HP restored per regen tick, as a fraction of max HP (origin: character.rs:118).
|
||||
pub const HP_REGEN_FRACTION_PER_TICK: f64 = 0.05;
|
||||
|
||||
/// Training cost curve base cost (origin: character.rs:126).
|
||||
pub const TRAINING_BASE_COST: f64 = 5.0;
|
||||
/// Training cost curve growth factor (origin: character.rs:126).
|
||||
pub const TRAINING_GROWTH_FACTOR: f64 = 1.3;
|
||||
/// Training cost curve stat scaler (origin: character.rs:125).
|
||||
pub const TRAINING_STAT_SCALER: f64 = 10.0;
|
||||
|
||||
/// Energy spent per training session (origin: services.rs:85, 100).
|
||||
pub const TRAINING_ENERGY_COST: i32 = 1;
|
||||
|
||||
/// Level assigned at character creation (origin: character.rs:157).
|
||||
pub const STARTING_LEVEL: i32 = 1;
|
||||
/// Gold assigned at character creation (origin: character.rs:166).
|
||||
pub const STARTING_GOLD: i32 = 50;
|
||||
|
||||
/// XP curve coefficient: 18 · 100 · (level−1)² (origin: character.rs:196).
|
||||
pub const XP_CURVE_COEFFICIENT: f64 = 100.0;
|
||||
|
||||
/// Gold cost per target level for level-up (origin: character.rs:200).
|
||||
pub const LEVEL_UP_GOLD_PER_LEVEL: i32 = 500;
|
||||
|
||||
/// Caster levels per spell level: (level / 3) + 1 curve (origin: spells.rs:59).
|
||||
pub const CASTER_LEVELS_PER_SPELL_LEVEL: i32 = 3;
|
||||
/// Spell points granted per caster spell level (origin: spells.rs:62).
|
||||
pub const SPELL_POINTS_PER_SPELL_LEVEL: i32 = 10;
|
||||
|
||||
/// Spell learn cost table, indexed by spell level − 1 (origin: spells.rs:44–52).
|
||||
pub const SPELL_LEARN_COST_BY_LEVEL: [i32; 7] = [0, 1000, 2000, 4000, 7000, 10000, 20000];
|
||||
/// Learn cost reported for spell levels outside 1..=7 (origin: spells.rs:52).
|
||||
pub const SPELL_LEARN_COST_UNKNOWN_LEVEL: i32 = 999_999;
|
||||
|
||||
/// Lowest learn-request spell level — request validation only (origin: routes/mod.rs:1146).
|
||||
pub const MIN_SPELL_LEVEL: i32 = 1;
|
||||
/// Highest learn-request spell level — request validation only (origin: routes/mod.rs:1146).
|
||||
pub const MAX_SPELL_LEVEL: i32 = 7;
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub mod character;
|
||||
pub mod combat;
|
||||
pub mod constants;
|
||||
pub mod data;
|
||||
pub mod items;
|
||||
pub mod services;
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ pub fn unequip_item(char: &mut Character, slot: &str) -> Result<String, &'static
|
|||
}
|
||||
|
||||
pub fn train_stat(char: &mut Character, stat: &str) -> Result<i64, &'static str> {
|
||||
if char.energy < 1 {
|
||||
if char.energy < crate::constants::TRAINING_ENERGY_COST {
|
||||
return Err("not enough energy");
|
||||
}
|
||||
let current = match stat {
|
||||
|
|
@ -115,7 +115,7 @@ pub fn train_stat(char: &mut Character, stat: &str) -> Result<i64, &'static str>
|
|||
if char.gold < cost {
|
||||
return Err("not enough gold");
|
||||
}
|
||||
char.energy -= 1;
|
||||
char.energy -= crate::constants::TRAINING_ENERGY_COST;
|
||||
char.gold -= cost;
|
||||
match stat {
|
||||
"strength" => char.stats.strength += 1,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use crate::constants;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
@ -41,25 +42,24 @@ pub fn find_spell(id: &str) -> Option<&'static SpellDef> {
|
|||
}
|
||||
|
||||
pub fn spell_learn_cost(spell_level: i32) -> i32 {
|
||||
match spell_level {
|
||||
1 => 0,
|
||||
2 => 1000,
|
||||
3 => 2000,
|
||||
4 => 4000,
|
||||
5 => 7000,
|
||||
6 => 10000,
|
||||
7 => 20000,
|
||||
_ => 999999,
|
||||
if (constants::MIN_SPELL_LEVEL..=constants::MAX_SPELL_LEVEL).contains(&spell_level) {
|
||||
constants::SPELL_LEARN_COST_BY_LEVEL[(spell_level - 1) as usize]
|
||||
} else {
|
||||
constants::SPELL_LEARN_COST_UNKNOWN_LEVEL
|
||||
}
|
||||
}
|
||||
|
||||
pub fn caster_spell_level(character_level: i32) -> i32 {
|
||||
(character_level / constants::CASTER_LEVELS_PER_SPELL_LEVEL) + 1
|
||||
}
|
||||
|
||||
pub fn max_spell_points(class: &str, level: i32) -> i32 {
|
||||
let spell_class = class_for_spells(class);
|
||||
let spell_level = match spell_class {
|
||||
"conjurer" | "magician" | "sorcerer" | "wizard" => (level / 3) + 1,
|
||||
"conjurer" | "magician" | "sorcerer" | "wizard" => caster_spell_level(level),
|
||||
_ => 0,
|
||||
};
|
||||
spell_level * 10
|
||||
spell_level * constants::SPELL_POINTS_PER_SPELL_LEVEL
|
||||
}
|
||||
|
||||
pub fn is_caster_class(class: &str) -> bool {
|
||||
|
|
@ -75,3 +75,39 @@ pub fn caster_classes() -> &'static [&'static str] {
|
|||
|
||||
use std::sync::LazyLock;
|
||||
static ALL_SPELLS: LazyLock<Vec<SpellDef>> = LazyLock::new(load_spells);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn caster_spell_level_spot_values() {
|
||||
assert_eq!(caster_spell_level(1), 1);
|
||||
assert_eq!(caster_spell_level(2), 1);
|
||||
assert_eq!(caster_spell_level(3), 2);
|
||||
assert_eq!(caster_spell_level(6), 3);
|
||||
assert_eq!(caster_spell_level(100), 34);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spell_learn_cost_guard_spot_checks() {
|
||||
assert_eq!(
|
||||
spell_learn_cost(0),
|
||||
constants::SPELL_LEARN_COST_UNKNOWN_LEVEL
|
||||
);
|
||||
assert_eq!(
|
||||
spell_learn_cost(-1),
|
||||
constants::SPELL_LEARN_COST_UNKNOWN_LEVEL
|
||||
);
|
||||
assert_eq!(
|
||||
spell_learn_cost(8),
|
||||
constants::SPELL_LEARN_COST_UNKNOWN_LEVEL
|
||||
);
|
||||
for level in constants::MIN_SPELL_LEVEL..=constants::MAX_SPELL_LEVEL {
|
||||
assert_eq!(
|
||||
spell_learn_cost(level),
|
||||
constants::SPELL_LEARN_COST_BY_LEVEL[(level - 1) as usize]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue