mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'neura/cultist-loot' into 'master'
Rebalance cultist dungeon loot tables and distribution, and improve Mindflayer anticheese See merge request veloren/veloren!4460
This commit is contained in:
commit
9ea234a17a
@ -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
|
- Refresh of voxel models for orichalcum armour
|
||||||
- Toned down the health of most wild entities.
|
- Toned down the health of most wild entities.
|
||||||
- Rocksnapper received new abilities and AI
|
- 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
|
### 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.
|
- 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
|
- Moderate buff to wild large bipeds, to bring in line with other balancing
|
||||||
- Loot protection for solo players and NPCs works again
|
- 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
|
## [0.16.0] - 2024-03-30
|
||||||
|
|
||||||
|
@ -676,6 +676,7 @@
|
|||||||
Simple(None, "common.abilities.custom.mindflayer.dimensionaldoor"),
|
Simple(None, "common.abilities.custom.mindflayer.dimensionaldoor"),
|
||||||
Simple(None, "common.abilities.custom.mindflayer.necroticsphere"),
|
Simple(None, "common.abilities.custom.mindflayer.necroticsphere"),
|
||||||
Simple(None, "common.abilities.custom.mindflayer.summonminions"),
|
Simple(None, "common.abilities.custom.mindflayer.summonminions"),
|
||||||
|
Simple(None, "common.abilities.custom.mindflayer.summonextraminions"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Custom("Flamekeeper"): (
|
Custom("Flamekeeper"): (
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
BasicBeam(
|
BasicBeam(
|
||||||
buildup_duration: 0.9,
|
buildup_duration: 0.8,
|
||||||
recover_duration: 1.0,
|
recover_duration: 0.9,
|
||||||
beam_duration: 1.0,
|
beam_duration: 1.0,
|
||||||
damage: 12.0,
|
damage: 9.5,
|
||||||
tick_rate: 5.0,
|
tick_rate: 5.0,
|
||||||
range: 22.0,
|
range: 28.0,
|
||||||
max_angle: 15.0,
|
max_angle: 15.0,
|
||||||
damage_effect: Some(Buff((
|
damage_effect: Some(Buff((
|
||||||
kind: Cursed,
|
kind: Cursed,
|
||||||
@ -13,7 +13,7 @@ BasicBeam(
|
|||||||
chance: 0.33,
|
chance: 0.33,
|
||||||
))),
|
))),
|
||||||
energy_regen: 0,
|
energy_regen: 0,
|
||||||
energy_drain: 0,
|
energy_drain: 45,
|
||||||
ori_rate: 0.4,
|
ori_rate: 0.45,
|
||||||
specifier: Cultist,
|
specifier: Cultist,
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
BasicRanged(
|
BasicRanged(
|
||||||
energy_cost: 0,
|
energy_cost: 0,
|
||||||
buildup_duration: 1.125,
|
buildup_duration: 1.00,
|
||||||
recover_duration: 0.8,
|
recover_duration: 0.70,
|
||||||
projectile: NecroticSphere(
|
projectile: NecroticSphere(
|
||||||
damage: 67.5,
|
damage: 50,
|
||||||
radius: 5.0,
|
radius: 7.0,
|
||||||
min_falloff: 0.9,
|
min_falloff: 0.2,
|
||||||
|
energy_regen: 40,
|
||||||
),
|
),
|
||||||
projectile_body: Object(FireworkPurple),
|
projectile_body: Object(FireworkPurple),
|
||||||
projectile_speed: 100.0,
|
projectile_speed: 100.0,
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
RapidMelee(
|
RapidMelee(
|
||||||
buildup_duration: 1.8,
|
buildup_duration: 1.0,
|
||||||
swing_duration: 0.5,
|
swing_duration: 0.45,
|
||||||
recover_duration: 1.2,
|
recover_duration: 1.0,
|
||||||
melee_constructor: (
|
melee_constructor: (
|
||||||
kind: NecroticVortex(
|
kind: NecroticVortex(
|
||||||
damage: 30,
|
damage: 20,
|
||||||
pull: 7,
|
pull: 6.5,
|
||||||
lifesteal: 2,
|
lifesteal: 3.0,
|
||||||
|
energy_regen: 30,
|
||||||
),
|
),
|
||||||
range: 16.0,
|
range: 17.0,
|
||||||
angle: 360.0,
|
angle: 360.0,
|
||||||
multi_target: Some(Normal),
|
multi_target: Some(Normal),
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
|
)
|
@ -3,7 +3,7 @@
|
|||||||
name: Name("Beastmaster"),
|
name: Name("Beastmaster"),
|
||||||
body: RandomWith("humanoid"),
|
body: RandomWith("humanoid"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Enemy),
|
||||||
loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"),
|
loot: LootTable("common.loot_tables.dungeon.cultist.beastmaster"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: Inline((
|
loadout: Inline((
|
||||||
inherit: Asset("common.loadout.dungeon.cultist.beastmaster"),
|
inherit: Asset("common.loadout.dungeon.cultist.beastmaster"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
name: Name("Tamed Darkhound"),
|
name: Name("Tamed Darkhound"),
|
||||||
body: RandomWith("darkhound"),
|
body: RandomWith("darkhound"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Enemy),
|
||||||
loot: LootTable("common.loot_tables.dungeon.cultist.minion"),
|
loot: LootTable("common.loot_tables.dungeon.cultist.hound"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: FromBody,
|
loadout: FromBody,
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
name: Name("Cultist Husk"),
|
name: Name("Cultist Husk"),
|
||||||
body: RandomWith("husk"),
|
body: RandomWith("husk"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Enemy),
|
||||||
loot: LootTable("common.loot_tables.dungeon.cultist.minion"),
|
loot: LootTable("common.loot_tables.dungeon.cultist.husk"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: Asset("common.loadout.dungeon.cultist.husk"),
|
loadout: Asset("common.loadout.dungeon.cultist.husk"),
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
name: Name("Husk Brute"),
|
name: Name("Husk Brute"),
|
||||||
body: RandomWith("husk_brute"),
|
body: RandomWith("husk_brute"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Enemy),
|
||||||
loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"),
|
loot: LootTable("common.loot_tables.dungeon.cultist.husk_brute"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: FromBody,
|
loadout: FromBody,
|
||||||
),
|
),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
name: Name("Cultist Warlock"),
|
name: Name("Cultist Warlock"),
|
||||||
body: RandomWith("cultist_warlock"),
|
body: RandomWith("cultist_warlock"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Enemy),
|
||||||
loot: LootTable("common.loot_tables.dungeon.cultist.enemy"),
|
loot: LootTable("common.loot_tables.dungeon.cultist.enemy_large"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: Inline((
|
loadout: Inline((
|
||||||
inherit: Asset("common.loadout.dungeon.cultist.warlock"),
|
inherit: Asset("common.loadout.dungeon.cultist.warlock"),
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
name: Name("Cultist Warlord"),
|
name: Name("Cultist Warlord"),
|
||||||
body: RandomWith("cultist_warlord"),
|
body: RandomWith("cultist_warlord"),
|
||||||
alignment: Alignment(Enemy),
|
alignment: Alignment(Enemy),
|
||||||
loot: LootTable("common.loot_tables.dungeon.cultist.enemy"),
|
loot: LootTable("common.loot_tables.dungeon.cultist.enemy_large"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: Inline((
|
loadout: Inline((
|
||||||
inherit: Asset("common.loadout.dungeon.cultist.warlord"),
|
inherit: Asset("common.loadout.dungeon.cultist.warlord"),
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
name: Name("Argo"),
|
name: Name("Argo"),
|
||||||
body: RandomWith("humanoid"),
|
body: RandomWith("humanoid"),
|
||||||
alignment: Alignment(Npc),
|
alignment: Alignment(Npc),
|
||||||
loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"),
|
loot: LootTable("common.loot_tables.dungeon.cultist.beastmaster"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: Inline((
|
loadout: Inline((
|
||||||
inherit: Asset("common.loadout.spots.wizard_tower.wizard_boss"),
|
inherit: Asset("common.loadout.spots.wizard_tower.wizard_boss"),
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
name: Name("Haku"),
|
name: Name("Haku"),
|
||||||
body: RandomWith("humanoid"),
|
body: RandomWith("humanoid"),
|
||||||
alignment: Alignment(Npc),
|
alignment: Alignment(Npc),
|
||||||
loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"),
|
loot: LootTable("common.loot_tables.dungeon.cultist.beastmaster"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: Inline((
|
loadout: Inline((
|
||||||
inherit: Asset("common.loadout.spots.wizard_tower.wizard_boss"),
|
inherit: Asset("common.loadout.spots.wizard_tower.wizard_boss"),
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
name: Name("Trish"),
|
name: Name("Trish"),
|
||||||
body: RandomWith("humanoid"),
|
body: RandomWith("humanoid"),
|
||||||
alignment: Alignment(Npc),
|
alignment: Alignment(Npc),
|
||||||
loot: LootTable("common.loot_tables.dungeon.cultist.miniboss"),
|
loot: LootTable("common.loot_tables.dungeon.cultist.beastmaster"),
|
||||||
inventory: (
|
inventory: (
|
||||||
loadout: Inline((
|
loadout: Inline((
|
||||||
inherit: Asset("common.loadout.spots.wizard_tower.wizard_boss"),
|
inherit: Asset("common.loadout.spots.wizard_tower.wizard_boss"),
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
(2, Item("common.items.weapons.staff.cultist_staff")),
|
(2, Item("common.items.weapons.staff.cultist_staff")),
|
||||||
(2, Item("common.items.weapons.hammer.cultist_purp_2h-0")),
|
(2, Item("common.items.weapons.hammer.cultist_purp_2h-0")),
|
||||||
(2, ModularWeapon(tool: Hammer, material: Orichalcum, hands: None)),
|
(2, ModularWeapon(tool: Hammer, material: Orichalcum, hands: None)),
|
||||||
|
(2, Item("common.items.weapons.axe.malachite_axe-0")),
|
||||||
(2, Item("common.items.weapons.bow.velorite")),
|
(2, Item("common.items.weapons.bow.velorite")),
|
||||||
(1, Item("common.items.weapons.sceptre.root_evil")),
|
(1, Item("common.items.weapons.sceptre.root_evil")),
|
||||||
]), None)),
|
]), None)),
|
||||||
|
11
assets/common/loot_tables/dungeon/cultist/beastmaster.ron
Normal file
11
assets/common/loot_tables/dungeon/cultist/beastmaster.ron
Normal file
@ -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),
|
||||||
|
]),
|
||||||
|
])),
|
||||||
|
]
|
@ -4,10 +4,11 @@
|
|||||||
// Gear
|
// Gear
|
||||||
(0.25, LootTable("common.loot_tables.weapons.cultist")),
|
(0.25, LootTable("common.loot_tables.weapons.cultist")),
|
||||||
(0.25, LootTable("common.loot_tables.armor.cultist")),
|
(0.25, LootTable("common.loot_tables.armor.cultist")),
|
||||||
|
(0.25, LootTable("common.loot_tables.weapons.cave")),
|
||||||
// Currency
|
// Currency
|
||||||
(3.0, MultiDrop(Item("common.items.utility.coins"), 1000, 2000)),
|
(3.0, MultiDrop(Item("common.items.utility.coins"), 1000, 2000)),
|
||||||
// Consumables
|
// 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)),
|
(0.1, MultiDrop(Item("common.items.food.spore_corruption"), 1, 3)),
|
||||||
// Food
|
// Food
|
||||||
(1.0, MultiDrop(LootTable("common.loot_tables.food.prepared"), 3, 6)),
|
(1.0, MultiDrop(LootTable("common.loot_tables.food.prepared"), 3, 6)),
|
||||||
|
@ -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")),
|
||||||
|
]
|
6
assets/common/loot_tables/dungeon/cultist/hound.ron
Normal file
6
assets/common/loot_tables/dungeon/cultist/hound.ron
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
// Currency
|
||||||
|
(30.0, MultiDrop(Item("common.items.utility.coins"), 50, 100)),
|
||||||
|
// Nothing
|
||||||
|
(30.0, Nothing),
|
||||||
|
]
|
12
assets/common/loot_tables/dungeon/cultist/husk_brute.ron
Normal file
12
assets/common/loot_tables/dungeon/cultist/husk_brute.ron
Normal file
@ -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),
|
||||||
|
]),
|
||||||
|
])),
|
||||||
|
]
|
@ -1,15 +1,27 @@
|
|||||||
[
|
[
|
||||||
(5.0, LootTable("common.loot_tables.weapons.cultist")),
|
(1, All([
|
||||||
(5.0, LootTable("common.loot_tables.weapons.cave")),
|
// Gear
|
||||||
(10.0, LootTable("common.loot_tables.armor.cultist")),
|
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
|
// 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.boss_drops.lantern")),
|
||||||
(1.0, Item("common.items.glider.skullgrin")),
|
(1.0, Item("common.items.glider.skullgrin")),
|
||||||
(0.01, Item("common.items.armor.misc.neck.ankh_of_life")),
|
(0.1, Item("common.items.armor.misc.neck.ankh_of_life")),
|
||||||
// Legendary weapons
|
// Legendary weapons
|
||||||
(1.0, Item("common.items.weapons.staff.laevateinn")),
|
(0.5, Item("common.items.weapons.staff.laevateinn")),
|
||||||
// Crafting material
|
// 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
|
// Allow for Eldwood to drop till entity droppers are implemented
|
||||||
(1.0, Item("common.items.crafting_ing.mindflayer_bag_damaged")),
|
Lottery([
|
||||||
(1.0, MultiDrop(Item("common.items.log.eldwood"), 2, 6)),
|
(1.0, MultiDrop(Item("common.items.log.eldwood"), 1, 3)),
|
||||||
|
(1.0, Nothing),
|
||||||
|
])
|
||||||
|
])),
|
||||||
]
|
]
|
||||||
|
@ -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")),
|
|
||||||
]
|
|
@ -1264,7 +1264,7 @@ impl Body {
|
|||||||
match self {
|
match self {
|
||||||
Body::Humanoid(_) => 100,
|
Body::Humanoid(_) => 100,
|
||||||
Body::BipedLarge(biped_large) => match biped_large.species {
|
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::Minotaur => 340,
|
||||||
biped_large::Species::Forgemaster => 300,
|
biped_large::Species::Forgemaster => 300,
|
||||||
biped_large::Species::Gigasfrost => 990,
|
biped_large::Species::Gigasfrost => 990,
|
||||||
|
@ -291,7 +291,10 @@ impl MeleeConstructor {
|
|||||||
damage,
|
damage,
|
||||||
pull,
|
pull,
|
||||||
lifesteal,
|
lifesteal,
|
||||||
|
energy_regen,
|
||||||
} => {
|
} => {
|
||||||
|
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
|
||||||
|
.with_requirement(CombatRequirement::AnyDamage);
|
||||||
let lifesteal = CombatEffect::Lifesteal(lifesteal);
|
let lifesteal = CombatEffect::Lifesteal(lifesteal);
|
||||||
|
|
||||||
let mut damage = AttackDamage::new(
|
let mut damage = AttackDamage::new(
|
||||||
@ -321,6 +324,7 @@ impl MeleeConstructor {
|
|||||||
Attack::default()
|
Attack::default()
|
||||||
.with_damage(damage)
|
.with_damage(damage)
|
||||||
.with_precision(precision_mult)
|
.with_precision(precision_mult)
|
||||||
|
.with_effect(energy)
|
||||||
.with_effect(knockback)
|
.with_effect(knockback)
|
||||||
},
|
},
|
||||||
SonicWave {
|
SonicWave {
|
||||||
@ -467,16 +471,19 @@ impl MeleeConstructor {
|
|||||||
damage: a_damage,
|
damage: a_damage,
|
||||||
pull: a_pull,
|
pull: a_pull,
|
||||||
lifesteal: a_lifesteal,
|
lifesteal: a_lifesteal,
|
||||||
|
energy_regen: a_energy_regen,
|
||||||
},
|
},
|
||||||
NecroticVortex {
|
NecroticVortex {
|
||||||
damage: b_damage,
|
damage: b_damage,
|
||||||
pull: b_pull,
|
pull: b_pull,
|
||||||
lifesteal: b_lifesteal,
|
lifesteal: b_lifesteal,
|
||||||
|
energy_regen: b_energy_regen,
|
||||||
},
|
},
|
||||||
) => NecroticVortex {
|
) => NecroticVortex {
|
||||||
damage: scale_values(a_damage, b_damage),
|
damage: scale_values(a_damage, b_damage),
|
||||||
pull: scale_values(a_pull, b_pull),
|
pull: scale_values(a_pull, b_pull),
|
||||||
lifesteal: scale_values(a_lifesteal, b_lifesteal),
|
lifesteal: scale_values(a_lifesteal, b_lifesteal),
|
||||||
|
energy_regen: scale_values(a_energy_regen, b_energy_regen),
|
||||||
},
|
},
|
||||||
(
|
(
|
||||||
Hook {
|
Hook {
|
||||||
@ -577,6 +584,7 @@ pub enum MeleeConstructorKind {
|
|||||||
damage: f32,
|
damage: f32,
|
||||||
pull: f32,
|
pull: f32,
|
||||||
lifesteal: f32,
|
lifesteal: f32,
|
||||||
|
energy_regen: f32,
|
||||||
},
|
},
|
||||||
SonicWave {
|
SonicWave {
|
||||||
damage: f32,
|
damage: f32,
|
||||||
@ -633,6 +641,7 @@ impl MeleeConstructorKind {
|
|||||||
ref mut damage,
|
ref mut damage,
|
||||||
ref mut pull,
|
ref mut pull,
|
||||||
ref mut lifesteal,
|
ref mut lifesteal,
|
||||||
|
energy_regen: _,
|
||||||
} => {
|
} => {
|
||||||
*damage *= stats.power;
|
*damage *= stats.power;
|
||||||
*pull *= stats.effect_power;
|
*pull *= stats.effect_power;
|
||||||
|
@ -87,6 +87,7 @@ pub enum ProjectileConstructor {
|
|||||||
damage: f32,
|
damage: f32,
|
||||||
radius: f32,
|
radius: f32,
|
||||||
min_falloff: f32,
|
min_falloff: f32,
|
||||||
|
energy_regen: f32,
|
||||||
},
|
},
|
||||||
Magicball {
|
Magicball {
|
||||||
damage: f32,
|
damage: f32,
|
||||||
@ -461,7 +462,10 @@ impl ProjectileConstructor {
|
|||||||
damage,
|
damage,
|
||||||
radius,
|
radius,
|
||||||
min_falloff,
|
min_falloff,
|
||||||
|
energy_regen,
|
||||||
} => {
|
} => {
|
||||||
|
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
|
||||||
|
.with_requirement(CombatRequirement::AnyDamage);
|
||||||
let damage = AttackDamage::new(
|
let damage = AttackDamage::new(
|
||||||
Damage {
|
Damage {
|
||||||
source: DamageSource::Explosion,
|
source: DamageSource::Explosion,
|
||||||
@ -474,7 +478,8 @@ impl ProjectileConstructor {
|
|||||||
let attack = Attack::default()
|
let attack = Attack::default()
|
||||||
.with_damage(damage)
|
.with_damage(damage)
|
||||||
.with_precision(precision_mult)
|
.with_precision(precision_mult)
|
||||||
.with_combo_increment();
|
.with_combo_increment()
|
||||||
|
.with_effect(energy);
|
||||||
let explosion = Explosion {
|
let explosion = Explosion {
|
||||||
effects: vec![RadiusEffect::Attack(attack)],
|
effects: vec![RadiusEffect::Attack(attack)],
|
||||||
radius,
|
radius,
|
||||||
|
@ -2312,7 +2312,7 @@ impl<'a> AgentData<'a> {
|
|||||||
AttackTimer = 0,
|
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 =
|
let attack_select =
|
||||||
if agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] < 3.0 {
|
if agent.combat_state.timers[ActionStateTimers::AttackTimer as usize] < 3.0 {
|
||||||
@ -3076,34 +3076,48 @@ impl<'a> AgentData<'a> {
|
|||||||
ConditionCounterInit = 0,
|
ConditionCounterInit = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Timers {
|
||||||
|
ExtraSummonTimer = 0,
|
||||||
|
}
|
||||||
|
|
||||||
const MINDFLAYER_ATTACK_DIST: f32 = 16.0;
|
const MINDFLAYER_ATTACK_DIST: f32 = 16.0;
|
||||||
const MINION_SUMMON_THRESHOLD: f32 = 0.20;
|
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 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
|
// Sets counter at start of combat, using `condition` to keep track of whether
|
||||||
// it was already initialized
|
// it was already initialized
|
||||||
if !agent.combat_state.conditions[ActionStateConditions::ConditionCounterInit as usize] {
|
if !agent.combat_state.conditions[ActionStateConditions::ConditionCounterInit as usize] {
|
||||||
agent.combat_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize] =
|
agent.combat_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize] =
|
||||||
1.0 - MINION_SUMMON_THRESHOLD;
|
1.0 - MINION_SUMMON_THRESHOLD;
|
||||||
|
agent.combat_state.int_counters[ActionStateICounters::ICounterNumFireballs as usize] =
|
||||||
|
rand::random::<u8>() % 4;
|
||||||
agent.combat_state.conditions[ActionStateConditions::ConditionCounterInit as usize] =
|
agent.combat_state.conditions[ActionStateConditions::ConditionCounterInit as usize] =
|
||||||
true;
|
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]
|
if agent.combat_state.counters[ActionStateFCounters::FCounterHealthThreshold as usize]
|
||||||
> health_fraction
|
> 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
|
|
||||||
{
|
{
|
||||||
|
// 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
|
// Summon minions at particular thresholds of health
|
||||||
controller.push_basic_input(InputKind::Ability(2));
|
controller.push_basic_input(InputKind::Ability(2));
|
||||||
|
}
|
||||||
|
|
||||||
if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover))
|
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] -=
|
[ActionStateFCounters::FCounterHealthThreshold as usize] -=
|
||||||
MINION_SUMMON_THRESHOLD;
|
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) {
|
} else if attack_data.dist_sqrd < MINDFLAYER_ATTACK_DIST.powi(2) {
|
||||||
if entities_have_line_of_sight(
|
if entities_have_line_of_sight(
|
||||||
self.pos,
|
self.pos,
|
||||||
@ -3122,16 +3158,18 @@ impl<'a> AgentData<'a> {
|
|||||||
read_data,
|
read_data,
|
||||||
) {
|
) {
|
||||||
// If close to target, use either primary or secondary ability
|
// 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);
|
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))
|
} 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
|
// If already using secondary, keep using secondary until 10 consecutive
|
||||||
// seconds
|
// seconds
|
||||||
controller.push_basic_input(InputKind::Secondary);
|
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
|
// Else if at high health, use primary
|
||||||
controller.push_basic_input(InputKind::Primary);
|
controller.push_basic_input(InputKind::Primary);
|
||||||
} else {
|
} else {
|
||||||
@ -3280,7 +3318,7 @@ impl<'a> AgentData<'a> {
|
|||||||
read_data,
|
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());
|
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
|
// Teleport back to home position if we're too far from our home position but in
|
||||||
// range of the blink ability
|
// range of the blink ability
|
||||||
|
@ -414,7 +414,7 @@ impl Civs {
|
|||||||
)?,
|
)?,
|
||||||
SiteKind::DwarvenMine,
|
SiteKind::DwarvenMine,
|
||||||
),
|
),
|
||||||
87..=92 => (
|
87..=90 => (
|
||||||
find_site_loc(
|
find_site_loc(
|
||||||
&mut ctx,
|
&mut ctx,
|
||||||
&ProximityRequirementsBuilder::new()
|
&ProximityRequirementsBuilder::new()
|
||||||
|
@ -20,6 +20,7 @@ pub struct Room {
|
|||||||
clear_center: Vec2<i32>,
|
clear_center: Vec2<i32>,
|
||||||
mob_room: bool,
|
mob_room: bool,
|
||||||
boss_room: bool,
|
boss_room: bool,
|
||||||
|
portal_to_boss: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Cultist {
|
pub struct Cultist {
|
||||||
@ -47,18 +48,25 @@ impl Cultist {
|
|||||||
for s in 0..=1 {
|
for s in 0..=1 {
|
||||||
// rooms
|
// rooms
|
||||||
let rooms = [1, 2];
|
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) {
|
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_base = base - (f * (2 * (room_size))) - (s * room_size);
|
||||||
let room_center = center + (dir * ((room_size * 2) - 5 + (10 * s)));
|
let room_center = center + (dir * ((room_size * 2) - 5 + (10 * s)));
|
||||||
let clear_center = center + (dir * ((room_size * 2) - 6 + (10 * s)));
|
let clear_center = center + (dir * ((room_size * 2) - 6 + (10 * s)));
|
||||||
let mob_room = s < 1;
|
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_data.push(Room {
|
||||||
room_base,
|
room_base,
|
||||||
room_center,
|
room_center,
|
||||||
clear_center,
|
clear_center,
|
||||||
mob_room,
|
mob_room,
|
||||||
boss_room: false,
|
boss_room: false,
|
||||||
|
portal_to_boss,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +79,7 @@ impl Cultist {
|
|||||||
clear_center: center,
|
clear_center: center,
|
||||||
mob_room: false,
|
mob_room: false,
|
||||||
boss_room: true,
|
boss_room: true,
|
||||||
|
portal_to_boss: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -332,12 +341,13 @@ impl Structure for Cultist {
|
|||||||
}
|
}
|
||||||
// room clears
|
// room clears
|
||||||
for room in room_data {
|
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_base,
|
||||||
room.room_center,
|
room.room_center,
|
||||||
room.clear_center,
|
room.clear_center,
|
||||||
room.mob_room,
|
room.mob_room,
|
||||||
room.boss_room,
|
room.boss_room,
|
||||||
|
room.portal_to_boss,
|
||||||
);
|
);
|
||||||
painter
|
painter
|
||||||
.cylinder(Aabb {
|
.cylinder(Aabb {
|
||||||
@ -541,7 +551,7 @@ impl Structure for Cultist {
|
|||||||
let npc_pos = (room_center + dir * ((spacing / 2) * d))
|
let npc_pos = (room_center + dir * ((spacing / 2) * d))
|
||||||
.with_z(room_base - room_size + ((room_size / 3) * f));
|
.with_z(room_base - room_size + ((room_size / 3) * f));
|
||||||
let pos_var = RandomField::new(0).get(npc_pos) % 10;
|
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(
|
painter.spawn(EntityInfo::at(npc_pos.as_()).with_asset_expect(
|
||||||
"common.entity.dungeon.cultist.cultist",
|
"common.entity.dungeon.cultist.cultist",
|
||||||
&mut thread_rng,
|
&mut thread_rng,
|
||||||
@ -596,66 +606,30 @@ impl Structure for Cultist {
|
|||||||
let exit_position = (center - 10).with_z(base - (6 * room_size));
|
let exit_position = (center - 10).with_z(base - (6 * room_size));
|
||||||
let boss_position = (center - 10).with_z(base - (7 * room_size));
|
let boss_position = (center - 10).with_z(base - (7 * room_size));
|
||||||
let boss_portal = center.with_z(base - (7 * room_size));
|
let boss_portal = center.with_z(base - (7 * room_size));
|
||||||
|
let mini_boss_portal_target = if portal_to_boss {
|
||||||
let mob_portal_pos = Vec3::new(
|
boss_position.as_::<f32>()
|
||||||
mob_portal.x as f32,
|
} else {
|
||||||
mob_portal.y as f32,
|
exit_position.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,
|
|
||||||
];
|
|
||||||
|
|
||||||
if mob_room {
|
if mob_room {
|
||||||
painter.spawn(EntityInfo::at(mob_portal_pos).into_special(
|
painter.spawn(EntityInfo::at(mob_portal.as_::<f32>()).into_special(
|
||||||
SpecialEntity::Teleporter(PortalData {
|
SpecialEntity::Teleporter(PortalData {
|
||||||
target: mob_portal_target_pos,
|
target: mob_portal_target.as_::<f32>(),
|
||||||
requires_no_aggro: true,
|
requires_no_aggro: true,
|
||||||
buildup_time: Secs(5.),
|
buildup_time: Secs(5.),
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
let mini_boss_portal_target_index = RandomField::new(0).get(mini_boss_portal)
|
painter.spawn(EntityInfo::at(mini_boss_portal.as_::<f32>()).into_special(
|
||||||
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(
|
|
||||||
SpecialEntity::Teleporter(PortalData {
|
SpecialEntity::Teleporter(PortalData {
|
||||||
target: mini_boss_portal_target_pos,
|
target: mini_boss_portal_target,
|
||||||
requires_no_aggro: true,
|
requires_no_aggro: true,
|
||||||
buildup_time: Secs(5.),
|
buildup_time: Secs(5.),
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
} else if boss_room {
|
} else if boss_room {
|
||||||
painter.spawn(EntityInfo::at(boss_portal_pos).into_special(
|
painter.spawn(EntityInfo::at(boss_portal.as_::<f32>()).into_special(
|
||||||
SpecialEntity::Teleporter(PortalData {
|
SpecialEntity::Teleporter(PortalData {
|
||||||
target: exit_pos,
|
target: exit_position.as_::<f32>(),
|
||||||
requires_no_aggro: true,
|
requires_no_aggro: true,
|
||||||
buildup_time: Secs(5.),
|
buildup_time: Secs(5.),
|
||||||
}),
|
}),
|
||||||
|
Loading…
Reference in New Issue
Block a user