Merge branch 'flaffwaffle/action_state_concurrent_states' into 'master'

ActionState extensions to allow for multiple concurrent state variables And More Complex AI

See merge request veloren/veloren!3659
This commit is contained in:
Samuel Keiffer 2022-10-25 03:21:46 +00:00
commit f676e732f6
6 changed files with 542 additions and 171 deletions

View File

@ -44,7 +44,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Item pickup UI now displays items that members of your group pick up. - Item pickup UI now displays items that members of your group pick up.
- Improved shiny water shaders - Improved shiny water shaders
- Tweaked armor stats - Tweaked armor stats
### Removed ### Removed
### Fixed ### Fixed

View File

@ -21,6 +21,27 @@ pub const TRADE_INTERACTION_TIME: f32 = 300.0;
const AWARENESS_DECREMENT_CONSTANT: f32 = 2.1; const AWARENESS_DECREMENT_CONSTANT: f32 = 2.1;
const SECONDS_BEFORE_FORGET_SOUNDS: f64 = 180.0; const SECONDS_BEFORE_FORGET_SOUNDS: f64 = 180.0;
//intentionally very few concurrent action state variables are allowed. This is
// to keep the complexity of our AI from getting too large, too quickly.
// Originally I was going to provide 30 of these, but if we decide later that
// this is too many and somebody is already using 30 in one of their AI, it will
// be difficult to go back.
/// The number of timers that a single Action node can track concurrently
/// Define constants within a given action node to index between them.
const ACTIONSTATE_NUMBER_OF_CONCURRENT_TIMERS: usize = 5;
/// The number of float counters that a single Action node can track
/// concurrently Define constants within a given action node to index between
/// them.
const ACTIONSTATE_NUMBER_OF_CONCURRENT_COUNTERS: usize = 5;
/// The number of integer counters that a single Action node can track
/// concurrently Define constants within a given action node to index between
/// them.
const ACTIONSTATE_NUMBER_OF_CONCURRENT_INT_COUNTERS: usize = 5;
/// The number of booleans that a single Action node can track concurrently
/// Define constants within a given action node to index between them.
const ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS: usize = 5;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Alignment { pub enum Alignment {
/// Wild animals and gentle giants /// Wild animals and gentle giants
@ -511,12 +532,16 @@ pub struct Agent {
pub position_pid_controller: Option<PidController<fn(Vec3<f32>, Vec3<f32>) -> f32, 16>>, pub position_pid_controller: Option<PidController<fn(Vec3<f32>, Vec3<f32>) -> f32, 16>>,
} }
/// State persistence object for the behavior tree
/// Allows for state to be stored between subsequent, sequential calls of a
/// single action node. If the executed action node changes between ticks, then
/// the state should be considered lost.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct ActionState { pub struct ActionState {
pub timer: f32, pub timers: [f32; ACTIONSTATE_NUMBER_OF_CONCURRENT_TIMERS],
pub counter: f32, pub counters: [f32; ACTIONSTATE_NUMBER_OF_CONCURRENT_COUNTERS],
pub condition: bool, pub conditions: [bool; ACTIONSTATE_NUMBER_OF_CONCURRENT_CONDITIONS],
pub int_counter: u8, pub int_counters: [u8; ACTIONSTATE_NUMBER_OF_CONCURRENT_INT_COUNTERS],
pub initialized: bool, pub initialized: bool,
} }

View File

