- 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
89 lines
3.3 KiB
Rust
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",
|
|
))
|
|
}
|
|
}
|