use bevy::prelude::*;
use bitflags::bitflags;
use crate::{game::GameTimeDelta, combat::Target};
bitflags! {
#[derive(Default)]
pub struct AgentCategory: u32 {
const FIGHTER = 0b00000001;
const FRIGATE = 0b00000010;
const CRUISER = 0b00000100;
const TURRET = 0b00001000;
const MISSILE = 0b00010000;
}
}
pub struct AggroRadius(pub f32);
#[derive(Default)]
pub struct AggroLocation(pub Vec3);
pub struct RetargetBehavior {
pub interval: f32,
pub remaining_time: f32
}
pub const MAX_AGGRO_RADIUS : f32 = 1000.0;
#[derive(Copy, Clone)]
pub struct TargetingOrders {
pub preferred: AgentCategory,
pub discouraged: AgentCategory,
pub target_same_team: bool
}
pub struct GuardBehavior {
pub protected: Entity
}
pub fn update_aggression_source(
mut guards_query: Query<(
&GuardBehavior,
&mut AggroLocation,
)>,
mut solo_query: Query<(
&GlobalTransform,
&mut AggroLocation,
), Without<GuardBehavior>>,
pos_query: Query<&GlobalTransform>
) {
for (guard, mut aggro_loc) in guards_query.iter_mut() {
match pos_query.get(guard.protected) {
Err(_) => { continue },
Ok(other_transform) => { aggro_loc.0 = other_transform.translation }
}
}
for (transform, mut aggro_loc) in solo_query.iter_mut() {
aggro_loc.0 = transform.translation
}
}
pub fn do_retargetting(
dt: Res<GameTimeDelta>,
mut query: Query<(
&mut Target,
&mut RetargetBehavior,
)>
) {
for (mut target, mut retarget) in query.iter_mut() {
retarget.remaining_time -= dt.0;
if retarget.remaining_time < 0.0
{
retarget.remaining_time = retarget.interval;
target.0 = None;
}
}
}
use crate::combat::{mortal::{Health, MaxHealth}, Team};
use multimap::MultiMap;
pub const HASH_CELL_SIZE : f32 = 50.0;
struct TargetInformation {
pub entity: Entity,
pub category: AgentCategory,
pub health_fraction: f32,
pub position: Vec3,
pub team: Team
}
struct Targetter {
pub team: Team,
pub position: Vec3,
pub orders: TargetingOrders,
pub radius: f32,
pub score: f32,
pub current_target: Target
}
impl Targetter {
fn consider(&mut self, candidate: &TargetInformation) {
if (self.team == candidate.team) != self.orders.target_same_team {
return;
}
let delta = (candidate.position - self.position).length_squared();
if delta > self.radius.powi(2)
{
return;
}
let mut score = delta;
if self.orders.target_same_team
{
score = candidate.health_fraction;
}
if self.orders.preferred.contains(candidate.category)
{
score /= 5.0;
}
if self.orders.discouraged.contains(candidate.category){
score *= 5.0;
}
if score > self.score{
return;
}
self.score = score;
self.current_target.0 = Some(candidate.entity);
}
}
fn get_cell_coordinates(position: Vec3) -> (i32, i32) {
((position.x / HASH_CELL_SIZE).floor() as i32, (position.y / HASH_CELL_SIZE).floor() as i32)
}
pub fn find_targets(
target_query: Query<(
Entity,
&GlobalTransform,
&Team,
&AgentCategory,
&Health,
&MaxHealth
)>,
mut targetter_query: Query<(
&AggroLocation,
&AggroRadius,
&Team,
&TargetingOrders,
&mut Target,
)>
) {
let mut sorted_targets = MultiMap::new();
for (entity, transform, team, category, health, max_health) in target_query.iter() {
let health_fraction = health.0 / max_health.0;
let position = transform.translation;
sorted_targets.insert(
get_cell_coordinates(position),
TargetInformation {
entity,
category: *category,
health_fraction,
position,
team: *team
}
);
}
for (aggro_loc, aggro_radius, team, orders, mut target) in targetter_query.iter_mut() {
if target.0.is_some()
{
continue;
}
let mut targetter = Targetter {
team: *team,
position: aggro_loc.0,
orders: *orders,
radius: aggro_radius.0,
score: std::f32::INFINITY,
current_target: Target::default()
};
let min_coords = get_cell_coordinates(targetter.position - Vec3::splat(targetter.radius));
let max_coords = get_cell_coordinates(targetter.position + Vec3::splat(targetter.radius));
for x in min_coords.0..=max_coords.0 {
for y in min_coords.1..=max_coords.1 {
let current_bucket = (x,y);
match sorted_targets.get_vec(¤t_bucket) {
None => {continue;}
Some(candidates) => {
for candidate in candidates {
targetter.consider(candidate);
}
}
}
}
}
target.0 = targetter.current_target.0;
}
}