mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Moved some agent code into separate crate to facilitate hot reloading of that agent code.
This commit is contained in:
parent
5d8a708d6b
commit
525630c37a
27
Cargo.lock
generated
27
Cargo.lock
generated
@ -6809,9 +6809,26 @@ dependencies = [
|
||||
"veloren-common-systems",
|
||||
"veloren-network",
|
||||
"veloren-plugin-api",
|
||||
"veloren-server-agent",
|
||||
"veloren-world",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "veloren-server-agent"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"rand 0.8.5",
|
||||
"specs",
|
||||
"tracing",
|
||||
"vek 0.15.8",
|
||||
"veloren-common",
|
||||
"veloren-common-base",
|
||||
"veloren-common-ecs",
|
||||
"veloren-server-dynlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "veloren-server-cli"
|
||||
version = "0.13.0"
|
||||
@ -6836,6 +6853,16 @@ dependencies = [
|
||||
"veloren-server",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "veloren-server-dynlib"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"find_folder",
|
||||
"libloading 0.7.3",
|
||||
"notify",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "veloren-voxygen"
|
||||
version = "0.13.0"
|
||||
|
@ -87,6 +87,11 @@ fn main() -> io::Result<()> {
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
#[cfg(feature = "hot-agent")]
|
||||
{
|
||||
agent::init();
|
||||
}
|
||||
|
||||
// Load server settings
|
||||
let mut server_settings = server::Settings::load(&server_data_dir);
|
||||
let mut editable_settings = server::EditableSettings::load(&server_data_dir);
|
||||
|
@ -10,6 +10,7 @@ simd = ["vek/platform_intrinsics"]
|
||||
plugins = ["common-state/plugins"]
|
||||
persistent_world = []
|
||||
hot-reloading = ["common/hot-reloading"]
|
||||
hot-agent = ["server-agent/use-dyn-lib"]
|
||||
|
||||
default = ["worldgen", "plugins", "persistent_world", "simd"]
|
||||
|
||||
@ -23,6 +24,8 @@ common-net = { package = "veloren-common-net", path = "../common/net" }
|
||||
world = { package = "veloren-world", path = "../world" }
|
||||
network = { package = "veloren-network", path = "../network", features = ["metrics", "compression", "quic"], default-features = false }
|
||||
|
||||
server-agent = {package = "veloren-server-agent", path = "agent"}
|
||||
|
||||
#inline_tweak = "1.0.8"
|
||||
|
||||
specs = { version = "0.18", features = ["shred-derive"] }
|
||||
|
22
server/agent/Cargo.toml
Normal file
22
server/agent/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
authors = ["Samuel Keiffer <samuelkeiffer@gmail.com>"]
|
||||
name = "veloren-server-agent"
|
||||
edition = "2021"
|
||||
version = "0.1.0"
|
||||
|
||||
[features]
|
||||
use-dyn-lib = ["server-dynlib"]
|
||||
be-dyn-lib = []
|
||||
|
||||
[dependencies]
|
||||
common = {package = "veloren-common", path = "../../common"}
|
||||
common-base = { package = "veloren-common-base", path = "../../common/base" }
|
||||
common-ecs = { package = "veloren-common-ecs", path = "../../common/ecs" }
|
||||
server-dynlib = {package = "veloren-server-dynlib", path = "../dynlib", optional = true}
|
||||
|
||||
specs = { version = "0.18", features = ["shred-derive"] }
|
||||
vek = { version = "0.15.8", features = ["serde"] }
|
||||
rand = { version = "0.8", features = ["small_rng"] }
|
||||
tracing = "0.1"
|
||||
itertools = "0.10"
|
||||
lazy_static = "1.4.0"
|
14
server/agent/dyn/Cargo.toml
Normal file
14
server/agent/dyn/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
authors = ["Samuel Keiffer <samuelkeiffer@gmail.com>"]
|
||||
edition = "2021"
|
||||
name = "veloren-server-agent-dyn"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["dylib"]
|
||||
|
||||
[features]
|
||||
be-dyn-lib = ["veloren-server-agent/be-dyn-lib"]
|
||||
|
||||
[dependencies]
|
||||
veloren-server-agent = { path = "../" }
|
14
server/agent/dyn/src/lib.rs
Normal file
14
server/agent/dyn/src/lib.rs
Normal file
@ -0,0 +1,14 @@
|
||||
//! This crate hacks around the inability to dynamically specify the
|
||||
//! `crate-type` for cargo to build.
|
||||
//!
|
||||
//! For more details on the issue this is a decent starting point: https://github.com/rust-lang/cargo/pull/8789
|
||||
//!
|
||||
//! This crate avoids use building the dynamic lib when it isn't needed and the
|
||||
//! same with the non dynamic build. Additionally, this allows compilation to
|
||||
//! start earlier since a cdylib doesn't pipeline with it's dependencies.
|
||||
//!
|
||||
//! NOTE: the `be-dyn-lib` feature must be used for this crate to be useful, it
|
||||
//! is not on by default because this causes cargo to switch the feature on in
|
||||
//! the anim crate when compiling the static lib into voxygen.
|
||||
#[cfg(feature = "be-dyn-lib")]
|
||||
pub use veloren_server_agent::*;
|
1506
server/agent/src/action_nodes.rs
Normal file
1506
server/agent/src/action_nodes.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,7 @@
|
||||
use crate::sys::agent::{
|
||||
consts::MAX_PATH_DIST, data::Path, util::entities_have_line_of_sight, AgentData, AttackData,
|
||||
ReadData, TargetData,
|
||||
use crate::{
|
||||
consts::MAX_PATH_DIST,
|
||||
data::{AgentData, AttackData, Path, ReadData, TargetData},
|
||||
util::entities_have_line_of_sight,
|
||||
};
|
||||
use common::{
|
||||
comp::{
|
@ -1,4 +1,3 @@
|
||||
use crate::rtsim::Entity as RtSimData;
|
||||
use common::{
|
||||
comp::{
|
||||
buff::Buffs, group, item::MaterialStatManifest, ActiveAbilities, Alignment, Body,
|
||||
@ -17,11 +16,9 @@ use specs::{
|
||||
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData,
|
||||
World,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct AgentData<'a> {
|
||||
pub entity: &'a EcsEntity,
|
||||
pub rtsim_entity: Option<&'a RtSimData>,
|
||||
pub uid: &'a Uid,
|
||||
pub pos: &'a Pos,
|
||||
pub vel: &'a Vel,
|
27
server/agent/src/lib.rs
Normal file
27
server/agent/src/lib.rs
Normal file
@ -0,0 +1,27 @@
|
||||
#[cfg(all(feature = "be-dyn-lib", feature = "use-dyn-lib"))]
|
||||
compile_error!("Can't use both \"be-dyn-lib\" and \"use-dyn-lib\" features at once");
|
||||
|
||||
#[cfg(all(target_os = "windows", feature = "be-dyn-lib"))]
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
pub mod action_nodes;
|
||||
pub mod attack;
|
||||
pub mod consts;
|
||||
pub mod data;
|
||||
pub mod util;
|
||||
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
use {
|
||||
lazy_static::lazy_static, server_dynlib::LoadedLib, std::ffi::CStr, std::sync::Arc,
|
||||
std::sync::Mutex,
|
||||
};
|
||||
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
lazy_static! {
|
||||
static ref LIB: Arc<Mutex<Option<LoadedLib>>> =
|
||||
server_dynlib::init("veloren-server-agent", "veloren-server-agent-dyn", "agent");
|
||||
}
|
||||
|
||||
#[cfg(feature = "use-dyn-lib")]
|
||||
pub fn init() { lazy_static::initialize(&LIB); }
|
@ -1,4 +1,4 @@
|
||||
use crate::sys::agent::{AgentData, ReadData};
|
||||
use crate::data::{AgentData, ReadData};
|
||||
use common::{
|
||||
comp::{
|
||||
agent::Psyche, buff::BuffKind, inventory::item::ItemTag, item::ItemDesc, Alignment, Body,
|
||||
@ -65,15 +65,6 @@ pub fn get_entity_by_id(id: u64, read_data: &ReadData) -> Option<EcsEntity> {
|
||||
read_data.uid_allocator.retrieve_entity_internal(id)
|
||||
}
|
||||
|
||||
impl<'a> AgentData<'a> {
|
||||
pub fn has_buff(&self, read_data: &ReadData, buff: BuffKind) -> bool {
|
||||
read_data
|
||||
.buffs
|
||||
.get(*self.entity)
|
||||
.map_or(false, |b| b.kinds.contains_key(&buff))
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates whether the agent should continue chase or let the target escape.
|
||||
///
|
||||
/// Will return true when score of letting target escape is higher then the
|
||||
@ -202,3 +193,12 @@ pub fn get_attacker(entity: EcsEntity, read_data: &ReadData) -> Option<EcsEntity
|
||||
.and_then(|health| health.last_change.damage_by())
|
||||
.and_then(|damage_contributor| get_entity_by_id(damage_contributor.uid().0, read_data))
|
||||
}
|
||||
|
||||
impl<'a> AgentData<'a> {
|
||||
pub fn has_buff(&self, read_data: &ReadData, buff: BuffKind) -> bool {
|
||||
read_data
|
||||
.buffs
|
||||
.get(*self.entity)
|
||||
.map_or(false, |b| b.kinds.contains_key(&buff))
|
||||
}
|
||||
}
|
11
server/dynlib/Cargo.toml
Normal file
11
server/dynlib/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "veloren-server-dynlib"
|
||||
version = "0.1.0"
|
||||
authors = ["Samuel Keiffer <samuelkeiffer@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
find_folder = {version = "0.3.0"}
|
||||
libloading = {version = "0.7"}
|
||||
notify = {version = "5.0.0"}
|
||||
tracing = "0.1"
|
272
server/dynlib/src/lib.rs
Normal file
272
server/dynlib/src/lib.rs
Normal file
@ -0,0 +1,272 @@
|
||||
use libloading::Library;
|
||||
use notify::{recommended_watcher, EventKind, RecursiveMode, Watcher};
|
||||
use std::{
|
||||
process::{Command, Stdio},
|
||||
sync::{mpsc, Mutex},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use find_folder::Search;
|
||||
use std::{
|
||||
env,
|
||||
env::consts::{DLL_PREFIX, DLL_SUFFIX},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
// Re-exports
|
||||
pub use libloading::Symbol;
|
||||
|
||||
/// LoadedLib holds a loaded dynamic library and the location of library file
|
||||
/// with the appropriate OS specific name and extension i.e.
|
||||
/// `libvoxygen_anim_dyn_active.dylib`, `voxygen_anim_dyn_active.dll`.
|
||||
///
|
||||
/// # NOTE
|
||||
/// DOES NOT WORK ON MACOS, due to some limitations with hot-reloading the
|
||||
/// `.dylib`.
|
||||
pub struct LoadedLib {
|
||||
/// Loaded library.
|
||||
pub lib: Library,
|
||||
/// Path to the library.
|
||||
pub lib_path: PathBuf,
|
||||
}
|
||||
|
||||
impl LoadedLib {
|
||||
/// Compile and load the dynamic library
|
||||
///
|
||||
/// This is necessary because the very first time you use hot reloading you
|
||||
/// wont have the library, so you can't load it until you have compiled it!
|
||||
fn compile_load(dyn_package: &str) -> Self {
|
||||
#[cfg(target_os = "macos")]
|
||||
error!("The hot reloading feature does not work on macos.");
|
||||
|
||||
// Compile
|
||||
if !compile(dyn_package) {
|
||||
panic!("{} compile failed.", dyn_package);
|
||||
} else {
|
||||
info!("{} compile succeeded.", dyn_package);
|
||||
}
|
||||
|
||||
copy(&LoadedLib::determine_path(dyn_package), dyn_package);
|
||||
|
||||
Self::load(dyn_package)
|
||||
}
|
||||
|
||||
/// Load a library from disk.
|
||||
///
|
||||
/// Currently this is pretty fragile, it gets the path of where it thinks
|
||||
/// the dynamic library should be and tries to load it. It will panic if it
|
||||
/// is missing.
|
||||
fn load(dyn_package: &str) -> Self {
|
||||
let lib_path = LoadedLib::determine_path(dyn_package);
|
||||
|
||||
// Try to load the library.
|
||||
let lib = match unsafe { Library::new(lib_path.clone()) } {
|
||||
Ok(lib) => lib,
|
||||
Err(e) => panic!(
|
||||
"Tried to load dynamic library from {:?}, but it could not be found. A potential \
|
||||
reason is we may require a special case for your OS so we can find it. {:?}",
|
||||
lib_path, e
|
||||
),
|
||||
};
|
||||
|
||||
Self { lib, lib_path }
|
||||
}
|
||||
|
||||
/// Determine the path to the dynamic library based on the path of the
|
||||
/// current executable.
|
||||
fn determine_path(dyn_package: &str) -> PathBuf {
|
||||
let current_exe = env::current_exe();
|
||||
|
||||
// If we got the current_exe, we need to go up a level and then down
|
||||
// in to debug (in case we were in release or another build dir).
|
||||
let mut lib_path = match current_exe {
|
||||
Ok(mut path) => {
|
||||
// Remove the filename to get the directory.
|
||||
path.pop();
|
||||
|
||||
// Search for the debug directory.
|
||||
let dir = Search::ParentsThenKids(1, 1)
|
||||
.of(path)
|
||||
.for_folder("debug")
|
||||
.expect(
|
||||
"Could not find the debug build directory relative to the current \
|
||||
executable.",
|
||||
);
|
||||
|
||||
debug!(?dir, "Found the debug build directory.");
|
||||
dir
|
||||
},
|
||||
Err(e) => {
|
||||
panic!(
|
||||
"Could not determine the path of the current executable, this is needed to \
|
||||
hot-reload the dynamic library. {:?}",
|
||||
e
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
// Determine the platform specific path and push it onto our already
|
||||
// established target/debug dir.
|
||||
lib_path.push(active_file(dyn_package));
|
||||
|
||||
lib_path
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialise a watcher.
|
||||
///
|
||||
/// This will search for the directory named `package_source_dir` and watch the
|
||||
/// files within it for any changes.
|
||||
pub fn init(
|
||||
package: &'static str,
|
||||
dyn_package: &'static str,
|
||||
package_source_dir: &'static str,
|
||||
) -> Arc<Mutex<Option<LoadedLib>>> {
|
||||
let lib_storage = Arc::new(Mutex::new(Some(LoadedLib::compile_load(dyn_package))));
|
||||
|
||||
// TODO: use crossbeam
|
||||
let (reload_send, reload_recv) = mpsc::channel();
|
||||
|
||||
// Start watcher
|
||||
let mut watcher = recommended_watcher(move |res| event_fn(res, &reload_send)).unwrap();
|
||||
|
||||
// Search for the source directory of the package being hot-reloaded.
|
||||
let watch_dir = Search::Kids(1)
|
||||
.for_folder(package_source_dir)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Could not find the {} crate directory relative to the current directory",
|
||||
package_source_dir
|
||||
)
|
||||
});
|
||||
|
||||
watcher.watch(&watch_dir, RecursiveMode::Recursive).unwrap();
|
||||
|
||||
// Start reloader that watcher signals
|
||||
// "Debounces" events since I can't find the option to do this in the latest
|
||||
// `notify`
|
||||
let lib_storage_clone = Arc::clone(&lib_storage);
|
||||
std::thread::Builder::new()
|
||||
.name(format!("{}_hotreload_watcher", package))
|
||||
.spawn(move || {
|
||||
let mut modified_paths = std::collections::HashSet::new();
|
||||
while let Ok(path) = reload_recv.recv() {
|
||||
modified_paths.insert(path);
|
||||
// Wait for any additional modify events before reloading
|
||||
while let Ok(path) = reload_recv.recv_timeout(Duration::from_millis(300)) {
|
||||
modified_paths.insert(path);
|
||||
}
|
||||
|
||||
info!(
|
||||
?modified_paths,
|
||||
"Hot reloading {} because files in `{}` modified.", package, package_source_dir
|
||||
);
|
||||
|
||||
hotreload(dyn_package, &lib_storage_clone);
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Let the watcher live forever
|
||||
std::mem::forget(watcher);
|
||||
|
||||
lib_storage
|
||||
}
|
||||
|
||||
fn compiled_file(dyn_package: &str) -> String { dyn_lib_file(dyn_package, false) }
|
||||
|
||||
fn active_file(dyn_package: &str) -> String { dyn_lib_file(dyn_package, true) }
|
||||
|
||||
fn dyn_lib_file(dyn_package: &str, active: bool) -> String {
|
||||
format!(
|
||||
"{}{}{}{}",
|
||||
DLL_PREFIX,
|
||||
dyn_package.replace('-', "_"),
|
||||
if active { "_active" } else { "" },
|
||||
DLL_SUFFIX
|
||||
)
|
||||
}
|
||||
|
||||
/// Event function to hotreload the dynamic library
|
||||
///
|
||||
/// This is called by the watcher to filter for modify events on `.rs` files
|
||||
/// before sending them back.
|
||||
fn event_fn(res: notify::Result<notify::Event>, sender: &mpsc::Sender<String>) {
|
||||
match res {
|
||||
Ok(event) => {
|
||||
if let EventKind::Modify(_) = event.kind {
|
||||
event
|
||||
.paths
|
||||
.iter()
|
||||
.filter(|p| p.extension().map(|e| e == "rs").unwrap_or(false))
|
||||
.map(|p| p.to_string_lossy().into_owned())
|
||||
// Signal reloader
|
||||
.for_each(|p| { let _ = sender.send(p); });
|
||||
}
|
||||
},
|
||||
Err(e) => error!(?e, "hotreload watcher error."),
|
||||
}
|
||||
}
|
||||
|
||||
/// Hotreload the dynamic library
|
||||
///
|
||||
/// This will reload the dynamic library by first internally calling compile
|
||||
/// and then reloading the library.
|
||||
fn hotreload(dyn_package: &str, loaded_lib: &Mutex<Option<LoadedLib>>) {
|
||||
// Do nothing if recompile failed.
|
||||
if compile(dyn_package) {
|
||||
let mut lock = loaded_lib.lock().unwrap();
|
||||
|
||||
// Close lib.
|
||||
let loaded_lib = lock.take().unwrap();
|
||||
loaded_lib.lib.close().unwrap();
|
||||
copy(&loaded_lib.lib_path, dyn_package);
|
||||
|
||||
// Open new lib.
|
||||
*lock = Some(LoadedLib::load(dyn_package));
|
||||
|
||||
info!("Updated {}.", dyn_package);
|
||||
}
|
||||
}
|
||||
|
||||
/// Recompile the dyn package
|
||||
///
|
||||
/// Returns `false` if the compile failed.
|
||||
fn compile(dyn_package: &str) -> bool {
|
||||
let output = Command::new("cargo")
|
||||
.stderr(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
.arg("build")
|
||||
.arg("--package")
|
||||
.arg(dyn_package)
|
||||
.arg("--features")
|
||||
.arg(format!("{}/be-dyn-lib", dyn_package))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
output.status.success()
|
||||
}
|
||||
|
||||
/// Copy the lib file, so we have an `_active` copy.
|
||||
///
|
||||
/// We do this for all OS's although it is only strictly necessary for windows.
|
||||
/// The reason we do this is to make the code easier to understand and debug.
|
||||
fn copy(lib_path: &Path, dyn_package: &str) {
|
||||
// Use the platform specific names.
|
||||
let lib_compiled_path = lib_path.with_file_name(compiled_file(dyn_package));
|
||||
let lib_output_path = lib_path.with_file_name(active_file(dyn_package));
|
||||
|
||||
// Get the path to where the lib was compiled to.
|
||||
debug!(?lib_compiled_path, ?lib_output_path, "Moving.");
|
||||
|
||||
// Copy the library file from where it is output, to where we are going to
|
||||
// load it from i.e. lib_path.
|
||||
std::fs::copy(&lib_compiled_path, &lib_output_path).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Failed to rename dynamic library from {:?} to {:?}. {:?}",
|
||||
lib_compiled_path, lib_output_path, err
|
||||
)
|
||||
});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,4 @@
|
||||
use crate::rtsim::Entity as RtSimEntity;
|
||||
use common::{
|
||||
comp::{
|
||||
agent::{
|
||||
@ -10,7 +11,10 @@ use common::{
|
||||
path::TraversalConfig,
|
||||
};
|
||||
use rand::{prelude::ThreadRng, Rng};
|
||||
use specs::saveload::{Marker, MarkerAllocator};
|
||||
use specs::{
|
||||
saveload::{Marker, MarkerAllocator},
|
||||
Entity as EcsEntity,
|
||||
};
|
||||
use vek::Vec2;
|
||||
|
||||
use self::interaction::{
|
||||
@ -34,6 +38,7 @@ mod interaction;
|
||||
pub struct BehaviorData<'a, 'b, 'c> {
|
||||
pub agent: &'a mut Agent,
|
||||
pub agent_data: AgentData<'a>,
|
||||
pub rtsim_entity: Option<&'a RtSimEntity>,
|
||||
pub read_data: &'a ReadData<'a>,
|
||||
pub event_emitter: &'a mut Emitter<'c, ServerEvent>,
|
||||
pub controller: &'a mut Controller,
|
||||
@ -240,10 +245,8 @@ fn target_if_attacked(bdata: &mut BehaviorData) -> bool {
|
||||
}
|
||||
|
||||
// Remember this attack if we're an RtSim entity
|
||||
if let Some(attacker_stats) = bdata
|
||||
.agent_data
|
||||
.rtsim_entity
|
||||
.and(bdata.read_data.stats.get(attacker))
|
||||
if let Some(attacker_stats) =
|
||||
bdata.rtsim_entity.and(bdata.read_data.stats.get(attacker))
|
||||
{
|
||||
bdata
|
||||
.agent
|
||||
@ -281,11 +284,7 @@ fn untarget_if_dead(bdata: &mut BehaviorData) -> bool {
|
||||
if let Some(tgt_health) = bdata.read_data.healths.get(target) {
|
||||
// If target is dead, forget them
|
||||
if tgt_health.is_dead {
|
||||
if let Some(tgt_stats) = bdata
|
||||
.agent_data
|
||||
.rtsim_entity
|
||||
.and(bdata.read_data.stats.get(target))
|
||||
{
|
||||
if let Some(tgt_stats) = bdata.rtsim_entity.and(bdata.read_data.stats.get(target)) {
|
||||
bdata.agent.forget_enemy(&tgt_stats.name);
|
||||
}
|
||||
bdata.agent.target = None;
|
||||
@ -461,6 +460,7 @@ fn handle_timed_events(bdata: &mut BehaviorData) -> bool {
|
||||
bdata.controller,
|
||||
bdata.read_data,
|
||||
bdata.event_emitter,
|
||||
will_ambush(bdata.rtsim_entity, &bdata.agent_data),
|
||||
);
|
||||
} else {
|
||||
bdata.agent_data.handle_sounds_heard(
|
||||
@ -503,6 +503,7 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
|
||||
let BehaviorData {
|
||||
agent,
|
||||
agent_data,
|
||||
rtsim_entity,
|
||||
read_data,
|
||||
event_emitter,
|
||||
controller,
|
||||
@ -581,7 +582,13 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
|
||||
read_data.time.0 - selected_at > RETARGETING_THRESHOLD_SECONDS;
|
||||
|
||||
if !in_aggro_range && is_time_to_retarget {
|
||||
agent_data.choose_target(agent, controller, read_data, event_emitter);
|
||||
agent_data.choose_target(
|
||||
agent,
|
||||
controller,
|
||||
read_data,
|
||||
event_emitter,
|
||||
will_ambush(*rtsim_entity, agent_data),
|
||||
);
|
||||
}
|
||||
|
||||
if aggro_on {
|
||||
@ -595,10 +602,55 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
|
||||
tgt_name.map(|tgt_name| agent.add_fight_to_memory(&tgt_name, read_data.time.0));
|
||||
agent_data.attack(agent, controller, &target_data, read_data, rng);
|
||||
} else {
|
||||
agent_data.menacing(agent, controller, target, read_data, event_emitter, rng);
|
||||
agent_data.menacing(
|
||||
agent,
|
||||
controller,
|
||||
target,
|
||||
read_data,
|
||||
event_emitter,
|
||||
rng,
|
||||
remembers_fight_with(*rtsim_entity, read_data, target),
|
||||
);
|
||||
remember_fight(*rtsim_entity, read_data, agent, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn will_ambush(rtsim_entity: Option<&RtSimEntity>, agent_data: &AgentData) -> bool {
|
||||
agent_data
|
||||
.health
|
||||
.map_or(false, |h| h.current() / h.maximum() > 0.7)
|
||||
&& rtsim_entity.map_or(false, |re| re.brain.personality.will_ambush)
|
||||
}
|
||||
|
||||
fn remembers_fight_with(
|
||||
rtsim_entity: Option<&RtSimEntity>,
|
||||
read_data: &ReadData,
|
||||
other: EcsEntity,
|
||||
) -> bool {
|
||||
let name = || read_data.stats.get(other).map(|stats| stats.name.clone());
|
||||
|
||||
rtsim_entity.map_or(false, |rtsim_entity| {
|
||||
name().map_or(false, |name| {
|
||||
rtsim_entity.brain.remembers_fight_with_character(&name)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Remember target.
|
||||
fn remember_fight(
|
||||
rtsim_entity: Option<&RtSimEntity>,
|
||||
read_data: &ReadData,
|
||||
agent: &mut Agent,
|
||||
target: EcsEntity,
|
||||
) {
|
||||
rtsim_entity.is_some().then(|| {
|
||||
read_data
|
||||
.stats
|
||||
.get(target)
|
||||
.map(|stats| agent.add_fight_to_memory(&stats.name, read_data.time.0))
|
||||
});
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ 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, &agent_data.rtsim_entity)
|
||||
(&agent.rtsim_controller.travel_to, &bdata.rtsim_entity)
|
||||
{
|
||||
let personality = &rtsim_entity.brain.personality;
|
||||
let standard_response_msg = || -> String {
|
||||
@ -166,7 +166,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
);
|
||||
} else {
|
||||
let default_msg = "npc-speech-merchant_busy";
|
||||
let msg = agent_data.rtsim_entity.map_or(default_msg, |e| {
|
||||
let msg = bdata.rtsim_entity.map_or(default_msg, |e| {
|
||||
if e.brain
|
||||
.personality
|
||||
.personality_traits
|
||||
@ -181,7 +181,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
}
|
||||
} else {
|
||||
let mut rng = thread_rng();
|
||||
if let Some(extreme_trait) = agent_data
|
||||
if let Some(extreme_trait) = bdata
|
||||
.rtsim_entity
|
||||
.and_then(|e| e.brain.personality.random_chat_trait(&mut rng))
|
||||
{
|
||||
@ -250,7 +250,7 @@ pub fn handle_inbox_talk(bdata: &mut BehaviorData) -> bool {
|
||||
}
|
||||
},
|
||||
Subject::Mood => {
|
||||
if let Some(rtsim_entity) = agent_data.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
|
||||
|
Loading…
Reference in New Issue
Block a user