scroll.server/crates/logic/src/battle/logic_character.rs
WiseDev 3cba764841 deal the summoner a starting hand
the card bar was empty because the hand lives on the king tower, not on
the battle. LogicSummoner::decode gates a whole block behind one boolean
- setEncodeDeckDataEnabled on the client side - and we always wrote it
false, so the client kept the hand and the draw queue it was born with,
which is nothing.

the block holds four deck indices for the hand, then two int lists: the
draw queue getNextSpell walks and the used pile reshuffleDeck folds back
into it. we now hand out the first four slots and queue the rest, leave
the used pile empty and keep last used at -1, which is what
getLastUsedSpell reads as "none".

starting mana comes from the START_MANA global instead of zero, and the
field after last-used is named after clearSpellCooldowns, which is the
only thing that writes it.
2026-08-23 11:39:49 +03:00

159 lines
5.1 KiB
Rust

use titan::{ByteStreamWriter, Result};
use crate::battle::logic_game_object::{LogicGameObject, LogicVector2};
pub const DIRECTION_TOP: i32 = 256;
pub const DIRECTION_BOTTOM: i32 = -256;
pub const DEFAULT_SIZE: i32 = 100;
pub const SUMMONER_HAND_SIZE: usize = 4;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LogicCharacter {
pub level_index: i32,
pub is_destroyed: bool,
pub is_leader: bool,
pub base: LogicGameObject,
pub direction: LogicVector2,
pub state: i32,
pub size: i32,
pub growth_timer: i32,
pub spawn_timer: i32,
pub field_120: i32,
pub remaining_spawn_count: i32,
pub destruction_wave_timer: i32,
pub lane_id: i32,
pub deploy_timer: i32,
pub hide_timer: i32,
pub field_100: i32,
pub pending_physical_damage: i32,
pub mana_generate_limit: Option<i32>,
pub reload: Option<(i32, i32)>,
}
impl Default for LogicCharacter {
fn default() -> Self {
Self {
level_index: 0,
is_destroyed: false,
is_leader: false,
base: LogicGameObject::default(),
direction: LogicVector2::new(0, DIRECTION_BOTTOM),
state: 0,
size: DEFAULT_SIZE,
growth_timer: 0,
spawn_timer: 0,
field_120: 0,
remaining_spawn_count: 0,
destruction_wave_timer: 0,
lane_id: 0,
deploy_timer: 0,
hide_timer: 0,
field_100: 0,
pending_physical_damage: 0,
mana_generate_limit: None,
reload: None,
}
}
}
impl LogicCharacter {
pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
writer.write_vint(self.level_index);
writer.write_boolean(self.is_destroyed);
writer.write_boolean(self.is_leader);
self.base.encode_base(writer)?;
writer.write_vint(self.direction.x);
writer.write_vint(self.direction.y);
writer.write_vint(self.state);
writer.write_vint(self.size);
writer.write_vint(self.growth_timer);
writer.write_vint(self.spawn_timer);
writer.write_vint(self.field_120);
writer.write_vint(self.remaining_spawn_count);
writer.write_vint(self.destruction_wave_timer);
writer.write_vint(self.lane_id);
writer.write_vint(self.deploy_timer);
writer.write_vint(self.hide_timer);
writer.write_vint(self.field_100);
writer.write_vint(self.pending_physical_damage);
if let Some(limit) = self.mana_generate_limit {
writer.write_vint(limit);
}
if let Some((timer, hits)) = self.reload {
writer.write_vint(timer);
writer.write_vint(hits);
}
Ok(())
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct LogicSummoner {
pub character: LogicCharacter,
pub deck: Option<LogicSummonerDeck>,
pub mana_regen_timer: i32,
pub mana: i32,
pub field_244: i32,
pub field_256: i32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LogicSummonerDeck {
pub field_220: i32,
pub hand: [i32; SUMMONER_HAND_SIZE],
pub draw_pile: Vec<i32>,
pub used_pile: Vec<i32>,
pub last_used_index: i32,
pub spell_cooldown: i32,
pub field_240: i32,
}
impl Default for LogicSummonerDeck {
fn default() -> Self {
Self {
field_220: 0,
hand: [-1; SUMMONER_HAND_SIZE],
draw_pile: Vec::new(),
used_pile: Vec::new(),
last_used_index: -1,
spell_cooldown: 0,
field_240: 0,
}
}
}
impl LogicSummonerDeck {
pub fn starting(slot_count: usize) -> Self {
let mut hand = [-1; SUMMONER_HAND_SIZE];
for (slot, index) in hand.iter_mut().zip(0..slot_count as i32) {
*slot = index;
}
Self {
hand,
draw_pile: (SUMMONER_HAND_SIZE as i32..slot_count as i32).collect(),
..Self::default()
}
}
}
impl LogicSummoner {
pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
self.character.encode(writer)?;
match &self.deck {
None => writer.write_boolean(false),
Some(deck) => {
writer.write_boolean(true);
writer.write_vint(deck.field_220);
for slot in deck.hand {
writer.write_vint(slot);
}
writer.write_vint(deck.draw_pile.len() as i32);
for value in &deck.draw_pile {
writer.write_vint(*value);
}
writer.write_vint(deck.used_pile.len() as i32);
for value in &deck.used_pile {
writer.write_vint(*value);
}
writer.write_vint(deck.last_used_index);
writer.write_vint(deck.spell_cooldown);
writer.write_vint(deck.field_240);
}
}
writer.write_vint(self.mana_regen_timer);
writer.write_vint(self.mana);
writer.write_vint(self.field_244);
writer.write_vint(self.field_256);
Ok(())
}
}