mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added action timer system for agent code
This commit is contained in:
parent
33e9bc79e3
commit
31a49d26a9
@ -9,11 +9,13 @@ use serde::Deserialize;
|
||||
use specs::{Component, Entity as EcsEntity};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::{collections::VecDeque, fmt};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
use vek::*;
|
||||
|
||||
use super::dialogue::Subject;
|
||||
|
||||
pub const DEFAULT_INTERACTION_TIME: f32 = 1.0;
|
||||
pub const DEFAULT_INTERACTION_TIME: f32 = 3.0;
|
||||
pub const TRADE_INTERACTION_TIME: f32 = 300.0;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Deserialize)]
|
||||
@ -340,6 +342,88 @@ pub struct Target {
|
||||
pub aggro_on: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, EnumIter)]
|
||||
pub enum TimerAction {
|
||||
Interact,
|
||||
}
|
||||
|
||||
/// A time used for managing agent-related timeouts. The timer is designed to
|
||||
/// keep track of the start of any number of previous actions. However,
|
||||
/// starting/progressing an action will end previous actions. Therefore, the
|
||||
/// timer should be used for actions that are mutually-exclusive.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Timer {
|
||||
action_starts: Vec<Option<f64>>,
|
||||
last_action: Option<TimerAction>,
|
||||
}
|
||||
|
||||
impl Default for Timer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
action_starts: TimerAction::iter().map(|_| None).collect(),
|
||||
last_action: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Timer {
|
||||
fn idx_for(action: TimerAction) -> usize {
|
||||
TimerAction::iter()
|
||||
.enumerate()
|
||||
.find(|(_, a)| a == &action)
|
||||
.unwrap()
|
||||
.0 // Can't fail, EnumIter is exhaustive
|
||||
}
|
||||
|
||||
/// Reset the timer for the given action, returning true if the timer was
|
||||
/// not already reset.
|
||||
pub fn reset(&mut self, action: TimerAction) -> bool {
|
||||
std::mem::replace(&mut self.action_starts[Self::idx_for(action)], None).is_some()
|
||||
}
|
||||
|
||||
/// Start the timer for the given action, even if it was already started.
|
||||
pub fn start(&mut self, time: f64, action: TimerAction) {
|
||||
self.action_starts[Self::idx_for(action)] = Some(time);
|
||||
self.last_action = Some(action);
|
||||
}
|
||||
|
||||
/// Continue timing the given action, starting it if it was not already
|
||||
/// started.
|
||||
pub fn progress(&mut self, time: f64, action: TimerAction) {
|
||||
if self.last_action != Some(action) {
|
||||
self.start(time, action);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the time that the given action was last performed at.
|
||||
pub fn time_of_last(&self, action: TimerAction) -> Option<f64> {
|
||||
self.action_starts[Self::idx_for(action)]
|
||||
}
|
||||
|
||||
/// Return `true` if the time since the action was last started exceeds the
|
||||
/// given timeout.
|
||||
pub fn time_since_exceeds(&self, time: f64, action: TimerAction, timeout: f64) -> bool {
|
||||
self.time_of_last(action)
|
||||
.map_or(true, |last_time| (time - last_time).max(0.0) > timeout)
|
||||
}
|
||||
|
||||
/// Return `true` while the time since the action was last started is less
|
||||
/// than the given period. Once the time has elapsed, reset the timer.
|
||||
pub fn timeout_elapsed(
|
||||
&mut self,
|
||||
time: f64,
|
||||
action: TimerAction,
|
||||
timeout: f64,
|
||||
) -> Option<bool> {
|
||||
if self.time_since_exceeds(time, action, timeout) {
|
||||
Some(self.reset(action))
|
||||
} else {
|
||||
self.progress(time, action);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Agent {
|
||||
@ -351,6 +435,7 @@ pub struct Agent {
|
||||
pub psyche: Psyche,
|
||||
pub inbox: VecDeque<AgentEvent>,
|
||||
pub action_state: ActionState,
|
||||
pub timer: Timer,
|
||||
pub bearing: Vec2<f32>,
|
||||
pub sounds_heard: Vec<Sound>,
|
||||
pub awareness: f32,
|
||||
@ -376,6 +461,7 @@ impl Agent {
|
||||
psyche: Psyche::from(body),
|
||||
inbox: VecDeque::new(),
|
||||
action_state: ActionState::default(),
|
||||
timer: Timer::default(),
|
||||
bearing: Vec2::zero(),
|
||||
sounds_heard: Vec::new(),
|
||||
awareness: 0.0,
|
||||
|
@ -3,7 +3,8 @@ use common::{
|
||||
comp::{
|
||||
self,
|
||||
agent::{
|
||||
AgentEvent, Sound, SoundKind, Target, DEFAULT_INTERACTION_TIME, TRADE_INTERACTION_TIME,
|
||||
AgentEvent, Sound, SoundKind, Target, TimerAction, DEFAULT_INTERACTION_TIME,
|
||||
TRADE_INTERACTION_TIME,
|
||||
},
|
||||
buff::{BuffKind, Buffs},
|
||||
compass::{Direction, Distance},
|
||||
@ -383,7 +384,6 @@ impl<'a> System<'a> for Sys {
|
||||
|agent: &mut Agent,
|
||||
controller: &mut Controller,
|
||||
event_emitter: &mut Emitter<ServerEvent>| {
|
||||
agent.target = None;
|
||||
data.idle_tree(agent, controller, &read_data, event_emitter);
|
||||
};
|
||||
|
||||
@ -608,27 +608,43 @@ impl<'a> AgentData<'a> {
|
||||
agent.action_state.timer = 0.1;
|
||||
}
|
||||
}
|
||||
if agent.action_state.timer > 0.0 {
|
||||
if agent.action_state.timer
|
||||
< (if agent.behavior.is(BehaviorState::TRADING) {
|
||||
TRADE_INTERACTION_TIME
|
||||
} else {
|
||||
DEFAULT_INTERACTION_TIME
|
||||
})
|
||||
{
|
||||
self.interact(agent, controller, read_data, event_emitter);
|
||||
} else {
|
||||
agent.action_state.timer = 0.0;
|
||||
agent.target = None;
|
||||
controller.actions.push(ControlAction::Stand);
|
||||
self.idle(agent, controller, read_data);
|
||||
}
|
||||
} else if thread_rng().gen::<f32>() < 0.1 {
|
||||
self.choose_target(agent, controller, read_data, event_emitter);
|
||||
} else if agent.awareness > AWARENESS_INVESTIGATE_THRESHOLD {
|
||||
self.handle_elevated_awareness(agent, controller, read_data);
|
||||
|
||||
// If we receive a new interaction, start the interaction timer
|
||||
if can_speak(agent) && self.recv_interaction(agent, controller, read_data, event_emitter) {
|
||||
agent.timer.start(read_data.time.0, TimerAction::Interact);
|
||||
}
|
||||
|
||||
let timeout = if agent.behavior.is(BehaviorState::TRADING) {
|
||||
TRADE_INTERACTION_TIME
|
||||
} else {
|
||||
self.idle(agent, controller, read_data);
|
||||
DEFAULT_INTERACTION_TIME
|
||||
};
|
||||
|
||||
match agent
|
||||
.timer
|
||||
.timeout_elapsed(read_data.time.0, TimerAction::Interact, timeout as f64)
|
||||
{
|
||||
None => {
|
||||
// Look toward the interacting entity for a while
|
||||
if let Some(Target { target, .. }) = &agent.target {
|
||||
self.look_toward(controller, read_data, *target);
|
||||
controller.actions.push(ControlAction::Talk);
|
||||
}
|
||||
},
|
||||
Some(just_ended) => {
|
||||
if just_ended {
|
||||
agent.target = None;
|
||||
controller.actions.push(ControlAction::Stand);
|
||||
}
|
||||
|
||||
if thread_rng().gen::<f32>() < 0.1 {
|
||||
self.choose_target(agent, controller, read_data, event_emitter);
|
||||
} else if agent.awareness > AWARENESS_INVESTIGATE_THRESHOLD {
|
||||
self.handle_elevated_awareness(agent, controller, read_data);
|
||||
} else {
|
||||
self.idle(agent, controller, read_data);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -967,13 +983,13 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn interact(
|
||||
fn recv_interaction(
|
||||
&self,
|
||||
agent: &mut Agent,
|
||||
controller: &mut Controller,
|
||||
read_data: &ReadData,
|
||||
event_emitter: &mut Emitter<'_, ServerEvent>,
|
||||
) {
|
||||
) -> bool {
|
||||
// TODO: Process group invites
|
||||
// TODO: Add Group AgentEvent
|
||||
// let accept = false; // set back to "matches!(alignment, Alignment::Npc)"
|
||||
@ -1302,18 +1318,11 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if can_speak(agent) {
|
||||
// No new events, continue looking towards the last
|
||||
// interacting player for some time
|
||||
if let Some(Target { target, .. }) = &agent.target {
|
||||
self.look_toward(controller, read_data, *target);
|
||||
} else {
|
||||
agent.action_state.timer = 0.0;
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(AgentEvent::ServerSound(_)) => {},
|
||||
Some(AgentEvent::Hurt) => {},
|
||||
None => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn look_toward(
|
||||
|
Loading…
Reference in New Issue
Block a user