Add a purple fireball to mindflayer and have it shoot it a few times before teleporting, and fix mindflayer AI's husk summons.

This commit is contained in:
Avi Weinstock 2021-05-09 14:28:01 -04:00
parent 9168ddac75
commit 1dfcdce1c0
9 changed files with 172 additions and 56 deletions

43
Cargo.lock generated
View File

@ -2399,6 +2399,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "inline_tweak"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7033e97b20277cc0d043226d1940fa7719ff08d2305d1fc7421e53066d00eb4b"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "inotify" name = "inotify"
version = "0.7.1" version = "0.7.1"
@ -5514,6 +5523,7 @@ name = "veloren-common"
version = "0.9.0" version = "0.9.0"
dependencies = [ dependencies = [
"approx 0.4.0", "approx 0.4.0",
"assets_manager",
"bitflags", "bitflags",
"criterion", "criterion",
"crossbeam-channel", "crossbeam-channel",
@ -5522,6 +5532,7 @@ dependencies = [
"dot_vox", "dot_vox",
"enum-iterator", "enum-iterator",
"hashbrown", "hashbrown",
"image",
"indexmap", "indexmap",
"lazy_static", "lazy_static",
"num-derive", "num-derive",
@ -5529,8 +5540,10 @@ dependencies = [
"ordered-float 2.1.1", "ordered-float 2.1.1",
"rand 0.8.3", "rand 0.8.3",
"rayon", "rayon",
"ron",
"roots", "roots",
"serde", "serde",
"serde_json",
"serde_repr", "serde_repr",
"slab", "slab",
"slotmap", "slotmap",
@ -5544,22 +5557,9 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
"uuid", "uuid",
"vek", "vek",
"veloren-common-assets",
"veloren-common-base", "veloren-common-base",
] ]
[[package]]
name = "veloren-common-assets"
version = "0.9.0"
dependencies = [
"assets_manager",
"dot_vox",
"image",
"lazy_static",
"ron",
"tracing",
]
[[package]] [[package]]
name = "veloren-common-base" name = "veloren-common-base"
version = "0.9.0" version = "0.9.0"
@ -5652,19 +5652,6 @@ dependencies = [
"veloren-common-net", "veloren-common-net",
] ]
[[package]]
name = "veloren-i18n"
version = "0.9.0"
dependencies = [
"deunicode",
"git2",
"hashbrown",
"ron",
"serde",
"tracing",
"veloren-common-assets",
]
[[package]] [[package]]
name = "veloren-network" name = "veloren-network"
version = "0.3.0" version = "0.3.0"
@ -5749,6 +5736,7 @@ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"futures-util", "futures-util",
"hashbrown", "hashbrown",
"inline_tweak",
"itertools 0.10.0", "itertools 0.10.0",
"lazy_static", "lazy_static",
"num_cpus", "num_cpus",
@ -5817,6 +5805,7 @@ dependencies = [
"cpal", "cpal",
"criterion", "criterion",
"crossbeam", "crossbeam",
"deunicode",
"directories-next", "directories-next",
"dispatch 0.1.4", "dispatch 0.1.4",
"dot_vox", "dot_vox",
@ -5826,6 +5815,7 @@ dependencies = [
"gfx_device_gl", "gfx_device_gl",
"gfx_gl", "gfx_gl",
"gilrs", "gilrs",
"git2",
"glsl-include", "glsl-include",
"glutin", "glutin",
"glyph_brush", "glyph_brush",
@ -5863,7 +5853,6 @@ dependencies = [
"veloren-common-net", "veloren-common-net",
"veloren-common-state", "veloren-common-state",
"veloren-common-systems", "veloren-common-systems",
"veloren-i18n",
"veloren-server", "veloren-server",
"veloren-voxygen-anim", "veloren-voxygen-anim",
"veloren-world", "veloren-world",

View File

@ -204,6 +204,7 @@
secondary: "common.abilities.custom.mindflayer.necroticvortex", secondary: "common.abilities.custom.mindflayer.necroticvortex",
abilities: [ abilities: [
(None, "common.abilities.custom.mindflayer.dimensionaldoor"), (None, "common.abilities.custom.mindflayer.dimensionaldoor"),
(None, "common.abilities.custom.mindflayer.necroticsphere"),
(None, "common.abilities.custom.mindflayer.summonminions"), (None, "common.abilities.custom.mindflayer.summonminions"),
], ],
), ),

View File

@ -0,0 +1,16 @@
BasicRanged(
energy_cost: 0,
buildup_duration: 0.75,
recover_duration: 0.4,
projectile: NecroticSphere(
damage: 300.0,
radius: 5.0,
),
projectile_body: Object(FireworkPurple),
/*projectile_light: Some(LightEmitter {
col: (1.0, 0.75, 0.11).into(),
..Default::default()
}),*/
projectile_speed: 100.0,
)

View File

@ -280,7 +280,7 @@ pub struct Agent {
pub struct ActionState { pub struct ActionState {
pub timer: f32, pub timer: f32,
pub counter: f32, pub counter: f32,
pub condition: bool, pub condition: u8,
} }
impl Agent { impl Agent {

View File

@ -54,6 +54,10 @@ pub enum ProjectileConstructor {
damage: f32, damage: f32,
radius: f32, radius: f32,
}, },
NecroticSphere {
damage: f32,
radius: f32,
},
Possess, Possess,
} }
@ -168,6 +172,32 @@ impl ProjectileConstructor {
ignore_group: true, ignore_group: true,
} }
}, },
NecroticSphere { damage, radius } => {
let damage = AttackDamage::new(
Damage {
source: DamageSource::Explosion,
kind: DamageKind::Energy,
value: damage,
},
Some(GroupTarget::OutOfGroup),
);
let attack = Attack::default()
.with_damage(damage)
.with_crit(crit_chance, crit_mult)
.with_combo_increment();
let explosion = Explosion {
effects: vec![RadiusEffect::Attack(attack)],
radius,
reagent: Some(Reagent::Purple),
};
Projectile {
hit_solid: vec![Effect::Explode(explosion.clone()), Effect::Vanish],
hit_entity: vec![Effect::Explode(explosion), Effect::Vanish],
time_left: Duration::from_secs(10),
owner,
ignore_group: true,
}
},
Possess => Projectile { Possess => Projectile {
hit_solid: vec![Effect::Stick], hit_solid: vec![Effect::Stick],
hit_entity: vec![Effect::Stick, Effect::Possess], hit_entity: vec![Effect::Stick, Effect::Possess],
@ -207,6 +237,14 @@ impl ProjectileConstructor {
*damage *= power; *damage *= power;
*radius *= range; *radius *= range;
}, },
NecroticSphere {
ref mut damage,
ref mut radius,
..
} => {
*damage *= power;
*radius *= range;
},
Possess => {}, Possess => {},
} }
self self

