diff --git a/crates/game-service/src/battle.rs b/crates/game-service/src/battle.rs index ed953fb..f8813cf 100644 --- a/crates/game-service/src/battle.rs +++ b/crates/game-service/src/battle.rs @@ -2,7 +2,8 @@ use std::path::{Path, PathBuf}; use logic::battle::{ LogicBattle, LogicCharacter, LogicCharacterBuffComponent, LogicCombatComponent, LogicComponent, LogicGameMode, LogicGameObject, LogicGameObjectEntry, LogicGameObjectManager, - LogicGameObjectRef, LogicHitpointComponent, LogicObjectBody, LogicSummoner, LogicSummonerDeck, + LogicGameObjectRef, LogicHitpointComponent, LogicMovementComponent, LogicObjectBody, + LogicSummoner, LogicSummonerDeck, LogicTilemap, LogicTime, LogicVector2, BATTLE_TYPE_NPC, CHARACTER_OBJECT_TYPE, COMPONENT_PASSES, DIRECTION_BOTTOM, @@ -92,10 +93,10 @@ impl BattleBuilder { .filter(|value| *value > 0) .unwrap_or(1) } - fn components_for(&self, hitpoints: i32) -> [Option; COMPONENT_PASSES] { + fn components_for(&self, hitpoints: i32, moves: bool) -> [Option; COMPONENT_PASSES] { [ Some(LogicComponent::Combat(LogicCombatComponent::default())), - None, + moves.then(|| LogicComponent::Movement(LogicMovementComponent::default())), Some(LogicComponent::Hitpoint(LogicHitpointComponent::healthy( hitpoints, ))), @@ -113,6 +114,10 @@ impl BattleBuilder { level_index, } = spec; let hitpoints = Self::hitpoints_of(&data, level_index); + let moves = data + .data() + .map(|row| row.int(CHARACTER_SPEED_COLUMN) > 0) + .unwrap_or(false); let character = LogicCharacter { level_index, base: LogicGameObject { @@ -130,7 +135,7 @@ impl BattleBuilder { ), ..LogicCharacter::default() }; - let components = self.components_for(hitpoints); + let components = self.components_for(hitpoints, moves); LogicGameObjectEntry::new( data, LogicGameObjectRef::of(CHARACTER_OBJECT_TYPE + 1, instance), @@ -215,7 +220,7 @@ impl BattleBuilder { data, LogicGameObjectRef::of(CHARACTER_OBJECT_TYPE + 1, instance), body, - self.components_for(hitpoints), + self.components_for(hitpoints, false), ) } pub fn build( diff --git a/crates/game-service/src/battle_session.rs b/crates/game-service/src/battle_session.rs index bf3ea0b..116bbb5 100644 --- a/crates/game-service/src/battle_session.rs +++ b/crates/game-service/src/battle_session.rs @@ -1,7 +1,9 @@ use std::collections::HashMap; +pub const SNAPSHOT_INTERVAL_TICKS: i32 = 20; use std::time::Instant; use logic::battle::{LogicGameMode, LogicGameObjectEntry, BATTLE_TICKS_PER_SECOND}; -use logic::battle::LogicBattleEvent; +use logic::battle::{verify_snapshot, LogicBattleEvent}; +use logic::SectorStateMessage; use logic::{BattleEventMessage, LogicDataRef, LogicRandom}; use titan::LogicLong; use service_rpc::{AccountRef, WireMessage}; @@ -15,6 +17,7 @@ pub struct BattleSession { random: LogicRandom, taunts: Vec, next_bot_emote: i32, + next_snapshot: i32, } impl BattleSession { pub fn new(mode: LogicGameMode, taunts: Vec) -> Self { @@ -29,8 +32,23 @@ impl BattleSession { random, taunts, next_bot_emote, + next_snapshot: SNAPSHOT_INTERVAL_TICKS, } } + fn snapshot_message(&mut self) -> Option { + let snapshot = self.mode.snapshot().ok()?; + let report = verify_snapshot(&snapshot); + if !report.is_ok() { + tracing::error!( + bytes = snapshot.len(), + trailing = report.trailing, + error = report.error.as_deref().unwrap_or("-"), + "the battle snapshot does not read back, not pushing it" + ); + return None; + } + crate::wire::encode(&SectorStateMessage::new(snapshot)).ok() + } fn roll_emote_tick(random: &mut LogicRandom, from: i32) -> i32 { let span = crate::bot::BOT_EMOTE_MAX_SECONDS - crate::bot::BOT_EMOTE_MIN_SECONDS; let seconds = crate::bot::BOT_EMOTE_MIN_SECONDS + random.next(span.max(1)); @@ -93,6 +111,12 @@ impl BattleSession { self.tick += 1; self.release_queued(self.tick); self.mode.battle.tick(); + if self.tick >= self.next_snapshot { + self.next_snapshot = self.tick + SNAPSHOT_INTERVAL_TICKS; + if let Some(message) = self.snapshot_message() { + self.outbound.push(message); + } + } if self.tick >= self.next_bot_emote { self.next_bot_emote = Self::roll_emote_tick(&mut self.random, self.tick); if let Some(event) = self.bot_emote() { diff --git a/crates/logic/src/battle/logic_component.rs b/crates/logic/src/battle/logic_component.rs index 892070b..960949d 100644 --- a/crates/logic/src/battle/logic_component.rs +++ b/crates/logic/src/battle/logic_component.rs @@ -1,4 +1,5 @@ use titan::{ByteStreamWriter, Payload, Result}; +use crate::battle::logic_game_object::LogicVector2; use crate::battle::logic_game_object_ref::LogicGameObjectRef; pub const COMPONENT_COMBAT: usize = 0; pub const COMPONENT_MOVEMENT: usize = 1; @@ -41,6 +42,86 @@ impl LogicCombatComponent { Ok(()) } } +pub const CHARGE_TIME_DISABLED: i32 = -1; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct LogicMovementComponent { + pub is_pushed_back: bool, + pub is_stopped: bool, + pub is_panicking: bool, + pub path_node_reached: bool, + pub path: Vec, + pub collision_push_count: i32, + pub collision_push_sum_x: i32, + pub collision_push_sum_y: i32, + pub panic_timer: i32, + pub panic_target_x: i32, + pub panic_target_y: i32, + pub panic_origin_x: i32, + pub panic_origin_y: i32, + pub charge_time: i32, + pub pushback_target: LogicVector2, + pub avoidance_side_step: i32, + pub path_target_normal: LogicVector2, + pub pushback_speed: i32, + pub move_timer: i32, + pub jump_distance: i32, +} +impl Default for LogicMovementComponent { + fn default() -> Self { + Self { + is_pushed_back: false, + is_stopped: false, + is_panicking: false, + path_node_reached: false, + path: Vec::new(), + collision_push_count: 0, + collision_push_sum_x: 0, + collision_push_sum_y: 0, + panic_timer: 0, + panic_target_x: 0, + panic_target_y: 0, + panic_origin_x: 0, + panic_origin_y: 0, + charge_time: CHARGE_TIME_DISABLED, + pushback_target: LogicVector2::default(), + avoidance_side_step: 0, + path_target_normal: LogicVector2::default(), + pushback_speed: 0, + move_timer: 0, + jump_distance: 0, + } + } +} +impl LogicMovementComponent { + pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> { + writer.write_boolean(self.is_pushed_back); + writer.write_boolean(self.is_stopped); + writer.write_boolean(self.is_panicking); + writer.write_boolean(self.path_node_reached); + writer.write_vint(self.path.len() as i32); + for node in &self.path { + writer.write_vint(*node); + } + writer.write_vint(self.collision_push_count); + writer.write_vint(self.collision_push_sum_x); + writer.write_vint(self.collision_push_sum_y); + writer.write_vint(self.panic_timer); + writer.write_vint(self.panic_target_x); + writer.write_vint(self.panic_target_y); + writer.write_vint(self.panic_origin_x); + writer.write_vint(self.panic_origin_y); + writer.write_vint(self.charge_time); + writer.write_vint(self.pushback_target.x); + writer.write_vint(self.pushback_target.y); + writer.write_vint(self.avoidance_side_step); + writer.write_vint(self.path_target_normal.x); + writer.write_vint(self.path_target_normal.y); + writer.write_vint(self.pushback_speed); + writer.write_vint(self.move_timer); + writer.write_vint(self.jump_distance); + Ok(()) + } +} #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct LogicHitpointComponent { pub hitpoints: i32, @@ -114,6 +195,7 @@ impl LogicCharacterBuffComponent { #[derive(Debug, Clone, PartialEq, Eq)] pub enum LogicComponent { Combat(LogicCombatComponent), + Movement(LogicMovementComponent), Hitpoint(LogicHitpointComponent), Buff(LogicCharacterBuffComponent), } @@ -121,6 +203,7 @@ impl LogicComponent { pub fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> { match self { LogicComponent::Combat(component) => component.encode(writer), + LogicComponent::Movement(component) => component.encode(writer), LogicComponent::Hitpoint(component) => component.encode(writer), LogicComponent::Buff(component) => component.encode(writer), } diff --git a/crates/logic/src/battle/mod.rs b/crates/logic/src/battle/mod.rs index f1d1912..062c34f 100644 --- a/crates/logic/src/battle/mod.rs +++ b/crates/logic/src/battle/mod.rs @@ -19,6 +19,7 @@ pub use logic_character::{ LogicCharacter, LogicSummoner, LogicSummonerDeck, DEFAULT_SIZE, DIRECTION_BOTTOM, DIRECTION_TOP, }; pub use logic_component::{ + LogicMovementComponent, LogicCharacterBuffComponent, LogicCombatComponent, LogicComponent, LogicHitpointComponent, COMPONENT_BUFF, COMPONENT_COMBAT, COMPONENT_HITPOINT, COMPONENT_MOVEMENT, }; diff --git a/crates/logic/src/battle/verify.rs b/crates/logic/src/battle/verify.rs index 21ac7a4..f9fb59f 100644 --- a/crates/logic/src/battle/verify.rs +++ b/crates/logic/src/battle/verify.rs @@ -3,6 +3,7 @@ use crate::battle::logic_game_mode::{SECTION_BATTLE, SECTION_TUTORIAL}; use crate::data::{table, LogicDataRef, LogicDataTables}; use crate::model::DECK_SLOT_COUNT; pub const BUFF_ARRAY_TABLE: i32 = table::DAMAGE_TYPES; +pub const MOVEMENT_SPEED_COLUMN: &str = "Speed"; #[derive(Debug, Clone, PartialEq, Eq)] pub struct SnapshotReport { pub steps: Vec<(String, usize)>, @@ -91,6 +92,15 @@ impl<'a, 'b> Verifier<'a, 'b> { self.vints(4)?; Ok(()) } + fn movement(&mut self) -> Result<()> { + for _ in 0..4 { + self.reader.read_boolean()?; + } + let path = self.reader.read_vint()?.max(0) as usize; + self.vints(path)?; + self.vints(18)?; + Ok(()) + } fn combat(&mut self) -> Result<()> { self.reader.read_boolean()?; self.reader.read_boolean()?; @@ -201,16 +211,9 @@ impl<'a, 'b> Verifier<'a, 'b> { for _ in 0..count { self.global_id()?; } - let summoner_bodies = !matches!( - std::env::var("SCROLL_BATTLE_SUMMONER") - .unwrap_or_default() - .as_str(), - "0" | "false" | "off" - ); let summoner = LogicDataTables::instance() .data_by_name(table::CHARACTERS_COMBINED, "KingTower") - .map(|row| row.global_id()) - .filter(|_| summoner_bodies); + .map(|row| row.global_id()); self.mark("object_bodies"); for entry in &data { let is_summoner = entry.global_id().is_some() && entry.global_id() == summoner; @@ -227,9 +230,14 @@ impl<'a, 'b> Verifier<'a, 'b> { .unwrap_or(0); self.mark("components"); for pass in 0..4 { - for _ in &data { + for entry in &data { + let moves = entry + .data() + .map(|row| row.int(MOVEMENT_SPEED_COLUMN) > 0) + .unwrap_or(false); match pass { 0 => self.combat()?, + 1 if moves => self.movement()?, 2 => self.hitpoint()?, 3 => self.buff(buff_rows)?, _ => {}