scroll.server/crates/logic/src/battle/logic_game_object_manager.rs
WiseDev 07f0ee5427 converge combat timers, projectile references, pending damage, and the spawn ring
- replay the shooter's combat update a second time when it launches a
  projectile, matching the client's own component-pass re-entry
- carry the shooter's damage effect on every projectile and null a
  shot's target/source when either leaves the board
- derive pending physical damage from the shots actually in flight
  instead of an incremental total, so it can never go negative
- split the spawn ring's two angle registers so three- and five-unit
  cards land where the client puts them
- attribute a combat target left at NONE to the exact site that did it,
  gated to report each object once
- number and annotate every checksum field so a client-reported
  mismatch resolves straight to a name
2026-08-28 16:27:07 +03:00

89 lines
3.3 KiB
Rust

use crate::battle::logic_game_object::{LogicGameObjectEntry, COMPONENT_PASSES, OBJECT_TYPE_COUNT};
use titan::{ByteStreamReader, ByteStreamWriter, Payload, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LogicGameObjectManager {
pub instance_counters: [i32; OBJECT_TYPE_COUNT],
pub objects: Vec<LogicGameObjectEntry>,
}
impl Default for LogicGameObjectManager {
fn default() -> Self {
Self {
instance_counters: [0; OBJECT_TYPE_COUNT],
objects: Vec::new(),
}
}
}
impl LogicGameObjectManager {
pub fn reserve_instance(&mut self, object_type: i32) -> i32 {
let Ok(index) = usize::try_from(object_type) else {
return 0;
};
let Some(counter) = self.instance_counters.get_mut(index) else {
return 0;
};
let instance = *counter;
*counter = instance + 1;
instance
}
fn note(&self, index: usize, entry: &LogicGameObjectEntry, section: &str) {
if !titan::checksum::checksum_trace_active() {
return;
}
let id = match entry.global_id.0 {
Some(id) => format!("{}:{}", id.class_id, id.instance_id),
None => "-".to_string(),
};
titan::checksum::checksum_trace_note(format!(
"objects[{index}].{section} {id} {}",
entry.data.name()
));
}
pub fn push(&mut self, entry: LogicGameObjectEntry) {
let object_type = entry.object_type();
if let Ok(index) = usize::try_from(object_type) {
if let Some(counter) = self.instance_counters.get_mut(index) {
let instance = entry.global_id.0.map(|id| id.instance_id).unwrap_or(0);
*counter = (*counter).max(instance + 1);
}
}
self.objects.push(entry);
self.objects
.sort_by_key(|entry| entry.global_id.0.map(|id| (id.class_id, id.instance_id)));
}
}
impl Payload for LogicGameObjectManager {
fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
titan::checksum::checksum_trace_note("objects.instance_counters");
for counter in self.instance_counters {
writer.write_vint(counter);
}
titan::checksum::checksum_trace_note("objects.count");
writer.write_vint(self.objects.len() as i32);
for (index, entry) in self.objects.iter().enumerate() {
self.note(index, entry, "data");
entry.data.encode(writer)?;
}
for (index, entry) in self.objects.iter().enumerate() {
self.note(index, entry, "global_id");
entry.global_id.encode(writer)?;
}
for (index, entry) in self.objects.iter().enumerate() {
self.note(index, entry, "body");
entry.body.encode(writer)?;
}
for pass in 0..COMPONENT_PASSES {
for (index, entry) in self.objects.iter().enumerate() {
if let Some(component) = &entry.components[pass] {
self.note(index, entry, component.kind());
component.encode(writer)?;
}
}
}
Ok(())
}
fn decode(_reader: &mut ByteStreamReader<'_>) -> Result<Self> {
Err(titan::Error::Unsupported(
"LogicGameObjectManager is encode only",
))
}
}