@ -163,6 +163,9 @@ impl<'a> AgentData<'a> {
read_data: &ReadData, read_data: &ReadData,
rng: &mut impl Rng, rng: &mut impl Rng,
) { ) {
enum ActionTimers {
TimerIdle = 0,
}
// Light lanterns at night // Light lanterns at night
// TODO Add a method to turn on NPC lanterns underground // TODO Add a method to turn on NPC lanterns underground
let lantern_equipped = self let lantern_equipped = self
@ -196,15 +199,15 @@ impl<'a> AgentData<'a> {
true true
}; };
if attempt_heal && self.heal_self(agent, controller, true) { if attempt_heal && self.heal_self(agent, controller, true) {
agent.action_state.timer = 0.01; agent.action_state.timers[ActionTimers::TimerIdle as usize] = 0.01;
return; return;
} }
} else { } else {
agent.action_state.timer = 0.01; agent.action_state.timers[ActionTimers::TimerIdle as usize] = 0.01;
return; return;
} }
agent.action_state.timer = 0.0; agent.action_state.timers[ActionTimers::TimerIdle as usize] = 0.0;
if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to { if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to {
// If it has an rtsim destination and can fly, then it should. // If it has an rtsim destination and can fly, then it should.
// If it is flying and bumps something above it, then it should move down. // If it is flying and bumps something above it, then it should move down.
@ -547,7 +550,10 @@ impl<'a> AgentData<'a> {
event_emitter: &mut Emitter<ServerEvent>, event_emitter: &mut Emitter<ServerEvent>,
will_ambush: bool, will_ambush: bool,
) { ) {
agent.action_state.timer = 0.0; enum ActionStateTimers {
TimerChooseTarget = 0,
}
agent.action_state.timers[ActionStateTimers::TimerChooseTarget as usize] = 0.0;
let mut aggro_on = false; let mut aggro_on = false;
// Search the area. // Search the area.

File diff suppressed because it is too large Load Diff

View File

@ -58,6 +58,22 @@ pub struct BehaviorTree {
tree: Vec<BehaviorFn>, tree: Vec<BehaviorFn>,
} }
/// Enumeration of the timers used by the behavior tree.
// FIXME: We shouldnt have a global timer enumeration for the whole behavior
// tree. It isnt entirely clear where a lot of the agents in some of the bdata
// objects in behavior tree functions come from, so it's hard to granularly
// define these timers per action node. As such, the behavior tree currently has
// one global enumeration for mapping timers in all functions, regardless as to
// use case or action node currently executed -- even if the agent might be
// different between calls. This doesn't break anything as each agent has its
// own instance of timers, but it is much less clear than I would like.
//
// This may require some refactoring to fix, and I don't feel confident doing
// so.
enum ActionStateBehaviorTreeTimers {
TimerBehaviorTree = 0,
}
impl BehaviorTree { impl BehaviorTree {
/// Base BehaviorTree /// Base BehaviorTree
/// ///
@ -479,7 +495,8 @@ fn heal_self_if_hurt(bdata: &mut BehaviorData) -> bool {
.agent_data .agent_data
.heal_self(bdata.agent, bdata.controller, false) .heal_self(bdata.agent, bdata.controller, false)
{ {
bdata.agent.action_state.timer = 0.01; bdata.agent.action_state.timers
[ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.01;
return true; return true;
} }
false false
@ -543,18 +560,27 @@ fn do_combat(bdata: &mut BehaviorData) -> bool {
let aggro_on = *aggro_on; let aggro_on = *aggro_on;
if agent_data.below_flee_health(agent) { if agent_data.below_flee_health(agent) {
let has_opportunity_to_flee = agent.action_state.timer < FLEE_DURATION; let has_opportunity_to_flee = agent.action_state.timers
[ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize]
< FLEE_DURATION;
let within_flee_distance = dist_sqrd < MAX_FLEE_DIST.powi(2); let within_flee_distance = dist_sqrd < MAX_FLEE_DIST.powi(2);
// FIXME: Using action state timer to see if allowed to speak is a hack. // FIXME: Using action state timer to see if allowed to speak is a hack.
if agent.action_state.timer == 0.0 { if agent.action_state.timers
[ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize]
== 0.0
{
agent_data.cry_out(agent, event_emitter, read_data); agent_data.cry_out(agent, event_emitter, read_data);
agent.action_state.timer = 0.01; agent.action_state.timers
[ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.01;
} else if within_flee_distance && has_opportunity_to_flee { } else if within_flee_distance && has_opportunity_to_flee {
agent_data.flee(agent, controller, tgt_pos, &read_data.terrain); agent_data.flee(agent, controller, tgt_pos, &read_data.terrain);
agent.action_state.timer += read_data.dt.0; agent.action_state.timers
[ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] +=
read_data.dt.0;
} else { } else {
agent.action_state.timer = 0.0; agent.action_state.timers
[ActionStateBehaviorTreeTimers::TimerBehaviorTree as usize] = 0.0;
agent.target = None; agent.target = None;
agent_data.idle(agent, controller, read_data, rng); agent_data.idle(agent, controller, read_data, rng);
} }

View File

@ -22,6 +22,10 @@ use crate::{
use super::{BehaviorData, BehaviorTree}; use super::{BehaviorData, BehaviorTree};
enum ActionStateInteractionTimers {
TimerInteraction = 0,
}
/// Interact if incoming messages /// Interact if incoming messages
pub fn process_inbox_sound_and_hurt(bdata: &mut BehaviorData) -> bool { pub fn process_inbox_sound_and_hurt(bdata: &mut BehaviorData) -> bool {
if !bdata.agent.inbox.is_empty() { if !bdata.agent.inbox.is_empty() {
@ -44,7 +48,8 @@ pub fn process_inbox_sound_and_hurt(bdata: &mut BehaviorData) -> bool {
Some(_) | None => {}, Some(_) | None => {},
} }
} else { } else {
bdata.agent.action_state.timer = 0.1; bdata.agent.action_state.timers
[ActionStateInteractionTimers::TimerInteraction as usize] = 0.1;
} }
} }
false false
@ -63,7 +68,8 @@ pub fn process_inbox_interaction(bdata: &mut BehaviorData) -> bool {
/// Increment agent's action_state timer /// Increment agent's action_state timer
pub fn increment_timer_deltatime(bdata: &mut BehaviorData) -> bool { pub fn increment_timer_deltatime(bdata: &mut BehaviorData) -> bool {
bdata.agent.action_state.timer += bdata.read_data.dt.0; bdata.agent.action_state.timers[ActionStateInteractionTimers::TimerInteraction as usize] +=
bdata.read_data.dt.0;
false false
} }