diff --git a/Cargo.lock b/Cargo.lock index 17481e6..24bde23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -187,9 +187,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.4.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9" [[package]] name = "bytemuck" @@ -482,13 +482,13 @@ dependencies = [ [[package]] name = "derivative" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaed5874effa6cde088c644ddcdcb4ffd1511391c5be4fdd7a5ccd02c7e4a183" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.58", + "syn 1.0.60", ] [[package]] @@ -499,7 +499,7 @@ checksum = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.58", + "syn 1.0.60", ] [[package]] @@ -579,9 +579,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ "cfg-if 1.0.0", "crc32fast", @@ -785,9 +785,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] @@ -868,18 +868,15 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jpeg-decoder" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc797adac5f083b8ff0ca6f6294a999393d76e197c36488e2ef732c4715f6fa3" -dependencies = [ - "byteorder", -] +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" [[package]] name = "js-sys" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" +checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65" dependencies = [ "wasm-bindgen", ] @@ -914,9 +911,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.82" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929" +checksum = "1cca32fa0182e8c0989459524dc356b8f2b5c10f1b9eb521b7d182c03cf8c5ff" [[package]] name = "libloading" @@ -973,11 +970,11 @@ dependencies = [ [[package]] name = "log" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", ] [[package]] @@ -1023,7 +1020,7 @@ dependencies = [ [[package]] name = "minigene" version = "0.3.0" -source = "git+https://github.com/jojolepro/minigene?branch=master#4e227a4647206aca2abad574438ff9bbe19d5ebb" +source = "git+https://github.com/jojolepro/minigene?branch=master#18927bf85e63ceb9c7505c8b54a0f93fe4c5ac62" dependencies = [ "bracket-lib", "crossterm 0.18.2", @@ -1294,7 +1291,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.58", + "syn 1.0.60", ] [[package]] @@ -1687,22 +1684,22 @@ checksum = "a0eddf2e8f50ced781f288c19f18621fa72a3779e3cb58dbf23b07469b0abeb4" [[package]] name = "serde" -version = "1.0.119" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.119" +version = "1.0.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.58", + "syn 1.0.60", ] [[package]] @@ -1861,9 +1858,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", @@ -1872,11 +1869,11 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447" +checksum = "d8208a331e1cb318dd5bd76951d2b8fc48ca38a69f5f4e4af1b6a9f8c6236915" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] @@ -1956,9 +1953,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" +checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" dependencies = [ "cfg-if 1.0.0", "serde", @@ -1968,24 +1965,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" +checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.58", + "syn 1.0.60", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35" +checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -1995,9 +1992,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" +checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c" dependencies = [ "quote 1.0.8", "wasm-bindgen-macro-support", @@ -2005,22 +2002,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" +checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.8", - "syn 1.0.58", + "syn 1.0.60", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.69" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" +checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64" [[package]] name = "wasm-timer" @@ -2098,9 +2095,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.46" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" +checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index cfce442..b2e9d69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,8 @@ edition = "2018" default = ["opengl"] terminal = ["minigene/terminal"] opengl = ["minigene/opengl"] +# Note: A rendering backend must still be specified if running headlessly +headless = ["minigene/headless"] wasm = ["wasm-bindgen", "minigene/wasm", "console_error_panic_hook", "opengl"] [dependencies] diff --git a/assets/effector_defs.yaml b/assets/effector_defs.yaml index 222a8b1..14cc073 100644 --- a/assets/effector_defs.yaml +++ b/assets/effector_defs.yaml @@ -60,6 +60,12 @@ defs: effects: - - GoldGainMultiplier - MultiplicativeMultiplier: 2.0 + Stun: + key: Stun + duration: 2.0 + effects: + - - ActionPointRefillRate + - MultiplicativeMultiplier: 0.0 AdditionalAttack: key: AdditionalAttack duration: 0.0 diff --git a/assets/leader_defs.yaml b/assets/leader_defs.yaml index 625685c..dbf8506 100644 --- a/assets/leader_defs.yaml +++ b/assets/leader_defs.yaml @@ -12,12 +12,6 @@ defs: skills: - AttackSpeedIncrease - SlowAOE - Generic3: - key: Generic3 - name: "Leader3" - skills: - - DoubleDamage - - AOE TreePersonLeader: key: TreePersonLeader name: "TreePersonLeader" @@ -42,6 +36,12 @@ defs: skills: - GreedyTouch - AirCorrosion + Erno: + key: Erno + name: "Erno" + skills: + - SpellSteal + - Telekinesis SoulsCollector: key: SoulsCollector name: "SoulsCollector" diff --git a/assets/skill_defs.yaml b/assets/skill_defs.yaml index bb6fe29..e15d7cd 100644 --- a/assets/skill_defs.yaml +++ b/assets/skill_defs.yaml @@ -166,6 +166,32 @@ defs: MinValue: 1.0 item_conditions: [] stat_effectors: [] + SpellSteal: + key: SpellSteal + name: SpellSteal + friendly_name: spell_steal + description: Gain access to an ability belonging to a leader in range until the caster dies. Only one ability may be stolen at a time. + cooldown: 0.0 + passive: true + conditions: + - stat_key: LeadersAround + condition: + MinValue: 1.0 + item_conditions: [] + stat_effectors: [] + Telekinesis: + key: Telekinesis + name: Telekinesis + friendly_name: telekinesis + description: Hurls the nearest opponent at the second nearest opponent, stunning in an AOE around the landing site. + cooldown: 12.0 + passive: true + conditions: + - stat_key: EnemiesAround + condition: + MinValue: 2.0 + item_conditions: [] + stat_effectors: [] StealSoul: key: StealSoul name: Steal Soul diff --git a/assets/stat_defs.yaml b/assets/stat_defs.yaml index 30fcdc2..5ed8058 100644 --- a/assets/stat_defs.yaml +++ b/assets/stat_defs.yaml @@ -12,7 +12,7 @@ defs: key: Health name: health friendly_name: HP - default_value: 100.0 + default_value: 1000.0 min_value: ~ max_value: ~ icon_path: ~ @@ -24,6 +24,14 @@ defs: min_value: ~ max_value: ~ icon_path: ~ + LeadersAround: + key: LeadersAround + name: leaders_around + friendly_name: Leaders Around + default_value: 0.0 + min_value: ~ + max_value: ~ + icon_path: ~ AttackSpeed: key: AttackSpeed name: attack_speed @@ -76,7 +84,7 @@ defs: key: ActionPointRefillRate name: action_point_refill_rate friendly_name: Action Point Refill Rate - default_value: 100.0 + default_value: 15.0 min_value: 0 max_value: ~ icon_path: ~ diff --git a/contributing.md b/contributing.md index 961ceea..78616be 100644 --- a/contributing.md +++ b/contributing.md @@ -26,7 +26,6 @@ If you’ve grokked the following resources, you should be able to work on the S #### Essentials - https://sokoban.iolivia.me/ -- https://pragprog.com/titles/hwrust/hands-on-rust/ - https://jojolepro.com/blog/2021-01-13_planks_ecs/ #### Extended @@ -35,6 +34,7 @@ If you’ve grokked the following resources, you should be able to work on the S - https://stevedonovan.github.io/rust-gentle-intro/readme.html - https://doc.rust-lang.org/rust-by-example/ - https://bfnightly.bracketproductions.com/rustbook/chapter_0.html +- https://pragprog.com/titles/hwrust/hands-on-rust/ (ask Erlend for early-access) - https://specs.amethyst.rs/docs/tutorials/01_intro.html #### Optional diff --git a/doc/create_leader.md b/doc/create_leader.md index e5d658b..aa2f657 100644 --- a/doc/create_leader.md +++ b/doc/create_leader.md @@ -29,7 +29,8 @@ they have an enum containing the identifiers and a collection of definitions. A skillset is the skills a specific leader has access to. It is created using a hashmap using the skill's key as key and the a SkillInstance as the value. A skill instance is simply the skill's key and the current cooldown (usually 0.0). -As of 0.4.0, this is automatically done and all you need to worry about is adding your leader and all associated skills to the `assets/leader_defs.yaml` file. +As of 0.4.0, this is done automatically and all you need to worry about is adding your leader and all associated skills to the `assets/leader_defs.yaml` file. +To be added to teams, your leader must also be present in the `leaders_vec` variable on line 480 of `main.rs`. ## Creating a leader's entity @@ -41,6 +42,7 @@ They have: - `SpriteIndex`: A number pointing to the 2d sprite in the spritesheet used by the game. There are 10 sprites per row and 10 rows, for a total of 100 sprites. - `Team`: Indicates which team the entity is on. Used by AI to determine which entity to attack or follow. - `SimpleMovement`: A marker component indicating that this entity should be moved by the SimpleMovementSystem (shared by creeps and leaders). +- `ProximityAttack`: A marker component indicating that this entity should attack nearby opponents. - `Inventory`: An inventory of the items this leader has. This can easily be cloned from the default inventory (default_inventory). - `SkillSet`: The skillset we defined earlier for this leader. - `AiPath`: The path that this entity will follow while moving. This is used by the AI systems to move the entities around. @@ -48,9 +50,11 @@ They have: - `Name`: The displayed name of the leader. - `StatSet`: The current stats of the leader. Can easily be cloned from the default statset (default_stats). - `EffectorSet`: Holds the currently active effectors of this leader. Used to keep the `StatSet` with the correct values during gameplay. -- `FleeToBase`: Marks the health threshold at which a leader retreats back to their base. -- `IsCaught`: Tells whether or not a leader is currently unable to escape an opponent. -As of 0.4.0, this is all done in `src/systems/spawn_leader.rs`. We are not currently immediately adding the entities for leaders, so you are not required to do this at the moment. This will change once we implement a total of 10 leaders so we can have two entirely asymmetrical teams. +- `FleeToBase`: Marks the health threshold at which a leader retreats back to their base. This is currently disabled. +- `IsCaught`: Tells whether or not a leader is unable to escape an opponent. This is currently disabled. + +As of 0.4.0, this is all done in `src/systems/spawn_leader.rs`. +Many components are assigned automatically, but `SimpleMovement`, `ProximityAttack`, `Sprite`, `SpriteIndex`, and any custom components that you may add to your leader need to be added as part of the `match` statement on line 65. ## Note diff --git a/src/components.rs b/src/components.rs index 6867efa..5126a8c 100644 --- a/src/components.rs +++ b/src/components.rs @@ -40,6 +40,8 @@ pub struct Leader2SimpleMovement; pub struct FleeToBase(pub f64); /// Added on entities which temporarily cannot move. pub struct IsCaught(pub bool); +/// Tracks whether or not Spell Steal has been used for heroes with that ability. +pub struct SpellSteal(pub bool); /// Tags a creep. pub struct Creep; /// Tags a creep spawner. Contains the delay in ticks between spawns. diff --git a/src/ids.rs b/src/ids.rs index fa0ca90..3619044 100644 --- a/src/ids.rs +++ b/src/ids.rs @@ -8,6 +8,7 @@ pub enum Stats { Mana, AttackSpeed, EnemiesAround, + LeadersAround, AttacksDealt, AttacksReceived, DamageDealt, @@ -42,6 +43,8 @@ pub enum Skills { BattleHunger, GreedyTouch, AirCorrosion, + SpellSteal, + Telekinesis, StealSoul, DarkPresence, ReturnDamage, @@ -72,6 +75,7 @@ pub enum Effectors { Enraged, HalfDefense, DoubleGoldGain, + Stun, AdditionalAttack, AdditionalDefense, } @@ -82,12 +86,12 @@ pub enum Effectors { pub enum Leaders { Generic1, Generic2, - Generic3, TreePersonLeader, BearPersonLeader, AxePersonLeader, CentaurPersonLeader, Celsus, + Erno, SoulsCollector, BristlebackPersonLeader, } diff --git a/src/main.rs b/src/main.rs index 327fcf6..fd2ca2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ extern crate serde; use minigene::*; +use rand::{seq::SliceRandom, thread_rng, Rng}; use std::collections::HashMap; add_wasm_support!(); @@ -13,16 +14,17 @@ const PLAY_WIDTH: u32 = 81; const PLAY_HEIGHT: u32 = 50; const SCREEN_WIDTH: u32 = 100; const SCREEN_HEIGHT: u32 = 50; -const CREEP_SPAWN_TICKS: u32 = 10; +const CREEP_SPAWN_TICKS: u32 = 125; const CREEP_ATTACK_RADIUS: f32 = 2.1; -//const MELEE_LEADER_ATTACK_RADIUS: f32 = 2.1; -const RANGED_LEADER_ATTACK_RADIUS: f32 = 6.3; +const MELEE_LEADER_ATTACK_RADIUS: f32 = 2.1; +const RANGED_LEADER_ATTACK_RADIUS: f32 = 21.0; const AOE_RADIUS: f32 = 4.0; const AOE_DAMAGE: f64 = 100.0; const SLOW_AOE_RADIUS: f32 = 8.0; const SLOW_AOE_DAMAGE: f64 = 50.0; const RETURN_AOE_RADIUS: f32 = 4.0; const RETURN_AOE_DAMAGE: f64 = 20.0; +const STUN_AOE_RADIUS: f32 = 4.0; const TOWER_RANGE: f32 = 5.0; const TOWER_PROJECTILE_EXPLOSION_RADIUS: f32 = 2.1; const TARGET_FPS: f32 = 20.0; @@ -228,6 +230,7 @@ fn main() -> BError { //leader1_proximity_attack_system, // TODO re-enable tower_projectile_system, update_enemies_around_system, + update_leaders_around_system, skill_cooldown_system::, trigger_passive_skill_system::, exec_skill_system::, @@ -238,6 +241,8 @@ fn main() -> BError { savagery_system, battle_hunger_system, air_corrosion_system, + telekinesis_system, + spell_steal_system, dark_presence_system, additional_attack_system, additional_defense_system, @@ -475,22 +480,48 @@ fn main() -> BError { } } - let team_leaders = TeamLeaders::new( - vec![ - Leaders::AxePersonLeader, - Leaders::Celsus, - Leaders::SoulsCollector, - Leaders::TreePersonLeader, - Leaders::BearPersonLeader, - ], - vec![ - Leaders::CentaurPersonLeader, - Leaders::SoulsCollector, - Leaders::Celsus, - Leaders::TreePersonLeader, - Leaders::BearPersonLeader, - ], - ); + // Spawn leaders + // TODO: optimize + let mut rng = thread_rng(); + let mut leaders_vec = vec![ + Leaders::Generic1, + Leaders::Generic2, + Leaders::TreePersonLeader, + Leaders::BearPersonLeader, + Leaders::AxePersonLeader, + Leaders::CentaurPersonLeader, + Leaders::Celsus, + Leaders::Erno, + Leaders::SoulsCollector, + Leaders::BristlebackPersonLeader, + ]; + + let mut team_leaders = TeamLeaders::new(vec![], vec![]); + let mut me_number = 0; + let mut other_number = 0; + + leaders_vec.shuffle(&mut rng); + + for leader in leaders_vec { + if rng.gen_range(1, 3) == 1 { + if me_number < 5 { + team_leaders.me.push(leader); + me_number += 1; + } else if other_number < 5 { + team_leaders.other.push(leader); + other_number += 1; + } + } else { + if other_number < 5 { + team_leaders.other.push(leader); + other_number += 1; + } else if me_number < 5 { + team_leaders.me.push(leader); + me_number += 1; + } + } + } + *world.get_mut::().unwrap() = team_leaders; { diff --git a/src/render_map.rs b/src/render_map.rs index 0a8b87c..032b97d 100644 --- a/src/render_map.rs +++ b/src/render_map.rs @@ -25,78 +25,82 @@ pub fn create_map_bg<'a>(world: &mut World) { } /// Renders the user interface on the screen. +#[allow(unused_variables)] pub fn render_ui(world: &mut World, ctx: &mut BTerm) { - ctx.draw_box( - PLAY_WIDTH, - 0, - SCREEN_WIDTH - PLAY_WIDTH - 1, - SCREEN_HEIGHT - 1, - WHITE, - BLACK, - ); - ctx.print(PLAY_WIDTH + 1, 1, "Leaders"); - ctx.print(PLAY_WIDTH + 1, 3, "My Team"); + #[cfg(not(feature = "headless"))] + { + ctx.draw_box( + PLAY_WIDTH, + 0, + SCREEN_WIDTH - PLAY_WIDTH - 1, + SCREEN_HEIGHT - 1, + WHITE, + BLACK, + ); + ctx.print(PLAY_WIDTH + 1, 1, "Leaders"); + ctx.print(PLAY_WIDTH + 1, 3, "My Team"); - let selected = world.get::().unwrap().0; + let selected = world.get::().unwrap().0; - for (i, key) in world.get::().unwrap().me.iter().enumerate() { - let name = world - .get::() - .unwrap() - .defs - .get(key) - .unwrap() - .name - .clone(); - ctx.print(PLAY_WIDTH + 1, i + 4, format!(" {}", name)); - } - ctx.print(PLAY_WIDTH + 1, 10, "Enemy Team"); - for (i, key) in world.get::().unwrap().me.iter().enumerate() { - let name = world - .get::() - .unwrap() - .defs - .get(key) - .unwrap() - .name - .clone(); - ctx.print(PLAY_WIDTH + 1, i + 11, format!(" Leader {}", name)); - } + for (i, key) in world.get::().unwrap().me.iter().enumerate() { + let name = world + .get::() + .unwrap() + .defs + .get(key) + .unwrap() + .name + .clone(); + ctx.print(PLAY_WIDTH + 1, i + 4, format!(" {}", name)); + } + ctx.print(PLAY_WIDTH + 1, 10, "Enemy Team"); + for (i, key) in world.get::().unwrap().me.iter().enumerate() { + let name = world + .get::() + .unwrap() + .defs + .get(key) + .unwrap() + .name + .clone(); + ctx.print(PLAY_WIDTH + 1, i + 11, format!(" Leader {}", name)); + } - ctx.print(PLAY_WIDTH + 1, selected + 4, ">"); + ctx.print(PLAY_WIDTH + 1, selected + 4, ">"); - ctx.print(PLAY_WIDTH + 1, 17, "Keybinds"); + ctx.print(PLAY_WIDTH + 1, 17, "Keybinds"); - let hm = world.get::>().unwrap(); - let mut keybinds = hm.iter().collect::>(); - keybinds.sort_by(|t1, t2| format!("{:?}", t1.1).cmp(&format!("{:?}", t2.1))); - for (idx, (k, v)) in keybinds.iter().enumerate() { - if **k as u32 == 13 { - ctx.print(PLAY_WIDTH + 1, 18 + idx, format!("Enter:{:?}", v)); - } else if **k as u32 == 27 { - ctx.print(PLAY_WIDTH + 1, 18 + idx, format!("Esc:{:?}", v)); - } else { - ctx.print(PLAY_WIDTH + 1, 18 + idx, format!("{}:{:?}", k, v)); + let hm = world.get::>().unwrap(); + let mut keybinds = hm.iter().collect::>(); + keybinds.sort_by(|t1, t2| format!("{:?}", t1.1).cmp(&format!("{:?}", t2.1))); + for (idx, (k, v)) in keybinds.iter().enumerate() { + if **k as u32 == 13 { + ctx.print(PLAY_WIDTH + 1, 18 + idx, format!("Enter:{:?}", v)); + } else if **k as u32 == 27 { + ctx.print(PLAY_WIDTH + 1, 18 + idx, format!("Esc:{:?}", v)); + } else { + ctx.print(PLAY_WIDTH + 1, 18 + idx, format!("{}:{:?}", k, v)); + } } - } - let game_stats = world.get::().unwrap(); - ctx.print(PLAY_WIDTH + 1, SCREEN_HEIGHT - 7, "Total Damage"); - ctx.print( - PLAY_WIDTH + 1, - SCREEN_HEIGHT - 6, - format!("{:.2}", game_stats.damage_dealt), - ); - ctx.print(PLAY_WIDTH + 1, SCREEN_HEIGHT - 5, "Kills"); - ctx.print( - PLAY_WIDTH + 1, - SCREEN_HEIGHT - 4, - format!("{}", game_stats.kill_count), - ); - ctx.print(PLAY_WIDTH + 1, SCREEN_HEIGHT - 3, "Earned Gold"); - ctx.print( - PLAY_WIDTH + 1, - SCREEN_HEIGHT - 2, - format!("{}", game_stats.earned_gold), - ); + let game_stats = world.get::().unwrap(); + ctx.print(PLAY_WIDTH + 1, SCREEN_HEIGHT - 7, "Total Damage"); + ctx.print( + PLAY_WIDTH + 1, + SCREEN_HEIGHT - 6, + format!("{:.2}", game_stats.damage_dealt), + ); + ctx.print(PLAY_WIDTH + 1, SCREEN_HEIGHT - 5, "Kills"); + ctx.print( + PLAY_WIDTH + 1, + SCREEN_HEIGHT - 4, + format!("{}", game_stats.kill_count), + ); + ctx.print(PLAY_WIDTH + 1, SCREEN_HEIGHT - 3, "Earned Gold"); + ctx.print( + PLAY_WIDTH + 1, + SCREEN_HEIGHT - 2, + format!("{}", game_stats.earned_gold), + ); + } } diff --git a/src/states/default.rs b/src/states/default.rs index a4b4fb2..0cc016f 100644 --- a/src/states/default.rs +++ b/src/states/default.rs @@ -3,45 +3,49 @@ use crate::*; /// The default state of the game. Where the gameplay happens. pub struct DefaultState; +#[allow(unused_variables)] impl minigene::State for DefaultState { fn update( &mut self, world: &mut World, - _dispatcher: &mut Dispatcher, + dispatcher: &mut Dispatcher, ctx: &mut BTerm, ) -> Trans { - ctx.set_active_console(0); - ctx.cls(); - #[cfg(feature = "opengl")] + #[cfg(not(feature = "headless"))] { - ctx.set_active_console(1); + ctx.set_active_console(0); ctx.cls(); - } - #[cfg(not(feature = "opengl"))] - { + #[cfg(feature = "opengl")] + { + ctx.set_active_console(1); + ctx.cls(); + } + #[cfg(not(feature = "opengl"))] + { + ctx.set_active_console(0); + render(ctx); + render_ascii( + ctx, + &*world.get().unwrap(), + &*world.get().unwrap(), + &*world.get().unwrap(), + &*world.get().unwrap(), + ); + } + #[cfg(feature = "opengl")] + { + ctx.set_active_console(1); + render_sprites( + ctx, + &*world.get().unwrap(), + &*world.get().unwrap(), + &*world.get().unwrap(), + Some(&*world.get().unwrap()), + ); + } ctx.set_active_console(0); - render(ctx); - render_ascii( - ctx, - &*world.get().unwrap(), - &*world.get().unwrap(), - &*world.get().unwrap(), - &*world.get().unwrap(), - ); - } - #[cfg(feature = "opengl")] - { - ctx.set_active_console(1); - render_sprites( - ctx, - &*world.get().unwrap(), - &*world.get().unwrap(), - &*world.get().unwrap(), - Some(&*world.get().unwrap()), - ); + render_ui(world, ctx); } - ctx.set_active_console(0); - render_ui(world, ctx); Trans::None - } + } } diff --git a/src/systems/aoe_damage.rs b/src/systems/aoe_damage.rs index bab6559..22c2b48 100644 --- a/src/systems/aoe_damage.rs +++ b/src/systems/aoe_damage.rs @@ -68,6 +68,47 @@ pub fn aoe_damage_system( game_events.push(GameEvent::DamageEntity(ev.0, e, RETURN_AOE_DAMAGE)); } } + } else if ev.1 == Skills::Telekinesis { + // Apply effector at the location of the second-closest enemy + if let (Some(from), Some(team)) = (positions.get(ev.0), teams.get(ev.0)) { + // Find the second-closest enemy + let enemies_around = entities_in_radius( + from, + &*entities, + &positions, + |e, _| teams.get(e).map(|t| t != team).unwrap_or(false), + |_, _, d| d <= RANGED_LEADER_ATTACK_RADIUS, + ); + + let target_position = positions.get(enemies_around.get(1).unwrap().0).unwrap(); + + // Apply effector + for (e, _, _) in entities_in_radius( + target_position, + &*entities, + &positions, + |e, _| teams.get(e).map(|t| t != team).unwrap_or(false), + |_, _, d| d <= STUN_AOE_RADIUS, + ) { + let stun_effector = effector_defs + .defs + .get(&Effectors::Stun) + .expect("Unknown effector key."); + + if effectors.get(e).is_none() { + effectors.insert(e, EffectorSet::default()); + } + + effectors + .get_mut(e) + .unwrap() + .effectors + .push(EffectorInstance::new( + Effectors::Stun, + stun_effector.duration, + )) + } + } } } Ok(()) diff --git a/src/systems/mod.rs b/src/systems/mod.rs index e0e8185..bcc682b 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -26,7 +26,9 @@ mod select_leader; mod simple_movement; mod spawn_creep; mod spawn_leader; +mod spell_steal; mod steal_soul; +mod telekinesis; mod thorn_volley; mod toggle_game_speed; mod tower_ai; @@ -34,6 +36,7 @@ mod tower_projectile; mod transfer_gold; mod update_collision_resource; mod update_enemies_around_stat; +mod update_leaders_around_stat; mod update_win_condition; pub use self::additional_attack::*; pub use self::additional_defense::*; @@ -64,7 +67,9 @@ pub use self::simple_movement::*; pub use self::simple_movement::*; pub use self::spawn_creep::*; pub use self::spawn_leader::*; +pub use self::spell_steal::*; pub use self::steal_soul::*; +pub use self::telekinesis::*; pub use self::thorn_volley::*; pub use self::toggle_game_speed::*; pub use self::tower_ai::*; @@ -72,4 +77,5 @@ pub use self::tower_projectile::*; pub use self::transfer_gold::*; pub use self::update_collision_resource::*; pub use self::update_enemies_around_stat::*; +pub use self::update_leaders_around_stat::*; pub use self::update_win_condition::*; diff --git a/src/systems/spawn_leader.rs b/src/systems/spawn_leader.rs index a9c456e..51d50e5 100644 --- a/src/systems/spawn_leader.rs +++ b/src/systems/spawn_leader.rs @@ -1,6 +1,6 @@ use crate::*; -/// Spawns a creep using the provided event. +/// Spawns a leader using the provided event. pub fn spawn_leader_system( game_events: &Vec, stat_def: &StatDefinitions, @@ -9,10 +9,14 @@ pub fn spawn_leader_system( entities: &mut Entities, positions: &mut Components, leaders: &mut Components, - //simple_movements: &mut Components, - //proximity_attacks: &mut Components, + // retreats: &mut Components, + // is_caught: &mut Components, + spell_steals: &mut Components, simple_movements: &mut Components, proximity_attacks: &mut Components, + // leader1_simple_movements: &mut Components, + // leader2_simple_movements: &mut Components, + // leader1_proximity_attacks: &mut Components, stats: &mut Components>, teams: &mut Components, sprites: &mut Components, @@ -28,24 +32,19 @@ pub fn spawn_leader_system( let team = if *id < 5 { Team::Me } else { Team::Other }; teams.insert(leader, team); stats.insert(leader, stat_def.to_statset()); - //simple_movements.insert(leader, Leader1SimpleMovement); - simple_movements.insert(leader, SimpleMovement); - //proximity_attacks.insert(leader, Leader1ProximityAttack::new(CREEP_ATTACK_RADIUS)); - proximity_attacks.insert(leader, ProximityAttack::new(CREEP_ATTACK_RADIUS)); + stats + .get_mut(leader) + .unwrap() + .stats + .get_mut(&Stats::ActionPointRefillRate) + .unwrap() + .value = 25.0; let bg = if team == Team::Me { RGBA::named(GREEN) } else { RGBA::named(WHITE) }; - sprites.insert( - leader, - Sprite { - glyph: to_cp437('L'), - fg: RGBA::named(RED), - bg, - }, - ); - sprite_indices.insert(leader, SpriteIndex(6)); + let leader_id = if *id < 5 { team_leaders .me @@ -56,6 +55,7 @@ pub fn spawn_leader_system( "Leader ID is higher than 9, or there isn't enough leaders in the other team!", ) }; + skillsets.insert( leader, leader_defs @@ -66,7 +66,202 @@ pub fn spawn_leader_system( .clone() .into(), ); + effectors.insert(leader, EffectorSet::::default()); + + match leader_id { + Leaders::Generic1 => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('1'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(6)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader1_simple_movements.insert(leader, Leader1SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttacks::new(MELEE_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + } + Leaders::Generic2 => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('2'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(5)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(RANGED_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader2_simple_movements.insert(leader, Leader2SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttack::new(RANGED_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + } + Leaders::TreePersonLeader => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('T'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(55)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader1_simple_movements.insert(leader, Leader1SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + } + Leaders::BearPersonLeader => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('B'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(4)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader1_simple_movements.insert(leader, Leader1SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + } + Leaders::AxePersonLeader => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('A'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(8)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader1_simple_movements.insert(leader, Leader1SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + } + Leaders::CentaurPersonLeader => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('N'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(16)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader1_simple_movements.insert(leader, Leader1SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + } + Leaders::Celsus => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('C'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(7)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(RANGED_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader2_simple_movements.insert(leader, Leader2SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttack::new(RANGED_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + } + Leaders::Erno => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('E'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(7)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(RANGED_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader2_simple_movements.insert(leader, Leader2SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttack::new(RANGED_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + spell_steals.insert(leader, SpellSteal(false)); + } + Leaders::SoulsCollector => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('S'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(19)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(RANGED_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader2_simple_movements.insert(leader, Leader2SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttack::new(RANGED_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + } + Leaders::BristlebackPersonLeader => { + sprites.insert( + leader, + Sprite { + glyph: to_cp437('B'), + fg: RGBA::named(RED), + bg, + }, + ); + sprite_indices.insert(leader, SpriteIndex(14)); + simple_movements.insert(leader, SimpleMovement); + proximity_attacks + .insert(leader, ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // TODO: Add higher threshold for retreating and re-enable + // leader1_simple_movements.insert(leader, Leader1SimpleMovement); + // leader1_proximity_attacks.insert(leader, Leader1ProximityAttack::new(MELEE_LEADER_ATTACK_RADIUS)); + // retreats.insert(leader, FleeToBase(0.0)); + // is_caught.insert(leader, IsCaught(false)); + } + } } } Ok(()) diff --git a/src/systems/spell_steal.rs b/src/systems/spell_steal.rs new file mode 100644 index 0000000..c7e7998 --- /dev/null +++ b/src/systems/spell_steal.rs @@ -0,0 +1,54 @@ +use crate::*; + +/// Gives the caster access to an ability belonging to another leader within range. +pub fn spell_steal_system( + entities: &Entities, + teams: &Components, + positions: &Components, + events: &Vec>, + leaders: &Components, + spell_steal: &mut Components, + skillsets: &mut Components>, +) -> SystemResult { + for ev in events.iter() { + if ev.1 == Skills::SpellSteal { + if let (Some(from), Some(team)) = (positions.get(ev.0), teams.get(ev.0)) { + for (e, pos) in join!(&entities && &positions) { + let e = e.unwrap(); + let pos = pos.unwrap(); + if pos == from && !spell_steal.get(e).unwrap().0 { + let closest_leader = entities_in_radius( + pos, + &*entities, + &positions, + |e, _| { + teams.get(e).map(|t| t != team).unwrap() && leaders.get(e).is_some() + }, + |_, _, d| d <= RANGED_LEADER_ATTACK_RADIUS, + ) + .first() + .map(|t| t.0) + .unwrap(); + + let skill = *skillsets + .get(closest_leader) + .unwrap() + .clone() + .skills + .keys() + .next() + .unwrap(); + + skillsets + .get_mut(e) + .unwrap() + .skills + .insert(skill, SkillInstance::new(skill, 0.0)); + spell_steal.get_mut(e).unwrap().0 = true; + } + } + } + } + } + Ok(()) +} diff --git a/src/systems/telekinesis.rs b/src/systems/telekinesis.rs new file mode 100644 index 0000000..7b6b84d --- /dev/null +++ b/src/systems/telekinesis.rs @@ -0,0 +1,34 @@ +use crate::*; + +/// Teleports the closest enemy to the second closest enemy. Stun AOE is applied in `aoe_damage.rs`. +pub fn telekinesis_system( + entities: &Entities, + teams: &Components, + events: &Vec>, + positions: &mut Components, +) -> SystemResult { + for ev in events.iter() { + if ev.1 == Skills::Telekinesis { + if let (Some(from), Some(team)) = (positions.get(ev.0), teams.get(ev.0)) { + let enemies_around = entities_in_radius( + from, + &*entities, + &positions, + |e, _| teams.get(e).map(|t| t != team).unwrap_or(false), + |_, _, d| d <= RANGED_LEADER_ATTACK_RADIUS, + ); + + if enemies_around.len() >= 2 { + let closest_enemy = enemies_around.first().unwrap().0; + let target_enemy = enemies_around.get(1).unwrap().0; + + positions.get_mut(closest_enemy).unwrap().x = + positions.get(target_enemy).unwrap().x; + positions.get_mut(closest_enemy).unwrap().y = + positions.get(target_enemy).unwrap().y; + } + } + } + } + Ok(()) +} diff --git a/src/systems/update_enemies_around_stat.rs b/src/systems/update_enemies_around_stat.rs index 3011190..7dce9c0 100644 --- a/src/systems/update_enemies_around_stat.rs +++ b/src/systems/update_enemies_around_stat.rs @@ -13,6 +13,8 @@ pub fn update_enemies_around_system( if let Some(_) = skill.unwrap().skills.get(&Skills::SlowAOE) { radius = SLOW_AOE_RADIUS; + } else if let Some(_) = skill.unwrap().skills.get(&Skills::Telekinesis) { + radius = RANGED_LEADER_ATTACK_RADIUS; } let c = entities_in_radius( diff --git a/src/systems/update_leaders_around_stat.rs b/src/systems/update_leaders_around_stat.rs new file mode 100644 index 0000000..08a70a4 --- /dev/null +++ b/src/systems/update_leaders_around_stat.rs @@ -0,0 +1,39 @@ +use crate::*; + +/// Update the `LeadersAround` stat using the entities that are close to the entity. +pub fn update_leaders_around_system( + entities: &Entities, + positions: &Components, + teams: &Components, + skills: &Components>, + leaders: &Components, + stats: &mut Components>, +) -> SystemResult { + for (pos, stat, team, skill) in join!(&positions && &mut stats && &teams && &skills) { + let mut radius = AOE_RADIUS; + + if let Some(_) = skill.unwrap().skills.get(&Skills::SlowAOE) { + radius = SLOW_AOE_RADIUS; + } else if let Some(_) = skill.unwrap().skills.get(&Skills::Telekinesis) { + radius = RANGED_LEADER_ATTACK_RADIUS; + } + + let c = entities_in_radius( + pos.unwrap(), + &*entities, + &positions, + |e, _| { + teams.get(e).map(|t| t != team.unwrap()).unwrap_or(false) + && leaders.get(e).is_some() + }, + |_, _, d| d <= radius, + ) + .len() as f64; + stat.unwrap() + .stats + .get_mut(&Stats::LeadersAround) + .expect("Failed to get LeadersAround stat") + .value = c; + } + Ok(()) +}