From a37e1f8d99246bdd22517037cd7b678ce88ebcc5 Mon Sep 17 00:00:00 2001 From: flo Date: Thu, 22 Sep 2022 01:04:01 +0000 Subject: [PATCH] seachapel npc work --- .../common/abilities/ability_set_manifest.ron | 8 +- .../abilities/custom/cardinal/steambeam.ron | 19 ++ .../custom/cardinal/summonseacrocs.ron | 6 +- .../abilities/custom/dagon/dagonbombs.ron | 12 +- .../abilities/custom/dagon/seaurchins.ron | 2 +- .../abilities/custom/dagon/steamheal.ron | 19 ++ .../abilities/custom/dagon/steamwave.ron | 17 ++ .../entity/dungeon/sea_chapel/prisoner.ron | 13 + assets/common/items/armor/cardinal/belt.ron | 9 +- assets/common/items/armor/cardinal/chest.ron | 9 +- assets/common/items/armor/cardinal/foot.ron | 9 +- assets/common/items/armor/cardinal/hand.ron | 9 +- assets/common/items/armor/cardinal/pants.ron | 9 +- .../common/items/armor/cardinal/shoulder.ron | 9 +- .../items/npc_armor/quadruped_low/dagon.ron | 13 + .../common/items/npc_weapons/unique/dagon.ron | 2 +- assets/common/material_stats_manifest.ron | 7 + assets/voxygen/audio/sfx.ron | 7 + .../audio/sfx/utterance/dagon_hurt1.ogg | 3 + .../audio/sfx/utterance/dagon_hurt2.ogg | 3 + assets/voxygen/i18n/en/npc.ftl | 8 +- assets/voxygen/shaders/particle-vert.glsl | 23 ++ common/src/comp/beam.rs | 1 + common/src/comp/inventory/loadout_builder.rs | 1 + common/src/comp/projectile.rs | 2 +- common/src/comp/shockwave.rs | 1 + server/agent/src/attack.rs | 50 ++-- server/src/rtsim/entity.rs | 5 +- server/src/rtsim/mod.rs | 27 ++ .../sys/agent/behavior_tree/interaction.rs | 264 ++++++++++-------- voxygen/src/audio/sfx/mod.rs | 3 + voxygen/src/render/pipelines/particle.rs | 3 +- voxygen/src/scene/particle.rs | 60 ++++ world/src/site2/plot/sea_chapel.rs | 49 +--- 34 files changed, 438 insertions(+), 244 deletions(-) create mode 100644 assets/common/abilities/custom/cardinal/steambeam.ron create mode 100644 assets/common/abilities/custom/dagon/steamheal.ron create mode 100644 assets/common/abilities/custom/dagon/steamwave.ron create mode 100644 assets/common/entity/dungeon/sea_chapel/prisoner.ron create mode 100644 assets/common/items/npc_armor/quadruped_low/dagon.ron create mode 100644 assets/voxygen/audio/sfx/utterance/dagon_hurt1.ogg create mode 100644 assets/voxygen/audio/sfx/utterance/dagon_hurt2.ogg diff --git a/assets/common/abilities/ability_set_manifest.ron b/assets/common/abilities/ability_set_manifest.ron index 4e205599ee..f448a5711c 100644 --- a/assets/common/abilities/ability_set_manifest.ron +++ b/assets/common/abilities/ability_set_manifest.ron @@ -381,13 +381,17 @@ Custom("Dagon"): ( primary: "common.abilities.custom.dagon.dagonbombs", secondary: "common.abilities.custom.dagon.seaurchins", - abilities: [], + abilities: [ + (None, "common.abilities.custom.dagon.steamwave"), + (None, "common.abilities.custom.cardinal.steambeam"), + (None, "common.abilities.custom.dagon.steamheal"), + ], ), Custom("Cardinal"): ( primary: "common.abilities.sceptre.lifestealbeam", secondary: "common.abilities.sceptre.healingaura", abilities: [ - (Some(Sceptre(UnlockAura)), "common.abilities.sceptre.wardingaura"), + (None, "common.abilities.custom.cardinal.steambeam"), (None, "common.abilities.custom.cardinal.summonseacrocs"), ], ), diff --git a/assets/common/abilities/custom/cardinal/steambeam.ron b/assets/common/abilities/custom/cardinal/steambeam.ron new file mode 100644 index 0000000000..c131b9859e --- /dev/null +++ b/assets/common/abilities/custom/cardinal/steambeam.ron @@ -0,0 +1,19 @@ +BasicBeam( + buildup_duration: 0.3, + recover_duration: 1.0, + beam_duration: 1.0, + damage: 22.5, + tick_rate: 5.0, + range: 8.0, + max_angle: 15.0, + damage_effect: Some(Buff(( + kind: Burning, + dur_secs: 3.0, + strength: Value(0.5), + chance: 1.0, + ))), + energy_regen: 2, + energy_drain: 0, + ori_rate: 0.5, + specifier: Steam, +) \ No newline at end of file diff --git a/assets/common/abilities/custom/cardinal/summonseacrocs.ron b/assets/common/abilities/custom/cardinal/summonseacrocs.ron index 216b7d097c..daf70af1ca 100644 --- a/assets/common/abilities/custom/cardinal/summonseacrocs.ron +++ b/assets/common/abilities/custom/cardinal/summonseacrocs.ron @@ -1,7 +1,7 @@ BasicSummon( - buildup_duration: 0.5, - cast_duration: 1.0, - recover_duration: 0.5, + buildup_duration: 0.2, + cast_duration: 0.3, + recover_duration: 0.3, summon_amount: 4, summon_distance: (4, 4), summon_info: ( diff --git a/assets/common/abilities/custom/dagon/dagonbombs.ron b/assets/common/abilities/custom/dagon/dagonbombs.ron index 6744061753..851601575f 100644 --- a/assets/common/abilities/custom/dagon/dagonbombs.ron +++ b/assets/common/abilities/custom/dagon/dagonbombs.ron @@ -1,16 +1,16 @@ BasicRanged( energy_cost: 0, - buildup_duration: 0.4, - recover_duration: 0.6, + buildup_duration: 0.5, + recover_duration: 1.5, projectile: DagonBomb( damage: 32.0, - knockback: 25.0, - radius: 10.0, - min_falloff: 0.6, + knockback: 15.0, + radius: 5.0, + min_falloff: 0.1, ), projectile_body: Object(DagonBomb), projectile_light: None, - projectile_speed: 30.0, + projectile_speed: 20.0, num_projectiles: 1, projectile_spread: 0.0, ) diff --git a/assets/common/abilities/custom/dagon/seaurchins.ron b/assets/common/abilities/custom/dagon/seaurchins.ron index ad76f2755d..0f01e6868f 100644 --- a/assets/common/abilities/custom/dagon/seaurchins.ron +++ b/assets/common/abilities/custom/dagon/seaurchins.ron @@ -3,6 +3,6 @@ SpriteSummon( cast_duration: 0.1, recover_duration: 0.9, sprite: SeaUrchin, - summon_distance: (3, 3.1), + summon_distance: (5, 3.1), sparseness: 0.2, ) \ No newline at end of file diff --git a/assets/common/abilities/custom/dagon/steamheal.ron b/assets/common/abilities/custom/dagon/steamheal.ron new file mode 100644 index 0000000000..ab485cae77 --- /dev/null +++ b/assets/common/abilities/custom/dagon/steamheal.ron @@ -0,0 +1,19 @@ +BasicAura( + buildup_duration: 0.2, + cast_duration: 0.4, + recover_duration: 5.0, + targets: InGroup, + auras: [ + ( + kind: Regeneration, + strength: 10.0, + duration: Some(5), + category: Magical, + ), + ], + aura_duration: 2.0, + range: 10.0, + energy_cost: 0.0, + scales_with_combo: false, + specifier: Some(HealingAura), +) \ No newline at end of file diff --git a/assets/common/abilities/custom/dagon/steamwave.ron b/assets/common/abilities/custom/dagon/steamwave.ron new file mode 100644 index 0000000000..ded3515f73 --- /dev/null +++ b/assets/common/abilities/custom/dagon/steamwave.ron @@ -0,0 +1,17 @@ +Shockwave( + energy_cost: 0, + buildup_duration: 0.3, + swing_duration: 0.3, + recover_duration: 0.0, + damage: 20.0, + poise_damage: 10, + knockback: (strength: 18.0, direction: Away), + shockwave_angle: 360.0, + shockwave_vertical_angle: 90.0, + shockwave_speed: 15.0, + shockwave_duration: 2.0, + requires_ground: true, + move_efficiency: 0.0, + damage_kind: Crushing, + specifier: Steam, +) diff --git a/assets/common/entity/dungeon/sea_chapel/prisoner.ron b/assets/common/entity/dungeon/sea_chapel/prisoner.ron new file mode 100644 index 0000000000..1758103368 --- /dev/null +++ b/assets/common/entity/dungeon/sea_chapel/prisoner.ron @@ -0,0 +1,13 @@ +#![enable(implicit_some)] +( + name: Name("Prisoner"), + body: RandomWith("humanoid"), + alignment: Alignment(Npc), + loot: LootTable("common.loot_tables.creature.humanoid"), + inventory: ( + loadout: Inline(( + inherit: Asset("common.loadout.village.villager"), + )), + ), + meta: [], +) \ No newline at end of file diff --git a/assets/common/items/armor/cardinal/belt.ron b/assets/common/items/armor/cardinal/belt.ron index c4b67693c7..1f8573cb25 100644 --- a/assets/common/items/armor/cardinal/belt.ron +++ b/assets/common/items/armor/cardinal/belt.ron @@ -3,14 +3,7 @@ ItemDef( description: "Seemlessly transitions...", kind: Armor(( kind: Belt, - stats: Direct(( - protection: Some(Normal(24.0)), - poise_resilience: Some(Normal(3.0)), - energy_max: Some(20), - energy_reward: Some(0.025), - crit_power: Some(0.06), - stealth: Some(0.0), - )), + stats: FromSet("Cardinal"), )), quality: Legendary, tags: [ diff --git a/assets/common/items/armor/cardinal/chest.ron b/assets/common/items/armor/cardinal/chest.ron index d20c5fc785..6887d78402 100644 --- a/assets/common/items/armor/cardinal/chest.ron +++ b/assets/common/items/armor/cardinal/chest.ron @@ -3,14 +3,7 @@ ItemDef( description: "A part of the cardinal's exquisite cloak.", kind: Armor(( kind: Chest, - stats: Direct(( - protection: Some(Normal(60.0)), - poise_resilience: Some(Normal(18.0)), - energy_max: Some(120), - energy_reward: Some(0.060), - crit_power: Some(0.375), - stealth: Some(0.0), - )), + stats: FromSet("Cardinal"), )), quality: Legendary, tags: [ diff --git a/assets/common/items/armor/cardinal/foot.ron b/assets/common/items/armor/cardinal/foot.ron index 0bfa1311dd..d24fc113c4 100644 --- a/assets/common/items/armor/cardinal/foot.ron +++ b/assets/common/items/armor/cardinal/foot.ron @@ -3,14 +3,7 @@ ItemDef( description: "The boots with millions of steps.", kind: Armor(( kind: Foot, - stats: Direct(( - protection: Some(Normal(24.0)), - poise_resilience: Some(Normal(6.0)), - energy_max: Some(85), - energy_reward: Some(0.105), - crit_power: Some(0.12), - stealth: Some(0.0), - )), + stats: FromSet("Cardinal"), )), quality: Legendary, tags: [ diff --git a/assets/common/items/armor/cardinal/hand.ron b/assets/common/items/armor/cardinal/hand.ron index 73dcb6f0e4..f3a552d70e 100644 --- a/assets/common/items/armor/cardinal/hand.ron +++ b/assets/common/items/armor/cardinal/hand.ron @@ -3,14 +3,7 @@ ItemDef( description: "Bloodstained and rugged.", kind: Armor(( kind: Hand, - stats: Direct(( - protection: Some(Normal(20.0)), - poise_resilience: Some(Normal(6.0)), - energy_max: Some(75), - energy_reward: Some(0.09), - crit_power: Some(0.12), - stealth: Some(0.0), - )), + stats: FromSet("Cardinal"), )), quality: Legendary, tags: [ diff --git a/assets/common/items/armor/cardinal/pants.ron b/assets/common/items/armor/cardinal/pants.ron index c4af2de612..52e168ae34 100644 --- a/assets/common/items/armor/cardinal/pants.ron +++ b/assets/common/items/armor/cardinal/pants.ron @@ -3,14 +3,7 @@ ItemDef( description: "Pants with many experiences.", kind: Armor(( kind: Pants, - stats: Direct(( - protection: Some(Normal(45.0)), - poise_resilience: Some(Normal(12.0)), - energy_max: Some(150.0), - energy_reward: Some(0.05), - crit_power: Some(0.24), - stealth: Some(0.00), - )), + stats: FromSet("Cardinal"), )), quality: Legendary, tags: [ diff --git a/assets/common/items/armor/cardinal/shoulder.ron b/assets/common/items/armor/cardinal/shoulder.ron index 4960b46639..e44ecb651c 100644 --- a/assets/common/items/armor/cardinal/shoulder.ron +++ b/assets/common/items/armor/cardinal/shoulder.ron @@ -3,14 +3,7 @@ ItemDef( description: "The other was lost in a vicious fight.", kind: Armor(( kind: Shoulder, - stats: Direct(( - protection: Some(Normal(30.0)), - poise_resilience: Some(Normal(15.0)), - energy_max: Some(90), - energy_reward: Some(0.05), - crit_power: Some(0.24), - stealth: Some(0.0), - )), + stats: FromSet("Cardinal"), )), quality: Legendary, tags: [ diff --git a/assets/common/items/npc_armor/quadruped_low/dagon.ron b/assets/common/items/npc_armor/quadruped_low/dagon.ron new file mode 100644 index 0000000000..84526acf12 --- /dev/null +++ b/assets/common/items/npc_armor/quadruped_low/dagon.ron @@ -0,0 +1,13 @@ +ItemDef( + name: "Dagon's Scales", + description: "Rigid enough to withstand the pressure of the deep ocean.", + kind: Armor(( + kind: Chest, + stats: Direct(( + protection: Some(Normal(150.0)), + poise_resilience: Some(Normal(5.0)), + )), + )), + quality: Epic, + tags: [], +) \ No newline at end of file diff --git a/assets/common/items/npc_weapons/unique/dagon.ron b/assets/common/items/npc_weapons/unique/dagon.ron index f7914ca412..2aab4298e3 100644 --- a/assets/common/items/npc_weapons/unique/dagon.ron +++ b/assets/common/items/npc_weapons/unique/dagon.ron @@ -1,6 +1,6 @@ ItemDef( name: "Dagon Kit", - description: "Placeholder", + description: "Ocean Power!", kind: Tool(( kind: Natural, hands: Two, diff --git a/assets/common/material_stats_manifest.ron b/assets/common/material_stats_manifest.ron index 652a117b4a..a8b8fd3dc3 100644 --- a/assets/common/material_stats_manifest.ron +++ b/assets/common/material_stats_manifest.ron @@ -310,5 +310,12 @@ energy_reward: Some(0.5), crit_power: Some(0.4), ), + "Cardinal": ( + protection: Some(Normal(666.0)), + poise_resilience: Some(Normal(60.0)), + energy_max: Some(45.0), + energy_reward: Some(0.5), + crit_power: Some(0.8), + ), }, ) diff --git a/assets/voxygen/audio/sfx.ron b/assets/voxygen/audio/sfx.ron index f1ddf0b06c..a72f8cba34 100644 --- a/assets/voxygen/audio/sfx.ron +++ b/assets/voxygen/audio/sfx.ron @@ -1260,6 +1260,13 @@ ], threshold: 1.0, ), + Utterance(Hurt, Dagon): ( + files: [ + "voxygen.audio.sfx.utterance.dagon_hurt1", + "voxygen.audio.sfx.utterance.dagon_hurt2", + ], + threshold: 1.0, + ), Utterance(Angry, Asp): ( files: [ "voxygen.audio.sfx.utterance.asp_angry1", diff --git a/assets/voxygen/audio/sfx/utterance/dagon_hurt1.ogg b/assets/voxygen/audio/sfx/utterance/dagon_hurt1.ogg new file mode 100644 index 0000000000..b13fa20300 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/dagon_hurt1.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf571d2f49840b64bec42705c0ae20408a7f107a7c9b1fc683120806678f07cd +size 31474 diff --git a/assets/voxygen/audio/sfx/utterance/dagon_hurt2.ogg b/assets/voxygen/audio/sfx/utterance/dagon_hurt2.ogg new file mode 100644 index 0000000000..9a5a174361 --- /dev/null +++ b/assets/voxygen/audio/sfx/utterance/dagon_hurt2.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2685dfc7ad8ffeb296b42fad969da6ba18923edae7179ae126936831569916e +size 24940 diff --git a/assets/voxygen/i18n/en/npc.ftl b/assets/voxygen/i18n/en/npc.ftl index 5d85c35b37..087a8ef099 100644 --- a/assets/voxygen/i18n/en/npc.ftl +++ b/assets/voxygen/i18n/en/npc.ftl @@ -216,4 +216,10 @@ npc-speech-cultist_low_health_fleeing = .a2 = Curse you! .a3 = I will curse you in the afterlife! .a4 = I must rest! - .a5 = They're too strong! \ No newline at end of file + .a5 = They're too strong! +npc-speech-prisoner = + .a0 = Them scoundrels took away my pickaxe! + .a1 = Being trapped is no fun. + .a3 = That Cardinal can't be trusted. + .a4 = These Clerics are up to no good. + .a5 = I wish i still had my pick! \ No newline at end of file diff --git a/assets/voxygen/shaders/particle-vert.glsl b/assets/voxygen/shaders/particle-vert.glsl index 8359e87409..645cd4f44c 100644 --- a/assets/voxygen/shaders/particle-vert.glsl +++ b/assets/voxygen/shaders/particle-vert.glsl @@ -76,6 +76,8 @@ const int ENERGY_BUFFING = 35; const int WEB_STRAND = 36; const int BLACK_SMOKE = 37; const int LIGHTNING = 38; +const int STEAM = 39; +const int BARRELORGAN = 40; // meters per second squared (acceleration) const float earth_gravity = 9.807; @@ -614,6 +616,27 @@ void main() { identity()//spin_in_axis(perp_axis, asin(inst_dir.z / length(inst_dir)) + PI / 2.0) ); break; + case STEAM: + f_reflect = 0.0; // Magic steam doesn't reflect light, it emits it + float steam_size = 8.0 * (1 - slow_start(0.1)) * slow_end(0.15); + attr = Attr( + (inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1, + vec3(steam_size), + vec4(vec3(0.7, 2.7, 1.3), 1), + spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) + ); + break; + case BARRELORGAN: + attr = Attr( + linear_motion( + vec3(rand0 * 0.25, rand1 * 0.25, 1.7 + rand5), + vec3(rand2 * 0.1, rand3 * 0.1, 1.0 + rand4 * 0.5) + ), + vec3(exp_scale(-0.2)) * rand0, + vec4(vec3(0.7, 2.7, 1.3), 1), + spin_in_axis(vec3(1,0,0),0) + ); + break; default: attr = Attr( linear_motion( diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 3a073e8774..e080b8f33d 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -52,6 +52,7 @@ pub enum FrontendSpecifier { Cultist, ClayGolem, Bubbles, + Steam, Frost, WebStrand, } diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs index 067142943e..c8334a2ba1 100644 --- a/common/src/comp/inventory/loadout_builder.rs +++ b/common/src/comp/inventory/loadout_builder.rs @@ -909,6 +909,7 @@ impl LoadoutBuilder { | quadruped_low::Species::Sandshark => { Some("common.items.npc_armor.quadruped_low.generic") }, + quadruped_low::Species::Dagon => Some("common.items.npc_armor.quadruped_low.dagon"), quadruped_low::Species::Tortoise => { Some("common.items.npc_armor.quadruped_low.shell") }, diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index f7d5c1bc46..607a673d0d 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -504,7 +504,7 @@ impl ProjectileConstructor { let explosion = Explosion { effects: vec![ RadiusEffect::Attack(attack), - RadiusEffect::TerrainDestruction(5.0), + RadiusEffect::TerrainDestruction(75.0), ], radius, reagent: Some(Reagent::Blue), diff --git a/common/src/comp/shockwave.rs b/common/src/comp/shockwave.rs index 4795aae2db..3d6fecce76 100644 --- a/common/src/comp/shockwave.rs +++ b/common/src/comp/shockwave.rs @@ -50,4 +50,5 @@ pub enum FrontendSpecifier { Fire, Water, IceSpikes, + Steam, } diff --git a/server/agent/src/attack.rs b/server/agent/src/attack.rs index fbc0c75f8e..a7360a3100 100644 --- a/server/agent/src/attack.rs +++ b/server/agent/src/attack.rs @@ -2401,7 +2401,7 @@ impl<'a> AgentData<'a> { .is_some() }) { - // Use ward if target is far enough away, self is not buffed, and have + // Use steam beam if target is far enough away, self is not buffed, and have // sufficient energy controller.push_basic_input(InputKind::Ability(0)); } else { @@ -2414,9 +2414,8 @@ impl<'a> AgentData<'a> { && self.energy.current() > CharacterAbility::default_roll().get_energy_cost() && !matches!(self.char_state, CharacterState::BasicAura(c) if !matches!(c.stage_section, StageSection::Recover)) { - // Else roll away if can roll and have enough energy, and not using aura or in - // recover - controller.push_basic_input(InputKind::Roll); + // Else use steam beam + controller.push_basic_input(InputKind::Ability(0)); } else if attack_data.angle < 15.0 { controller.push_basic_input(InputKind::Primary); } @@ -2501,29 +2500,40 @@ impl<'a> AgentData<'a> { tgt_data: &TargetData, read_data: &ReadData, ) { - // if close to target, shoot dagon bombs and lay out sea urchins - if attack_data.angle < 70.0 - && attack_data.dist_sqrd < (1.3 * attack_data.min_attack_dist).powi(2) - { + if agent.action_state.timer > 2.5 { + agent.action_state.timer = 0.0; + } + // if close to target lay out sea urchins, use steambeam and shoot dagon bombs + if attack_data.dist_sqrd < (1.3 * attack_data.min_attack_dist).powi(2) { controller.inputs.move_dir = Vec2::zero(); - if agent.action_state.timer > 1.0 { + if agent.action_state.timer > 2.0 { controller.push_basic_input(InputKind::Primary); agent.action_state.timer += read_data.dt.0; + } else if agent.action_state.timer > 1.0 { + controller.push_basic_input(InputKind::Ability(1)); } else { controller.push_basic_input(InputKind::Secondary); agent.action_state.timer += read_data.dt.0; } - } else if attack_data.angle < 30.0 - && entities_have_line_of_sight( - self.pos, - self.body, - tgt_data.pos, - tgt_data.body, - read_data, - ) - { - // if in range, angle and sight, shoot dagon bombs at target - controller.push_basic_input(InputKind::Primary); + } else if attack_data.dist_sqrd > (3.0 * attack_data.min_attack_dist).powi(2) { + // if enemy is far, heal + controller.push_basic_input(InputKind::Ability(2)); + agent.action_state.timer += read_data.dt.0; + } else if entities_have_line_of_sight( + self.pos, + self.body, + tgt_data.pos, + tgt_data.body, + read_data, + ) { + // if in range shoot dagon bombs and steamwave + if agent.action_state.timer > 1.0 { + controller.push_basic_input(InputKind::Primary); + agent.action_state.timer += read_data.dt.0; + } else { + controller.push_basic_input(InputKind::Ability(0)); + agent.action_state.timer += read_data.dt.0; + } } // chase let path = if attack_data.dist_sqrd < MAX_PATH_DIST.powi(2) { diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index a26662bd30..503ad5d58f 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -26,7 +26,7 @@ pub struct Entity { pub brain: Brain, } -#[derive(Clone, Copy, strum::EnumIter)] +#[derive(Clone, Copy, strum::EnumIter, PartialEq)] pub enum RtSimEntityKind { Wanderer, Cultist, @@ -36,6 +36,7 @@ pub enum RtSimEntityKind { Blacksmith, Chef, Alchemist, + Prisoner, } const BIRD_MEDIUM_ROSTER: &[comp::bird_medium::Species] = &[ @@ -101,6 +102,7 @@ impl Entity { | RtSimEntityKind::Chef | RtSimEntityKind::Alchemist | RtSimEntityKind::Blacksmith + | RtSimEntityKind::Prisoner | RtSimEntityKind::Merchant => { let species = *comp::humanoid::ALL_SPECIES .choose(&mut self.rng(PERM_SPECIES)) @@ -986,6 +988,7 @@ fn humanoid_config(kind: RtSimEntityKind, rank: TravelerRank) -> &'static str { RtSimEntityKind::Blacksmith => "common.entity.village.blacksmith", RtSimEntityKind::Chef => "common.entity.village.chef", RtSimEntityKind::Alchemist => "common.entity.village.alchemist", + RtSimEntityKind::Prisoner => "common.entity.dungeon.sea_chapel.prisoner", } } diff --git a/server/src/rtsim/mod.rs b/server/src/rtsim/mod.rs index 690c9780f3..acad75ee0a 100644 --- a/server/src/rtsim/mod.rs +++ b/server/src/rtsim/mod.rs @@ -368,6 +368,33 @@ pub fn init( }); } }, + SiteKind::ChapelSite(site2) => { + // prisoners + for _ in 0..10 { + rtsim.entities.insert(Entity { + is_loaded: false, + pos: site2 + .plots() + .filter(|plot| { + matches!(plot.kind(), world::site2::PlotKind::SeaChapel(_)) + }) + .choose(&mut thread_rng()) + .map_or(site.get_origin(), |plot| { + site2.tile_center_wpos(Vec2::new( + plot.root_tile().x, + plot.root_tile().y + 4, + )) + }) + .with_z(0) + .map(|e| e as f32), + seed: thread_rng().gen(), + controller: RtSimController::default(), + last_time_ticked: 0.0, + kind: RtSimEntityKind::Prisoner, + brain: Brain::villager(site_id, &mut thread_rng()), + }); + } + }, _ => {}, } } diff --git a/server/src/sys/agent/behavior_tree/interaction.rs b/server/src/sys/agent/behavior_tree/interaction.rs index 719b6c04d5..08b99805a3 100644 --- a/server/src/sys/agent/behavior_tree/interaction.rs +++ b/server/src/sys/agent/behavior_tree/interaction.rs @@ -13,7 +13,10 @@ use common::{ use rand::{thread_rng, Rng}; use specs::saveload::Marker; -use crate::{rtsim::entity::PersonalityTrait, sys::agent::util::get_entity_by_id}; +use crate::{ + rtsim::entity::{PersonalityTrait, RtSimEntityKind}, + sys::agent::util::get_entity_by_id, +}; use super::{BehaviorData, BehaviorTree}; @@ -89,145 +92,160 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool { match subject { Subject::Regular => { - if let (Some((_travel_to, destination_name)), Some(rtsim_entity)) = - (&agent.rtsim_controller.travel_to, &bdata.rtsim_entity) - { - let personality = &rtsim_entity.brain.personality; - let standard_response_msg = || -> String { - if personality.will_ambush { - format!( - "I'm heading to {}! Want to come along? We'll make \ - great travel buddies, hehe.", - destination_name - ) - } else if personality - .personality_traits - .contains(PersonalityTrait::Extroverted) - { - format!( - "I'm heading to {}! Want to come along?", - destination_name - ) - } else if personality - .personality_traits - .contains(PersonalityTrait::Disagreeable) - { - "Hrm.".to_string() - } else { - "Hello!".to_string() - } - }; - let msg = if let Some(tgt_stats) = read_data.stats.get(target) { - agent.rtsim_controller.events.push(RtSimEvent::AddMemory( - Memory { - item: MemoryItem::CharacterInteraction { - name: tgt_stats.name.clone(), - }, - time_to_forget: read_data.time.0 + 600.0, - }, - )); - if rtsim_entity.brain.remembers_character(&tgt_stats.name) { + if let Some(rtsim_entity) = &bdata.rtsim_entity { + if matches!(rtsim_entity.kind, RtSimEntityKind::Prisoner) { + agent_data.chat_npc("npc-speech-prisoner", event_emitter); + } else if let ( + Some((_travel_to, destination_name)), + Some(rtsim_entity), + ) = + (&agent.rtsim_controller.travel_to, &&bdata.rtsim_entity) + { + let personality = &rtsim_entity.brain.personality; + let standard_response_msg = || -> String { if personality.will_ambush { - "Just follow me a bit more, hehe.".to_string() + format!( + "I'm heading to {}! Want to come along? We'll \ + make great travel buddies, hehe.", + destination_name + ) } else if personality .personality_traits .contains(PersonalityTrait::Extroverted) { format!( - "Greetings fair {}! It has been far too long \ - since last I saw you. I'm going to {} right now.", - &tgt_stats.name, destination_name + "I'm heading to {}! Want to come along?", + destination_name ) } else if personality .personality_traits .contains(PersonalityTrait::Disagreeable) { - "Oh. It's you again.".to_string() + "Hrm.".to_string() } else { - format!( - "Hi again {}! Unfortunately I'm in a hurry right \ - now. See you!", - &tgt_stats.name - ) + "Hello!".to_string() + } + }; + let msg = if let Some(tgt_stats) = read_data.stats.get(target) { + agent.rtsim_controller.events.push(RtSimEvent::AddMemory( + Memory { + item: MemoryItem::CharacterInteraction { + name: tgt_stats.name.clone(), + }, + time_to_forget: read_data.time.0 + 600.0, + }, + )); + if rtsim_entity.brain.remembers_character(&tgt_stats.name) { + if personality.will_ambush { + "Just follow me a bit more, hehe.".to_string() + } else if personality + .personality_traits + .contains(PersonalityTrait::Extroverted) + { + format!( + "Greetings fair {}! It has been far too long \ + since last I saw you. I'm going to {} right \ + now.", + &tgt_stats.name, destination_name + ) + } else if personality + .personality_traits + .contains(PersonalityTrait::Disagreeable) + { + "Oh. It's you again.".to_string() + } else { + format!( + "Hi again {}! Unfortunately I'm in a hurry \ + right now. See you!", + &tgt_stats.name + ) + } + } else { + standard_response_msg() } } else { standard_response_msg() - } - } else { - standard_response_msg() - }; - agent_data.chat_npc(msg, event_emitter); - } else if agent.behavior.can_trade() { - if !agent.behavior.is(BehaviorState::TRADING) { - controller.push_initiate_invite(by, InviteKind::Trade); - agent_data.chat_npc( - "npc-speech-merchant_advertisement", - event_emitter, - ); - } else { - let default_msg = "npc-speech-merchant_busy"; - let msg = bdata.rtsim_entity.map_or(default_msg, |e| { - if e.brain - .personality - .personality_traits - .contains(PersonalityTrait::Disagreeable) - { - "npc-speech-merchant_busy_rude" - } else { - default_msg - } - }); - agent_data.chat_npc(msg, event_emitter); - } - } else { - let mut rng = thread_rng(); - if let Some(extreme_trait) = bdata - .rtsim_entity - .and_then(|e| e.brain.personality.random_chat_trait(&mut rng)) - { - let msg = match extreme_trait { - PersonalityTrait::Open => "npc-speech-villager_open", - PersonalityTrait::Adventurous => { - "npc-speech-villager_adventurous" - }, - PersonalityTrait::Closed => "npc-speech-villager_closed", - PersonalityTrait::Conscientious => { - "npc-speech-villager_conscientious" - }, - PersonalityTrait::Busybody => { - "npc-speech-villager_busybody" - }, - PersonalityTrait::Unconscientious => { - "npc-speech-villager_unconscientious" - }, - PersonalityTrait::Extroverted => { - "npc-speech-villager_extroverted" - }, - PersonalityTrait::Introverted => { - "npc-speech-villager_introverted" - }, - PersonalityTrait::Agreeable => { - "npc-speech-villager_agreeable" - }, - PersonalityTrait::Sociable => { - "npc-speech-villager_sociable" - }, - PersonalityTrait::Disagreeable => { - "npc-speech-villager_disagreeable" - }, - PersonalityTrait::Neurotic => { - "npc-speech-villager_neurotic" - }, - PersonalityTrait::Seeker => "npc-speech-villager_seeker", - PersonalityTrait::SadLoner => { - "npc-speech-villager_sad_loner" - }, - PersonalityTrait::Worried => "npc-speech-villager_worried", - PersonalityTrait::Stable => "npc-speech-villager_stable", }; agent_data.chat_npc(msg, event_emitter); + } else if agent.behavior.can_trade() { + if !agent.behavior.is(BehaviorState::TRADING) { + controller.push_initiate_invite(by, InviteKind::Trade); + agent_data.chat_npc( + "npc-speech-merchant_advertisement", + event_emitter, + ); + } else { + let default_msg = "npc-speech-merchant_busy"; + let msg = &bdata.rtsim_entity.map_or(default_msg, |e| { + if e.brain + .personality + .personality_traits + .contains(PersonalityTrait::Disagreeable) + { + "npc-speech-merchant_busy_rude" + } else { + default_msg + } + }); + agent_data.chat_npc(msg, event_emitter); + } } else { - agent_data.chat_npc("npc-speech-villager", event_emitter); + let mut rng = thread_rng(); + if let Some(extreme_trait) = &bdata.rtsim_entity.and_then(|e| { + e.brain.personality.random_chat_trait(&mut rng) + }) { + let msg = match extreme_trait { + PersonalityTrait::Open => "npc-speech-villager_open", + PersonalityTrait::Adventurous => { + "npc-speech-villager_adventurous" + }, + PersonalityTrait::Closed => { + "npc-speech-villager_closed" + }, + PersonalityTrait::Conscientious => { + "npc-speech-villager_conscientious" + }, + PersonalityTrait::Busybody => { + "npc-speech-villager_busybody" + }, + PersonalityTrait::Unconscientious => { + "npc-speech-villager_unconscientious" + }, + PersonalityTrait::Extroverted => { + "npc-speech-villager_extroverted" + }, + PersonalityTrait::Introverted => { + "npc-speech-villager_introverted" + }, + PersonalityTrait::Agreeable => { + "npc-speech-villager_agreeable" + }, + PersonalityTrait::Sociable => { + "npc-speech-villager_sociable" + }, + PersonalityTrait::Disagreeable => { + "npc-speech-villager_disagreeable" + }, + PersonalityTrait::Neurotic => { + "npc-speech-villager_neurotic" + }, + PersonalityTrait::Seeker => { + "npc-speech-villager_seeker" + }, + PersonalityTrait::SadLoner => { + "npc-speech-villager_sad_loner" + }, + PersonalityTrait::Worried => { + "npc-speech-villager_worried" + }, + PersonalityTrait::Stable => { + "npc-speech-villager_stable" + }, + }; + agent_data.chat_npc(msg, event_emitter); + } else { + agent_data.chat_npc("npc-speech-villager", event_emitter); + } } } }, @@ -250,7 +268,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool { } }, Subject::Mood => { - if let Some(rtsim_entity) = bdata.rtsim_entity { + if let Some(rtsim_entity) = &bdata.rtsim_entity { if !rtsim_entity.brain.remembers_mood() { // TODO: the following code will need a rework to // implement more mood contexts diff --git a/voxygen/src/audio/sfx/mod.rs b/voxygen/src/audio/sfx/mod.rs index a712e7a2bb..afe6660b92 100644 --- a/voxygen/src/audio/sfx/mod.rs +++ b/voxygen/src/audio/sfx/mod.rs @@ -208,6 +208,7 @@ pub enum VoiceKind { Pig, Cow, Canine, + Dagon, Lion, Mindflayer, Marlin, @@ -236,6 +237,7 @@ fn body_to_voice(body: &Body) -> Option { quadruped_low::Species::Maneater => VoiceKind::Maneater, quadruped_low::Species::Alligator => VoiceKind::Alligator, quadruped_low::Species::SeaCrocodile => VoiceKind::SeaCrocodile, + quadruped_low::Species::Dagon => VoiceKind::Dagon, quadruped_low::Species::Asp => VoiceKind::Asp, _ => return None, }, @@ -527,6 +529,7 @@ impl SfxMgr { }, beam::FrontendSpecifier::ClayGolem | beam::FrontendSpecifier::Bubbles + | beam::FrontendSpecifier::Steam | beam::FrontendSpecifier::Frost | beam::FrontendSpecifier::WebStrand => {}, }, diff --git a/voxygen/src/render/pipelines/particle.rs b/voxygen/src/render/pipelines/particle.rs index 9b44894b48..35febbe5ef 100644 --- a/voxygen/src/render/pipelines/particle.rs +++ b/voxygen/src/render/pipelines/particle.rs @@ -89,7 +89,8 @@ pub enum ParticleMode { WebStrand = 36, BlackSmoke = 37, Lightning = 38, - BarrelOrgan = 39, + Steam = 39, + BarrelOrgan = 40, } impl ParticleMode { diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 4eeccdefd4..9e6366a62f 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -947,6 +947,31 @@ impl ParticleMgr { }, ); }, + beam::FrontendSpecifier::Steam => { + let mut rng = thread_rng(); + let (from, to) = (Vec3::::unit_z(), *ori.look_dir()); + let m = Mat3::::rotation_from_to_3d(from, to); + self.particles.resize_with( + self.particles.len() + usize::from(beam_tick_count) / 15, + || { + let phi: f32 = rng.gen_range(0.0..beam.properties.angle); + let theta: f32 = rng.gen_range(0.0..2.0 * PI); + let offset_z = Vec3::new( + phi.sin() * theta.cos(), + phi.sin() * theta.sin(), + phi.cos(), + ); + let random_ori = offset_z * m * Vec3::new(-1.0, -1.0, 1.0); + Particle::new_directed( + beam.properties.duration, + time, + ParticleMode::Steam, + pos, + pos + random_ori * range, + ) + }, + ); + }, beam::FrontendSpecifier::Frost => { let mut rng = thread_rng(); let (from, to) = (Vec3::::unit_z(), *ori.look_dir()); @@ -1561,6 +1586,41 @@ impl ParticleMgr { } } }, + FrontendSpecifier::Steam => { + // 1 particle per unit length of arc + let particles_per_length = arc_length as usize; + let dtheta = radians / particles_per_length as f32; + // Scales number of desired heartbeats from speed - thicker arc = higher speed = + // lower duration = more particles + let heartbeats = self + .scheduler + .heartbeats(Duration::from_secs_f32(1.0 / speed)); + + // Reserves capacity for new particles + let new_particle_count = particles_per_length * heartbeats as usize; + self.particles.reserve(new_particle_count); + + for i in 0..particles_per_length { + let angle = dtheta * i as f32; + let direction = Vec3::new(angle.cos(), angle.sin(), 0.0); + for j in 0..heartbeats { + // Sub tick dt + let dt = (j as f32 / heartbeats as f32) * dt; + let distance = distance + speed * dt; + let pos1 = pos + distance * direction - Vec3::unit_z(); + let pos2 = pos1 + (Vec3::unit_z() + direction) * 3.0; + let time = time + dt as f64; + + self.particles.push(Particle::new_directed( + Duration::from_secs_f32(0.5), + time, + ParticleMode::Steam, + pos1, + pos2, + )); + } + } + }, FrontendSpecifier::IceSpikes => { // 1 / 3 the size of terrain voxel let scale = 1.0 / 3.0; diff --git a/world/src/site2/plot/sea_chapel.rs b/world/src/site2/plot/sea_chapel.rs index 623be45f6f..3bc699ccf9 100644 --- a/world/src/site2/plot/sea_chapel.rs +++ b/world/src/site2/plot/sea_chapel.rs @@ -77,6 +77,7 @@ impl Structure for SeaChapel { .unwrap(), ); let glass_barrier = Fill::Block(Block::air(SpriteKind::GlassBarrier)); + let sea_urchins = Fill::Block(Block::air(SpriteKind::SeaUrchin)); // random exit from water basin to side building let mut connect_gate_types = vec![ SpriteKind::GlassBarrier, @@ -2242,6 +2243,18 @@ impl Structure for SeaChapel { min: (center - 8).with_z(base - (2 * (diameter / 3)) + 14), max: (center + 8).with_z(base - (2 * (diameter / 3)) + 16), }) + .fill(white_coral.clone()); + painter + .cylinder(Aabb { + min: (center - 9).with_z(base - (2 * (diameter / 3)) + 16), + max: (center + 9).with_z(base - (2 * (diameter / 3)) + 18), + }) + .fill(sea_urchins); + painter + .cylinder(Aabb { + min: (center - 8).with_z(base - (2 * (diameter / 3)) + 16), + max: (center + 8).with_z(base - (2 * (diameter / 3)) + 18), + }) .clear(); painter .cylinder(Aabb { @@ -2829,20 +2842,6 @@ impl Structure for SeaChapel { ); } // Holding Cell2 - painter - .sphere(Aabb { - min: Vec3::new( - center.x - (diameter / 2) - (diameter / 8) - 1, - center.y + (diameter / 16) - 1, - base - (diameter / 4) - 2, - ), - max: Vec3::new( - center.x - (diameter / 2) + (diameter / 8) + 1, - center.y + (diameter / 16) + (diameter / 4) + 1, - base, - ), - }) - .fill(white.clone()); painter .sphere(Aabb { min: Vec3::new( @@ -2909,20 +2908,6 @@ impl Structure for SeaChapel { }) .fill(glass_barrier.clone()); // Holding Cell3 - painter - .sphere(Aabb { - min: Vec3::new( - center.x + (diameter / 2) - (diameter / 8) - 1, - center.y - (diameter / 4) - (diameter / 16) - 1, - base - (diameter / 4) - 2, - ), - max: Vec3::new( - center.x + (diameter / 2) + (diameter / 8) + 1, - center.y - (diameter / 16) + 1, - base, - ), - }) - .fill(white.clone()); painter .sphere(Aabb { min: Vec3::new( @@ -3375,14 +3360,6 @@ impl Structure for SeaChapel { ), }) .fill(glass_barrier.clone()); - // Holding Cell Prisoners - let prisoners_pos = Vec3::new(center.x, center.y + (diameter / 3), base + 3); - for _ in 0..(5 + ((RandomField::new(0).get((prisoners_pos).with_z(base))) % 5)) { - painter.spawn( - EntityInfo::at(prisoners_pos.as_()) - .with_asset_expect("common.entity.village.villager", &mut rng), - ); - } // stairway3 tube painter .cylinder(Aabb {