From 6c79e5dd9a0d7ddbce704aae321a3151b6aa7857 Mon Sep 17 00:00:00 2001 From: Youser Nayme <7685106-NeutralModder@users.noreply.gitlab.com> Date: Thu, 23 May 2024 06:07:20 +0000 Subject: [PATCH] Rebalance cultist dungeon loot tables and distribution, and improve Mindflayer anticheese --- CHANGELOG.md | 4 + .../common/abilities/ability_set_manifest.ron | 1 + .../custom/mindflayer/cursedflames.ron | 12 +-- .../custom/mindflayer/necroticsphere.ron | 11 +-- .../custom/mindflayer/necroticvortex.ron | 15 ++-- .../custom/mindflayer/summonextraminions.ron | 19 +++++ .../entity/dungeon/cultist/beastmaster.ron | 2 +- .../common/entity/dungeon/cultist/hound.ron | 4 +- assets/common/entity/dungeon/cultist/husk.ron | 4 +- .../entity/dungeon/cultist/husk_brute.ron | 4 +- .../common/entity/dungeon/cultist/warlock.ron | 4 +- .../common/entity/dungeon/cultist/warlord.ron | 4 +- .../common/entity/spot/wizard/wizard_argo.ron | 4 +- .../common/entity/spot/wizard/wizard_haku.ron | 4 +- .../entity/spot/wizard/wizard_trish.ron | 4 +- .../loadout/dungeon/cultist/cultist.ron | 1 + .../dungeon/cultist/beastmaster.ron | 11 +++ .../loot_tables/dungeon/cultist/chest.ron | 3 +- .../dungeon/cultist/enemy_large.ron | 9 +++ .../loot_tables/dungeon/cultist/hound.ron | 6 ++ .../dungeon/cultist/{minion.ron => husk.ron} | 0 .../dungeon/cultist/husk_brute.ron | 12 +++ .../dungeon/cultist/mindflayer.ron | 38 ++++++---- .../loot_tables/dungeon/cultist/miniboss.ron | 12 --- common/src/comp/body.rs | 2 +- common/src/comp/melee.rs | 9 +++ common/src/comp/projectile.rs | 7 +- server/agent/src/attack.rs | 74 ++++++++++++++----- world/src/civ/mod.rs | 2 +- world/src/site2/plot/cultist.rs | 74 ++++++------------- 30 files changed, 224 insertions(+), 132 deletions(-) create mode 100644 assets/common/abilities/custom/mindflayer/summonextraminions.ron create mode 100644 assets/common/loot_tables/dungeon/cultist/beastmaster.ron create mode 100644 assets/common/loot_tables/dungeon/cultist/enemy_large.ron create mode 100644 assets/common/loot_tables/dungeon/cultist/hound.ron rename assets/common/loot_tables/dungeon/cultist/{minion.ron => husk.ron} (100%) create mode 100644 assets/common/loot_tables/dungeon/cultist/husk_brute.ron delete mode 100644 assets/common/loot_tables/dungeon/cultist/miniboss.ron diff --git a/CHANGELOG.md b/CHANGELOG.md index 7daf566c18..a1909ef1a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refresh of voxel models for orichalcum armour - Toned down the health of most wild entities. - Rocksnapper received new abilities and AI +- Rebalanced cultist dungeon loot tables; among other things, the drops Glowing Remains and Ankh of Life from Mindflayer are now 2.5x and 25x more frequent, respectively. +- Improved Mindflayer anticheese measures. ### Removed @@ -40,6 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - NPC path-finding, especially for merchants and travellers is now less dumb. - Moderate buff to wild large bipeds, to bring in line with other balancing - Loot protection for solo players and NPCs works again +- New cultist dungeons are less overly abundant, sahagin dungeons spawn again. +- Cultist dungeons now always have exactly one portal which leads to the boss room. ## [0.16.0] - 2024-03-30 diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index f265d6c913..a5a3901210 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -676,6 +676,7 @@ Simple(None, "common.abilities.custom.mindflayer.dimensionaldoor"), Simple(None, "common.abilities.custom.mindflayer.necroticsphere"), Simple(None, "common.abilities.custom.mindflayer.summonminions"), + Simple(None, "common.abilities.custom.mindflayer.summonextraminions"), ], ), Custom("Flamekeeper"): ( diff --git a/assets/common/abilities/custom/mindflayer/cursedflames.ron b/assets/common/abilities/custom/mindflayer/cursedflames.ron index 5bd27d6259..37ef3a2c5b 100644 --- a/assets/common/abilities/custom/mindflayer/cursedflames.ron +++ b/assets/common/abilities/custom/mindflayer/cursedflames.ron @@ -1,10 +1,10 @@ BasicBeam( - buildup_duration: 0.9, - recover_duration: 1.0, + buildup_duration: 0.8, + recover_duration: 0.9, beam_duration: 1.0, - damage: 12.0, + damage: 9.5, tick_rate: 5.0, - range: 22.0, + range: 28.0, max_angle: 15.0, damage_effect: Some(Buff(( kind: Cursed, @@ -13,7 +13,7 @@ BasicBeam( chance: 0.33, ))), energy_regen: 0, - energy_drain: 0, - ori_rate: 0.4, + energy_drain: 45, + ori_rate: 0.45, specifier: Cultist, ) diff --git a/assets/common/abilities/custom/mindflayer/necroticsphere.ron b/assets/common/abilities/custom/mindflayer/necroticsphere.ron index e25d4a1a84..351ffbc9a8 100644 --- a/assets/common/abilities/custom/mindflayer/necroticsphere.ron +++ b/assets/common/abilities/custom/mindflayer/necroticsphere.ron @@ -1,11 +1,12 @@ BasicRanged( energy_cost: 0, - buildup_duration: 1.125, - recover_duration: 0.8, + buildup_duration: 1.00, + recover_duration: 0.70, projectile: NecroticSphere( - damage: 67.5, - radius: 5.0, - min_falloff: 0.9, + damage: 50, + radius: 7.0, + min_falloff: 0.2, + energy_regen: 40, ), projectile_body: Object(FireworkPurple), projectile_speed: 100.0, diff --git a/assets/common/abilities/custom/mindflayer/necroticvortex.ron b/assets/common/abilities/custom/mindflayer/necroticvortex.ron index 1e28df280a..f147745e84 100644 --- a/assets/common/abilities/custom/mindflayer/necroticvortex.ron +++ b/assets/common/abilities/custom/mindflayer/necroticvortex.ron @@ -1,14 +1,15 @@ RapidMelee( - buildup_duration: 1.8, - swing_duration: 0.5, - recover_duration: 1.2, + buildup_duration: 1.0, + swing_duration: 0.45, + recover_duration: 1.0, melee_constructor: ( kind: NecroticVortex( - damage: 30, - pull: 7, - lifesteal: 2, + damage: 20, + pull: 6.5, + lifesteal: 3.0, + energy_regen: 30, ), - range: 16.0, + range: 17.0, angle: 360.0, multi_target: Some(Normal), ), diff --git a/assets/common/abilities/custom/mindflayer/summonextraminions.ron b/assets/common/abilities/custom/mindflayer/summonextraminions.ron new file mode 100644 index 0000000000..83eed0766f --- /dev/null +++ b/assets/common/abilities/custom/mindflayer/summonextraminions.ron @@ -0,0 +1,19 @@ +BasicSummon( + buildup_duration: 0.5, + cast_duration: 1.0, + recover_duration: 0.5, + summon_amount: 2, + summon_distance: (1, 1), + summon_info: ( + body: BipedSmall(( + species: Husk, + body_type: Male, + )), + use_npc_name: true, + scale: None, + has_health: true, + loadout_config: Some(HuskSummon), + skillset_config: Some(Rank5), + ), + duration: None, +) diff --git a/assets/common/entity/dungeon/cultist/beastmaster.ron b/assets/common/entity/dungeon/cultist/beastmaster.ron index 07b170141c..2cb77aa515 100644 --- a/assets/common/entity/dungeon/cultist/beastmaster.ron +++ b/assets/common/entity/dungeon/cultist/beastmaster.ron @@ -3,7 +3,7 @@ name: Name("Beastmaster"), body: RandomWith("humanoid"), alignment: Alignment(Enemy), - loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"), + loot: LootTable("common.loot_tables.dungeon.cultist.beastmaster"), inventory: ( loadout: Inline(( inherit: Asset("common.loadout.dungeon.cultist.beastmaster"), diff --git a/assets/common/entity/dungeon/cultist/hound.ron b/assets/common/entity/dungeon/cultist/hound.ron index ff6706ee53..7f8335e017 100644 --- a/assets/common/entity/dungeon/cultist/hound.ron +++ b/assets/common/entity/dungeon/cultist/hound.ron @@ -3,9 +3,9 @@ name: Name("Tamed Darkhound"), body: RandomWith("darkhound"), alignment: Alignment(Enemy), - loot: LootTable("common.loot_tables.dungeon.cultist.minion"), + loot: LootTable("common.loot_tables.dungeon.cultist.hound"), inventory: ( loadout: FromBody, ), meta: [], -) \ No newline at end of file +) diff --git a/assets/common/entity/dungeon/cultist/husk.ron b/assets/common/entity/dungeon/cultist/husk.ron index 22e63a17b6..4a6eae4c02 100644 --- a/assets/common/entity/dungeon/cultist/husk.ron +++ b/assets/common/entity/dungeon/cultist/husk.ron @@ -3,9 +3,9 @@ name: Name("Cultist Husk"), body: RandomWith("husk"), alignment: Alignment(Enemy), - loot: LootTable("common.loot_tables.dungeon.cultist.minion"), + loot: LootTable("common.loot_tables.dungeon.cultist.husk"), inventory: ( loadout: Asset("common.loadout.dungeon.cultist.husk"), ), meta: [], -) \ No newline at end of file +) diff --git a/assets/common/entity/dungeon/cultist/husk_brute.ron b/assets/common/entity/dungeon/cultist/husk_brute.ron index e6fb00b4ef..dcafd683a5 100644 --- a/assets/common/entity/dungeon/cultist/husk_brute.ron +++ b/assets/common/entity/dungeon/cultist/husk_brute.ron @@ -3,9 +3,9 @@ name: Name("Husk Brute"), body: RandomWith("husk_brute"), alignment: Alignment(Enemy), - loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"), + loot: LootTable("common.loot_tables.dungeon.cultist.husk_brute"), inventory: ( loadout: FromBody, ), meta: [], -) \ No newline at end of file +) diff --git a/assets/common/entity/dungeon/cultist/warlock.ron b/assets/common/entity/dungeon/cultist/warlock.ron index aadbe9787a..05badd8642 100644 --- a/assets/common/entity/dungeon/cultist/warlock.ron +++ b/assets/common/entity/dungeon/cultist/warlock.ron @@ -3,7 +3,7 @@ name: Name("Cultist Warlock"), body: RandomWith("cultist_warlock"), alignment: Alignment(Enemy), - loot: LootTable("common.loot_tables.dungeon.cultist.enemy"), + loot: LootTable("common.loot_tables.dungeon.cultist.enemy_large"), inventory: ( loadout: Inline(( inherit: Asset("common.loadout.dungeon.cultist.warlock"), @@ -14,4 +14,4 @@ )), ), meta: [], -) \ No newline at end of file +) diff --git a/assets/common/entity/dungeon/cultist/warlord.ron b/assets/common/entity/dungeon/cultist/warlord.ron index 7cbfdb2a39..c6fe345ab5 100644 --- a/assets/common/entity/dungeon/cultist/warlord.ron +++ b/assets/common/entity/dungeon/cultist/warlord.ron @@ -3,7 +3,7 @@ name: Name("Cultist Warlord"), body: RandomWith("cultist_warlord"), alignment: Alignment(Enemy), - loot: LootTable("common.loot_tables.dungeon.cultist.enemy"), + loot: LootTable("common.loot_tables.dungeon.cultist.enemy_large"), inventory: ( loadout: Inline(( inherit: Asset("common.loadout.dungeon.cultist.warlord"), @@ -14,4 +14,4 @@ )), ), meta: [], -) \ No newline at end of file +) diff --git a/assets/common/entity/spot/wizard/wizard_argo.ron b/assets/common/entity/spot/wizard/wizard_argo.ron index 10605576de..a5ef94f05f 100644 --- a/assets/common/entity/spot/wizard/wizard_argo.ron +++ b/assets/common/entity/spot/wizard/wizard_argo.ron @@ -4,7 +4,7 @@ name: Name("Argo"), body: RandomWith("humanoid"), alignment: Alignment(Npc), - loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"), + loot: LootTable("common.loot_tables.dungeon.cultist.beastmaster"), inventory: ( loadout: Inline(( inherit: Asset("common.loadout.spots.wizard_tower.wizard_boss"), @@ -16,4 +16,4 @@ meta: [ SkillSetAsset("common.skillset.preset.rank5.fullskill"), ], -) \ No newline at end of file +) diff --git a/assets/common/entity/spot/wizard/wizard_haku.ron b/assets/common/entity/spot/wizard/wizard_haku.ron index a7b6e69563..5df719ae2f 100644 --- a/assets/common/entity/spot/wizard/wizard_haku.ron +++ b/assets/common/entity/spot/wizard/wizard_haku.ron @@ -4,7 +4,7 @@ name: Name("Haku"), body: RandomWith("humanoid"), alignment: Alignment(Npc), - loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"), + loot: LootTable("common.loot_tables.dungeon.cultist.beastmaster"), inventory: ( loadout: Inline(( inherit: Asset("common.loadout.spots.wizard_tower.wizard_boss"), @@ -16,4 +16,4 @@ meta: [ SkillSetAsset("common.skillset.preset.rank5.fullskill"), ], -) \ No newline at end of file +) diff --git a/assets/common/entity/spot/wizard/wizard_trish.ron b/assets/common/entity/spot/wizard/wizard_trish.ron index c13232b1f7..558742bdab 100644 --- a/assets/common/entity/spot/wizard/wizard_trish.ron +++ b/assets/common/entity/spot/wizard/wizard_trish.ron @@ -4,7 +4,7 @@ name: Name("Trish"), body: RandomWith("humanoid"), alignment: Alignment(Npc), - loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"), + loot: LootTable("common.loot_tables.dungeon.cultist.beastmaster"), inventory: ( loadout: Inline(( inherit: Asset("common.loadout.spots.wizard_tower.wizard_boss"), @@ -16,4 +16,4 @@ meta: [ SkillSetAsset("common.skillset.preset.rank5.fullskill"), ], -) \ No newline at end of file +) diff --git a/assets/common/loadout/dungeon/cultist/cultist.ron b/assets/common/loadout/dungeon/cultist/cultist.ron index 1346c584ad..6d6aca4cd2 100644 --- a/assets/common/loadout/dungeon/cultist/cultist.ron +++ b/assets/common/loadout/dungeon/cultist/cultist.ron @@ -23,6 +23,7 @@ (2, Item("common.items.weapons.staff.cultist_staff")), (2, Item("common.items.weapons.hammer.cultist_purp_2h-0")), (2, ModularWeapon(tool: Hammer, material: Orichalcum, hands: None)), + (2, Item("common.items.weapons.axe.malachite_axe-0")), (2, Item("common.items.weapons.bow.velorite")), (1, Item("common.items.weapons.sceptre.root_evil")), ]), None)), diff --git a/assets/common/loot_tables/dungeon/cultist/beastmaster.ron b/assets/common/loot_tables/dungeon/cultist/beastmaster.ron new file mode 100644 index 0000000000..d4c61ebca8 --- /dev/null +++ b/assets/common/loot_tables/dungeon/cultist/beastmaster.ron @@ -0,0 +1,11 @@ +[ + (1, All([ + MultiDrop(Item("common.items.utility.coins"), 500, 1000), + MultiDrop(Item("common.items.consumable.potion_minor"), 2, 4), + Lottery([ + (4.0, LootTable("common.loot_tables.armor.cultist")), + (1.0, Item("common.items.glider.blue")), + (15.0, Nothing), + ]), + ])), +] diff --git a/assets/common/loot_tables/dungeon/cultist/chest.ron b/assets/common/loot_tables/dungeon/cultist/chest.ron index 58f2c8c174..6508db2140 100644 --- a/assets/common/loot_tables/dungeon/cultist/chest.ron +++ b/assets/common/loot_tables/dungeon/cultist/chest.ron @@ -4,10 +4,11 @@ // Gear (0.25, LootTable("common.loot_tables.weapons.cultist")), (0.25, LootTable("common.loot_tables.armor.cultist")), + (0.25, LootTable("common.loot_tables.weapons.cave")), // Currency (3.0, MultiDrop(Item("common.items.utility.coins"), 1000, 2000)), // Consumables - (2.0, MultiDrop(Item("common.items.consumable.potion_minor"), 4, 8)), + (2.0, MultiDrop(Item("common.items.consumable.potion_minor"), 2, 4)), (0.1, MultiDrop(Item("common.items.food.spore_corruption"), 1, 3)), // Food (1.0, MultiDrop(LootTable("common.loot_tables.food.prepared"), 3, 6)), diff --git a/assets/common/loot_tables/dungeon/cultist/enemy_large.ron b/assets/common/loot_tables/dungeon/cultist/enemy_large.ron new file mode 100644 index 0000000000..843e90f08a --- /dev/null +++ b/assets/common/loot_tables/dungeon/cultist/enemy_large.ron @@ -0,0 +1,9 @@ +[ + // Currency + (10.0, MultiDrop(Item("common.items.utility.coins"), 250, 500)), + // Food + (5.0, LootTable("common.loot_tables.food.prepared")), + // Weapons + (0.5, LootTable("common.loot_tables.weapons.cultist")), + (0.5, LootTable("common.loot_tables.weapons.cave")), +] diff --git a/assets/common/loot_tables/dungeon/cultist/hound.ron b/assets/common/loot_tables/dungeon/cultist/hound.ron new file mode 100644 index 0000000000..d0c3e7f5e4 --- /dev/null +++ b/assets/common/loot_tables/dungeon/cultist/hound.ron @@ -0,0 +1,6 @@ +[ + // Currency + (30.0, MultiDrop(Item("common.items.utility.coins"), 50, 100)), + // Nothing + (30.0, Nothing), +] diff --git a/assets/common/loot_tables/dungeon/cultist/minion.ron b/assets/common/loot_tables/dungeon/cultist/husk.ron similarity index 100% rename from assets/common/loot_tables/dungeon/cultist/minion.ron rename to assets/common/loot_tables/dungeon/cultist/husk.ron diff --git a/assets/common/loot_tables/dungeon/cultist/husk_brute.ron b/assets/common/loot_tables/dungeon/cultist/husk_brute.ron new file mode 100644 index 0000000000..e6f35bcc3d --- /dev/null +++ b/assets/common/loot_tables/dungeon/cultist/husk_brute.ron @@ -0,0 +1,12 @@ +[ + (1, All([ + MultiDrop(Item("common.items.utility.coins"), 500, 1000), + MultiDrop(Item("common.items.consumable.potion_minor"), 2, 4), + Lottery([ + (4.0, LootTable("common.loot_tables.armor.cultist")), + (1.0, Item("common.items.glider.blue")), + (1.0, MultiDrop(Item("common.items.food.spore_corruption"), 1, 2)), + (14.0, Nothing), + ]), + ])), +] diff --git a/assets/common/loot_tables/dungeon/cultist/mindflayer.ron b/assets/common/loot_tables/dungeon/cultist/mindflayer.ron index 839d8e8634..9368e6337f 100644 --- a/assets/common/loot_tables/dungeon/cultist/mindflayer.ron +++ b/assets/common/loot_tables/dungeon/cultist/mindflayer.ron @@ -1,15 +1,27 @@ [ - (5.0, LootTable("common.loot_tables.weapons.cultist")), - (5.0, LootTable("common.loot_tables.weapons.cave")), - (10.0, LootTable("common.loot_tables.armor.cultist")), - // Rare misc items - (1.0, Item("common.items.boss_drops.lantern")), - (1.0, Item("common.items.glider.skullgrin")), - (0.01, Item("common.items.armor.misc.neck.ankh_of_life")), - // Legendary weapons - (1.0, Item("common.items.weapons.staff.laevateinn")), - // Crafting material - // Allow for Eldwood to drop till entity droppers are implemented - (1.0, Item("common.items.crafting_ing.mindflayer_bag_damaged")), - (1.0, MultiDrop(Item("common.items.log.eldwood"), 2, 6)), + (1, All([ + // Gear + MultiDrop(Lottery([ + (2.0, LootTable("common.loot_tables.armor.cultist")), + (1.0, LootTable("common.loot_tables.weapons.cultist")), + (1.0, LootTable("common.loot_tables.weapons.cave")), + ]), 1, 2), + Lottery([ + // Rare misc items + // Allow for Ankh to drop till it finds a proper home + (1.0, Item("common.items.boss_drops.lantern")), + (1.0, Item("common.items.glider.skullgrin")), + (0.1, Item("common.items.armor.misc.neck.ankh_of_life")), + // Legendary weapons + (0.5, Item("common.items.weapons.staff.laevateinn")), + // Crafting material + (0.5, Item("common.items.crafting_ing.mindflayer_bag_damaged")), + (6.9, Nothing), + ]), + // Allow for Eldwood to drop till entity droppers are implemented + Lottery([ + (1.0, MultiDrop(Item("common.items.log.eldwood"), 1, 3)), + (1.0, Nothing), + ]) + ])), ] diff --git a/assets/common/loot_tables/dungeon/cultist/miniboss.ron b/assets/common/loot_tables/dungeon/cultist/miniboss.ron deleted file mode 100644 index 402bd90682..0000000000 --- a/assets/common/loot_tables/dungeon/cultist/miniboss.ron +++ /dev/null @@ -1,12 +0,0 @@ -[ - // Currency - (10.0, MultiDrop(Item("common.items.utility.coins"), 500, 1000)), - // Consumables - (5.0, MultiDrop(Item("common.items.consumable.potion_minor"), 4, 8)), - // Back - (1.0, Item("common.items.armor.misc.back.dungeon_purple")), - // Ring - (0.5, LootTable("common.loot_tables.armor.cultist")), - // Glider - (1.0, Item("common.items.glider.blue")), -] diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index af759b0994..cad424bf90 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -1264,7 +1264,7 @@ impl Body { match self { Body::Humanoid(_) => 100, Body::BipedLarge(biped_large) => match biped_large.species { - biped_large::Species::Mindflayer => 390, + biped_large::Species::Mindflayer => 777, biped_large::Species::Minotaur => 340, biped_large::Species::Forgemaster => 300, biped_large::Species::Gigasfrost => 990, diff --git a/common/src/comp/melee.rs b/common/src/comp/melee.rs index 695b6d5e34..7ce574964f 100644 --- a/common/src/comp/melee.rs +++ b/common/src/comp/melee.rs @@ -291,7 +291,10 @@ impl MeleeConstructor { damage, pull, lifesteal, + energy_regen, } => { + let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen)) + .with_requirement(CombatRequirement::AnyDamage); let lifesteal = CombatEffect::Lifesteal(lifesteal); let mut damage = AttackDamage::new( @@ -321,6 +324,7 @@ impl MeleeConstructor { Attack::default() .with_damage(damage) .with_precision(precision_mult) + .with_effect(energy) .with_effect(knockback) }, SonicWave { @@ -467,16 +471,19 @@ impl MeleeConstructor { damage: a_damage, pull: a_pull, lifesteal: a_lifesteal, + energy_regen: a_energy_regen, }, NecroticVortex { damage: b_damage, pull: b_pull, lifesteal: b_lifesteal, + energy_regen: b_energy_regen, }, ) => NecroticVortex { damage: scale_values(a_damage, b_damage), pull: scale_values(a_pull, b_pull), lifesteal: scale_values(a_lifesteal, b_lifesteal), + energy_regen: scale_values(a_energy_regen, b_energy_regen), }, ( Hook { @@ -577,6 +584,7 @@ pub enum MeleeConstructorKind { damage: f32, pull: f32, lifesteal: f32, + energy_regen: f32, }, SonicWave { damage: f32, @@ -633,6 +641,7 @@ impl MeleeConstructorKind { ref mut damage, ref mut pull, ref mut lifesteal, + energy_regen: _, } => { *damage *= stats.power; *pull *= stats.effect_power; diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index a2406a25a4..ba247b2b41 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -87,6 +87,7 @@ pub enum ProjectileConstructor { damage: f32, radius: f32, min_falloff: f32, + energy_regen: f32, }, Magicball { damage: f32, @@ -461,7 +462,10 @@ impl ProjectileConstructor { damage, radius, min_falloff, + energy_regen, } => { + let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen)) + .with_requirement(CombatRequirement::AnyDamage); let damage = AttackDamage::new( Damage { source: DamageSource::Explosion, @@ -474,7 +478,8 @@ impl ProjectileConstructor { let attack = Attack::default() .with_damage(damage) .with_precision(precision_mult) - .with_combo_increment(); + .with_combo_increment() + .with_effect(energy); let explosion = Explosion { effects: vec![RadiusEffect::Attack(attack)], radius, diff --git a/server/agent/src/attack.rs b/server/agent/src/attack.rs index 51e0b2ae1d..62e2af1958 100644 --- a/server/agent/src/attack.rs +++ b/server/agent/src/attack.rs @@ -2312,7 +2312,7 @@ impl<'a> AgentData<'a> { AttackTimer = 0, } - let home = agent.patrol_origin.unwrap_or(self.pos.0.round()); + let home = agent.patrol_origin.unwrap_or(self.pos.0); let attack_select = if agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] < 3.0 { @@ -3076,34 +3076,48 @@ impl<'a> AgentData<'a> { ConditionCounterInit = 0, } + enum Timers { + ExtraSummonTimer = 0, + } + const MINDFLAYER_ATTACK_DIST: f32 = 16.0; const MINION_SUMMON_THRESHOLD: f32 = 0.20; + const MIN_CURSEDFLAMES_ENERGY: f32 = 180.0; + const MAX_BLINK_DISTANCE: f32 = 150.0; let health_fraction = self.health.map_or(0.5, |h| h.fraction()); + let home = agent.patrol_origin.unwrap_or(self.pos.0); + // Sets counter at start of combat, using `condition` to keep track of whether // it was already initialized if !agent.combat_state.conditions[ActionStateConditions::ConditionCounterInit as usize] { agent.combat_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize] = 1.0 - MINION_SUMMON_THRESHOLD; + agent.combat_state.int_counters[ActionStateICounters::ICounterNumFireballs as usize] = + rand::random::() % 4; agent.combat_state.conditions[ActionStateConditions::ConditionCounterInit as usize] = true; } + agent.combat_state.timers[Timers::ExtraSummonTimer as usize] += read_data.dt.0; + if (tgt_data.pos.0 - home).xy().magnitude_squared() < (25.0_f32).powi(2) { + agent.combat_state.timers[Timers::ExtraSummonTimer as usize] = 0.0; + } if agent.combat_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize] > health_fraction - && (matches!(self.char_state, CharacterState::BasicSummon(_)) - || entities_have_line_of_sight( - self.pos, - self.body, - self.scale, - tgt_data.pos, - tgt_data.body, - tgt_data.scale, - read_data, - )) - // TODO: Better check for if there's room to spawn summons { - // Summon minions at particular thresholds of health - controller.push_basic_input(InputKind::Ability(2)); + // teleport to room center for summon, to avoid walls + if (5.0_f32.powi(2)..=MAX_BLINK_DISTANCE.powi(2)) + .contains(&home.distance_squared(self.pos.0)) + { + controller.push_action(ControlAction::StartInput { + input: InputKind::Ability(0), + target_entity: None, + select_pos: Some(home), + }); + } else { + // Summon minions at particular thresholds of health + controller.push_basic_input(InputKind::Ability(2)); + } if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover)) { @@ -3111,6 +3125,28 @@ impl<'a> AgentData<'a> { [ActionStateFCounters::FCounterHealthThreshold as usize] -= MINION_SUMMON_THRESHOLD; } + } else if agent.combat_state.timers[Timers::ExtraSummonTimer as usize] > 12.0 { + // teleport to target for extra summons + if (3.0_f32.powi(2)..=MAX_BLINK_DISTANCE.powi(2)) + .contains(&tgt_data.pos.0.distance_squared(self.pos.0)) + { + controller.push_action(ControlAction::StartInput { + input: InputKind::Ability(0), + target_entity: agent + .target + .as_ref() + .and_then(|t| read_data.uids.get(t.target)) + .copied(), + select_pos: None, + }); + } else { + controller.push_basic_input(InputKind::Ability(3)); + } + + if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover)) + { + agent.combat_state.timers[Timers::ExtraSummonTimer as usize] = 0.0; + } } else if attack_data.dist_sqrd < MINDFLAYER_ATTACK_DIST.powi(2) { if entities_have_line_of_sight( self.pos, @@ -3122,16 +3158,18 @@ impl<'a> AgentData<'a> { read_data, ) { // If close to target, use either primary or secondary ability - if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs(10) && !matches!(c.stage_section, StageSection::Recover)) + if matches!(self.char_state, CharacterState::BasicBeam(c) if c.timer < Duration::from_secs(5) && !matches!(c.stage_section, StageSection::Recover)) { - // If already using primary, keep using primary until 10 consecutive seconds + // If already using primary, keep using primary until 5 consecutive seconds controller.push_basic_input(InputKind::Primary); } else if matches!(self.char_state, CharacterState::RapidMelee(c) if c.current_strike < 50 && !matches!(c.stage_section, StageSection::Recover)) { // If already using secondary, keep using secondary until 10 consecutive // seconds controller.push_basic_input(InputKind::Secondary); - } else if rng.gen_bool(health_fraction.into()) { + } else if self.energy.current() > MIN_CURSEDFLAMES_ENERGY + && rng.gen_bool(health_fraction.into()) + { // Else if at high health, use primary controller.push_basic_input(InputKind::Primary); } else { @@ -3280,7 +3318,7 @@ impl<'a> AgentData<'a> { read_data, ) }; - let home = agent.patrol_origin.unwrap_or(self.pos.0.round()); + let home = agent.patrol_origin.unwrap_or(self.pos.0); let health_fraction = self.health.map_or(0.5, |h| h.fraction()); // Teleport back to home position if we're too far from our home position but in // range of the blink ability diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index d2b9236cc0..3cd5d8b6f6 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -414,7 +414,7 @@ impl Civs { )?, SiteKind::DwarvenMine, ), - 87..=92 => ( + 87..=90 => ( find_site_loc( &mut ctx, &ProximityRequirementsBuilder::new() diff --git a/world/src/site2/plot/cultist.rs b/world/src/site2/plot/cultist.rs index 7db604fc78..8ec4d272ce 100644 --- a/world/src/site2/plot/cultist.rs +++ b/world/src/site2/plot/cultist.rs @@ -20,6 +20,7 @@ pub struct Room { clear_center: Vec2, mob_room: bool, boss_room: bool, + portal_to_boss: bool, } pub struct Cultist { @@ -47,18 +48,25 @@ impl Cultist { for s in 0..=1 { // rooms let rooms = [1, 2]; + let boss_portal_floor = + (1 + (RandomField::new(0).get(center.with_z(base + 1)) % 2)) as i32; + let portal_to_boss_index = + (RandomField::new(0).get(center.with_z(base)) % 4) as usize; if rooms.contains(&f) { - for dir in DIAGONALS { + for (d, dir) in DIAGONALS.iter().enumerate() { let room_base = base - (f * (2 * (room_size))) - (s * room_size); let room_center = center + (dir * ((room_size * 2) - 5 + (10 * s))); let clear_center = center + (dir * ((room_size * 2) - 6 + (10 * s))); let mob_room = s < 1; + let portal_to_boss = + mob_room && d == portal_to_boss_index && f == boss_portal_floor; room_data.push(Room { room_base, room_center, clear_center, mob_room, boss_room: false, + portal_to_boss, }); } } @@ -71,6 +79,7 @@ impl Cultist { clear_center: center, mob_room: false, boss_room: true, + portal_to_boss: false, }); Self { @@ -332,12 +341,13 @@ impl Structure for Cultist { } // room clears for room in room_data { - let (room_base, room_center, clear_center, mob_room, boss_room) = ( + let (room_base, room_center, clear_center, mob_room, boss_room, portal_to_boss) = ( room.room_base, room.room_center, room.clear_center, room.mob_room, room.boss_room, + room.portal_to_boss, ); painter .cylinder(Aabb { @@ -541,7 +551,7 @@ impl Structure for Cultist { let npc_pos = (room_center + dir * ((spacing / 2) * d)) .with_z(room_base - room_size + ((room_size / 3) * f)); let pos_var = RandomField::new(0).get(npc_pos) % 10; - if pos_var < 1 { + if pos_var < 2 { painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect( "common.entity.dungeon.cultist.cultist", &mut thread_rng, @@ -596,66 +606,30 @@ impl Structure for Cultist { let exit_position = (center - 10).with_z(base - (6 * room_size)); let boss_position = (center - 10).with_z(base - (7 * room_size)); let boss_portal = center.with_z(base - (7 * room_size)); - - let mob_portal_pos = Vec3::new( - mob_portal.x as f32, - mob_portal.y as f32, - mob_portal.z as f32, - ); - let mob_portal_target_pos = Vec3::new( - mob_portal_target.x as f32, - mob_portal_target.y as f32, - mob_portal_target.z as f32, - ); - let mini_boss_portal_pos = Vec3::new( - mini_boss_portal.x as f32, - mini_boss_portal.y as f32, - mini_boss_portal.z as f32, - ); - let exit_pos = Vec3::new( - exit_position.x as f32, - exit_position.y as f32, - exit_position.z as f32, - ); - let boss_pos = Vec3::new( - boss_position.x as f32, - boss_position.y as f32, - boss_position.z as f32, - ); - let boss_portal_pos = Vec3::new( - boss_portal.x as f32, - boss_portal.y as f32, - boss_portal.z as f32, - ); - - let mini_boss_portal_target = [ - exit_pos, exit_pos, exit_pos, exit_pos, exit_pos, boss_pos, boss_pos, boss_pos, - ]; - + let mini_boss_portal_target = if portal_to_boss { + boss_position.as_::() + } else { + exit_position.as_::() + }; if mob_room { - painter.spawn(EntityInfo::at(mob_portal_pos).into_special( + painter.spawn(EntityInfo::at(mob_portal.as_::()).into_special( SpecialEntity::Teleporter(PortalData { - target: mob_portal_target_pos, + target: mob_portal_target.as_::(), requires_no_aggro: true, buildup_time: Secs(5.), }), )); - let mini_boss_portal_target_index = RandomField::new(0).get(mini_boss_portal) - as usize - % mini_boss_portal_target.len(); - let mini_boss_portal_target_pos = - mini_boss_portal_target[mini_boss_portal_target_index]; - painter.spawn(EntityInfo::at(mini_boss_portal_pos).into_special( + painter.spawn(EntityInfo::at(mini_boss_portal.as_::()).into_special( SpecialEntity::Teleporter(PortalData { - target: mini_boss_portal_target_pos, + target: mini_boss_portal_target, requires_no_aggro: true, buildup_time: Secs(5.), }), )); } else if boss_room { - painter.spawn(EntityInfo::at(boss_portal_pos).into_special( + painter.spawn(EntityInfo::at(boss_portal.as_::()).into_special( SpecialEntity::Teleporter(PortalData { - target: exit_pos, + target: exit_position.as_::(), requires_no_aggro: true, buildup_time: Secs(5.), }),