scroll.server/crates/logic/src/battle/logic_game_object.rs
WiseDev 7a0ba1b652 give the server its own battle clock and end condition
LogicBattle::isEndConditionMatched, transcribed: the battle is over when
end_counter is positive, when either king is dead, when tick/20 seconds
reach MatchLength + OvertimeSeconds, or - once past MatchLength - when
the crowns differ. the divisor is the 0x66666667/2^35 multiply in the
client, which is a divide by twenty, so the battle runs at 20 ticks a
second.

crowns come from LogicSummoner::getStars: three when the enemy king is
down, otherwise two minus the enemy princess towers still standing,
which is exactly what leader_towers holds.

BattleRegistry keeps the LogicGameMode we built for each account and
advances it on the tick the client reports in its turn message, so the
server now tracks the clock, the crowns and whether the battle is over,
and drops the session when the player goes home. the two isSummoner
guards in the overtime branch are left out: they only fire for an object
that is not a summoner, which a king tower always is.
2026-08-23 12:01:26 +03:00

99 lines
3.1 KiB
Rust

use titan::Payload;
use crate::battle::logic_game_object_ref::LogicGameObjectRef;
use crate::data::LogicDataRef;
pub const COMPONENT_PASSES: usize = 4;
pub const OBJECT_TYPE_COUNT: usize = 6;
pub const CHARACTER_OBJECT_TYPE: i32 = 5;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Payload)]
pub struct LogicVector2 {
#[codec(vint)]
pub x: i32,
#[codec(vint)]
pub y: i32,
}
impl LogicVector2 {
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Payload)]
pub struct LogicGameObject {
#[codec(vint)]
pub owner_index: i32,
#[codec(vint)]
pub component_mask: i32,
pub position: LogicVector2,
#[codec(vint)]
pub z: i32,
}
impl LogicGameObject {
pub fn encode_base(&self, writer: &mut titan::ByteStreamWriter) -> titan::Result<()> {
<Self as Payload>::encode(self, writer)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LogicObjectBody {
Character(Box<crate::battle::logic_character::LogicCharacter>),
Summoner(Box<crate::battle::logic_character::LogicSummoner>),
}
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<()> {
match self {
LogicObjectBody::Character(character) => character.encode(writer),
LogicObjectBody::Summoner(summoner) => summoner.encode(writer),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LogicGameObjectEntry {
pub data: LogicDataRef,
pub global_id: LogicGameObjectRef,
pub body: LogicObjectBody,
pub components: [Option<crate::battle::logic_component::LogicComponent>; COMPONENT_PASSES],
}
impl LogicGameObjectEntry {
pub fn hitpoints(&self) -> Option<i32> {
match self
.components
.get(crate::battle::logic_component::COMPONENT_HITPOINT)?
.as_ref()?
{
crate::battle::logic_component::LogicComponent::Hitpoint(component) => {
Some(component.hitpoints)
}
_ => None,
}
}
pub fn is_alive(&self) -> bool {
self.hitpoints().map(|value| value > 0).unwrap_or(false)
}
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 {
self.global_id.0.map(|id| id.class_id - 1).unwrap_or(-1)
}
}