mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'seachapel_npc_work' into 'master'
seachapel npc work See merge request veloren/veloren!3599
This commit is contained in:
commit
59d8d287d9
@ -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"),
|
||||
],
|
||||
),
|
||||
|
19
assets/common/abilities/custom/cardinal/steambeam.ron
Normal file
19
assets/common/abilities/custom/cardinal/steambeam.ron
Normal 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,
|
||||
)
|
@ -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: (
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
19
assets/common/abilities/custom/dagon/steamheal.ron
Normal file
19
assets/common/abilities/custom/dagon/steamheal.ron
Normal 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),
|
||||
)
|
17
assets/common/abilities/custom/dagon/steamwave.ron
Normal file
17
assets/common/abilities/custom/dagon/steamwave.ron
Normal 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,
|
||||
)
|
13
assets/common/entity/dungeon/sea_chapel/prisoner.ron
Normal file
13
assets/common/entity/dungeon/sea_chapel/prisoner.ron
Normal 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: [],
|
||||
)
|
@ -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: [
|
||||
|
@ -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: [
|
||||
|
@ -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: [
|
||||
|
@ -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: [
|
||||
|
@ -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: [
|
||||
|
@ -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: [
|
||||
|
13
assets/common/items/npc_armor/quadruped_low/dagon.ron
Normal file
13
assets/common/items/npc_armor/quadruped_low/dagon.ron
Normal 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: [],
|
||||
)
|
@ -1,6 +1,6 @@
|
||||
ItemDef(
|
||||
name: "Dagon Kit",
|
||||
description: "Placeholder",
|
||||
description: "Ocean Power!",
|
||||
kind: Tool((
|
||||
kind: Natural,
|
||||
hands: Two,
|
||||
|
@ -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),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
@ -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
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
BIN
assets/voxygen/audio/sfx/utterance/dagon_hurt2.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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!
|
@ -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(
|
||||
|
@ -52,6 +52,7 @@ pub enum FrontendSpecifier {
|
||||
Cultist,
|
||||
ClayGolem,
|
||||
Bubbles,
|
||||
Steam,
|
||||
Frost,
|
||||
WebStrand,
|
||||
}
|
||||
|
@ -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")
|
||||
},
|
||||
|
@ -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),
|
||||
|
@ -50,4 +50,5 @@ pub enum FrontendSpecifier {
|
||||
Fire,
|
||||
Water,
|
||||
IceSpikes,
|
||||
Steam,
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()),
|
||||
});
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 => {},
|
||||
},
|
||||
|
@ -89,7 +89,8 @@ pub enum ParticleMode {
|
||||
WebStrand = 36,
|
||||
BlackSmoke = 37,
|
||||
Lightning = 38,
|
||||
BarrelOrgan = 39,
|
||||
Steam = 39,
|
||||
BarrelOrgan = 40,
|
||||
}
|
||||
|
||||
impl ParticleMode {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user