diff --git a/crates/game-service/src/battle_session.rs b/crates/game-service/src/battle_session.rs index 6ed0570..5929f94 100644 --- a/crates/game-service/src/battle_session.rs +++ b/crates/game-service/src/battle_session.rs @@ -4,8 +4,7 @@ pub const SNAPSHOT_INTERVAL_TICKS: i32 = 4; pub const MAX_CATCH_UP_TICKS: i32 = 40; use std::time::Instant; use logic::battle::{ - LogicGameMode, LogicGameObjectEntry, LogicGameObjectRef, LogicVector2, - BATTLE_TICKS_PER_SECOND, BATTLE_TYPE_PVP, CHARACTER_OBJECT_TYPE, + LogicGameMode, LogicGameObjectEntry, LogicVector2, BATTLE_TICKS_PER_SECOND, BATTLE_TYPE_PVP, }; use logic::battle::{verify_snapshot, LogicBattleEvent}; use logic::SectorStateMessage; @@ -59,38 +58,31 @@ impl BattleSession { pending_bot_play: None, } } - pub fn reannounce(&mut self) { - let mut remap: Vec<(LogicGameObjectRef, LogicGameObjectRef)> = Vec::new(); - for entry in self.mode.battle.objects.objects.iter_mut() { - let fresh = LogicGameObjectRef::of(CHARACTER_OBJECT_TYPE + 1, self.next_instance); - self.next_instance += 1; - remap.push((entry.global_id, fresh)); - entry.global_id = fresh; + fn opening(&self) -> LogicGameMode { + let mut mode = self.mode.clone(); + mode.battle.objects.objects.clear(); + mode.battle.leaders = Default::default(); + mode.battle.leader_towers = Default::default(); + mode + } + pub fn announce(&mut self) -> bool { + if self.announced { + return false; } - let translate = |old: &LogicGameObjectRef| -> Option { - remap - .iter() - .find(|(was, _)| was == old) - .map(|(_, now)| *now) - }; - for leader in self.mode.battle.leaders.iter_mut() { - if let Some(now) = translate(leader) { - *leader = now; - } - } - for towers in self.mode.battle.leader_towers.iter_mut() { - for tower in towers.iter_mut() { - if let Some(now) = translate(tower) { - *tower = now; - } - } - } - self.mode - .battle - .objects - .objects - .sort_by_key(|entry| entry.global_id.0.map(|id| (id.class_id, id.instance_id))); + self.announced = true; self.next_snapshot = self.tick; + true + } + pub fn spend_mana(&mut self, owner: i32, cost: i32) { + for entry in self.mode.battle.objects.objects.iter_mut() { + if entry.owner_index() != owner { + continue; + } + if let logic::battle::LogicObjectBody::Summoner(summoner) = &mut entry.body { + summoner.mana = (summoner.mana - cost).max(0); + return; + } + } } pub fn take_bot_play(&mut self) -> Option<(LogicDataRef, LogicVector2, i32)> { self.pending_bot_play.take() @@ -151,7 +143,11 @@ impl BattleSession { }) } pub fn snapshot_message(&mut self) -> Option { - let snapshot = self.mode.snapshot().ok()?; + let snapshot = if self.announced { + self.mode.snapshot().ok()? + } else { + self.opening().snapshot().ok()? + }; let report = verify_snapshot(&snapshot); let unit = self .mode @@ -383,7 +379,6 @@ impl BattleRegistry { pub async fn resend(&self, account: AccountRef) -> Option { let handle = self.session(account).await?; let mut session = handle.lock().await; - session.reannounce(); session.snapshot_message() } pub async fn announce_once(&self, account: AccountRef) -> bool { @@ -391,12 +386,7 @@ impl BattleRegistry { return false; }; let mut session = handle.lock().await; - if session.announced { - return false; - } - session.announced = true; - session.reannounce(); - true + session.announce() } pub async fn send(&self, account: AccountRef, message: WireMessage) { if let Some(handle) = self.session(account).await { @@ -440,6 +430,8 @@ impl BattleRegistry { let Some(card) = session.deck_card(owner, slot) else { return 0; }; + let cost = card.data().map(|row| row.int("ManaCost")).unwrap_or(0); + session.spend_mana(owner, cost); let entries = summon(&card, position, owner, session.next_instance()); let spawned = entries.len(); session.queue(at_tick, entries); diff --git a/crates/game-service/src/service.rs b/crates/game-service/src/service.rs index 7773d1d..66ef95b 100644 --- a/crates/game-service/src/service.rs +++ b/crates/game-service/src/service.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use logic::table; use logic::{ AvailableServerCommandMessage, EndClientTurnMessage, LogicCommandManager, LogicDataRef, - LogicShopSeedChangedCommand, OutOfSyncMessage, OwnHomeDataMessage, SectorStateMessage, + LogicShopSeedChangedCommand, OutOfSyncMessage, OwnHomeDataMessage, SectorCommandMessage, SendBattleEventMessage, StartMissionMessage, StopHomeLogicMessage, }; @@ -146,38 +146,24 @@ impl GameService { self.config.random_seed, ) .map_err(|error| RpcError::Rejected(error.to_string()))?; - let snapshot = battle - .snapshot() - .map_err(|error| RpcError::Rejected(error.to_string()))?; - tracing::debug!(head = %snapshot.iter().take(48).map(|b| format!("{b:02x}")).collect::>().join(" "), "snapshot head"); - let report = logic::battle::verify_snapshot(&snapshot); - if report.is_ok() { - tracing::debug!(steps = ?report.steps, "snapshot sections"); - tracing::info!( - %account, - opponent = %opponent_name, - location = %location, - objects = battle.battle.objects.objects.len(), - bytes = snapshot.len(), - "sending a battle sector state" - ); - } else { - tracing::error!( - %account, - bytes = snapshot.len(), - trailing = report.trailing, - error = report.error.as_deref().unwrap_or("-"), - steps = ?report.steps, - "the battle snapshot does not read back, refusing to send it" - ); - return Err(RpcError::Rejected(report.error.unwrap_or_else(|| { - format!("{} trailing byte(s)", report.trailing) - }))); - } + let objects = battle.battle.objects.objects.len(); self.running_battles .start(&players, battle, crate::bot::menu_taunts()) .await; - Ok(vec![encode(&SectorStateMessage::new(snapshot))?]) + let Some(message) = self.running_battles.resend(account).await else { + self.running_battles.finish(account).await; + return Err(RpcError::Rejected( + "the opening sector state does not read back".to_owned(), + )); + }; + tracing::info!( + %account, + opponent = %opponent_name, + location = %location, + objects, + "sending the opening sector state, objects follow once the client is up" + ); + Ok(vec![message]) } async fn roller(&self, account: AccountRef) -> RpcResult { let profile = self.profile(account).await?;