diff --git a/Cargo.lock b/Cargo.lock index c3cf499305..a66b55e18c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2399,6 +2399,15 @@ dependencies = [ "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]] name = "inotify" version = "0.7.1" @@ -5514,6 +5523,7 @@ name = "veloren-common" version = "0.9.0" dependencies = [ "approx 0.4.0", + "assets_manager", "bitflags", "criterion", "crossbeam-channel", @@ -5522,6 +5532,7 @@ dependencies = [ "dot_vox", "enum-iterator", "hashbrown", + "image", "indexmap", "lazy_static", "num-derive", @@ -5529,8 +5540,10 @@ dependencies = [ "ordered-float 2.1.1", "rand 0.8.3", "rayon", + "ron", "roots", "serde", + "serde_json", "serde_repr", "slab", "slotmap", @@ -5544,22 +5557,9 @@ dependencies = [ "tracing-subscriber", "uuid", "vek", - "veloren-common-assets", "veloren-common-base", ] -[[package]] -name = "veloren-common-assets" -version = "0.9.0" -dependencies = [ - "assets_manager", - "dot_vox", - "image", - "lazy_static", - "ron", - "tracing", -] - [[package]] name = "veloren-common-base" version = "0.9.0" @@ -5652,19 +5652,6 @@ dependencies = [ "veloren-common-net", ] -[[package]] -name = "veloren-i18n" -version = "0.9.0" -dependencies = [ - "deunicode", - "git2", - "hashbrown", - "ron", - "serde", - "tracing", - "veloren-common-assets", -] - [[package]] name = "veloren-network" version = "0.3.0" @@ -5749,6 +5736,7 @@ dependencies = [ "crossbeam-channel", "futures-util", "hashbrown", + "inline_tweak", "itertools 0.10.0", "lazy_static", "num_cpus", @@ -5817,6 +5805,7 @@ dependencies = [ "cpal", "criterion", "crossbeam", + "deunicode", "directories-next", "dispatch 0.1.4", "dot_vox", @@ -5826,6 +5815,7 @@ dependencies = [ "gfx_device_gl", "gfx_gl", "gilrs", + "git2", "glsl-include", "glutin", "glyph_brush", @@ -5863,7 +5853,6 @@ dependencies = [ "veloren-common-net", "veloren-common-state", "veloren-common-systems", - "veloren-i18n", "veloren-server", "veloren-voxygen-anim", "veloren-world", diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 33150c51e5..f0ef774303 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -204,6 +204,7 @@ secondary: "common.abilities.custom.mindflayer.necroticvortex", abilities: [ (None, "common.abilities.custom.mindflayer.dimensionaldoor"), + (None, "common.abilities.custom.mindflayer.necroticsphere"), (None, "common.abilities.custom.mindflayer.summonminions"), ], ), diff --git a/assets/common/abilities/custom/mindflayer/necroticsphere.ron b/assets/common/abilities/custom/mindflayer/necroticsphere.ron new file mode 100644 index 0000000000..32c2a54976 --- /dev/null +++ b/assets/common/abilities/custom/mindflayer/necroticsphere.ron @@ -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, +) + diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 480d25197a..53e20f0b40 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -280,7 +280,7 @@ pub struct Agent { pub struct ActionState { pub timer: f32, pub counter: f32, - pub condition: bool, + pub condition: u8, } impl Agent { diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index 392ed68ed9..7bf4ed5f35 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -54,6 +54,10 @@ pub enum ProjectileConstructor { damage: f32, radius: f32, }, + NecroticSphere { + damage: f32, + radius: f32, + }, Possess, } @@ -168,6 +172,32 @@ impl ProjectileConstructor { 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 { hit_solid: vec![Effect::Stick], hit_entity: vec![Effect::Stick, Effect::Possess], @@ -207,6 +237,14 @@ impl ProjectileConstructor { *damage *= power; *radius *= range; }, + NecroticSphere { + ref mut damage, + ref mut radius, + .. + } => { + *damage *= power; + *radius *= range; + }, Possess => {}, } self diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs index 7b155d1787..3f0d010252 100644 --- a/common/src/states/utils.rs +++ b/common/src/states/utils.rs @@ -577,27 +577,33 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) { .inventory .equipped(equip_slot) .map(|i| &i.item_config_expect().abilities) - .and_then(|abilities| match input { - InputKind::Primary => Some(abilities.primary.clone()), - InputKind::Secondary => Some(abilities.secondary.clone()), - InputKind::Ability(0) => abilities.abilities.get(0).cloned().and_then(unlocked), - InputKind::Ability(_) => abilities - .abilities - .get(skill_index) - .cloned() - .and_then(unlocked), - InputKind::Roll | InputKind::Jump | InputKind::Fly | InputKind::Block => None, + .and_then(|abilities| { + tracing::info!("ability: {:?} {:?}", input, abilities); + match input { + InputKind::Primary => Some(abilities.primary.clone()), + InputKind::Secondary => Some(abilities.secondary.clone()), + InputKind::Ability(0) => abilities.abilities.get(0).cloned().and_then(unlocked), + InputKind::Ability(skill_index) => abilities + .abilities + .get(skill_index) + .cloned() + .and_then(unlocked), + InputKind::Roll | InputKind::Jump | InputKind::Fly | InputKind::Block => None, + } }) .map(|a| { 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) }) .filter(|ability| ability.requirements_paid(data, update)) { + tracing::info!("ability before setting state: {:?} {:?}", input, ability); update.character = CharacterState::from(( &ability, 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)), ); - Self { + let ret = Self { tool, hand, input, input_attr: data.controller.queued_inputs.get(&input).copied(), - } + }; + tracing::info!("AbilityInfo::from_input: {:?} {:?}", input, ret); + ret } } diff --git a/server/Cargo.toml b/server/Cargo.toml index b30c8b1a12..3cfce9de29 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -22,6 +22,8 @@ common-net = { package = "veloren-common-net", path = "../common/net" } world = { package = "veloren-world", path = "../world" } 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-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "b65fb220e94f5d3c9bc30074a076149763795556" } diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index a64eab9050..1631dc3fa5 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -2996,33 +2996,73 @@ impl<'a> AgentData<'a> { ) { const MINDFLAYER_ATTACK_DIST: f32 = 16.0; 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()); // 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.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); if agent.action_state.counter > health_fraction { // Summon minions at particular thresholds of health controller .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)) { 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 { - // If too far from target, blink to them. - controller.actions.push(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, - }); + // If too far from target, throw a random number of necrotic spheres at them and then + // blink to them. + let num_fireballs = (agent.action_state.condition & 0b110) >> 1; + if num_fireballs == 0 { + let new_num_fireballs = rand::random::() % 4; + agent.action_state.condition &= !0b110; + agent.action_state.condition |= new_num_fireballs << 1; + controller.actions.push(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 { + 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 { // 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)) @@ -3353,6 +3393,9 @@ impl<'a> AgentData<'a> { const MINOTAUR_FRENZY_THRESHOLD: f32 = 0.5; const MINOTAUR_ATTACK_RANGE: f32 = 5.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 = self.body.map_or(0.0, |b| b.radius()) + MINOTAUR_ATTACK_RANGE; 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))); } } 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 controller .actions .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() { // Cleave target if not just used cleave controller .actions .push(ControlAction::basic_input(InputKind::Primary)); - agent.action_state.condition = true; + agent.action_state.condition |= 1 << MINOTAUR_STATE_SECONDARY; } } // Make minotaur move towards target diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index fdd166abaf..142de8e0c6 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -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::::zero() + .map(|_| rng.gen_range(-1.0..1.0)) + .normalized() + * *radius, + ) + }, + ); + }, _ => {}, } } else {