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.
99 lines
3.1 KiB
Rust
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)
|
|
}
|
|
}
|