battle model: LogicBattle, the object manager and BattleResultMessage
field order taken straight off LogicBattle::encode. the object manager is columnar, not object major: 6 counters, a count, then all data refs, then all global ids, then all objects, then four component passes. global ids must be ascending, the client binary searches them. LogicRandom moved out of the shop into its own module. its abs was checked_abs().unwrap_or(0), the binary uses NEGS so i32::MIN stays i32::MIN. one seed in four billion, but it would have desynced the card shop. per object subclass encodes and the component payloads are still missing, so no snapshot can be sent yet.
This commit is contained in:
parent
68e2b87a0a
commit
41eca5b620
13 changed files with 492 additions and 28 deletions
201
crates/logic/src/battle/logic_battle.rs
Normal file
201
crates/logic/src/battle/logic_battle.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
use titan::{ByteStreamReader, ByteStreamWriter, LogicLong, Payload, Result};
|
||||||
|
use crate::battle::logic_game_object_manager::LogicGameObjectManager;
|
||||||
|
use crate::battle::logic_game_object_ref::LogicGameObjectRef;
|
||||||
|
use crate::data::LogicDataRef;
|
||||||
|
use crate::model::LogicSpellDeck;
|
||||||
|
pub const BATTLE_TYPE_PVP: i32 = 0;
|
||||||
|
pub const BATTLE_TYPE_NPC: i32 = 1;
|
||||||
|
pub const BATTLE_TYPE_REPLAY: i32 = 3;
|
||||||
|
pub const BATTLE_INT_ARRAY: usize = 8;
|
||||||
|
pub const BATTLE_TRAILING_INTS: usize = 6;
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct LogicBattle {
|
||||||
|
pub location: LogicDataRef,
|
||||||
|
pub npc: LogicDataRef,
|
||||||
|
pub arena: LogicDataRef,
|
||||||
|
pub account_ids: [LogicLong; 2],
|
||||||
|
pub unknown_20: i32,
|
||||||
|
pub unknown_24: i32,
|
||||||
|
pub counters: [i32; BATTLE_INT_ARRAY],
|
||||||
|
pub battle_type: i32,
|
||||||
|
pub end_counter: i32,
|
||||||
|
pub winner_index: i32,
|
||||||
|
pub battle_ended_called: bool,
|
||||||
|
pub battle_ended_with_timeout: bool,
|
||||||
|
pub unknown_182: bool,
|
||||||
|
pub player_finished_npc_level: bool,
|
||||||
|
pub show_start_hud: bool,
|
||||||
|
pub is_on_overtime: bool,
|
||||||
|
pub objects: LogicGameObjectManager,
|
||||||
|
pub decks: [Option<LogicSpellDeck>; 2],
|
||||||
|
pub leaders: [LogicGameObjectRef; 2],
|
||||||
|
pub leader_towers: [Vec<LogicGameObjectRef>; 2],
|
||||||
|
pub winner_score_change: i32,
|
||||||
|
pub loser_score_change: i32,
|
||||||
|
pub trailing: [i32; BATTLE_TRAILING_INTS],
|
||||||
|
}
|
||||||
|
impl Default for LogicBattle {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
location: LogicDataRef::None,
|
||||||
|
npc: LogicDataRef::None,
|
||||||
|
arena: LogicDataRef::None,
|
||||||
|
account_ids: [LogicLong::default(); 2],
|
||||||
|
unknown_20: 0,
|
||||||
|
unknown_24: 0,
|
||||||
|
counters: [0; BATTLE_INT_ARRAY],
|
||||||
|
battle_type: BATTLE_TYPE_NPC,
|
||||||
|
end_counter: 0,
|
||||||
|
winner_index: 0,
|
||||||
|
battle_ended_called: false,
|
||||||
|
battle_ended_with_timeout: false,
|
||||||
|
unknown_182: false,
|
||||||
|
player_finished_npc_level: false,
|
||||||
|
show_start_hud: true,
|
||||||
|
is_on_overtime: false,
|
||||||
|
objects: LogicGameObjectManager::default(),
|
||||||
|
decks: [None, None],
|
||||||
|
leaders: [LogicGameObjectRef::NONE; 2],
|
||||||
|
leader_towers: [Vec::new(), Vec::new()],
|
||||||
|
winner_score_change: 0,
|
||||||
|
loser_score_change: 0,
|
||||||
|
trailing: [0; BATTLE_TRAILING_INTS],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl LogicBattle {
|
||||||
|
pub fn is_npc_battle(&self) -> bool {
|
||||||
|
self.battle_type == BATTLE_TYPE_NPC && !self.npc.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Payload for LogicBattle {
|
||||||
|
fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
|
||||||
|
self.location.encode(writer)?;
|
||||||
|
self.npc.encode(writer)?;
|
||||||
|
self.arena.encode(writer)?;
|
||||||
|
for account in &self.account_ids {
|
||||||
|
writer.write_vlong(account.high, account.low);
|
||||||
|
}
|
||||||
|
writer.write_vint(self.unknown_20);
|
||||||
|
writer.write_vint(self.unknown_24);
|
||||||
|
for value in self.counters {
|
||||||
|
writer.write_vint(value);
|
||||||
|
}
|
||||||
|
writer.write_vint(self.battle_type);
|
||||||
|
writer.write_vint(self.end_counter);
|
||||||
|
writer.write_vint(self.winner_index);
|
||||||
|
writer.write_boolean(self.battle_ended_called);
|
||||||
|
writer.write_boolean(self.battle_ended_with_timeout);
|
||||||
|
writer.write_boolean(self.unknown_182);
|
||||||
|
writer.write_boolean(self.player_finished_npc_level);
|
||||||
|
writer.write_boolean(self.show_start_hud);
|
||||||
|
writer.write_boolean(self.is_on_overtime);
|
||||||
|
self.objects.encode(writer)?;
|
||||||
|
for deck in &self.decks {
|
||||||
|
match deck {
|
||||||
|
None => writer.write_boolean(false),
|
||||||
|
Some(deck) => {
|
||||||
|
writer.write_boolean(true);
|
||||||
|
deck.encode(writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for leader in &self.leaders {
|
||||||
|
leader.encode(writer)?;
|
||||||
|
}
|
||||||
|
for towers in &self.leader_towers {
|
||||||
|
writer.write_vint(towers.len() as i32);
|
||||||
|
for tower in towers {
|
||||||
|
tower.encode(writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.battle_ended_called {
|
||||||
|
writer.write_vint(self.winner_score_change);
|
||||||
|
writer.write_vint(self.loser_score_change);
|
||||||
|
}
|
||||||
|
for value in self.trailing {
|
||||||
|
writer.write_vint(value);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn decode(reader: &mut ByteStreamReader<'_>) -> Result<Self> {
|
||||||
|
let location = LogicDataRef::decode(reader)?;
|
||||||
|
let npc = LogicDataRef::decode(reader)?;
|
||||||
|
let arena = LogicDataRef::decode(reader)?;
|
||||||
|
let account_ids = {
|
||||||
|
let first = reader.read_vlong()?;
|
||||||
|
let second = reader.read_vlong()?;
|
||||||
|
[
|
||||||
|
LogicLong::new(first.0, first.1),
|
||||||
|
LogicLong::new(second.0, second.1),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let unknown_20 = reader.read_vint()?;
|
||||||
|
let unknown_24 = reader.read_vint()?;
|
||||||
|
let mut counters = [0; BATTLE_INT_ARRAY];
|
||||||
|
for value in counters.iter_mut() {
|
||||||
|
*value = reader.read_vint()?;
|
||||||
|
}
|
||||||
|
let battle_type = reader.read_vint()?;
|
||||||
|
let end_counter = reader.read_vint()?;
|
||||||
|
let winner_index = reader.read_vint()?;
|
||||||
|
let battle_ended_called = reader.read_boolean()?;
|
||||||
|
let battle_ended_with_timeout = reader.read_boolean()?;
|
||||||
|
let unknown_182 = reader.read_boolean()?;
|
||||||
|
let player_finished_npc_level = reader.read_boolean()?;
|
||||||
|
let show_start_hud = reader.read_boolean()?;
|
||||||
|
let is_on_overtime = reader.read_boolean()?;
|
||||||
|
let objects = LogicGameObjectManager::decode(reader)?;
|
||||||
|
let mut decks = [None, None];
|
||||||
|
for deck in decks.iter_mut() {
|
||||||
|
if reader.read_boolean()? {
|
||||||
|
*deck = Some(LogicSpellDeck::decode(reader)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let leaders = [
|
||||||
|
LogicGameObjectRef::decode(reader)?,
|
||||||
|
LogicGameObjectRef::decode(reader)?,
|
||||||
|
];
|
||||||
|
let mut leader_towers = [Vec::new(), Vec::new()];
|
||||||
|
for towers in leader_towers.iter_mut() {
|
||||||
|
let count = reader.read_vint()?.max(0) as usize;
|
||||||
|
for _ in 0..count {
|
||||||
|
towers.push(LogicGameObjectRef::decode(reader)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (winner_score_change, loser_score_change) = if battle_ended_called {
|
||||||
|
(reader.read_vint()?, reader.read_vint()?)
|
||||||
|
} else {
|
||||||
|
(0, 0)
|
||||||
|
};
|
||||||
|
let mut trailing = [0; BATTLE_TRAILING_INTS];
|
||||||
|
for value in trailing.iter_mut() {
|
||||||
|
*value = reader.read_vint()?;
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
location,
|
||||||
|
npc,
|
||||||
|
arena,
|
||||||
|
account_ids,
|
||||||
|
unknown_20,
|
||||||
|
unknown_24,
|
||||||
|
counters,
|
||||||
|
battle_type,
|
||||||
|
end_counter,
|
||||||
|
winner_index,
|
||||||
|
battle_ended_called,
|
||||||
|
battle_ended_with_timeout,
|
||||||
|
unknown_182,
|
||||||
|
player_finished_npc_level,
|
||||||
|
show_start_hud,
|
||||||
|
is_on_overtime,
|
||||||
|
objects,
|
||||||
|
decks,
|
||||||
|
leaders,
|
||||||
|
leader_towers,
|
||||||
|
winner_score_change,
|
||||||
|
loser_score_change,
|
||||||
|
trailing,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
39
crates/logic/src/battle/logic_game_object.rs
Normal file
39
crates/logic/src/battle/logic_game_object.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
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 hitpoints: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub owner_index: i32,
|
||||||
|
pub position: LogicVector2,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub state: i32,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
|
pub struct LogicGameObjectEntry {
|
||||||
|
pub data: LogicDataRef,
|
||||||
|
pub global_id: LogicGameObjectRef,
|
||||||
|
pub object: LogicGameObject,
|
||||||
|
}
|
||||||
|
impl LogicGameObjectEntry {
|
||||||
|
pub fn object_type(&self) -> i32 {
|
||||||
|
self.global_id.0.map(|id| id.class_id - 1).unwrap_or(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
81
crates/logic/src/battle/logic_game_object_manager.rs
Normal file
81
crates/logic/src/battle/logic_game_object_manager.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
use titan::{ByteStreamReader, ByteStreamWriter, Payload, Result};
|
||||||
|
use crate::battle::logic_game_object::{
|
||||||
|
LogicGameObject, LogicGameObjectEntry, COMPONENT_PASSES, OBJECT_TYPE_COUNT,
|
||||||
|
};
|
||||||
|
use crate::battle::logic_game_object_ref::LogicGameObjectRef;
|
||||||
|
use crate::data::LogicDataRef;
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct LogicGameObjectManager {
|
||||||
|
pub instance_counters: [i32; OBJECT_TYPE_COUNT],
|
||||||
|
pub objects: Vec<LogicGameObjectEntry>,
|
||||||
|
}
|
||||||
|
impl Default for LogicGameObjectManager {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
instance_counters: [0; OBJECT_TYPE_COUNT],
|
||||||
|
objects: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl LogicGameObjectManager {
|
||||||
|
pub fn push(&mut self, entry: LogicGameObjectEntry) {
|
||||||
|
let object_type = entry.object_type();
|
||||||
|
if let Ok(index) = usize::try_from(object_type) {
|
||||||
|
if let Some(counter) = self.instance_counters.get_mut(index) {
|
||||||
|
let instance = entry.global_id.0.map(|id| id.instance_id).unwrap_or(0);
|
||||||
|
*counter = (*counter).max(instance + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.objects.push(entry);
|
||||||
|
self.objects
|
||||||
|
.sort_by_key(|entry| entry.global_id.0.map(|id| (id.class_id, id.instance_id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Payload for LogicGameObjectManager {
|
||||||
|
fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
|
||||||
|
for counter in self.instance_counters {
|
||||||
|
writer.write_vint(counter);
|
||||||
|
}
|
||||||
|
writer.write_vint(self.objects.len() as i32);
|
||||||
|
for entry in &self.objects {
|
||||||
|
entry.data.encode(writer)?;
|
||||||
|
}
|
||||||
|
for entry in &self.objects {
|
||||||
|
entry.global_id.encode(writer)?;
|
||||||
|
}
|
||||||
|
for entry in &self.objects {
|
||||||
|
entry.object.encode(writer)?;
|
||||||
|
}
|
||||||
|
for _ in 0..COMPONENT_PASSES {
|
||||||
|
for _ in &self.objects {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn decode(reader: &mut ByteStreamReader<'_>) -> Result<Self> {
|
||||||
|
let mut instance_counters = [0; OBJECT_TYPE_COUNT];
|
||||||
|
for counter in instance_counters.iter_mut() {
|
||||||
|
*counter = reader.read_vint()?;
|
||||||
|
}
|
||||||
|
let count = reader.read_vint()?.max(0) as usize;
|
||||||
|
let mut data = Vec::with_capacity(count);
|
||||||
|
for _ in 0..count {
|
||||||
|
data.push(LogicDataRef::decode(reader)?);
|
||||||
|
}
|
||||||
|
let mut ids = Vec::with_capacity(count);
|
||||||
|
for _ in 0..count {
|
||||||
|
ids.push(LogicGameObjectRef::decode(reader)?);
|
||||||
|
}
|
||||||
|
let mut objects = Vec::with_capacity(count);
|
||||||
|
for index in 0..count {
|
||||||
|
objects.push(LogicGameObjectEntry {
|
||||||
|
data: data[index].clone(),
|
||||||
|
global_id: ids[index],
|
||||||
|
object: LogicGameObject::decode(reader)?,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
instance_counters,
|
||||||
|
objects,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
32
crates/logic/src/battle/logic_game_object_ref.rs
Normal file
32
crates/logic/src/battle/logic_game_object_ref.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
use titan::{ByteStreamReader, ByteStreamWriter, GlobalId, Payload, Result};
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct LogicGameObjectRef(pub Option<GlobalId>);
|
||||||
|
impl LogicGameObjectRef {
|
||||||
|
pub const NONE: Self = Self(None);
|
||||||
|
pub fn of(class_id: i32, instance_id: i32) -> Self {
|
||||||
|
Self(Some(GlobalId::new(class_id, instance_id)))
|
||||||
|
}
|
||||||
|
pub fn is_none(&self) -> bool {
|
||||||
|
self.0.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Payload for LogicGameObjectRef {
|
||||||
|
fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
|
||||||
|
match self.0 {
|
||||||
|
None => writer.write_vint(0),
|
||||||
|
Some(id) => {
|
||||||
|
writer.write_vint(id.class_id);
|
||||||
|
writer.write_vint(id.instance_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn decode(reader: &mut ByteStreamReader<'_>) -> Result<Self> {
|
||||||
|
let class_id = reader.read_vint()?;
|
||||||
|
if class_id == 0 {
|
||||||
|
return Ok(Self(None));
|
||||||
|
}
|
||||||
|
let instance_id = reader.read_vint()?;
|
||||||
|
Ok(Self::of(class_id, instance_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
11
crates/logic/src/battle/logic_time.rs
Normal file
11
crates/logic/src/battle/logic_time.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
use titan::Payload;
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Payload)]
|
||||||
|
pub struct LogicTime {
|
||||||
|
#[codec(vint)]
|
||||||
|
pub tick: i32,
|
||||||
|
}
|
||||||
|
impl LogicTime {
|
||||||
|
pub fn new(tick: i32) -> Self {
|
||||||
|
Self { tick }
|
||||||
|
}
|
||||||
|
}
|
||||||
10
crates/logic/src/battle/logic_tutorial_manager.rs
Normal file
10
crates/logic/src/battle/logic_tutorial_manager.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
use titan::Payload;
|
||||||
|
use crate::battle::logic_game_object_ref::LogicGameObjectRef;
|
||||||
|
use crate::data::LogicDataRef;
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Payload)]
|
||||||
|
pub struct LogicTutorialManager {
|
||||||
|
pub tutorial: LogicDataRef,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub countdown: i32,
|
||||||
|
pub target: LogicGameObjectRef,
|
||||||
|
}
|
||||||
18
crates/logic/src/battle/mod.rs
Normal file
18
crates/logic/src/battle/mod.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
mod logic_battle;
|
||||||
|
mod logic_game_object;
|
||||||
|
mod logic_game_object_manager;
|
||||||
|
mod logic_game_object_ref;
|
||||||
|
mod logic_time;
|
||||||
|
mod logic_tutorial_manager;
|
||||||
|
pub use logic_battle::{
|
||||||
|
LogicBattle, BATTLE_INT_ARRAY, BATTLE_TRAILING_INTS, BATTLE_TYPE_NPC, BATTLE_TYPE_PVP,
|
||||||
|
BATTLE_TYPE_REPLAY,
|
||||||
|
};
|
||||||
|
pub use logic_game_object::{
|
||||||
|
LogicGameObject, LogicGameObjectEntry, LogicVector2, CHARACTER_OBJECT_TYPE, COMPONENT_PASSES,
|
||||||
|
OBJECT_TYPE_COUNT,
|
||||||
|
};
|
||||||
|
pub use logic_game_object_manager::LogicGameObjectManager;
|
||||||
|
pub use logic_game_object_ref::LogicGameObjectRef;
|
||||||
|
pub use logic_time::LogicTime;
|
||||||
|
pub use logic_tutorial_manager::LogicTutorialManager;
|
||||||
|
|
@ -2,6 +2,5 @@ mod logic_home_mode;
|
||||||
mod shop;
|
mod shop;
|
||||||
pub use logic_home_mode::LogicHomeMode;
|
pub use logic_home_mode::LogicHomeMode;
|
||||||
pub use shop::{
|
pub use shop::{
|
||||||
cards_on_sale, randomize_shop_items, LogicRandom, CARD_TABLES, EPIC_SUNDAY_WEEKDAY,
|
cards_on_sale, randomize_shop_items, CARD_TABLES, EPIC_SUNDAY_WEEKDAY, RARITIES_ON_SALE,
|
||||||
RARITIES_ON_SALE,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::data::{table, LogicDataRef, LogicDataTables, RARITY_COMMON, RARITY_EPIC, RARITY_RARE};
|
use crate::data::{table, LogicDataRef, LogicDataTables, RARITY_COMMON, RARITY_EPIC, RARITY_RARE};
|
||||||
|
use crate::logic_random::LogicRandom;
|
||||||
use crate::model::LogicDataSlot;
|
use crate::model::LogicDataSlot;
|
||||||
pub const EPIC_SUNDAY_WEEKDAY: i32 = 1;
|
pub const EPIC_SUNDAY_WEEKDAY: i32 = 1;
|
||||||
pub const RARITIES_ON_SALE: [&str; 3] = [RARITY_COMMON, RARITY_RARE, RARITY_EPIC];
|
pub const RARITIES_ON_SALE: [&str; 3] = [RARITY_COMMON, RARITY_RARE, RARITY_EPIC];
|
||||||
|
|
@ -7,31 +8,6 @@ pub const CARD_TABLES: [i32; 3] = [
|
||||||
table::SPELLS_BUILDINGS,
|
table::SPELLS_BUILDINGS,
|
||||||
table::SPELLS_OTHER,
|
table::SPELLS_OTHER,
|
||||||
];
|
];
|
||||||
pub struct LogicRandom {
|
|
||||||
seed: i32,
|
|
||||||
}
|
|
||||||
impl LogicRandom {
|
|
||||||
pub fn new(seed: i32) -> Self {
|
|
||||||
Self { seed }
|
|
||||||
}
|
|
||||||
pub fn seed(&self) -> i32 {
|
|
||||||
self.seed
|
|
||||||
}
|
|
||||||
pub fn next(&mut self, bound: i32) -> i32 {
|
|
||||||
if bound <= 0 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if self.seed == 0 {
|
|
||||||
self.seed = -1;
|
|
||||||
}
|
|
||||||
let mut state = self.seed;
|
|
||||||
state ^= state.wrapping_shl(13);
|
|
||||||
state ^= state >> 17;
|
|
||||||
state ^= state.wrapping_shl(5);
|
|
||||||
self.seed = state;
|
|
||||||
state.checked_abs().unwrap_or(0) % bound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn cards_on_sale(rarity: &str, arena: &LogicDataRef) -> Vec<LogicDataRef> {
|
pub fn cards_on_sale(rarity: &str, arena: &LogicDataRef) -> Vec<LogicDataRef> {
|
||||||
let tables = LogicDataTables::instance();
|
let tables = LogicDataTables::instance();
|
||||||
let mut pool = Vec::new();
|
let mut pool = Vec::new();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
extern crate self as logic;
|
extern crate self as logic;
|
||||||
|
pub mod battle;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
pub mod factory;
|
pub mod factory;
|
||||||
pub mod home;
|
pub mod home;
|
||||||
|
pub mod logic_random;
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
pub use commands::{
|
pub use commands::{
|
||||||
|
|
@ -22,7 +24,8 @@ pub use data::{
|
||||||
TABLE_COUNT,
|
TABLE_COUNT,
|
||||||
};
|
};
|
||||||
pub use factory::{scroll_message_registry, LogicScrollMessageFactory};
|
pub use factory::{scroll_message_registry, LogicScrollMessageFactory};
|
||||||
pub use home::{randomize_shop_items, LogicHomeMode, LogicRandom};
|
pub use home::{randomize_shop_items, LogicHomeMode};
|
||||||
|
pub use logic_random::LogicRandom;
|
||||||
pub use messages::*;
|
pub use messages::*;
|
||||||
pub use model::*;
|
pub use model::*;
|
||||||
pub use titan::{GlobalId, LogicLong};
|
pub use titan::{GlobalId, LogicLong};
|
||||||
|
|
|
||||||
41
crates/logic/src/logic_random.rs
Normal file
41
crates/logic/src/logic_random.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
use titan::{ByteStreamReader, ByteStreamWriter, Payload, Result};
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct LogicRandom {
|
||||||
|
seed: i32,
|
||||||
|
}
|
||||||
|
impl LogicRandom {
|
||||||
|
pub fn new(seed: i32) -> Self {
|
||||||
|
Self { seed }
|
||||||
|
}
|
||||||
|
pub fn seed(&self) -> i32 {
|
||||||
|
self.seed
|
||||||
|
}
|
||||||
|
pub fn set_seed(&mut self, seed: i32) {
|
||||||
|
self.seed = seed;
|
||||||
|
}
|
||||||
|
pub fn next(&mut self, bound: i32) -> i32 {
|
||||||
|
if bound < 1 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if self.seed == 0 {
|
||||||
|
self.seed = -1;
|
||||||
|
}
|
||||||
|
let mut state = self.seed;
|
||||||
|
state ^= state.wrapping_shl(13);
|
||||||
|
state ^= state >> 17;
|
||||||
|
state ^= state.wrapping_shl(5);
|
||||||
|
self.seed = state;
|
||||||
|
state.wrapping_abs() % bound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Payload for LogicRandom {
|
||||||
|
fn encode(&self, writer: &mut ByteStreamWriter) -> Result<()> {
|
||||||
|
writer.write_int(self.seed);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn decode(reader: &mut ByteStreamReader<'_>) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
seed: reader.read_int()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
49
crates/logic/src/messages/battle_result.rs
Normal file
49
crates/logic/src/messages/battle_result.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
use titan::Message;
|
||||||
|
use crate::battle::LogicGameObjectRef;
|
||||||
|
pub const BATTLE_RESULT_WIN: i32 = 1;
|
||||||
|
pub const BATTLE_RESULT_LOSE: i32 = 2;
|
||||||
|
pub const BATTLE_RESULT_DRAW: i32 = 3;
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, Message)]
|
||||||
|
#[message(id = 20225, direction = "server", name = "BattleResultMessage")]
|
||||||
|
pub struct BattleResultMessage {
|
||||||
|
#[codec(vint)]
|
||||||
|
pub result: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub score_change: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub unknown_56: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub unknown_60: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub unknown_64: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub unknown_68: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub gold_reward: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub exp_reward: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub own_stars: i32,
|
||||||
|
#[codec(vint)]
|
||||||
|
pub opponent_stars: i32,
|
||||||
|
pub treasure_chest: LogicGameObjectRef,
|
||||||
|
#[codec(bytes)]
|
||||||
|
pub full_update: Option<Vec<u8>>,
|
||||||
|
#[codec(bool)]
|
||||||
|
pub unknown_100: bool,
|
||||||
|
pub unknown_104: LogicGameObjectRef,
|
||||||
|
pub unknown_108: LogicGameObjectRef,
|
||||||
|
pub unknown_112: LogicGameObjectRef,
|
||||||
|
pub unknown_116: LogicGameObjectRef,
|
||||||
|
}
|
||||||
|
impl BattleResultMessage {
|
||||||
|
pub fn npc_win(exp_reward: i32, gold_reward: i32, own_stars: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
result: BATTLE_RESULT_WIN,
|
||||||
|
exp_reward,
|
||||||
|
gold_reward,
|
||||||
|
own_stars,
|
||||||
|
..Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
mod available_server_command;
|
mod available_server_command;
|
||||||
|
mod battle_result;
|
||||||
mod client_capabilities;
|
mod client_capabilities;
|
||||||
mod client_requests;
|
mod client_requests;
|
||||||
mod end_client_turn;
|
mod end_client_turn;
|
||||||
|
|
@ -12,6 +13,9 @@ mod own_home_data;
|
||||||
mod server_error;
|
mod server_error;
|
||||||
mod start_mission;
|
mod start_mission;
|
||||||
pub use available_server_command::AvailableServerCommandMessage;
|
pub use available_server_command::AvailableServerCommandMessage;
|
||||||
|
pub use battle_result::{
|
||||||
|
BattleResultMessage, BATTLE_RESULT_DRAW, BATTLE_RESULT_LOSE, BATTLE_RESULT_WIN,
|
||||||
|
};
|
||||||
pub use client_capabilities::ClientCapabilitiesMessage;
|
pub use client_capabilities::ClientCapabilitiesMessage;
|
||||||
pub use client_requests::{
|
pub use client_requests::{
|
||||||
AskForAvatarStreamMessage, AskForBattleReplayStreamMessage,
|
AskForAvatarStreamMessage, AskForBattleReplayStreamMessage,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue