scroll.server/crates/logic/src/battle/logic_battle.rs
WiseDev 41eca5b620 battle model: LogicBattle, the object manager and BattleResultMessage
field order taken straight off LogicBattle::encode. the object manager is
columnar, not object major: 6 counters, a count, then all data refs, then all
global ids, then all objects, then four component passes. global ids must be
ascending, the client binary searches them.

LogicRandom moved out of the shop into its own module. its abs was
checked_abs().unwrap_or(0), the binary uses NEGS so i32::MIN stays i32::MIN.
one seed in four billion, but it would have desynced the card shop.

per object subclass encodes and the component payloads are still missing, so no
snapshot can be sent yet.
2026-08-23 10:00:16 +03:00

201 lines
7 KiB
Rust

use titan::{ByteStreamReader, ByteStreamWriter, LogicLong, Payload, Result};
use crate::battle::logic_game_object_manager::LogicGameObjectManager;
use crate::battle::logic_game_object_ref::LogicGameObjectRef;
use crate::data::LogicDataRef;
use crate::model::LogicSpellDeck;
pub const BATTLE_TYPE_PVP: i32 = 0;
pub const BATTLE_TYPE_NPC: i32 = 1;
pub const BATTLE_TYPE_REPLAY: i32 = 3;
pub const BATTLE_INT_ARRAY: usize = 8;
pub const BATTLE_TRAILING_INTS: usize = 6;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LogicBattle {
pub location: LogicDataRef,
pub npc: LogicDataRef,
pub arena: LogicDataRef,
pub account_ids: [LogicLong; 2],
pub unknown_20: i32,
pub unknown_24: i32,
pub counters: [i32; BATTLE_INT_ARRAY],
pub battle_type: i32,
pub end_counter: i32,
pub winner_index: i32,
pub battle_ended_called: bool,
pub battle_ended_with_timeout: bool,
pub unknown_182: bool,
pub player_finished_npc_level: bool,
pub show_start_hud: bool,
pub is_on_overtime: bool,
pub objects: LogicGameObjectManager,
pub decks: [Option<LogicSpellDeck>; 2],
pub leaders: [LogicGameObjectRef; 2],
pub leader_towers: [Vec<LogicGameObjectRef>; 2],
pub winner_score_change: i32,
pub loser_score_change: i32,
pub trailing: [i32; BATTLE_TRAILING_INTS],
}
impl Default for LogicBattle {
fn default() -> Self {
Self {
location: LogicDataRef::None,
npc: LogicDataRef::None,
arena: LogicDataRef::None,
account_ids: [LogicLong::default(); 2],
unknown_20: 0,
unknown_24: 0,
counters: [0; BATTLE_INT_ARRAY],
battle_type: BATTLE_TYPE_NPC,
end_counter: 0,
winner_index: 0,
battle_ended_called: false,
battle_ended_with_timeout: false,
unknown_182: false,
player_finished_npc_level: false,
show_start_hud: true,
is_on_overtime: false,
objects: LogicGameObjectManager::default(),
decks: [None, None],
leaders: [LogicGameObjectRef::NONE; 2],
leader_towers: [Vec::new(), Vec::new()],
winner_score_change: 0,
loser_score_change: 0,
trailing: [0; BATTLE_TRAILING_INTS],
}
}
}
impl LogicBattle {
pub fn is_npc_battle(&self) -> bool {
self.battle_type == BATTLE_TYPE_NPC && !self.npc.is_none()
}
}
impl Payload for LogicBattle {
fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
self.location.encode(writer)?;
self.npc.encode(writer)?;
self.arena.encode(writer)?;
for account in &self.account_ids {
writer.write_vlong(account.high, account.low);
}
writer.write_vint(self.unknown_20);
writer.write_vint(self.unknown_24);
for value in self.counters {
writer.write_vint(value);
}
writer.write_vint(self.battle_type);
writer.write_vint(self.end_counter);
writer.write_vint(self.winner_index);
writer.write_boolean(self.battle_ended_called);
writer.write_boolean(self.battle_ended_with_timeout);
writer.write_boolean(self.unknown_182);
writer.write_boolean(self.player_finished_npc_level);
writer.write_boolean(self.show_start_hud);
writer.write_boolean(self.is_on_overtime);
self.objects.encode(writer)?;
for deck in &self.decks {
match deck {
None => writer.write_boolean(false),
Some(deck) => {
writer.write_boolean(true);
deck.encode(writer)?;
}
}
}
for leader in &self.leaders {
leader.encode(writer)?;
}
for towers in &self.leader_towers {
writer.write_vint(towers.len() as i32);
for tower in towers {
tower.encode(writer)?;
}
}
if self.battle_ended_called {
writer.write_vint(self.winner_score_change);
writer.write_vint(self.loser_score_change);
}
for value in self.trailing {
writer.write_vint(value);
}
Ok(())
}
fn decode(reader: &mut ByteStreamReader<'_>) -> Result<Self> {
let location = LogicDataRef::decode(reader)?;
let npc = LogicDataRef::decode(reader)?;
let arena = LogicDataRef::decode(reader)?;
let account_ids = {
let first = reader.read_vlong()?;
let second = reader.read_vlong()?;
[
LogicLong::new(first.0, first.1),
LogicLong::new(second.0, second.1),
]
};
let unknown_20 = reader.read_vint()?;
let unknown_24 = reader.read_vint()?;
let mut counters = [0; BATTLE_INT_ARRAY];
for value in counters.iter_mut() {
*value = reader.read_vint()?;
}
let battle_type = reader.read_vint()?;
let end_counter = reader.read_vint()?;
let winner_index = reader.read_vint()?;
let battle_ended_called = reader.read_boolean()?;
let battle_ended_with_timeout = reader.read_boolean()?;
let unknown_182 = reader.read_boolean()?;
let player_finished_npc_level = reader.read_boolean()?;
let show_start_hud = reader.read_boolean()?;
let is_on_overtime = reader.read_boolean()?;
let objects = LogicGameObjectManager::decode(reader)?;
let mut decks = [None, None];
for deck in decks.iter_mut() {
if reader.read_boolean()? {
*deck = Some(LogicSpellDeck::decode(reader)?);
}
}
let leaders = [
LogicGameObjectRef::decode(reader)?,
LogicGameObjectRef::decode(reader)?,
];
let mut leader_towers = [Vec::new(), Vec::new()];
for towers in leader_towers.iter_mut() {
let count = reader.read_vint()?.max(0) as usize;
for _ in 0..count {
towers.push(LogicGameObjectRef::decode(reader)?);
}
}
let (winner_score_change, loser_score_change) = if battle_ended_called {
(reader.read_vint()?, reader.read_vint()?)
} else {
(0, 0)
};
let mut trailing = [0; BATTLE_TRAILING_INTS];
for value in trailing.iter_mut() {
*value = reader.read_vint()?;
}
Ok(Self {
location,
npc,
arena,
account_ids,
unknown_20,
unknown_24,
counters,
battle_type,
end_counter,
winner_index,
battle_ended_called,
battle_ended_with_timeout,
unknown_182,
player_finished_npc_level,
show_start_hud,
is_on_overtime,
objects,
decks,
leaders,
leader_towers,
winner_score_change,
loser_score_change,
trailing,
})
}
}