the tick was running A* over the whole 36x64 grid for every unit, every tick. catching up ten seconds meant thousands of searches inside the session lock, so the tick loop never finished, snapshots never went out, and the client - which refuses to send a command while isFullUpdatePending is true - sat there showing the connection icon and would not spawn anything. ctrl-c looked like a hang for the same reason: a task stuck in that loop. the client does not do this either. LogicMovementComponent carries a path array precisely so the route is found once and walked. we keep the route and the goal it was found for, drop a node once we are within 250 units of it, and only search again when the goal moves or the route runs out. advance_to also refuses to simulate more than forty ticks in one call, so a late tick can never turn into an unbounded loop under the lock.
215 lines
7.1 KiB
Rust
215 lines
7.1 KiB
Rust
use titan::{ByteStreamWriter, Payload, Result};
|
|
use crate::battle::logic_game_object::LogicVector2;
|
|
use crate::battle::logic_game_object_ref::LogicGameObjectRef;
|
|
pub const COMPONENT_COMBAT: usize = 0;
|
|
pub const COMPONENT_MOVEMENT: usize = 1;
|
|
pub const COMPONENT_HITPOINT: usize = 2;
|
|
pub const COMPONENT_BUFF: usize = 3;
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub struct LogicCombatComponent {
|
|
pub flag_72: bool,
|
|
pub flag_73: bool,
|
|
pub field_48: i32,
|
|
pub field_52: i32,
|
|
pub field_60: i32,
|
|
pub field_64: i32,
|
|
pub field_68: i32,
|
|
pub target: LogicGameObjectRef,
|
|
pub attackers: Vec<(LogicGameObjectRef, i32)>,
|
|
pub observers: Vec<LogicGameObjectRef>,
|
|
pub target_index: Option<usize>,
|
|
pub hit_timer: i32,
|
|
}
|
|
impl LogicCombatComponent {
|
|
pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
|
|
writer.write_boolean(self.flag_72);
|
|
writer.write_boolean(self.flag_73);
|
|
writer.write_vint(self.hit_timer);
|
|
writer.write_vint(self.field_52);
|
|
writer.write_vint(self.field_60);
|
|
writer.write_vint(self.field_64);
|
|
writer.write_vint(self.field_68);
|
|
writer.write_vint(self.attackers.len() as i32);
|
|
writer.write_vint(self.observers.len() as i32);
|
|
self.target.encode(writer)?;
|
|
for (reference, value) in &self.attackers {
|
|
reference.encode(writer)?;
|
|
writer.write_vint(*value);
|
|
}
|
|
for reference in &self.observers {
|
|
reference.encode(writer)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
pub const CHARGE_TIME_DISABLED: i32 = -1;
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct LogicMovementComponent {
|
|
pub is_pushed_back: bool,
|
|
pub is_stopped: bool,
|
|
pub is_panicking: bool,
|
|
pub path_node_reached: bool,
|
|
pub path: Vec<i32>,
|
|
pub collision_push_count: i32,
|
|
pub collision_push_sum_x: i32,
|
|
pub collision_push_sum_y: i32,
|
|
pub panic_timer: i32,
|
|
pub panic_target_x: i32,
|
|
pub panic_target_y: i32,
|
|
pub panic_origin_x: i32,
|
|
pub panic_origin_y: i32,
|
|
pub charge_time: i32,
|
|
pub pushback_target: LogicVector2,
|
|
pub avoidance_side_step: i32,
|
|
pub path_target_normal: LogicVector2,
|
|
pub pushback_speed: i32,
|
|
pub move_timer: i32,
|
|
pub jump_distance: i32,
|
|
pub goal: Option<(i32, i32)>,
|
|
pub route: Vec<(i32, i32)>,
|
|
}
|
|
impl Default for LogicMovementComponent {
|
|
fn default() -> Self {
|
|
Self {
|
|
is_pushed_back: false,
|
|
is_stopped: false,
|
|
is_panicking: false,
|
|
path_node_reached: false,
|
|
path: Vec::new(),
|
|
collision_push_count: 0,
|
|
collision_push_sum_x: 0,
|
|
collision_push_sum_y: 0,
|
|
panic_timer: 0,
|
|
panic_target_x: 0,
|
|
panic_target_y: 0,
|
|
panic_origin_x: 0,
|
|
panic_origin_y: 0,
|
|
charge_time: CHARGE_TIME_DISABLED,
|
|
pushback_target: LogicVector2::default(),
|
|
avoidance_side_step: 0,
|
|
path_target_normal: LogicVector2::default(),
|
|
pushback_speed: 0,
|
|
move_timer: 0,
|
|
jump_distance: 0,
|
|
goal: None,
|
|
route: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
impl LogicMovementComponent {
|
|
pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
|
|
writer.write_boolean(self.is_pushed_back);
|
|
writer.write_boolean(self.is_stopped);
|
|
writer.write_boolean(self.is_panicking);
|
|
writer.write_boolean(self.path_node_reached);
|
|
writer.write_vint(self.path.len() as i32);
|
|
for node in &self.path {
|
|
writer.write_vint(*node);
|
|
}
|
|
writer.write_vint(self.collision_push_count);
|
|
writer.write_vint(self.collision_push_sum_x);
|
|
writer.write_vint(self.collision_push_sum_y);
|
|
writer.write_vint(self.panic_timer);
|
|
writer.write_vint(self.panic_target_x);
|
|
writer.write_vint(self.panic_target_y);
|
|
writer.write_vint(self.panic_origin_x);
|
|
writer.write_vint(self.panic_origin_y);
|
|
writer.write_vint(self.charge_time);
|
|
writer.write_vint(self.pushback_target.x);
|
|
writer.write_vint(self.pushback_target.y);
|
|
writer.write_vint(self.avoidance_side_step);
|
|
writer.write_vint(self.path_target_normal.x);
|
|
writer.write_vint(self.path_target_normal.y);
|
|
writer.write_vint(self.pushback_speed);
|
|
writer.write_vint(self.move_timer);
|
|
writer.write_vint(self.jump_distance);
|
|
Ok(())
|
|
}
|
|
}
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub struct LogicHitpointComponent {
|
|
pub hitpoints: i32,
|
|
pub base_hitpoints: i32,
|
|
pub lifetime_damage: i32,
|
|
pub lifetime_accumulator: i32,
|
|
pub death_hit_angle: i32,
|
|
}
|
|
impl LogicHitpointComponent {
|
|
pub fn healthy(hitpoints: i32) -> Self {
|
|
Self {
|
|
hitpoints,
|
|
base_hitpoints: hitpoints,
|
|
..Self::default()
|
|
}
|
|
}
|
|
pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
|
|
writer.write_vint(self.hitpoints);
|
|
writer.write_vint(self.base_hitpoints);
|
|
writer.write_vint(self.lifetime_damage);
|
|
if self.lifetime_damage != 0 {
|
|
writer.write_vint(self.lifetime_accumulator);
|
|
}
|
|
if self.hitpoints <= 0 {
|
|
writer.write_vint(self.death_hit_angle);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
|
pub struct LogicCharacterBuffComponent {
|
|
pub field_32: i32,
|
|
pub field_36: i32,
|
|
pub field_40: i32,
|
|
pub buff_type_count: usize,
|
|
pub field_52: i32,
|
|
pub field_56: i32,
|
|
pub field_60: i32,
|
|
pub field_64: i32,
|
|
pub field_68: i32,
|
|
pub field_72: i32,
|
|
}
|
|
impl LogicCharacterBuffComponent {
|
|
pub fn empty(buff_type_count: usize) -> Self {
|
|
Self {
|
|
buff_type_count,
|
|
..Self::default()
|
|
}
|
|
}
|
|
pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
|
|
writer.write_vint(0);
|
|
writer.write_vint(0);
|
|
writer.write_vint(self.field_32);
|
|
writer.write_vint(self.field_36);
|
|
writer.write_vint(self.field_40);
|
|
for _ in 0..self.buff_type_count {
|
|
writer.write_vint(0);
|
|
}
|
|
for _ in 0..self.buff_type_count {
|
|
writer.write_boolean(false);
|
|
}
|
|
writer.write_vint(self.field_52);
|
|
writer.write_vint(self.field_56);
|
|
writer.write_vint(self.field_60);
|
|
writer.write_vint(self.field_64);
|
|
writer.write_vint(self.field_68);
|
|
writer.write_vint(self.field_72);
|
|
Ok(())
|
|
}
|
|
}
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum LogicComponent {
|
|
Combat(LogicCombatComponent),
|
|
Movement(LogicMovementComponent),
|
|
Hitpoint(LogicHitpointComponent),
|
|
Buff(LogicCharacterBuffComponent),
|
|
}
|
|
impl LogicComponent {
|
|
pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
|
|
match self {
|
|
LogicComponent::Combat(component) => component.encode(writer),
|
|
LogicComponent::Movement(component) => component.encode(writer),
|
|
LogicComponent::Hitpoint(component) => component.encode(writer),
|
|
LogicComponent::Buff(component) => component.encode(writer),
|
|
}
|
|
}
|
|
}
|