diff --git a/server/src/sys/agent.rs b/server/src/sys/agent.rs index 2fc070b857..a38e55bbc9 100644 --- a/server/src/sys/agent.rs +++ b/server/src/sys/agent.rs @@ -87,8 +87,8 @@ pub struct ReadData<'a> { } // This is 3.1 to last longer than the last damage timer (3.0 seconds) -const DAMAGE_MEMORY_DURATION: f64 = 3.0; -const FLEE_DURATION: f32 = 3.1; +const DAMAGE_MEMORY_DURATION: f64 = 0.1; +const FLEE_DURATION: f32 = 3.0; const MAX_FOLLOW_DIST: f32 = 12.0; const MAX_CHASE_DIST: f32 = 20.0; const MAX_FLEE_DIST: f32 = 20.0; @@ -250,71 +250,103 @@ impl<'a> System<'a> for Sys { // glider fall vertical speed) if vel.0.z < -26.0 { controller.actions.push(ControlAction::GlideWield); + if let Some(Target { target, hostile: _ }) = agent.target { + if let Some(tgt_pos) = read_data.positions.get(target) { + controller.inputs.move_dir = (pos.0 - tgt_pos.0) + .xy() + .try_normalized() + .unwrap_or_else(Vec2::zero); + } + } } - - // Pet branch - } else if let Some(Alignment::Owned(owner)) = alignment { - if let Some(owner) = - read_data.uid_allocator.retrieve_entity_internal(owner.id()) - { - if let (Some(owner_pos), Some(owner_health)) = - (read_data.positions.get(owner), read_data.healths.get(owner)) - { - agent.target = Some(Target { - target: owner, - hostile: false, - }); - let dist_sqrd = pos.0.distance_squared(owner_pos.0); - - // If really far away drop everything and follow - if dist_sqrd > (2.0 * MAX_FOLLOW_DIST).powi(2) { - data.follow(agent, controller, &read_data.terrain, owner_pos); - // Attack owner's attacker - } else if owner_health.last_change.0 < 5.0 - && owner_health.last_change.1.amount < 0 - { - if let comp::HealthSource::Damage { by: Some(by), .. } = - owner_health.last_change.1.cause - { - if let Some(attacker) = read_data - .uid_allocator - .retrieve_entity_internal(by.id()) + } else if let Some(Target { target, hostile }) = agent.target { + if let Some(tgt_health) = read_data.healths.get(target) { + // If the target is hostile (either based on alignment or if + // the target just attacked + if !tgt_health.is_dead { + if hostile { + data.hostile_tree( + agent, + controller, + &read_data, + &mut event_emitter, + ); + // Target is something worth following methinks + } else if let Some(Alignment::Owned(_)) = data.alignment { + if let Some(tgt_pos) = read_data.positions.get(target) { + let dist_sqrd = pos.0.distance_squared(tgt_pos.0); + // If really far away drop everything and follow + if dist_sqrd > (2.0 * MAX_FOLLOW_DIST).powi(2) { + agent.bearing = Vec2::zero(); + data.follow( + agent, + controller, + &read_data.terrain, + tgt_pos, + ); + // Attack target's attacker + } else if tgt_health.last_change.0 < 5.0 + && tgt_health.last_change.1.amount < 0 { - agent.target = Some(Target { - target: attacker, - hostile: true, - }); - if let (Some(tgt_pos), Some(tgt_health)) = ( - read_data.positions.get(attacker), - read_data.healths.get(attacker), - ) { - if tgt_health.is_dead - || dist_sqrd > MAX_CHASE_DIST.powi(2) + if let comp::HealthSource::Damage { + by: Some(by), .. + } = tgt_health.last_change.1.cause + { + if let Some(attacker) = read_data + .uid_allocator + .retrieve_entity_internal(by.id()) { agent.target = Some(Target { - target: owner, - hostile: false, + target: attacker, + hostile: true, }); - data.idle(agent, controller, &read_data); - } else { - data.attack( - agent, - controller, - &read_data.terrain, - tgt_pos, - read_data.bodies.get(attacker), - &read_data.dt, - ); + if let (Some(tgt_pos), Some(tgt_health)) = ( + read_data.positions.get(attacker), + read_data.healths.get(attacker), + ) { + if tgt_health.is_dead + || dist_sqrd > MAX_CHASE_DIST.powi(2) + { + agent.target = Some(Target { + target, + hostile: false, + }); + data.idle( + agent, controller, &read_data, + ); + } else { + data.attack( + agent, + controller, + &read_data.terrain, + tgt_pos, + read_data.bodies.get(attacker), + &read_data.dt, + ); + } + } } } + // Follow owner if too far away and not + // fighting + } else if dist_sqrd > MAX_FOLLOW_DIST.powi(2) { + data.follow( + agent, + controller, + &read_data.terrain, + tgt_pos, + ); + + // Otherwise just idle + } else { + data.idle_tree( + agent, + controller, + &read_data, + &mut event_emitter, + ); } } - - // Follow owner if too far away and not fighting - } else if dist_sqrd > MAX_FOLLOW_DIST.powi(2) { - data.follow(agent, controller, &read_data.terrain, owner_pos); - - // Otherwise just idle } else { data.idle_tree( agent, @@ -323,26 +355,12 @@ impl<'a> System<'a> for Sys { &mut event_emitter, ); } - } - } - - // Non-owned agent branch - } else if let Some(Target { target, hostile }) = agent.target { - if let Some(tgt_health) = read_data.healths.get(target) { - // If the target is hostile (either based on alignment or if - // the target just attacked - if hostile && !tgt_health.is_dead { - data.hostile_tree( - agent, - controller, - &read_data, - &mut event_emitter, - ); - // If the target is not hostile, just idle } else { + agent.target = None; data.idle_tree(agent, controller, &read_data, &mut event_emitter); } } else { + agent.target = None; data.idle_tree(agent, controller, &read_data, &mut event_emitter); } } else { @@ -355,11 +373,20 @@ impl<'a> System<'a> for Sys { read_data.uid_allocator.retrieve_entity_internal(by.id()) { if let Some(tgt_pos) = read_data.positions.get(attacker) { + // If the target is dead, remove the target and idle. if read_data .healths .get(attacker) - .map_or(false, |a| !a.is_dead) + .map_or(true, |a| a.is_dead) { + agent.target = None; + data.idle_tree( + agent, + controller, + &read_data, + &mut event_emitter, + ); + } else { agent.target = Some(Target { target: attacker, hostile: true, @@ -372,14 +399,6 @@ impl<'a> System<'a> for Sys { read_data.bodies.get(attacker), &read_data.dt, ); - } else { - agent.target = None; - data.idle_tree( - agent, - controller, - &read_data, - &mut event_emitter, - ); } } else { agent.target = None; @@ -424,23 +443,28 @@ impl<'a> AgentData<'a> { read_data: &ReadData, event_emitter: &mut Emitter<'_, ServerEvent>, ) { + // Set owner if no target + if agent.target.is_none() && thread_rng().gen_bool(0.1) { + if let Some(Alignment::Owned(owner)) = self.alignment { + if let Some(owner) = read_data.uid_allocator.retrieve_entity_internal(owner.id()) { + agent.target = Some(Target { + target: owner, + hostile: false, + }); + } + } + } // Interact if incoming messages if !agent.inbox.is_empty() { - agent.action_timer = 0.1 + agent.action_timer = 0.1; } if agent.action_timer > 0.0 { - if self.flees && agent.can_speak { - if agent.action_timer < DEFAULT_INTERACTION_TIME { - self.interact(agent, controller, &read_data, event_emitter); - } else { - agent.action_timer = 0.0; - agent.target = None; - controller.actions.push(ControlAction::Stand); - self.idle(agent, controller, &read_data); - } + if agent.action_timer < DEFAULT_INTERACTION_TIME { + self.interact(agent, controller, &read_data, event_emitter); } else { - agent.inbox.clear(); + agent.action_timer = 0.0; agent.target = None; + controller.actions.push(ControlAction::Stand); self.idle(agent, controller, &read_data); } } else if thread_rng().gen::() < 0.1 { @@ -526,29 +550,20 @@ impl<'a> AgentData<'a> { let lantern_turned_on = self.light_emitter.is_some(); let day_period = DayPeriod::from(read_data.time_of_day.0); // Only emit event for agents that have a lantern equipped - if lantern_equipped { - let mut rng = thread_rng(); + if lantern_equipped && thread_rng().gen_bool(0.001) { if day_period.is_dark() && !lantern_turned_on { // Agents with turned off lanterns turn them on randomly once it's // nighttime and keep them on // Only emit event for agents that sill need to // turn on their lantern - if let 0 = rng.gen_range(0..1000) { - controller.events.push(ControlEvent::EnableLantern) - } + controller.events.push(ControlEvent::EnableLantern) } else if lantern_turned_on && day_period.is_light() { // agents with turned on lanterns turn them off randomly once it's // daytime and keep them off - if let 0 = rng.gen_range(0..2000) { - controller.events.push(ControlEvent::DisableLantern) - } + controller.events.push(ControlEvent::DisableLantern) } }; - if self.physics_state.on_ground { - controller.actions.push(ControlAction::Unwield); - } - agent.action_timer = 0.0; if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to { // if it has an rtsim destination and can fly then it should @@ -607,7 +622,7 @@ impl<'a> AgentData<'a> { }; // Put away weapon - if thread_rng().gen::() < 0.05 + if thread_rng().gen_bool(0.1) && matches!( read_data.char_states.get(*self.entity), Some(CharacterState::Wielding) @@ -654,7 +669,7 @@ impl<'a> AgentData<'a> { } // Put away weapon - if thread_rng().gen::() < 0.05 + if thread_rng().gen_bool(0.1) && matches!( read_data.char_states.get(*self.entity), Some(CharacterState::Wielding) @@ -692,54 +707,58 @@ impl<'a> AgentData<'a> { .push(ControlEvent::InviteResponse(InviteResponse::Decline)); } agent.action_timer += read_data.dt.0; - if let Some(AgentEvent::Talk(by)) = agent.inbox.pop_back() { - if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id()) { - agent.target = Some(Target { - target, - hostile: false, - }); - if let Some(tgt_pos) = read_data.positions.get(target) { + if agent.can_speak { + if let Some(AgentEvent::Talk(by)) = agent.inbox.pop_back() { + if let Some(target) = read_data.uid_allocator.retrieve_entity_internal(by.id()) { + agent.target = Some(Target { + target, + hostile: false, + }); + if let Some(tgt_pos) = read_data.positions.get(target) { + let eye_offset = self.body.map_or(0.0, |b| b.eye_height()); + let tgt_eye_offset = + read_data.bodies.get(target).map_or(0.0, |b| b.eye_height()); + if let Some(dir) = Dir::from_unnormalized( + Vec3::new(tgt_pos.0.x, tgt_pos.0.y, tgt_pos.0.z + tgt_eye_offset) + - Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset), + ) { + controller.inputs.look_dir = dir; + } + controller.actions.push(ControlAction::Stand); + controller.actions.push(ControlAction::Talk); + if let Some((_travel_to, destination_name)) = + &agent.rtsim_controller.travel_to + { + let msg = + format!("I'm heading to {}! Want to come along?", destination_name); + event_emitter + .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); + } else { + let msg = "npc.speech.villager".to_string(); + event_emitter + .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); + } + } + } + } else if let Some(Target { target, .. }) = &agent.target { + if let Some(tgt_pos) = read_data.positions.get(*target) { let eye_offset = self.body.map_or(0.0, |b| b.eye_height()); - let tgt_eye_offset = - read_data.bodies.get(target).map_or(0.0, |b| b.eye_height()); + let tgt_eye_offset = read_data + .bodies + .get(*target) + .map_or(0.0, |b| b.eye_height()); if let Some(dir) = Dir::from_unnormalized( Vec3::new(tgt_pos.0.x, tgt_pos.0.y, tgt_pos.0.z + tgt_eye_offset) - Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset), ) { controller.inputs.look_dir = dir; } - controller.actions.push(ControlAction::Stand); - controller.actions.push(ControlAction::Talk); - if let Some((_travel_to, destination_name)) = &agent.rtsim_controller.travel_to - { - let msg = - format!("I'm heading to {}! Want to come along?", destination_name); - event_emitter - .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); - } else { - let msg = "npc.speech.villager".to_string(); - event_emitter - .emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg))); - } - //} - } - } - } else if let Some(Target { target, .. }) = &agent.target { - if let Some(tgt_pos) = read_data.positions.get(*target) { - let eye_offset = self.body.map_or(0.0, |b| b.eye_height()); - let tgt_eye_offset = read_data - .bodies - .get(*target) - .map_or(0.0, |b| b.eye_height()); - if let Some(dir) = Dir::from_unnormalized( - Vec3::new(tgt_pos.0.x, tgt_pos.0.y, tgt_pos.0.z + tgt_eye_offset) - - Vec3::new(self.pos.0.x, self.pos.0.y, self.pos.0.z + eye_offset), - ) { - controller.inputs.look_dir = dir; } + } else { + agent.action_timer = 0.0; } } else { - agent.action_timer = 0.0; + agent.inbox.clear(); } } @@ -842,10 +861,13 @@ impl<'a> AgentData<'a> { None } }) { - Some(ToolKind::Bow) => Tactic::Bow, - Some(ToolKind::Staff) => Tactic::Staff, + Some(ToolKind::Bow) | Some(ToolKind::BowSimple) => Tactic::Bow, + Some(ToolKind::Staff) | Some(ToolKind::StaffSimple) => Tactic::Staff, Some(ToolKind::Hammer) => Tactic::Hammer, - Some(ToolKind::Sword) => Tactic::Sword, + Some(ToolKind::Sword) + | Some(ToolKind::Spear) + | Some(ToolKind::SwordSimple) + | Some(ToolKind::AxeSimple) => Tactic::Sword, Some(ToolKind::Axe) => Tactic::Axe, Some(ToolKind::Unique(UniqueKind::StoneGolemFist)) => Tactic::StoneGolemBoss, Some(ToolKind::Unique(UniqueKind::QuadMedQuick)) => Tactic::CircleCharge { @@ -863,7 +885,8 @@ impl<'a> AgentData<'a> { Some(ToolKind::Unique(UniqueKind::QuadLowTail)) => Tactic::TailSlap, Some(ToolKind::Unique(UniqueKind::QuadLowQuick)) => Tactic::QuadLowQuick, Some(ToolKind::Unique(UniqueKind::QuadLowBasic)) => Tactic::QuadLowBasic, - Some(ToolKind::Unique(UniqueKind::QuadLowBreathe)) => Tactic::Lavadrake, + Some(ToolKind::Unique(UniqueKind::QuadLowBreathe)) + | Some(ToolKind::Unique(UniqueKind::QuadLowBeam)) => Tactic::Lavadrake, Some(ToolKind::Unique(UniqueKind::TheropodBasic)) => Tactic::Theropod, Some(ToolKind::Unique(UniqueKind::TheropodBird)) => Tactic::Theropod, Some(ToolKind::Unique(UniqueKind::ObjectTurret)) => Tactic::Turret, @@ -1605,7 +1628,7 @@ impl<'a> AgentData<'a> { agent.target = None; } }, - Tactic::Lavadrake => { + Tactic::Lavadrake | Tactic::QuadLowBeam => { if dist_sqrd < (2.5 * min_attack_dist * self.scale).powi(2) { controller.inputs.move_dir = Vec2::zero(); controller.inputs.secondary.set_state(true); @@ -1677,8 +1700,7 @@ impl<'a> AgentData<'a> { } }, Tactic::Turret => { - if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) - { + if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) { controller.inputs.primary.set_state(true); } else { agent.target = None; @@ -1686,8 +1708,7 @@ impl<'a> AgentData<'a> { }, Tactic::FixedTurret => { controller.inputs.look_dir = self.ori.look_dir(); - if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) - { + if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) { controller.inputs.primary.set_state(true); } else { agent.target = None; @@ -1696,13 +1717,12 @@ impl<'a> AgentData<'a> { Tactic::RotatingTurret => { controller.inputs.look_dir = Dir::new( Quaternion::from_xyzw(self.ori.look_dir().x, self.ori.look_dir().y, 0.0, 0.0) - .rotated_z(6.0 * dt.0 as f32) - .into_vec3() - .try_normalized() - .unwrap_or_default(), + .rotated_z(6.0 * dt.0 as f32) + .into_vec3() + .try_normalized() + .unwrap_or_default(), ); - if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) - { + if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) { controller.inputs.primary.set_state(true); } else { agent.target = None;