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
|
||||
- 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
|
||||
|
||||
|
@ -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"): (
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
),
|
||||
|
@ -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"),
|
||||
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"),
|
||||
|
@ -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: [],
|
||||
)
|
||||
)
|
||||
|
@ -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: [],
|
||||
)
|
||||
)
|
||||
|
@ -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: [],
|
||||
)
|
||||
)
|
||||
|
@ -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: [],
|
||||
)
|
||||
)
|
||||
|
@ -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: [],
|
||||
)
|
||||
)
|
||||
|
@ -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"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -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"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -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"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -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)),
|
||||
|
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
|
||||
(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)),
|
||||
|
@ -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")),
|
||||
(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),
|
||||
])
|
||||
])),
|
||||
]
|
||||
|
@ -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 {
|
||||
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,
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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::<u8>() % 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
|
||||
|
@ -414,7 +414,7 @@ impl Civs {
|
||||
)?,
|
||||
SiteKind::DwarvenMine,
|
||||
),
|
||||
87..=92 => (
|
||||
87..=90 => (
|
||||
find_site_loc(
|
||||
&mut ctx,
|
||||
&ProximityRequirementsBuilder::new()
|
||||
|
@ -20,6 +20,7 @@ pub struct Room {
|
||||
clear_center: Vec2<i32>,
|
||||
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_::<f32>()
|
||||
} else {
|
||||
exit_position.as_::<f32>()
|
||||
};
|
||||
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 {
|
||||
target: mob_portal_target_pos,
|
||||
target: mob_portal_target.as_::<f32>(),
|
||||
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_::<f32>()).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_::<f32>()).into_special(
|
||||
SpecialEntity::Teleporter(PortalData {
|
||||
target: exit_pos,
|
||||
target: exit_position.as_::<f32>(),
|
||||
requires_no_aggro: true,
|
||||
buildup_time: Secs(5.),
|
||||
}),
|
||||
|
Loading…
Reference in New Issue
Block a user