walk units around the river instead of through it
units now path. A* over the tilemap grid, water costing 800 against 1 for ground so the route runs to a bridge, or 20 when the unit flies and crosses anywhere. bit 5 of a map cell is the water flag, which is what makes 48 water and leaves the bridge cells - carrying the lane ids 1 and 2 - dry. the unit walks to the next node rather than at its target, and still stops at Range. also stops pushing snapshots while the battle type is 1. the client is simulating the same battle itself there, and our state does not match it yet, so every push yanked the units back to where the server thought they were. the snapshots are still built and verified each second, ready for the switch to type 0 once the two simulations agree; they are simply not sent.
This commit is contained in:
parent
ba235a9b4c
commit
9084163f71
7 changed files with 168 additions and 9 deletions
|
|
@ -302,6 +302,7 @@ impl BattleBuilder {
|
|||
.unwrap_or_default(),
|
||||
];
|
||||
let battle = LogicBattle {
|
||||
tilemap: Some(tilemap),
|
||||
location,
|
||||
npc,
|
||||
arena,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ use std::collections::HashMap;
|
|||
use std::sync::Arc;
|
||||
pub const SNAPSHOT_INTERVAL_TICKS: i32 = 20;
|
||||
use std::time::Instant;
|
||||
use logic::battle::{LogicGameMode, LogicGameObjectEntry, LogicVector2, BATTLE_TICKS_PER_SECOND};
|
||||
use logic::battle::{
|
||||
LogicGameMode, LogicGameObjectEntry, LogicVector2, BATTLE_TICKS_PER_SECOND, BATTLE_TYPE_PVP,
|
||||
};
|
||||
use logic::battle::{verify_snapshot, LogicBattleEvent};
|
||||
use logic::SectorStateMessage;
|
||||
use logic::{BattleEventMessage, LogicDataRef, LogicRandom};
|
||||
|
|
@ -83,6 +85,9 @@ impl BattleSession {
|
|||
.unwrap_or(king_x);
|
||||
Some((card, LogicVector2::new(x, ahead), self.next_instance()))
|
||||
}
|
||||
pub fn pushes_snapshots(&self) -> bool {
|
||||
self.mode.battle.battle_type == BATTLE_TYPE_PVP
|
||||
}
|
||||
fn snapshot_message(&mut self) -> Option<WireMessage> {
|
||||
let snapshot = self.mode.snapshot().ok()?;
|
||||
let report = verify_snapshot(&snapshot);
|
||||
|
|
@ -159,7 +164,7 @@ impl BattleSession {
|
|||
self.tick += 1;
|
||||
self.release_queued(self.tick);
|
||||
self.mode.battle.tick();
|
||||
if self.tick >= self.next_snapshot {
|
||||
if self.pushes_snapshots() && self.tick >= self.next_snapshot {
|
||||
self.next_snapshot = self.tick + SNAPSHOT_INTERVAL_TICKS;
|
||||
if let Some(message) = self.snapshot_message() {
|
||||
self.outbound.push(message);
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ pub struct LogicBattle {
|
|||
pub winner_score_change: i32,
|
||||
pub loser_score_change: i32,
|
||||
pub trailing: [i32; BATTLE_TRAILING_INTS],
|
||||
pub tilemap: Option<crate::battle::LogicTilemap>,
|
||||
}
|
||||
impl Default for LogicBattle {
|
||||
fn default() -> Self {
|
||||
|
|
@ -66,6 +67,7 @@ impl Default for LogicBattle {
|
|||
winner_score_change: 0,
|
||||
loser_score_change: 0,
|
||||
trailing: [0; BATTLE_TRAILING_INTS],
|
||||
tilemap: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -257,6 +259,7 @@ impl Payload for LogicBattle {
|
|||
winner_score_change,
|
||||
loser_score_change,
|
||||
trailing,
|
||||
tilemap: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
116
crates/logic/src/battle/logic_pathfinder.rs
Normal file
116
crates/logic/src/battle/logic_pathfinder.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
use std::collections::BinaryHeap;
|
||||
use crate::battle::logic_tilemap::{
|
||||
LogicTilemap, GROUND_COST, SUBTILE_UNITS, WATER_COST, WATER_COST_JUMPING,
|
||||
};
|
||||
pub const PATH_STEP_LIMIT: usize = 4096;
|
||||
fn tile_of(units: i32) -> i32 {
|
||||
units.div_euclid(SUBTILE_UNITS)
|
||||
}
|
||||
fn centre_of(tile: i32) -> i32 {
|
||||
tile * SUBTILE_UNITS + SUBTILE_UNITS / 2
|
||||
}
|
||||
#[derive(PartialEq, Eq)]
|
||||
struct Step {
|
||||
estimate: i32,
|
||||
cost: i32,
|
||||
index: usize,
|
||||
}
|
||||
impl Ord for Step {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
other
|
||||
.estimate
|
||||
.cmp(&self.estimate)
|
||||
.then_with(|| other.index.cmp(&self.index))
|
||||
}
|
||||
}
|
||||
impl PartialOrd for Step {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
pub fn find_path(
|
||||
tilemap: &LogicTilemap,
|
||||
from: (i32, i32),
|
||||
to: (i32, i32),
|
||||
jumps: bool,
|
||||
) -> Vec<(i32, i32)> {
|
||||
let start = (tile_of(from.0), tile_of(from.1));
|
||||
let goal = (tile_of(to.0), tile_of(to.1));
|
||||
if start == goal || !tilemap.is_inside(goal.0, goal.1) {
|
||||
return Vec::new();
|
||||
}
|
||||
let width = tilemap.width().max(1) as usize;
|
||||
let height = tilemap.height().max(1) as usize;
|
||||
let index_of = |x: i32, y: i32| y as usize * width + x as usize;
|
||||
let mut cost = vec![i32::MAX; width * height];
|
||||
let mut came_from = vec![usize::MAX; width * height];
|
||||
let mut open = BinaryHeap::new();
|
||||
let start_index = index_of(start.0, start.1);
|
||||
cost[start_index] = 0;
|
||||
open.push(Step {
|
||||
estimate: 0,
|
||||
cost: 0,
|
||||
index: start_index,
|
||||
});
|
||||
let goal_index = index_of(goal.0, goal.1);
|
||||
let mut visited = 0;
|
||||
while let Some(step) = open.pop() {
|
||||
if step.index == goal_index {
|
||||
break;
|
||||
}
|
||||
if step.cost > cost[step.index] {
|
||||
continue;
|
||||
}
|
||||
visited += 1;
|
||||
if visited > PATH_STEP_LIMIT {
|
||||
break;
|
||||
}
|
||||
let x = (step.index % width) as i32;
|
||||
let y = (step.index / width) as i32;
|
||||
for (dx, dy) in [(1, 0), (-1, 0), (0, 1), (0, -1)] {
|
||||
let (nx, ny) = (x + dx, y + dy);
|
||||
if !tilemap.is_inside(nx, ny) {
|
||||
continue;
|
||||
}
|
||||
let water = tilemap.is_water(nx, ny);
|
||||
let step_cost = if water {
|
||||
if jumps {
|
||||
WATER_COST_JUMPING
|
||||
} else {
|
||||
WATER_COST
|
||||
}
|
||||
} else {
|
||||
GROUND_COST
|
||||
};
|
||||
let next = index_of(nx, ny);
|
||||
let total = step.cost.saturating_add(step_cost);
|
||||
if total >= cost[next] {
|
||||
continue;
|
||||
}
|
||||
cost[next] = total;
|
||||
came_from[next] = step.index;
|
||||
let heuristic = (goal.0 - nx).abs() + (goal.1 - ny).abs();
|
||||
open.push(Step {
|
||||
estimate: total.saturating_add(heuristic),
|
||||
cost: total,
|
||||
index: next,
|
||||
});
|
||||
}
|
||||
}
|
||||
if came_from[goal_index] == usize::MAX {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut path = Vec::new();
|
||||
let mut cursor = goal_index;
|
||||
while cursor != start_index {
|
||||
let x = (cursor % width) as i32;
|
||||
let y = (cursor / width) as i32;
|
||||
path.push((centre_of(x), centre_of(y)));
|
||||
cursor = came_from[cursor];
|
||||
if cursor == usize::MAX {
|
||||
return Vec::new();
|
||||
}
|
||||
}
|
||||
path.reverse();
|
||||
path
|
||||
}
|
||||
|
|
@ -210,16 +210,28 @@ impl LogicBattle {
|
|||
};
|
||||
let from = positions[index];
|
||||
let stand_off = stats.range;
|
||||
let dx = (goal.0 - from.0) as i64;
|
||||
let dy = (goal.1 - from.1) as i64;
|
||||
let distance = integer_sqrt(dx * dx + dy * dy);
|
||||
if distance <= stand_off as i64 || distance == 0 {
|
||||
let straight = integer_sqrt(
|
||||
((goal.0 - from.0) as i64).pow(2) + ((goal.1 - from.1) as i64).pow(2),
|
||||
);
|
||||
if straight <= stand_off as i64 || straight == 0 {
|
||||
continue;
|
||||
}
|
||||
let step = (stats.speed as i64).min(distance - stand_off as i64);
|
||||
let waypoint = self
|
||||
.tilemap
|
||||
.as_ref()
|
||||
.map(|tilemap| crate::battle::find_path(tilemap, from, goal, stats.flying))
|
||||
.and_then(|path| path.first().copied())
|
||||
.unwrap_or(goal);
|
||||
let dx = (waypoint.0 - from.0) as i64;
|
||||
let dy = (waypoint.1 - from.1) as i64;
|
||||
let leg = integer_sqrt(dx * dx + dy * dy);
|
||||
if leg == 0 {
|
||||
continue;
|
||||
}
|
||||
let step = (stats.speed as i64).min(straight - stand_off as i64).min(leg);
|
||||
let base = self.objects.objects[index].body.base_mut();
|
||||
base.position.x = from.0 + (dx * step / distance) as i32;
|
||||
base.position.y = from.1 + (dy * step / distance) as i32;
|
||||
base.position.x = from.0 + (dx * step / leg) as i32;
|
||||
base.position.y = from.1 + (dy * step / leg) as i32;
|
||||
}
|
||||
}
|
||||
fn resolve_attacks(&mut self) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
pub const SUBTILE_UNITS: i32 = 500;
|
||||
pub const WATER_BIT: i32 = 5;
|
||||
pub const WATER_COST: i32 = 800;
|
||||
pub const WATER_COST_JUMPING: i32 = 20;
|
||||
pub const GROUND_COST: i32 = 1;
|
||||
pub const SECTION_OBJECTS: &str = "Objects";
|
||||
pub const SECTION_MAP: &str = "Map";
|
||||
pub const OBJECT_KING_TOWER: &str = "KingTower";
|
||||
|
|
@ -82,6 +86,22 @@ impl LogicTilemap {
|
|||
let source = std::fs::read_to_string(&path)?;
|
||||
Ok(Self::parse(&source))
|
||||
}
|
||||
pub fn tile_at(&self, x: i32, y: i32) -> i32 {
|
||||
if x < 0 || y < 0 {
|
||||
return 0;
|
||||
}
|
||||
self.tiles
|
||||
.get(y as usize)
|
||||
.and_then(|row| row.get(x as usize))
|
||||
.copied()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
pub fn is_water(&self, x: i32, y: i32) -> bool {
|
||||
(self.tile_at(x, y) >> WATER_BIT) & 1 == 1
|
||||
}
|
||||
pub fn is_inside(&self, x: i32, y: i32) -> bool {
|
||||
x >= 0 && y >= 0 && x < self.width() && y < self.height()
|
||||
}
|
||||
pub fn height(&self) -> i32 {
|
||||
self.tiles.len() as i32
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ mod logic_game_mode;
|
|||
mod logic_game_object;
|
||||
mod logic_game_object_manager;
|
||||
mod logic_game_object_ref;
|
||||
mod logic_pathfinder;
|
||||
mod logic_simulation;
|
||||
mod logic_tilemap;
|
||||
mod logic_time;
|
||||
|
|
@ -31,6 +32,7 @@ pub use logic_game_object::{
|
|||
};
|
||||
pub use logic_game_object_manager::LogicGameObjectManager;
|
||||
pub use logic_game_object_ref::LogicGameObjectRef;
|
||||
pub use logic_pathfinder::find_path;
|
||||
pub use logic_simulation::{LogicCharacterStats, TICK_MILLISECONDS};
|
||||
pub use logic_tilemap::{LogicTilemap, OBJECT_KING_TOWER, OBJECT_PRINCESS_TOWER, SUBTILE_UNITS};
|
||||
pub use logic_time::LogicTime;
|
||||
|
|
|
|||
Loading…
Reference in a new issue