View File

@ -577,27 +577,33 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
.inventory .inventory
.equipped(equip_slot) .equipped(equip_slot)
.map(|i| &i.item_config_expect().abilities) .map(|i| &i.item_config_expect().abilities)
.and_then(|abilities| match input { .and_then(|abilities| {
InputKind::Primary => Some(abilities.primary.clone()), tracing::info!("ability: {:?} {:?}", input, abilities);
InputKind::Secondary => Some(abilities.secondary.clone()), match input {
InputKind::Ability(0) => abilities.abilities.get(0).cloned().and_then(unlocked), InputKind::Primary => Some(abilities.primary.clone()),
InputKind::Ability(_) => abilities InputKind::Secondary => Some(abilities.secondary.clone()),
.abilities InputKind::Ability(0) => abilities.abilities.get(0).cloned().and_then(unlocked),
.get(skill_index) InputKind::Ability(skill_index) => abilities
.cloned() .abilities
.and_then(unlocked), .get(skill_index)
InputKind::Roll | InputKind::Jump | InputKind::Fly | InputKind::Block => None, .cloned()
.and_then(unlocked),
InputKind::Roll | InputKind::Jump | InputKind::Fly | InputKind::Block => None,
}
}) })
.map(|a| { .map(|a| {
let tool = unwrap_tool_data(data, equip_slot).map(|t| t.kind); let tool = unwrap_tool_data(data, equip_slot).map(|t| t.kind);
tracing::info!("ability tool: {:?} {:?}", input, tool);
a.adjusted_by_skills(&data.skill_set, tool) a.adjusted_by_skills(&data.skill_set, tool)
}) })
.filter(|ability| ability.requirements_paid(data, update)) .filter(|ability| ability.requirements_paid(data, update))
{ {
tracing::info!("ability before setting state: {:?} {:?}", input, ability);
update.character = CharacterState::from(( update.character = CharacterState::from((
&ability, &ability,
AbilityInfo::from_input(data, matches!(equip_slot, EquipSlot::Offhand), input), AbilityInfo::from_input(data, matches!(equip_slot, EquipSlot::Offhand), input),
)); ));
tracing::info!("ability setting state: {:?} {:?}", input, update.character);
} }
} }
} }
@ -801,12 +807,14 @@ impl AbilityInfo {
tool_data.map(|t| HandInfo::from_main_tool(t, from_offhand)), tool_data.map(|t| HandInfo::from_main_tool(t, from_offhand)),
); );
Self { let ret = Self {
tool, tool,
hand, hand,
input, input,
input_attr: data.controller.queued_inputs.get(&input).copied(), input_attr: data.controller.queued_inputs.get(&input).copied(),
} };
tracing::info!("AbilityInfo::from_input: {:?} {:?}", input, ret);
ret
} }
} }

