Merge branch 'seachapel_npc_work' into 'master'

seachapel npc work

See merge request veloren/veloren!3599
This commit is contained in:
Samuel Keiffer 2022-09-22 01:04:01 +00:00
commit 59d8d287d9
34 changed files with 438 additions and 244 deletions

View File

@ -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"),
],
),

View File

@ -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,
)

View File

@ -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: (

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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),
)

View File

@ -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,
)

View File

@ -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: [],
)

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [

View File

@ -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: [],
)

View File

@ -1,6 +1,6 @@
ItemDef(
name: "Dagon Kit",
description: "Placeholder",
description: "Ocean Power!",
kind: Tool((
kind: Natural,
hands: Two,

View File

@ -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),
),
},
)

View File

@ -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",

BIN
assets/voxygen/audio/sfx/utterance/dagon_hurt1.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/utterance/dagon_hurt2.ogg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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!
.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!

View File

@ -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(

View File

@ -52,6 +52,7 @@ pub enum FrontendSpecifier {
Cultist,
ClayGolem,
Bubbles,
Steam,
Frost,
WebStrand,
}

View File

@ -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")
},

View File

@ -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),

View File

@ -50,4 +50,5 @@ pub enum FrontendSpecifier {
Fire,
Water,
IceSpikes,
Steam,
}

View File

@ -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) {

View File

@ -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",
}
}

View File

@ -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()),
});
}
},
_ => {},
}
}

View File

@ -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

View File

@ -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<VoiceKind> {
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 => {},
},

View File

@ -89,7 +89,8 @@ pub enum ParticleMode {
WebStrand = 36,
BlackSmoke = 37,
Lightning = 38,
BarrelOrgan = 39,
Steam = 39,
BarrelOrgan = 40,
}
impl ParticleMode {

View File

@ -947,6 +947,31 @@ impl ParticleMgr {
},
);
},
beam::FrontendSpecifier::Steam => {
let mut rng = thread_rng();
let (from, to) = (Vec3::<f32>::unit_z(), *ori.look_dir());
let m = Mat3::<f32>::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::<f32>::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;

View File

@ -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 {