mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Various RtSim and Agent Interaction Fixes
This commit is contained in:
parent
39ac300bef
commit
6ea43cfd75
@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Skill trees
|
- Skill trees
|
||||||
- Lactose tolerant golems
|
- Lactose tolerant golems
|
||||||
- 6 different gems. (Topaz, Amethyst, Sapphire, Emerald, Ruby and Diamond)
|
- 6 different gems. (Topaz, Amethyst, Sapphire, Emerald, Ruby and Diamond)
|
||||||
- Poise system (not currently accessible to players for balancing reasons)
|
- Poise system
|
||||||
- Snow particles
|
- Snow particles
|
||||||
- Basic NPC interaction
|
- Basic NPC interaction
|
||||||
- Lights in dungeons
|
- Lights in dungeons
|
||||||
@ -97,6 +97,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Fixed a bug where the stairs to the boss floor in dungeons would sometimes not spawn
|
- Fixed a bug where the stairs to the boss floor in dungeons would sometimes not spawn
|
||||||
- Fixed waypoints being placed underwater
|
- Fixed waypoints being placed underwater
|
||||||
- Objects and golems are not affected by bleed debuff anymore
|
- Objects and golems are not affected by bleed debuff anymore
|
||||||
|
- Fixed RtSim entity memory loss
|
||||||
|
- Mandated that merchants not wander away during a trade
|
||||||
|
- Fixed the villager conception of evil by encouraging them to react violently to characters wearing cultist gear
|
||||||
|
|
||||||
## [0.8.0] - 2020-11-28
|
## [0.8.0] - 2020-11-28
|
||||||
|
|
||||||
|
@ -9,5 +9,5 @@ ItemDef(
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
quality: Epic,
|
quality: Epic,
|
||||||
tags: [],
|
tags: [Cultist],
|
||||||
)
|
)
|
||||||
|
@ -9,5 +9,5 @@ ItemDef(
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
quality: Epic,
|
quality: Epic,
|
||||||
tags: [],
|
tags: [Cultist],
|
||||||
)
|
)
|
||||||
|
@ -9,5 +9,5 @@ ItemDef(
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
quality: Epic,
|
quality: Epic,
|
||||||
tags: [],
|
tags: [Cultist],
|
||||||
)
|
)
|
||||||
|
@ -9,5 +9,5 @@ ItemDef(
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
quality: Epic,
|
quality: Epic,
|
||||||
tags: [],
|
tags: [Cultist],
|
||||||
)
|
)
|
||||||
|
@ -9,5 +9,5 @@ ItemDef(
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
quality: Epic,
|
quality: Epic,
|
||||||
tags: [],
|
tags: [Cultist],
|
||||||
)
|
)
|
||||||
|
@ -9,5 +9,5 @@ ItemDef(
|
|||||||
),
|
),
|
||||||
)),
|
)),
|
||||||
quality: Epic,
|
quality: Epic,
|
||||||
tags: [],
|
tags: [Cultist],
|
||||||
)
|
)
|
||||||
|
18
assets/common/items/tag_examples/cultist.ron
Normal file
18
assets/common/items/tag_examples/cultist.ron
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
ItemDef(
|
||||||
|
name: "Anything related to cultists",
|
||||||
|
description: "These items are a little creepy.",
|
||||||
|
kind: TagExamples(
|
||||||
|
item_ids: [
|
||||||
|
"common.items.armor.cultist.belt",
|
||||||
|
"common.items.armor.cultist.chest",
|
||||||
|
"common.items.armor.cultist.pants",
|
||||||
|
"common.items.armor.cultist.foot",
|
||||||
|
"common.items.armor.cultist.hand",
|
||||||
|
"common.items.armor.cultist.shoulder",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
quality: Common,
|
||||||
|
tags: [],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
@ -88,6 +88,20 @@
|
|||||||
"I wish someone would keep the wolves away from the village.",
|
"I wish someone would keep the wolves away from the village.",
|
||||||
"I had a wonderful dream about cheese last night. What does it mean?",
|
"I had a wonderful dream about cheese last night. What does it mean?",
|
||||||
],
|
],
|
||||||
|
"npc.speech.villager_cultist_alarm": [
|
||||||
|
"Lookout! There is a cultist on the loose!",
|
||||||
|
"To arms! The cultists are attacking!",
|
||||||
|
"How dare the cultists attack our village!",
|
||||||
|
"Death to the cultists!",
|
||||||
|
"Cultists will not be tolerated here!",
|
||||||
|
"Murderous cultist!",
|
||||||
|
"Taste the edge of my sword, you dirty cultist!",
|
||||||
|
"Nothing can clean the blood from your hands, cultist!",
|
||||||
|
"Billions of blistering blue barnacles! A cultist among us!",
|
||||||
|
"The evils of this cultist are about to be over!",
|
||||||
|
"This cultist is mine!",
|
||||||
|
"Prepare to meet your maker, foul cultist!",
|
||||||
|
],
|
||||||
"npc.speech.villager_under_attack": [
|
"npc.speech.villager_under_attack": [
|
||||||
"Help, I'm under attack!",
|
"Help, I'm under attack!",
|
||||||
"Help! I'm under attack!",
|
"Help! I'm under attack!",
|
||||||
|
@ -2124,6 +2124,7 @@ impl Client {
|
|||||||
// NPCs can't talk. Should be filtered by hud/mod.rs for voxygen and should be filtered
|
// NPCs can't talk. Should be filtered by hud/mod.rs for voxygen and should be filtered
|
||||||
// by server (due to not having a Pos) for chat-cli
|
// by server (due to not having a Pos) for chat-cli
|
||||||
comp::ChatType::Npc(_uid, _r) => "".to_string(),
|
comp::ChatType::Npc(_uid, _r) => "".to_string(),
|
||||||
|
comp::ChatType::NpcSay(uid, _r) => message_format(uid, message, None),
|
||||||
comp::ChatType::Meta => message.to_string(),
|
comp::ChatType::Meta => message.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +106,8 @@ pub enum ChatType<G> {
|
|||||||
///
|
///
|
||||||
/// The u16 field is a random number for selecting localization variants.
|
/// The u16 field is a random number for selecting localization variants.
|
||||||
Npc(Uid, u16),
|
Npc(Uid, u16),
|
||||||
|
/// From NPCs but in the chat for clients in the near vicinity
|
||||||
|
NpcSay(Uid, u16),
|
||||||
/// Anything else
|
/// Anything else
|
||||||
Meta,
|
Meta,
|
||||||
// Looted items
|
// Looted items
|
||||||
@ -136,6 +138,7 @@ pub type UnresolvedChatMsg = GenericChatMsg<Group>;
|
|||||||
|
|
||||||
impl<G> GenericChatMsg<G> {
|
impl<G> GenericChatMsg<G> {
|
||||||
pub const NPC_DISTANCE: f32 = 100.0;
|
pub const NPC_DISTANCE: f32 = 100.0;
|
||||||
|
pub const NPC_SAY_DISTANCE: f32 = 30.0;
|
||||||
pub const REGION_DISTANCE: f32 = 1000.0;
|
pub const REGION_DISTANCE: f32 = 1000.0;
|
||||||
pub const SAY_DISTANCE: f32 = 100.0;
|
pub const SAY_DISTANCE: f32 = 100.0;
|
||||||
|
|
||||||
@ -144,6 +147,11 @@ impl<G> GenericChatMsg<G> {
|
|||||||
Self { chat_type, message }
|
Self { chat_type, message }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn npc_say(uid: Uid, message: String) -> Self {
|
||||||
|
let chat_type = ChatType::NpcSay(uid, rand::random());
|
||||||
|
Self { chat_type, message }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
|
pub fn map_group<T>(self, mut f: impl FnMut(G) -> T) -> GenericChatMsg<T> {
|
||||||
let chat_type = match self.chat_type {
|
let chat_type = match self.chat_type {
|
||||||
ChatType::Online(a) => ChatType::Online(a),
|
ChatType::Online(a) => ChatType::Online(a),
|
||||||
@ -161,6 +169,7 @@ impl<G> GenericChatMsg<G> {
|
|||||||
ChatType::Region(a) => ChatType::Region(a),
|
ChatType::Region(a) => ChatType::Region(a),
|
||||||
ChatType::World(a) => ChatType::World(a),
|
ChatType::World(a) => ChatType::World(a),
|
||||||
ChatType::Npc(a, b) => ChatType::Npc(a, b),
|
ChatType::Npc(a, b) => ChatType::Npc(a, b),
|
||||||
|
ChatType::NpcSay(a, b) => ChatType::NpcSay(a, b),
|
||||||
ChatType::Meta => ChatType::Meta,
|
ChatType::Meta => ChatType::Meta,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -172,7 +181,7 @@ impl<G> GenericChatMsg<G> {
|
|||||||
|
|
||||||
pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
|
pub fn to_bubble(&self) -> Option<(SpeechBubble, Uid)> {
|
||||||
let icon = self.icon();
|
let icon = self.icon();
|
||||||
if let ChatType::Npc(from, r) = self.chat_type {
|
if let ChatType::Npc(from, r) | ChatType::NpcSay(from, r) = self.chat_type {
|
||||||
Some((SpeechBubble::npc_new(&self.message, r, icon), from))
|
Some((SpeechBubble::npc_new(&self.message, r, icon), from))
|
||||||
} else {
|
} else {
|
||||||
self.uid()
|
self.uid()
|
||||||
@ -197,6 +206,7 @@ impl<G> GenericChatMsg<G> {
|
|||||||
ChatType::Region(_u) => SpeechBubbleType::Region,
|
ChatType::Region(_u) => SpeechBubbleType::Region,
|
||||||
ChatType::World(_u) => SpeechBubbleType::World,
|
ChatType::World(_u) => SpeechBubbleType::World,
|
||||||
ChatType::Npc(_u, _r) => SpeechBubbleType::None,
|
ChatType::Npc(_u, _r) => SpeechBubbleType::None,
|
||||||
|
ChatType::NpcSay(_u, _r) => SpeechBubbleType::Say,
|
||||||
ChatType::Meta => SpeechBubbleType::None,
|
ChatType::Meta => SpeechBubbleType::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,6 +228,7 @@ impl<G> GenericChatMsg<G> {
|
|||||||
ChatType::Region(u) => Some(*u),
|
ChatType::Region(u) => Some(*u),
|
||||||
ChatType::World(u) => Some(*u),
|
ChatType::World(u) => Some(*u),
|
||||||
ChatType::Npc(u, _r) => Some(*u),
|
ChatType::Npc(u, _r) => Some(*u),
|
||||||
|
ChatType::NpcSay(u, _r) => Some(*u),
|
||||||
ChatType::Meta => None,
|
ChatType::Meta => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,7 @@ pub enum ItemTag {
|
|||||||
ClothItem,
|
ClothItem,
|
||||||
ModularComponent(ModularComponentTag),
|
ModularComponent(ModularComponentTag),
|
||||||
MetalIngot,
|
MetalIngot,
|
||||||
|
Cultist,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TagExampleInfo for ItemTag {
|
impl TagExampleInfo for ItemTag {
|
||||||
@ -102,6 +103,7 @@ impl TagExampleInfo for ItemTag {
|
|||||||
ItemTag::ClothItem => "cloth item",
|
ItemTag::ClothItem => "cloth item",
|
||||||
ItemTag::ModularComponent(kind) => kind.name(),
|
ItemTag::ModularComponent(kind) => kind.name(),
|
||||||
ItemTag::MetalIngot => "metal ingot",
|
ItemTag::MetalIngot => "metal ingot",
|
||||||
|
ItemTag::Cultist => "cultist",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +112,7 @@ impl TagExampleInfo for ItemTag {
|
|||||||
ItemTag::ClothItem => "common.items.tag_examples.cloth_item",
|
ItemTag::ClothItem => "common.items.tag_examples.cloth_item",
|
||||||
ItemTag::ModularComponent(tag) => tag.exemplar_identifier(),
|
ItemTag::ModularComponent(tag) => tag.exemplar_identifier(),
|
||||||
ItemTag::MetalIngot => "common.items.tag_examples.metal_ingot",
|
ItemTag::MetalIngot => "common.items.tag_examples.metal_ingot",
|
||||||
|
ItemTag::Cultist => "common.items.tag_examples.cultist",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,26 @@ impl Component for RtSimEntity {
|
|||||||
type Storage = IdvStorage<Self>;
|
type Storage = IdvStorage<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum RtSimEvent {
|
||||||
|
AddMemory(Memory),
|
||||||
|
PrintMemories,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Memory {
|
||||||
|
pub item: MemoryItem,
|
||||||
|
pub time_to_forget: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum MemoryItem {
|
||||||
|
// These are structs to allow more data beyond name to be stored
|
||||||
|
// such as clothing worn, weapon used, etc.
|
||||||
|
CharacterInteraction { name: String },
|
||||||
|
CharacterFight { name: String },
|
||||||
|
}
|
||||||
|
|
||||||
/// This type is the map route through which the rtsim (real-time simulation)
|
/// This type is the map route through which the rtsim (real-time simulation)
|
||||||
/// aspect of the game communicates with the rest of the game. It is analagous
|
/// aspect of the game communicates with the rest of the game. It is analagous
|
||||||
/// to `comp::Controller` in that it provides a consistent interface for
|
/// to `comp::Controller` in that it provides a consistent interface for
|
||||||
@ -33,6 +53,8 @@ pub struct RtSimController {
|
|||||||
pub travel_to: Option<(Vec3<f32>, String)>,
|
pub travel_to: Option<(Vec3<f32>, String)>,
|
||||||
/// Proportion of full speed to move
|
/// Proportion of full speed to move
|
||||||
pub speed_factor: f32,
|
pub speed_factor: f32,
|
||||||
|
/// Events
|
||||||
|
pub events: Vec<RtSimEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RtSimController {
|
impl Default for RtSimController {
|
||||||
@ -40,6 +62,7 @@ impl Default for RtSimController {
|
|||||||
Self {
|
Self {
|
||||||
travel_to: None,
|
travel_to: None,
|
||||||
speed_factor: 1.0,
|
speed_factor: 1.0,
|
||||||
|
events: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -51,6 +74,7 @@ impl RtSimController {
|
|||||||
Self {
|
Self {
|
||||||
travel_to: Some((pos, format!("{:0.1?}", pos))),
|
travel_to: Some((pos, format!("{:0.1?}", pos))),
|
||||||
speed_factor: 0.25,
|
speed_factor: 0.25,
|
||||||
|
events: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use common::{comp::inventory::loadout_builder::LoadoutBuilder, store::Id, terrain::TerrainGrid};
|
use common::{
|
||||||
|
comp::inventory::loadout_builder::LoadoutBuilder,
|
||||||
|
resources::Time,
|
||||||
|
rtsim::{Memory, MemoryItem},
|
||||||
|
store::Id,
|
||||||
|
terrain::TerrainGrid,
|
||||||
|
};
|
||||||
use world::{
|
use world::{
|
||||||
civ::{Site, Track},
|
civ::{Site, Track},
|
||||||
util::RandomPerm,
|
util::RandomPerm,
|
||||||
@ -127,7 +133,7 @@ impl Entity {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(&mut self, terrain: &TerrainGrid, world: &World, index: &IndexRef) {
|
pub fn tick(&mut self, time: &Time, terrain: &TerrainGrid, world: &World, index: &IndexRef) {
|
||||||
let tgt_site = self.brain.tgt.or_else(|| {
|
let tgt_site = self.brain.tgt.or_else(|| {
|
||||||
world
|
world
|
||||||
.civs()
|
.civs()
|
||||||
@ -183,6 +189,11 @@ impl Entity {
|
|||||||
self.controller.travel_to = Some((travel_to, destination_name));
|
self.controller.travel_to = Some((travel_to, destination_name));
|
||||||
self.controller.speed_factor = 0.70;
|
self.controller.speed_factor = 0.70;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Forget old memories
|
||||||
|
self.brain
|
||||||
|
.memories
|
||||||
|
.retain(|memory| memory.time_to_forget > time.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,4 +201,17 @@ impl Entity {
|
|||||||
pub struct Brain {
|
pub struct Brain {
|
||||||
tgt: Option<Id<Site>>,
|
tgt: Option<Id<Site>>,
|
||||||
track: Option<(Track, usize)>,
|
track: Option<(Track, usize)>,
|
||||||
|
memories: Vec<Memory>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Brain {
|
||||||
|
pub fn add_memory(&mut self, memory: Memory) { self.memories.push(memory); }
|
||||||
|
|
||||||
|
pub fn remembers_character(&self, name_to_remember: &str) -> bool {
|
||||||
|
self.memories.iter().any(|memory| matches!(&memory.item, MemoryItem::CharacterInteraction { name, .. } if name == name_to_remember))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remembers_fight_with_character(&self, name_to_remember: &str) -> bool {
|
||||||
|
self.memories.iter().any(|memory| matches!(&memory.item, MemoryItem::CharacterFight { name, .. } if name == name_to_remember))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@ mod load_chunks;
|
|||||||
mod tick;
|
mod tick;
|
||||||
mod unload_chunks;
|
mod unload_chunks;
|
||||||
|
|
||||||
use self::{chunks::Chunks, entity::Entity};
|
use self::chunks::Chunks;
|
||||||
use common::{
|
use common::{
|
||||||
comp,
|
comp,
|
||||||
rtsim::{RtSimController, RtSimEntity, RtSimId},
|
rtsim::{Memory, RtSimController, RtSimEntity, RtSimId},
|
||||||
terrain::TerrainChunk,
|
terrain::TerrainChunk,
|
||||||
vol::RectRasterableVol,
|
vol::RectRasterableVol,
|
||||||
};
|
};
|
||||||
@ -20,6 +20,8 @@ use slab::Slab;
|
|||||||
use specs::{DispatcherBuilder, WorldExt};
|
use specs::{DispatcherBuilder, WorldExt};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
|
pub use self::entity::Entity;
|
||||||
|
|
||||||
pub struct RtSim {
|
pub struct RtSim {
|
||||||
tick: u64,
|
tick: u64,
|
||||||
chunks: Chunks,
|
chunks: Chunks,
|
||||||
@ -71,6 +73,14 @@ impl RtSim {
|
|||||||
// tracing::info!("Destroyed rtsim entity {}", entity);
|
// tracing::info!("Destroyed rtsim entity {}", entity);
|
||||||
self.entities.remove(entity);
|
self.entities.remove(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_entity(&self, entity: RtSimId) -> Option<&Entity> { self.entities.get(entity) }
|
||||||
|
|
||||||
|
pub fn insert_entity_memory(&mut self, entity: RtSimId, memory: Memory) {
|
||||||
|
self.entities
|
||||||
|
.get_mut(entity)
|
||||||
|
.map(|entity| entity.brain.add_memory(memory));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
pub fn add_server_systems(dispatch_builder: &mut DispatcherBuilder) {
|
||||||
|
@ -5,7 +5,7 @@ use common::{
|
|||||||
comp,
|
comp,
|
||||||
comp::inventory::loadout_builder::LoadoutBuilder,
|
comp::inventory::loadout_builder::LoadoutBuilder,
|
||||||
event::{EventBus, ServerEvent},
|
event::{EventBus, ServerEvent},
|
||||||
resources::DeltaTime,
|
resources::{DeltaTime, Time},
|
||||||
terrain::TerrainGrid,
|
terrain::TerrainGrid,
|
||||||
};
|
};
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
@ -19,6 +19,7 @@ pub struct Sys;
|
|||||||
impl<'a> System<'a> for Sys {
|
impl<'a> System<'a> for Sys {
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
|
Read<'a, Time>,
|
||||||
Read<'a, DeltaTime>,
|
Read<'a, DeltaTime>,
|
||||||
Read<'a, EventBus<ServerEvent>>,
|
Read<'a, EventBus<ServerEvent>>,
|
||||||
WriteExpect<'a, RtSim>,
|
WriteExpect<'a, RtSim>,
|
||||||
@ -37,6 +38,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
fn run(
|
fn run(
|
||||||
_job: &mut Job<Self>,
|
_job: &mut Job<Self>,
|
||||||
(
|
(
|
||||||
|
time,
|
||||||
dt,
|
dt,
|
||||||
server_event_bus,
|
server_event_bus,
|
||||||
mut rtsim,
|
mut rtsim,
|
||||||
@ -56,7 +58,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
let mut to_reify = Vec::new();
|
let mut to_reify = Vec::new();
|
||||||
for (id, entity) in rtsim.entities.iter_mut() {
|
for (id, entity) in rtsim.entities.iter_mut() {
|
||||||
if entity.is_loaded {
|
if entity.is_loaded {
|
||||||
// No load-specific behaviour yet
|
// Nothing here yet
|
||||||
} else if rtsim
|
} else if rtsim
|
||||||
.chunks
|
.chunks
|
||||||
.chunk_at(entity.pos.xy())
|
.chunk_at(entity.pos.xy())
|
||||||
@ -87,7 +89,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
// Tick entity AI
|
// Tick entity AI
|
||||||
if entity.last_tick + ENTITY_TICK_PERIOD <= rtsim.tick {
|
if entity.last_tick + ENTITY_TICK_PERIOD <= rtsim.tick {
|
||||||
entity.tick(&terrain, &world, &index.as_index_ref());
|
entity.tick(&time, &terrain, &world, &index.as_index_ref());
|
||||||
entity.last_tick = rtsim.tick;
|
entity.last_tick = rtsim.tick;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -523,7 +523,18 @@ impl StateExt for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
comp::ChatType::NpcSay(uid, _r) => {
|
||||||
|
let entity_opt =
|
||||||
|
(*ecs.read_resource::<UidAllocator>()).retrieve_entity_internal(uid.0);
|
||||||
|
let positions = ecs.read_storage::<comp::Pos>();
|
||||||
|
if let Some(speaker_pos) = entity_opt.and_then(|e| positions.get(e)) {
|
||||||
|
for (client, pos) in (&ecs.read_storage::<Client>(), &positions).join() {
|
||||||
|
if is_within(comp::ChatMsg::NPC_SAY_DISTANCE, pos, speaker_pos) {
|
||||||
|
client.send_fallible(ServerGeneral::ChatMsg(resolved_msg.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
comp::ChatType::FactionMeta(s) | comp::ChatType::Faction(_, s) => {
|
comp::ChatType::FactionMeta(s) | comp::ChatType::Faction(_, s) => {
|
||||||
for (client, faction) in (
|
for (client, faction) in (
|
||||||
&ecs.read_storage::<Client>(),
|
&ecs.read_storage::<Client>(),
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
|
use crate::rtsim::{Entity as RtSimData, RtSim};
|
||||||
use common::{
|
use common::{
|
||||||
comp::{
|
comp::{
|
||||||
self,
|
self,
|
||||||
agent::{AgentEvent, Tactic, Target, DEFAULT_INTERACTION_TIME, TRADE_INTERACTION_TIME},
|
agent::{AgentEvent, Tactic, Target, DEFAULT_INTERACTION_TIME, TRADE_INTERACTION_TIME},
|
||||||
group,
|
group,
|
||||||
inventory::{slot::EquipSlot, trade_pricing::TradePricing},
|
inventory::{item::ItemTag, slot::EquipSlot, trade_pricing::TradePricing},
|
||||||
invite::InviteResponse,
|
invite::InviteResponse,
|
||||||
item::{
|
item::{
|
||||||
tool::{ToolKind, UniqueKind},
|
tool::{ToolKind, UniqueKind},
|
||||||
ItemKind,
|
ItemDesc, ItemKind,
|
||||||
},
|
},
|
||||||
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
|
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
|
||||||
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy,
|
Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy,
|
||||||
@ -16,7 +17,8 @@ use common::{
|
|||||||
},
|
},
|
||||||
event::{Emitter, EventBus, ServerEvent},
|
event::{Emitter, EventBus, ServerEvent},
|
||||||
path::TraversalConfig,
|
path::TraversalConfig,
|
||||||
resources::{DeltaTime, TimeOfDay},
|
resources::{DeltaTime, Time, TimeOfDay},
|
||||||
|
rtsim::{Memory, MemoryItem, RtSimEntity, RtSimEvent},
|
||||||
terrain::{Block, TerrainGrid},
|
terrain::{Block, TerrainGrid},
|
||||||
time::DayPeriod,
|
time::DayPeriod,
|
||||||
trade::{Good, TradeAction, TradePhase, TradeResult},
|
trade::{Good, TradeAction, TradePhase, TradeResult},
|
||||||
@ -32,13 +34,14 @@ use specs::{
|
|||||||
saveload::{Marker, MarkerAllocator},
|
saveload::{Marker, MarkerAllocator},
|
||||||
shred::ResourceId,
|
shred::ResourceId,
|
||||||
Entities, Entity as EcsEntity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, World,
|
Entities, Entity as EcsEntity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, World,
|
||||||
Write, WriteStorage,
|
Write, WriteExpect, WriteStorage,
|
||||||
};
|
};
|
||||||
use std::{f32::consts::PI, sync::Arc};
|
use std::{f32::consts::PI, sync::Arc};
|
||||||
use vek::*;
|
use vek::*;
|
||||||
|
|
||||||
struct AgentData<'a> {
|
struct AgentData<'a> {
|
||||||
entity: &'a EcsEntity,
|
entity: &'a EcsEntity,
|
||||||
|
rtsim_entity: Option<&'a RtSimData>,
|
||||||
uid: &'a Uid,
|
uid: &'a Uid,
|
||||||
pos: &'a Pos,
|
pos: &'a Pos,
|
||||||
vel: &'a Vel,
|
vel: &'a Vel,
|
||||||
@ -63,6 +66,7 @@ pub struct ReadData<'a> {
|
|||||||
entities: Entities<'a>,
|
entities: Entities<'a>,
|
||||||
uid_allocator: Read<'a, UidAllocator>,
|
uid_allocator: Read<'a, UidAllocator>,
|
||||||
dt: Read<'a, DeltaTime>,
|
dt: Read<'a, DeltaTime>,
|
||||||
|
time: Read<'a, Time>,
|
||||||
group_manager: Read<'a, group::GroupManager>,
|
group_manager: Read<'a, group::GroupManager>,
|
||||||
energies: ReadStorage<'a, Energy>,
|
energies: ReadStorage<'a, Energy>,
|
||||||
positions: ReadStorage<'a, Pos>,
|
positions: ReadStorage<'a, Pos>,
|
||||||
@ -83,6 +87,7 @@ pub struct ReadData<'a> {
|
|||||||
time_of_day: Read<'a, TimeOfDay>,
|
time_of_day: Read<'a, TimeOfDay>,
|
||||||
light_emitter: ReadStorage<'a, LightEmitter>,
|
light_emitter: ReadStorage<'a, LightEmitter>,
|
||||||
world: ReadExpect<'a, Arc<world::World>>,
|
world: ReadExpect<'a, Arc<world::World>>,
|
||||||
|
rtsim_entities: ReadStorage<'a, RtSimEntity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is 3.1 to last longer than the last damage timer (3.0 seconds)
|
// This is 3.1 to last longer than the last damage timer (3.0 seconds)
|
||||||
@ -107,6 +112,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
Write<'a, EventBus<ServerEvent>>,
|
Write<'a, EventBus<ServerEvent>>,
|
||||||
WriteStorage<'a, Agent>,
|
WriteStorage<'a, Agent>,
|
||||||
WriteStorage<'a, Controller>,
|
WriteStorage<'a, Controller>,
|
||||||
|
WriteExpect<'a, RtSim>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const NAME: &'static str = "agent";
|
const NAME: &'static str = "agent";
|
||||||
@ -116,8 +122,9 @@ impl<'a> System<'a> for Sys {
|
|||||||
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
||||||
fn run(
|
fn run(
|
||||||
job: &mut Job<Self>,
|
job: &mut Job<Self>,
|
||||||
(read_data, event_bus, mut agents, mut controllers): Self::SystemData,
|
(read_data, event_bus, mut agents, mut controllers, mut rtsim): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
|
let rtsim = &mut *rtsim;
|
||||||
job.cpu_stats.measure(ParMode::Rayon);
|
job.cpu_stats.measure(ParMode::Rayon);
|
||||||
(
|
(
|
||||||
&read_data.entities,
|
&read_data.entities,
|
||||||
@ -220,12 +227,16 @@ impl<'a> System<'a> for Sys {
|
|||||||
let flees = alignment
|
let flees = alignment
|
||||||
.map(|a| !matches!(a, Alignment::Enemy | Alignment::Owned(_)))
|
.map(|a| !matches!(a, Alignment::Enemy | Alignment::Owned(_)))
|
||||||
.unwrap_or(true);
|
.unwrap_or(true);
|
||||||
|
|
||||||
let damage = health.current() as f32 / health.maximum() as f32;
|
let damage = health.current() as f32 / health.maximum() as f32;
|
||||||
|
let rtsim_entity = read_data
|
||||||
|
.rtsim_entities
|
||||||
|
.get(entity)
|
||||||
|
.and_then(|rtsim_ent| rtsim.get_entity(rtsim_ent.0));
|
||||||
|
|
||||||
// Package all this agent's data into a convenient struct
|
// Package all this agent's data into a convenient struct
|
||||||
let data = AgentData {
|
let data = AgentData {
|
||||||
entity: &entity,
|
entity: &entity,
|
||||||
|
rtsim_entity,
|
||||||
uid,
|
uid,
|
||||||
pos,
|
pos,
|
||||||
vel,
|
vel,
|
||||||
@ -404,6 +415,20 @@ impl<'a> System<'a> for Sys {
|
|||||||
read_data.bodies.get(attacker),
|
read_data.bodies.get(attacker),
|
||||||
&read_data.dt,
|
&read_data.dt,
|
||||||
);
|
);
|
||||||
|
// Remember this encounter if an RtSim entity
|
||||||
|
if let Some(tgt_stats) = read_data.stats.get(attacker) {
|
||||||
|
if data.rtsim_entity.is_some() {
|
||||||
|
agent.rtsim_controller.events.push(
|
||||||
|
RtSimEvent::AddMemory(Memory {
|
||||||
|
item: MemoryItem::CharacterFight {
|
||||||
|
name: tgt_stats.name.clone(),
|
||||||
|
},
|
||||||
|
time_to_forget: read_data.time.0
|
||||||
|
+ 300.0,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
agent.target = None;
|
agent.target = None;
|
||||||
@ -428,6 +453,15 @@ impl<'a> System<'a> for Sys {
|
|||||||
debug_assert!(controller.inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
debug_assert!(controller.inputs.look_dir.map(|e| !e.is_nan()).reduce_and());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
for (agent, rtsim_entity) in (&mut agents, &read_data.rtsim_entities).join() {
|
||||||
|
// Entity must be loaded in as it has an agent component :)
|
||||||
|
// React to all events in the controller
|
||||||
|
for event in core::mem::take(&mut agent.rtsim_controller.events) {
|
||||||
|
if let RtSimEvent::AddMemory(memory) = event {
|
||||||
|
rtsim.insert_entity_memory(rtsim_entity.0, memory.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -472,7 +506,7 @@ impl<'a> AgentData<'a> {
|
|||||||
self.idle(agent, controller, &read_data);
|
self.idle(agent, controller, &read_data);
|
||||||
}
|
}
|
||||||
} else if thread_rng().gen::<f32>() < 0.1 {
|
} else if thread_rng().gen::<f32>() < 0.1 {
|
||||||
self.choose_target(agent, controller, &read_data);
|
self.choose_target(agent, controller, &read_data, event_emitter);
|
||||||
} else {
|
} else {
|
||||||
self.idle(agent, controller, &read_data);
|
self.idle(agent, controller, &read_data);
|
||||||
}
|
}
|
||||||
@ -775,16 +809,43 @@ impl<'a> AgentData<'a> {
|
|||||||
) {
|
) {
|
||||||
controller.inputs.look_dir = dir;
|
controller.inputs.look_dir = dir;
|
||||||
}
|
}
|
||||||
controller.actions.push(ControlAction::Stand);
|
|
||||||
controller.actions.push(ControlAction::Talk);
|
controller.actions.push(ControlAction::Talk);
|
||||||
if let Some((_travel_to, destination_name)) =
|
if let (Some((_travel_to, destination_name)), Some(rtsim_entity)) =
|
||||||
&agent.rtsim_controller.travel_to
|
(&agent.rtsim_controller.travel_to, &self.rtsim_entity)
|
||||||
{
|
{
|
||||||
let msg = format!(
|
let msg = if let Some(tgt_stats) = read_data.stats.get(target) {
|
||||||
"I'm heading to {}! Want to come along?",
|
agent.rtsim_controller.events.push(RtSimEvent::AddMemory(
|
||||||
destination_name
|
Memory {
|
||||||
);
|
item: MemoryItem::CharacterInteraction {
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
name: tgt_stats.name.clone(),
|
||||||
|
},
|
||||||
|
time_to_forget: read_data.time.0 + 600.0,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
if rtsim_entity.brain.remembers_character(&tgt_stats.name) {
|
||||||
|
format!(
|
||||||
|
"Greetings fair {}! It has been far too long since \
|
||||||
|
last I saw you.",
|
||||||
|
&tgt_stats.name
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"I'm heading to {}! Want to come along?",
|
||||||
|
destination_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"I'm heading to {}! Want to come along?",
|
||||||
|
destination_name
|
||||||
|
)
|
||||||
|
};
|
||||||
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||||
|
*self.uid, msg,
|
||||||
|
)));
|
||||||
|
} else if agent.trade_for_site.is_some() {
|
||||||
|
let msg = "Can I interest you in a trade?".to_string();
|
||||||
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||||
*self.uid, msg,
|
*self.uid, msg,
|
||||||
)));
|
)));
|
||||||
} else {
|
} else {
|
||||||
@ -800,7 +861,6 @@ impl<'a> AgentData<'a> {
|
|||||||
Some(AgentEvent::TradeInvite(_with)) => {
|
Some(AgentEvent::TradeInvite(_with)) => {
|
||||||
if agent.trade_for_site.is_some() && !agent.trading {
|
if agent.trade_for_site.is_some() && !agent.trading {
|
||||||
// stand still and looking towards the trading player
|
// stand still and looking towards the trading player
|
||||||
controller.actions.push(ControlAction::Stand);
|
|
||||||
controller.actions.push(ControlAction::Talk);
|
controller.actions.push(ControlAction::Talk);
|
||||||
controller
|
controller
|
||||||
.events
|
.events
|
||||||
@ -817,12 +877,12 @@ impl<'a> AgentData<'a> {
|
|||||||
if agent.trading {
|
if agent.trading {
|
||||||
match result {
|
match result {
|
||||||
TradeResult::Completed => {
|
TradeResult::Completed => {
|
||||||
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||||
*self.uid,
|
*self.uid,
|
||||||
"Thank you for trading with me!".to_string(),
|
"Thank you for trading with me!".to_string(),
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
_ => event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(
|
_ => event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||||
*self.uid,
|
*self.uid,
|
||||||
"Maybe another time, have a good day!".to_string(),
|
"Maybe another time, have a good day!".to_string(),
|
||||||
))),
|
))),
|
||||||
@ -892,8 +952,9 @@ impl<'a> AgentData<'a> {
|
|||||||
"That only covers {:.1}% of my costs!",
|
"That only covers {:.1}% of my costs!",
|
||||||
balance0 / balance1 * 100.0
|
balance0 / balance1 * 100.0
|
||||||
);
|
);
|
||||||
event_emitter
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(
|
||||||
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
*self.uid, msg,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
if pending.phase != TradePhase::Mutate {
|
if pending.phase != TradePhase::Mutate {
|
||||||
// we got into the review phase but without balanced goods, decline
|
// we got into the review phase but without balanced goods, decline
|
||||||
@ -981,14 +1042,20 @@ impl<'a> AgentData<'a> {
|
|||||||
agent.action_timer += dt.0;
|
agent.action_timer += dt.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_target(&self, agent: &mut Agent, controller: &mut Controller, read_data: &ReadData) {
|
fn choose_target(
|
||||||
|
&self,
|
||||||
|
agent: &mut Agent,
|
||||||
|
controller: &mut Controller,
|
||||||
|
read_data: &ReadData,
|
||||||
|
event_emitter: &mut Emitter<'_, ServerEvent>,
|
||||||
|
) {
|
||||||
agent.action_timer = 0.0;
|
agent.action_timer = 0.0;
|
||||||
|
|
||||||
// Search for new targets (this looks expensive, but it's only run occasionally)
|
// Search for new targets (this looks expensive, but it's only run occasionally)
|
||||||
// TODO: Replace this with a better system that doesn't consider *all* entities
|
// TODO: Replace this with a better system that doesn't consider *all* entities
|
||||||
let target = (&read_data.entities, &read_data.positions, &read_data.healths, read_data.alignments.maybe(), read_data.char_states.maybe())
|
let target = (&read_data.entities, &read_data.positions, &read_data.healths, &read_data.stats, &read_data.inventories, read_data.alignments.maybe(), read_data.char_states.maybe())
|
||||||
.join()
|
.join()
|
||||||
.filter(|(e, e_pos, e_health, e_alignment, char_state)| {
|
.filter(|(e, e_pos, e_health, e_stats, e_inventory, e_alignment, char_state)| {
|
||||||
let mut search_dist = SEARCH_DIST;
|
let mut search_dist = SEARCH_DIST;
|
||||||
let mut listen_dist = LISTEN_DIST;
|
let mut listen_dist = LISTEN_DIST;
|
||||||
if char_state.map_or(false, |c_s| c_s.is_stealthy()) {
|
if char_state.map_or(false, |c_s| c_s.is_stealthy()) {
|
||||||
@ -1003,16 +1070,56 @@ impl<'a> AgentData<'a> {
|
|||||||
|| e_pos.0.distance_squared(self.pos.0) < listen_dist.powi(2)) // TODO implement proper sound system for agents
|
|| e_pos.0.distance_squared(self.pos.0) < listen_dist.powi(2)) // TODO implement proper sound system for agents
|
||||||
&& e != self.entity
|
&& e != self.entity
|
||||||
&& !e_health.is_dead
|
&& !e_health.is_dead
|
||||||
&& self.alignment.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b))).unwrap_or(false)
|
&& (self.alignment.and_then(|a| e_alignment.map(|b| a.hostile_towards(*b))).unwrap_or(false) || (
|
||||||
|
if let Some(rtsim_entity) = &self.rtsim_entity {
|
||||||
|
if rtsim_entity.brain.remembers_fight_with_character(&e_stats.name) {
|
||||||
|
agent.rtsim_controller.events.push(
|
||||||
|
RtSimEvent::AddMemory(Memory {
|
||||||
|
item: MemoryItem::CharacterFight { name: e_stats.name.clone() },
|
||||||
|
time_to_forget: read_data.time.0 + 300.0,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
let msg = format!("{}! How dare you cross me again!", e_stats.name.clone());
|
||||||
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc_say(*self.uid, msg)));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
) ||
|
||||||
|
(
|
||||||
|
self.alignment.map_or(false, |alignment| {
|
||||||
|
if matches!(alignment, Alignment::Npc) && e_inventory.equipped_items().filter(|item| item.tags().contains(&ItemTag::Cultist)).count() > 2 {
|
||||||
|
if agent.can_speak {
|
||||||
|
if self.rtsim_entity.is_some() {
|
||||||
|
agent.rtsim_controller.events.push(
|
||||||
|
RtSimEvent::AddMemory(Memory {
|
||||||
|
item: MemoryItem::CharacterFight { name: e_stats.name.clone() },
|
||||||
|
time_to_forget: read_data.time.0 + 300.0,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let msg = "npc.speech.villager_cultist_alarm".to_string();
|
||||||
|
event_emitter.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
))
|
||||||
|
|
||||||
})
|
})
|
||||||
// Can we even see them?
|
// Can we even see them?
|
||||||
.filter(|(_, e_pos, _, _, _)| read_data.terrain
|
.filter(|(_, e_pos, _, _, _, _, _)| read_data.terrain
|
||||||
.ray(self.pos.0 + Vec3::unit_z(), e_pos.0 + Vec3::unit_z())
|
.ray(self.pos.0 + Vec3::unit_z(), e_pos.0 + Vec3::unit_z())
|
||||||
.until(Block::is_opaque)
|
.until(Block::is_opaque)
|
||||||
.cast()
|
.cast()
|
||||||
.0 >= e_pos.0.distance(self.pos.0))
|
.0 >= e_pos.0.distance(self.pos.0))
|
||||||
.min_by_key(|(_, e_pos, _, _, _)| (e_pos.0.distance_squared(self.pos.0) * 100.0) as i32) // TODO choose target by more than just distance
|
.min_by_key(|(_, e_pos, _, _, _, _, _)| (e_pos.0.distance_squared(self.pos.0) * 100.0) as i32) // TODO choose target by more than just distance
|
||||||
.map(|(e, _, _, _, _)| e);
|
.map(|(e, _, _, _, _, _, _)| e);
|
||||||
if let Some(target) = target {
|
if let Some(target) = target {
|
||||||
agent.target = Some(Target {
|
agent.target = Some(Target {
|
||||||
target,
|
target,
|
||||||
|
@ -613,7 +613,8 @@ fn render_chat_line(chat_type: &ChatType<String>, imgs: &Imgs) -> (Color, conrod
|
|||||||
ChatType::Faction(_uid, _s) => (FACTION_COLOR, imgs.chat_faction_small),
|
ChatType::Faction(_uid, _s) => (FACTION_COLOR, imgs.chat_faction_small),
|
||||||
ChatType::Region(_uid) => (REGION_COLOR, imgs.chat_region_small),
|
ChatType::Region(_uid) => (REGION_COLOR, imgs.chat_region_small),
|
||||||
ChatType::World(_uid) => (WORLD_COLOR, imgs.chat_world_small),
|
ChatType::World(_uid) => (WORLD_COLOR, imgs.chat_world_small),
|
||||||
ChatType::Npc(_uid, _r) => panic!("NPCs can't talk"), // Should be filtered by hud/mod.rs
|
ChatType::Npc(_uid, _r) => panic!("NPCs can't talk!"), // Should be filtered by hud/mod.rs
|
||||||
|
ChatType::NpcSay(_uid, _r) => (SAY_COLOR, imgs.chat_say_small),
|
||||||
ChatType::Meta => (INFO_COLOR, imgs.chat_command_info_small),
|
ChatType::Meta => (INFO_COLOR, imgs.chat_command_info_small),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,16 +186,14 @@ fn armor_desc(armor: &Armor, desc: &str, slots: u16) -> String {
|
|||||||
Protection::Normal(a) => a.to_string(),
|
Protection::Normal(a) => a.to_string(),
|
||||||
Protection::Invincible => "Inf".to_string(),
|
Protection::Invincible => "Inf".to_string(),
|
||||||
};
|
};
|
||||||
//let armor_poise_resilience = match armor.get_poise_resilience() {
|
let armor_poise_resilience = match armor.get_poise_resilience() {
|
||||||
// Protection::Normal(a) => a.to_string(),
|
Protection::Normal(a) => a.to_string(),
|
||||||
// Protection::Invincible => "Inf".to_string(),
|
Protection::Invincible => "Inf".to_string(),
|
||||||
//};
|
};
|
||||||
|
|
||||||
let mut description = format!(
|
let mut description = format!(
|
||||||
"{}\n\nArmor: {}",
|
"{}\n\nArmor: {}\n\nPoise Resilience: {}",
|
||||||
//"{}\n\nArmor: {}\n\nPoise Resilience: {}",
|
kind, armor_protection, armor_poise_resilience
|
||||||
kind,
|
|
||||||
armor_protection, /* armor_poise_resilience // Add back when we are ready for poise */
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if !desc.is_empty() {
|
if !desc.is_empty() {
|
||||||
@ -235,7 +233,6 @@ fn tool_desc(tool: &Tool, components: &[Item], msm: &MaterialStatManifest, desc:
|
|||||||
// Get tool stats
|
// Get tool stats
|
||||||
let stats = tool.stats.resolve_stats(msm, components).clamp_speed();
|
let stats = tool.stats.resolve_stats(msm, components).clamp_speed();
|
||||||
|
|
||||||
//let poise_strength = tool.base_poise_strength();
|
|
||||||
let hands = match tool.hands {
|
let hands = match tool.hands {
|
||||||
Hands::One => "One",
|
Hands::One => "One",
|
||||||
Hands::Two => "Two",
|
Hands::Two => "Two",
|
||||||
@ -260,13 +257,9 @@ fn tool_desc(tool: &Tool, components: &[Item], msm: &MaterialStatManifest, desc:
|
|||||||
|
|
||||||
fn statblock_desc(stats: &Stats) -> String {
|
fn statblock_desc(stats: &Stats) -> String {
|
||||||
format!(
|
format!(
|
||||||
"DPS: {:0.1}\n\nPower: {:0.1}\n\nSpeed: {:0.1}\n\n",
|
"Power: {:0.1}\n\nPoise Strength: {:0.1}\n\nSpeed: {:0.1}\n\n",
|
||||||
// add back when ready for poise
|
|
||||||
//"{}\n\nDPS: {:0.1}\n\nPower: {:0.1}\n\nPoise Strength: {:0.1}\n\nSpeed: \
|
|
||||||
// {:0.1}\n\n{}\n\n<Right-Click to use>",
|
|
||||||
stats.speed * stats.power * 10.0, // Damage per second
|
|
||||||
stats.power * 10.0,
|
stats.power * 10.0,
|
||||||
//stats.poise_strength * 10.0,
|
stats.poise_strength * 10.0,
|
||||||
stats.speed,
|
stats.speed,
|
||||||
) + &format!(
|
) + &format!(
|
||||||
"Critical chance: {:0.1}%\n\nCritical multiplier: {:0.1}x\n\n",
|
"Critical chance: {:0.1}%\n\nCritical multiplier: {:0.1}x\n\n",
|
||||||
@ -339,8 +332,9 @@ mod tests {
|
|||||||
ingredient_desc("mushrooms", "common.items.food.mushroom", &testmsm)
|
ingredient_desc("mushrooms", "common.items.food.mushroom", &testmsm)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"Crafting Ingredient\n\nA bronze ingot.\n\nStat multipliers:\nDPS: 210.0\n\nPower: \
|
"Crafting Ingredient\n\nA bronze ingot.\n\nStat multipliers:\nPower: 30.0\n\nPoise \
|
||||||
30.0\n\nSpeed: 7.0\n\nCritical chance: 50.0%\n\nCritical multiplier: 2.0x\n\n",
|
Strength: 50.0\n\nSpeed: 7.0\n\nCritical chance: 50.0%\n\nCritical multiplier: \
|
||||||
|
2.0x\n\n",
|
||||||
ingredient_desc(
|
ingredient_desc(
|
||||||
"A bronze ingot.",
|
"A bronze ingot.",
|
||||||
"common.items.crafting_ing.bronze_ingot",
|
"common.items.crafting_ing.bronze_ingot",
|
||||||
|
Loading…
Reference in New Issue
Block a user