View File

@ -22,6 +22,8 @@ common-net = { package = "veloren-common-net", path = "../common/net" }
world = { package = "veloren-world", path = "../world" } world = { package = "veloren-world", path = "../world" }
network = { package = "veloren-network", path = "../network", features = ["metrics", "compression"], default-features = false } network = { package = "veloren-network", path = "../network", features = ["metrics", "compression"], default-features = false }
inline_tweak = "1.0.8"
specs = { git = "https://github.com/amethyst/specs.git", features = ["shred-derive"], rev = "5a9b71035007be0e3574f35184acac1cd4530496" } specs = { git = "https://github.com/amethyst/specs.git", features = ["shred-derive"], rev = "5a9b71035007be0e3574f35184acac1cd4530496" }
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "b65fb220e94f5d3c9bc30074a076149763795556" } specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "b65fb220e94f5d3c9bc30074a076149763795556" }

View File

@ -2996,33 +2996,73 @@ impl<'a> AgentData<'a> {
) { ) {
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;
// Bit index of action_state.condition for whether the mindflayer initialized
// minion summoning state
const MINDFLAYER_INITIALIZED_THRESHOLD: usize = 0;
// Bit index of action_state.condition for how many fireballs left to shoot
// before blinking (this is a 2 bit number, from 0-3)
const MINDFLAYER_NUM_FIREBALLS_LO: usize = 1;
const MINDFLAYER_NUM_FIREBALLS_MASK: usize = 0b110;
let health_fraction = self.health.map_or(0.5, |h| h.fraction()); let health_fraction = self.health.map_or(0.5, |h| h.fraction());
// Sets counter at start of combat // Sets counter at start of combat
if agent.action_state.condition { if (agent.action_state.condition & (1 << MINDFLAYER_INITIALIZED_THRESHOLD)) == 0 {
agent.action_state.counter = 1.0 - MINION_SUMMON_THRESHOLD; agent.action_state.counter = 1.0 - MINION_SUMMON_THRESHOLD;
agent.action_state.condition = true; agent.action_state.condition |= 1 << MINDFLAYER_INITIALIZED_THRESHOLD;
}
agent.action_state.timer = (agent.action_state.timer - read_data.dt.0 as f32).max(0.0);
if agent.action_state.timer > 0.0 {
return;
} }
let mindflayer_is_far = attack_data.dist_sqrd > MINDFLAYER_ATTACK_DIST.powi(2); let mindflayer_is_far = attack_data.dist_sqrd > MINDFLAYER_ATTACK_DIST.powi(2);
if agent.action_state.counter > health_fraction { if agent.action_state.counter > health_fraction {
// Summon minions at particular thresholds of health // Summon minions at particular thresholds of health
controller controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Ability(1))); .push(ControlAction::basic_input(InputKind::Ability(2)));
//tracing::info!("Pushing summon state: {:?}", agent);
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))
{ {
agent.action_state.counter -= MINION_SUMMON_THRESHOLD; agent.action_state.counter -= MINION_SUMMON_THRESHOLD;
} }
} else if matches!(
self.char_state,
CharacterState::BasicSummon(_) | CharacterState::Blink(_)
) {
// Deliberately do nothing here to prevent overwriting summon/blink state with another
// input
} else if mindflayer_is_far { } else if mindflayer_is_far {
// If too far from target, blink to them. // If too far from target, throw a random number of necrotic spheres at them and then
controller.actions.push(ControlAction::StartInput { // blink to them.
input: InputKind::Ability(0), let num_fireballs = (agent.action_state.condition & 0b110) >> 1;
target_entity: agent if num_fireballs == 0 {
.target let new_num_fireballs = rand::random::<u8>() % 4;
.as_ref() agent.action_state.condition &= !0b110;
.and_then(|t| read_data.uids.get(t.target)) agent.action_state.condition |= new_num_fireballs << 1;
.copied(), controller.actions.push(ControlAction::StartInput {
select_pos: None, input: InputKind::Ability(0),
}); target_entity: agent
.target
.as_ref()
.and_then(|t| read_data.uids.get(t.target))
.copied(),
select_pos: None,
});
} else {
let new_num_fireballs = num_fireballs - 1;
agent.action_state.condition &= !0b110;
agent.action_state.condition |= new_num_fireballs << 1;
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(1),
target_entity: agent
.target
.as_ref()
.and_then(|t| read_data.uids.get(t.target))
.copied(),
select_pos: None,
});
}
agent.action_state.timer = 0.1;
} else { } else {
// 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(10) && !matches!(c.stage_section, StageSection::Recover))
@ -3353,6 +3393,9 @@ impl<'a> AgentData<'a> {
const MINOTAUR_FRENZY_THRESHOLD: f32 = 0.5; const MINOTAUR_FRENZY_THRESHOLD: f32 = 0.5;
const MINOTAUR_ATTACK_RANGE: f32 = 5.0; const MINOTAUR_ATTACK_RANGE: f32 = 5.0;
const MINOTAUR_CHARGE_DISTANCE: f32 = 15.0; const MINOTAUR_CHARGE_DISTANCE: f32 = 15.0;
// Bit index of the action_state.condition if the minotaur should use secondary
// instead of primary
const MINOTAUR_STATE_SECONDARY: usize = 0;
let minotaur_attack_distance = let minotaur_attack_distance =
self.body.map_or(0.0, |b| b.radius()) + MINOTAUR_ATTACK_RANGE; self.body.map_or(0.0, |b| b.radius()) + MINOTAUR_ATTACK_RANGE;
let health_fraction = self.health.map_or(1.0, |h| h.fraction()); let health_fraction = self.health.map_or(1.0, |h| h.fraction());
@ -3391,18 +3434,20 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Ability(0))); .push(ControlAction::basic_input(InputKind::Ability(0)));
} }
} else if attack_data.dist_sqrd < minotaur_attack_distance.powi(2) { } else if attack_data.dist_sqrd < minotaur_attack_distance.powi(2) {
if agent.action_state.condition && !self.char_state.is_attack() { if (agent.action_state.condition & (1 << MINOTAUR_STATE_SECONDARY)) != 0
&& !self.char_state.is_attack()
{
// Cripple target if not just used cripple // Cripple target if not just used cripple
controller controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Secondary)); .push(ControlAction::basic_input(InputKind::Secondary));
agent.action_state.condition = false; agent.action_state.condition &= !(1 << MINOTAUR_STATE_SECONDARY);
} else if !self.char_state.is_attack() { } else if !self.char_state.is_attack() {
// Cleave target if not just used cleave // Cleave target if not just used cleave
controller controller
.actions .actions
.push(ControlAction::basic_input(InputKind::Primary)); .push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.condition = true; agent.action_state.condition |= 1 << MINOTAUR_STATE_SECONDARY;
} }
} }
// Make minotaur move towards target // Make minotaur move towards target

View File

@ -117,6 +117,23 @@ impl ParticleMgr {
}, },
); );
}, },
Some(Reagent::Purple) => {
self.particles.resize_with(
self.particles.len() + (75.0 * power.abs()) as usize,
|| {
Particle::new_directed(
Duration::from_millis(500),
time,
ParticleMode::CultistFlame,
*pos,
*pos + Vec3::<f32>::zero()
.map(|_| rng.gen_range(-1.0..1.0))
.normalized()
* *radius,
)
},
);
},
_ => {}, _ => {},
} }
} else { } else {