fix the LogicGameObject field mapping

the base object writes owner index, component mask, position and z, in
that order. we had the first field unnamed and were writing the owner
into the z slot, so every object belonged to player 0, and the mask slot
carried a zero.

the mask matters: getHitpointComponent tests bit 2 of it before touching
components[2], so a zero mask made every tower report no hitpoints,
LogicCharacter::isAlive fell through to the z field, shouldDestruct went
true and the towers were destroyed on the first tick - which then hit
"cant find summoner tower" in LogicBattle::removeGameObjectReferences.

entries are built through LogicGameObjectEntry::new now so the mask is
derived from the component array and cannot drift from it.

also rename the first character flag after what sets it: kamikaze
death and morph both raise it right before the object is removed.
This commit is contained in:
WiseDev 2026-08-23 11:17:10 +03:00
parent bc04651a73
commit 6c5295bc07
3 changed files with 36 additions and 10 deletions

View file

@ -110,11 +110,11 @@ impl BattleBuilder {
} else { } else {
LogicObjectBody::Character(Box::new(character)) LogicObjectBody::Character(Box::new(character))
}; };
LogicGameObjectEntry { LogicGameObjectEntry::new(
data, data,
global_id: LogicGameObjectRef::of(CHARACTER_OBJECT_TYPE + 1, instance), LogicGameObjectRef::of(CHARACTER_OBJECT_TYPE + 1, instance),
body, body,
components: [ [
Some(LogicComponent::Combat(LogicCombatComponent::default())), Some(LogicComponent::Combat(LogicCombatComponent::default())),
None, None,
Some(LogicComponent::Hitpoint(LogicHitpointComponent::healthy( Some(LogicComponent::Hitpoint(LogicHitpointComponent::healthy(
@ -124,7 +124,7 @@ impl BattleBuilder {
self.buff_type_count, self.buff_type_count,
))), ))),
], ],
} )
} }
pub fn build( pub fn build(
&self, &self,

View file

@ -6,7 +6,7 @@ pub const DEFAULT_SIZE: i32 = 100;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct LogicCharacter { pub struct LogicCharacter {
pub level_index: i32, pub level_index: i32,
pub flag_160: bool, pub is_destroyed: bool,
pub is_leader: bool, pub is_leader: bool,
pub base: LogicGameObject, pub base: LogicGameObject,
pub direction: LogicVector2, pub direction: LogicVector2,
@ -29,7 +29,7 @@ impl Default for LogicCharacter {
fn default() -> Self { fn default() -> Self {
Self { Self {
level_index: 0, level_index: 0,
flag_160: false, is_destroyed: false,
is_leader: false, is_leader: false,
base: LogicGameObject::default(), base: LogicGameObject::default(),
direction: LogicVector2::new(0, DIRECTION_BOTTOM), direction: LogicVector2::new(0, DIRECTION_BOTTOM),
@ -53,7 +53,7 @@ impl Default for LogicCharacter {
impl LogicCharacter { impl LogicCharacter {
pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> { pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
writer.write_vint(self.level_index); writer.write_vint(self.level_index);
writer.write_boolean(self.flag_160); writer.write_boolean(self.is_destroyed);
writer.write_boolean(self.is_leader); writer.write_boolean(self.is_leader);
self.base.encode_base(writer)?; self.base.encode_base(writer)?;
writer.write_vint(self.direction.x); writer.write_vint(self.direction.x);

View file

@ -19,12 +19,12 @@ impl LogicVector2 {
#[derive(Debug, Default, Clone, PartialEq, Eq, Payload)] #[derive(Debug, Default, Clone, PartialEq, Eq, Payload)]
pub struct LogicGameObject { pub struct LogicGameObject {
#[codec(vint)] #[codec(vint)]
pub field_28: i32, pub owner_index: i32,
#[codec(vint)] #[codec(vint)]
pub field_56: i32, pub component_mask: i32,
pub position: LogicVector2, pub position: LogicVector2,
#[codec(vint)] #[codec(vint)]
pub owner_index: i32, pub z: i32,
} }
impl LogicGameObject { impl LogicGameObject {
pub fn encode_base(&self, writer: &mut titan::ByteStreamWriter) -> titan::Result<()> { pub fn encode_base(&self, writer: &mut titan::ByteStreamWriter) -> titan::Result<()> {
@ -37,6 +37,12 @@ pub enum LogicObjectBody {
Summoner(Box<crate::battle::logic_character::LogicSummoner>), Summoner(Box<crate::battle::logic_character::LogicSummoner>),
} }
impl LogicObjectBody { impl LogicObjectBody {
pub fn base_mut(&mut self) -> &mut LogicGameObject {
match self {
LogicObjectBody::Character(character) => &mut character.base,
LogicObjectBody::Summoner(summoner) => &mut summoner.character.base,
}
}
pub fn encode(&self, writer: &mut titan::ByteStreamWriter) -> titan::Result<()> { pub fn encode(&self, writer: &mut titan::ByteStreamWriter) -> titan::Result<()> {
match self { match self {
LogicObjectBody::Character(character) => character.encode(writer), LogicObjectBody::Character(character) => character.encode(writer),
@ -52,6 +58,26 @@ pub struct LogicGameObjectEntry {
pub components: [Option<crate::battle::logic_component::LogicComponent>; COMPONENT_PASSES], pub components: [Option<crate::battle::logic_component::LogicComponent>; COMPONENT_PASSES],
} }
impl LogicGameObjectEntry { impl LogicGameObjectEntry {
pub fn new(
data: LogicDataRef,
global_id: LogicGameObjectRef,
mut body: LogicObjectBody,
components: [Option<crate::battle::logic_component::LogicComponent>; COMPONENT_PASSES],
) -> Self {
let mut component_mask = 0;
for (index, component) in components.iter().enumerate() {
if component.is_some() {
component_mask |= 1 << index;
}
}
body.base_mut().component_mask = component_mask;
Self {
data,
global_id,
body,
components,
}
}
pub fn object_type(&self) -> i32 { pub fn object_type(&self) -> i32 {
self.global_id.0.map(|id| id.class_id - 1).unwrap_or(-1) self.global_id.0.map(|id| id.class_id - 1).unwrap_or(-1)
} }