veloren/server/agent/src/data.rs

385 lines
11 KiB
Rust
Raw Normal View History

2022-01-18 03:02:43 +00:00
use common::{
comp::{
2023-01-21 19:02:27 +00:00
ability::CharacterAbility,
2022-10-23 17:10:37 +00:00
buff::{BuffKind, Buffs},
group,
item::MaterialStatManifest,
ActiveAbilities, Alignment, Body, CharacterState, Combo, Energy, Health, Inventory,
LightEmitter, LootOwner, Ori, PhysicsState, Poise, Pos, Scale, SkillSet, Stance, Stats,
Vel,
2022-01-18 03:02:43 +00:00
},
link::Is,
mounting::Mount,
path::TraversalConfig,
resources::{DeltaTime, Time, TimeOfDay},
rtsim::RtSimEntity,
terrain::TerrainGrid,
uid::{Uid, UidAllocator},
};
use specs::{
shred::ResourceId, Entities, Entity as EcsEntity, Read, ReadExpect, ReadStorage, SystemData,
World,
};
2022-09-17 17:10:01 +00:00
// TODO: Move rtsim back into AgentData after rtsim2 when it has a separate
// crate
2022-01-18 03:02:43 +00:00
pub struct AgentData<'a> {
pub entity: &'a EcsEntity,
pub uid: &'a Uid,
pub pos: &'a Pos,
pub vel: &'a Vel,
pub ori: &'a Ori,
pub energy: &'a Energy,
pub body: Option<&'a Body>,
pub inventory: &'a Inventory,
pub skill_set: &'a SkillSet,
#[allow(dead_code)] // may be useful for pathing
pub physics_state: &'a PhysicsState,
pub alignment: Option<&'a Alignment>,
pub traversal_config: TraversalConfig,
pub scale: f32,
pub damage: f32,
pub light_emitter: Option<&'a LightEmitter>,
pub glider_equipped: bool,
pub is_gliding: bool,
pub health: Option<&'a Health>,
pub char_state: &'a CharacterState,
pub active_abilities: &'a ActiveAbilities,
2022-09-18 02:11:41 +00:00
pub combo: Option<&'a Combo>,
2022-10-08 18:46:32 +00:00
pub buffs: Option<&'a Buffs>,
pub stats: Option<&'a Stats>,
2022-10-12 19:53:26 +00:00
pub poise: Option<&'a Poise>,
pub stance: Option<&'a Stance>,
2022-01-18 03:02:43 +00:00
pub cached_spatial_grid: &'a common::CachedSpatialGrid,
2022-05-28 23:41:31 +00:00
pub msm: &'a MaterialStatManifest,
2022-01-18 03:02:43 +00:00
}
pub struct TargetData<'a> {
pub pos: &'a Pos,
pub body: Option<&'a Body>,
pub scale: Option<&'a Scale>,
2022-09-18 02:11:41 +00:00
pub char_state: Option<&'a CharacterState>,
2022-10-06 01:25:24 +00:00
pub health: Option<&'a Health>,
2022-10-09 00:45:37 +00:00
pub buffs: Option<&'a Buffs>,
2022-01-18 03:02:43 +00:00
}
impl<'a> TargetData<'a> {
2022-10-06 01:25:24 +00:00
pub fn new(pos: &'a Pos, target: EcsEntity, read_data: &'a ReadData) -> Self {
2022-09-18 02:11:41 +00:00
Self {
pos,
2022-10-06 01:25:24 +00:00
body: read_data.bodies.get(target),
scale: read_data.scales.get(target),
char_state: read_data.char_states.get(target),
health: read_data.healths.get(target),
2022-10-09 00:45:37 +00:00
buffs: read_data.buffs.get(target),
2022-09-18 02:11:41 +00:00
}
2022-01-18 03:02:43 +00:00
}
}
pub struct AttackData {
pub min_attack_dist: f32,
pub dist_sqrd: f32,
pub angle: f32,
2022-01-21 23:28:15 +00:00
pub angle_xy: f32,
2022-01-18 03:02:43 +00:00
}
impl AttackData {
pub fn in_min_range(&self) -> bool { self.dist_sqrd < self.min_attack_dist.powi(2) }
}
#[derive(Eq, PartialEq)]
2022-01-21 23:28:15 +00:00
// When adding a new variant, first decide if it should instead fall under one
2022-07-15 16:59:37 +00:00
// of the pre-existing tactics
2022-01-18 03:02:43 +00:00
pub enum Tactic {
2022-01-21 23:28:15 +00:00
// General tactics
SimpleMelee,
2022-10-23 01:18:23 +00:00
SimpleFlyingMelee,
2022-01-21 23:28:15 +00:00
SimpleBackstab,
ElevatedRanged,
Turret,
FixedTurret,
RotatingTurret,
RadialTurret,
// Tool specific tactics
2022-01-18 03:02:43 +00:00
Axe,
Hammer,
Sword,
Bow,
Staff,
Sceptre,
2022-01-21 23:28:15 +00:00
// Broad creature tactics
2022-01-18 03:02:43 +00:00
CircleCharge { radius: u32, circle_time: u32 },
QuadLowRanged,
TailSlap,
QuadLowQuick,
QuadLowBasic,
QuadLowBeam,
QuadMedJump,
QuadMedBasic,
QuadMedHoof,
2022-01-18 03:02:43 +00:00
Theropod,
BirdLargeBreathe,
BirdLargeFire,
BirdLargeBasic,
2023-01-28 21:25:17 +00:00
Wyvern,
2023-02-14 23:17:20 +00:00
BirdMediumBasic,
2022-04-23 14:54:01 +00:00
ArthropodMelee,
ArthropodRanged,
2022-04-23 14:54:01 +00:00
ArthropodAmbush,
2022-01-21 23:28:15 +00:00
// Specific species tactics
Mindflayer,
2022-01-18 03:02:43 +00:00
Minotaur,
ClayGolem,
TidalWarrior,
Yeti,
Harvester,
2022-01-21 23:28:15 +00:00
StoneGolem,
2022-01-30 23:04:09 +00:00
Deadwood,
2022-02-02 05:17:06 +00:00
Mandragora,
2022-02-03 01:45:43 +00:00
WoodGolem,
2022-02-09 01:23:23 +00:00
GnarlingChieftain,
2022-08-24 21:50:53 +00:00
OrganAura,
Dagon,
Cardinal,
2022-11-10 12:53:12 +00:00
Roshwalr,
2023-02-15 00:10:37 +00:00
FrostGigas,
BorealHammer,
2022-01-18 03:02:43 +00:00
}
#[derive(SystemData)]
pub struct ReadData<'a> {
pub entities: Entities<'a>,
pub uid_allocator: Read<'a, UidAllocator>,
pub dt: Read<'a, DeltaTime>,
pub time: Read<'a, Time>,
pub cached_spatial_grid: Read<'a, common::CachedSpatialGrid>,
pub group_manager: Read<'a, group::GroupManager>,
pub energies: ReadStorage<'a, Energy>,
pub positions: ReadStorage<'a, Pos>,
pub velocities: ReadStorage<'a, Vel>,
pub orientations: ReadStorage<'a, Ori>,
pub scales: ReadStorage<'a, Scale>,
pub healths: ReadStorage<'a, Health>,
pub inventories: ReadStorage<'a, Inventory>,
pub stats: ReadStorage<'a, Stats>,
pub skill_set: ReadStorage<'a, SkillSet>,
pub physics_states: ReadStorage<'a, PhysicsState>,
pub char_states: ReadStorage<'a, CharacterState>,
pub uids: ReadStorage<'a, Uid>,
pub groups: ReadStorage<'a, group::Group>,
pub terrain: ReadExpect<'a, TerrainGrid>,
pub alignments: ReadStorage<'a, Alignment>,
pub bodies: ReadStorage<'a, Body>,
pub is_mounts: ReadStorage<'a, Is<Mount>>,
pub time_of_day: Read<'a, TimeOfDay>,
pub light_emitter: ReadStorage<'a, LightEmitter>,
#[cfg(feature = "worldgen")]
pub world: ReadExpect<'a, Arc<world::World>>,
pub rtsim_entities: ReadStorage<'a, RtSimEntity>,
pub buffs: ReadStorage<'a, Buffs>,
pub combos: ReadStorage<'a, Combo>,
pub active_abilities: ReadStorage<'a, ActiveAbilities>,
pub loot_owners: ReadStorage<'a, LootOwner>,
2022-05-28 23:41:31 +00:00
pub msm: ReadExpect<'a, MaterialStatManifest>,
2022-10-12 19:53:26 +00:00
pub poises: ReadStorage<'a, Poise>,
pub stances: ReadStorage<'a, Stance>,
2022-01-18 03:02:43 +00:00
}
pub enum Path {
Full,
Separate,
Partial,
}
2022-10-23 17:10:37 +00:00
2023-01-21 19:02:27 +00:00
pub enum AbilityData {
ComboMelee {
range: f32,
angle: f32,
energy_per_strike: f32,
},
FinisherMelee {
range: f32,
angle: f32,
energy: f32,
combo: u32,
},
SelfBuff {
buff: BuffKind,
energy: f32,
},
DiveMelee {
range: f32,
angle: f32,
energy: f32,
},
DashMelee {
range: f32,
angle: f32,
initial_energy: f32,
energy_drain: f32,
speed: f32,
charge_dur: f32,
},
RapidMelee {
range: f32,
angle: f32,
energy_per_strike: f32,
strikes: u32,
combo: u32,
},
2022-10-23 17:10:37 +00:00
}
2023-01-21 19:02:27 +00:00
impl AbilityData {
pub fn from_ability(ability: &CharacterAbility) -> Option<Self> {
use CharacterAbility::*;
let inner = match ability {
ComboMelee2 {
strikes,
energy_cost_per_strike,
..
} => {
let (range, angle) = strikes
.iter()
.map(|s| (s.melee_constructor.range, s.melee_constructor.angle))
.fold(
(100.0, 360.0),
|(r1, a1): (f32, f32), (r2, a2): (f32, f32)| (r1.min(r2), a1.min(a2)),
);
Self::ComboMelee {
range,
angle,
energy_per_strike: *energy_cost_per_strike,
}
},
FinisherMelee {
energy_cost,
melee_constructor,
minimum_combo,
..
} => Self::FinisherMelee {
energy: *energy_cost,
range: melee_constructor.range,
angle: melee_constructor.angle,
combo: *minimum_combo,
},
SelfBuff {
buff_kind,
energy_cost,
..
} => Self::SelfBuff {
buff: *buff_kind,
energy: *energy_cost,
},
DiveMelee {
energy_cost,
melee_constructor,
..
} => Self::DiveMelee {
energy: *energy_cost,
range: melee_constructor.range,
angle: melee_constructor.angle,
},
DashMelee {
energy_cost,
energy_drain,
forward_speed,
melee_constructor,
charge_duration,
..
} => Self::DashMelee {
initial_energy: *energy_cost,
energy_drain: *energy_drain,
range: melee_constructor.range,
angle: melee_constructor.angle,
charge_dur: *charge_duration,
speed: *forward_speed,
},
RapidMelee {
energy_cost,
max_strikes,
minimum_combo,
melee_constructor,
..
} => Self::RapidMelee {
energy_per_strike: *energy_cost,
range: melee_constructor.range,
angle: melee_constructor.angle,
strikes: max_strikes.unwrap_or(100),
combo: *minimum_combo,
},
_ => return None,
};
Some(inner)
2022-10-23 17:10:37 +00:00
}
pub fn could_use(&self, attack_data: &AttackData, agent_data: &AgentData) -> bool {
2023-01-21 19:02:27 +00:00
let melee_check = |range: f32, angle| {
attack_data.dist_sqrd
< (range + agent_data.body.map_or(0.0, |b| b.max_radius())).powi(2)
&& attack_data.angle < angle
};
let energy_check = |energy| agent_data.energy.current() >= energy;
let combo_check = |combo| agent_data.combo.map_or(false, |c| c.counter() >= combo);
use AbilityData::*;
match self {
ComboMelee {
range,
angle,
energy_per_strike,
} => melee_check(*range, *angle) && energy_check(*energy_per_strike),
FinisherMelee {
range,
angle,
energy,
combo,
} => melee_check(*range, *angle) && energy_check(*energy) && combo_check(*combo),
SelfBuff { buff, energy } => {
energy_check(*energy)
&& agent_data
.buffs
.map_or(false, |buffs| !buffs.contains(*buff))
},
DiveMelee {
range,
angle,
energy,
} => melee_check(*range, *angle) && energy_check(*energy),
DashMelee {
range,
angle,
initial_energy,
energy_drain,
speed,
charge_dur,
} => {
// TODO: Maybe figure out better way of pulling in base accel from body and
// accounting for friction?
const BASE_SPEED: f32 = 3.0;
const ORI_RATE: f32 = 30.0;
let charge_dur = ((agent_data.energy.current() - initial_energy) / energy_drain)
.clamp(0.0, *charge_dur);
let charge_dist = charge_dur * speed * BASE_SPEED;
let attack_dist = charge_dist + range;
let ori_gap = ORI_RATE * charge_dur;
melee_check(attack_dist, angle + ori_gap)
&& energy_check(*initial_energy)
&& attack_data.dist_sqrd / charge_dist.powi(2) > 0.75_f32.powi(2)
},
RapidMelee {
range,
angle,
energy_per_strike,
strikes,
combo,
} => {
melee_check(*range, *angle)
&& energy_check(*energy_per_strike * *strikes as f32)
&& combo_check(*combo)
},
}
2022-10-23 17:10:37 +00:00
}
}