mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Fix #61 base crystal stealing with block damage model
This commit is contained in:
@@ -12303,7 +12303,8 @@
|
||||
"type": "NotAFunctionalItem",
|
||||
"active": true,
|
||||
"visibility": "None",
|
||||
"ignore_in_weapon_list": true
|
||||
"ignore_in_weapon_list": true,
|
||||
"indestructible": true
|
||||
},
|
||||
"spriteName": "CubeThumb_LightChassis_Cube",
|
||||
"nameStrKey": "strProtoniumClaspName",
|
||||
|
||||
@@ -1,3 +1,33 @@
|
||||
pub const DEFAULT_CONNECTION: CubeConnections = CubeConnections {
|
||||
id: 0, // (default)
|
||||
connections: &[
|
||||
CubeConnection {
|
||||
direction: (0, 0, 1),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (0, 1, 0),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (1, 0, 0),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (0, 0, -1),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (0, -1, 0),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (-1, 0, 0),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
pub const CUBE_CONNECTIONS: &[CubeConnections] = &[
|
||||
CubeConnections {
|
||||
id: 606866102, // Protonium Clasp
|
||||
@@ -343,35 +373,7 @@ pub const CUBE_CONNECTIONS: &[CubeConnections] = &[
|
||||
},
|
||||
],
|
||||
},
|
||||
CubeConnections {
|
||||
id: 0, // (default)
|
||||
connections: &[
|
||||
CubeConnection {
|
||||
direction: (0, 0, 1),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (0, 1, 0),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (1, 0, 0),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (0, 0, -1),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (0, -1, 0),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
CubeConnection {
|
||||
direction: (-1, 0, 0),
|
||||
position: (0, 0, 0),
|
||||
},
|
||||
],
|
||||
},
|
||||
DEFAULT_CONNECTION,
|
||||
];
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
||||
446
rc_core/src/cubes/graph.rs
Normal file
446
rc_core/src/cubes/graph.rs
Normal file
@@ -0,0 +1,446 @@
|
||||
struct CubeDescriptor {
|
||||
connected_to: std::sync::RwLock<std::collections::HashMap<CellPoint, CellPoint>>,
|
||||
connections: std::sync::Arc<Vec<Connection>>,
|
||||
//references: std::sync::atomic::AtomicU64,
|
||||
health: std::sync::atomic::AtomicU32,
|
||||
}
|
||||
|
||||
impl std::clone::Clone for CubeDescriptor {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
connected_to: std::sync::RwLock::new(self.connected_to.read().unwrap().to_owned()),
|
||||
connections: self.connections.clone(),
|
||||
health: std::sync::atomic::AtomicU32::new(self.health.load(std::sync::atomic::Ordering::Relaxed)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Connection {
|
||||
position: CellPoint,
|
||||
direction: AxisDirection,
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
enum AxisDirection {
|
||||
Up, // +y
|
||||
Down, // -y
|
||||
Right, // +x
|
||||
Left, // -x
|
||||
Back, // +z,
|
||||
Front, // -z
|
||||
}
|
||||
|
||||
impl AxisDirection {
|
||||
#[inline]
|
||||
fn opposite(&self) -> Self {
|
||||
match self {
|
||||
Self::Up => Self::Down,
|
||||
Self::Down => Self::Up,
|
||||
Self::Right => Self::Left,
|
||||
Self::Left => Self::Right,
|
||||
Self::Back => Self::Front,
|
||||
Self::Front => Self::Back,
|
||||
}
|
||||
}
|
||||
|
||||
/// WARNING: will panic if you give it bad data
|
||||
fn from_i8_direction(value: (i8, i8, i8)) -> Self {
|
||||
match value {
|
||||
(0, 1, 0) => Self::Up,
|
||||
(0, -1, 0) => Self::Down,
|
||||
(1, 0, 0) => Self::Right,
|
||||
(-1, 0, 0) => Self::Left,
|
||||
(0, 0, 1) => Self::Back,
|
||||
(0, 0, -1) => Self::Front,
|
||||
_ => panic!("Invalid axial direction {:?}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CubeGraph {
|
||||
cubes: std::sync::RwLock<std::collections::HashMap<CellPoint, CubeDescriptor>>,
|
||||
connects_to: std::sync::RwLock<std::collections::HashMap<CellPoint, Vec<Connection>>>,
|
||||
base: std::sync::RwLock<Option<CellPoint>>,
|
||||
}
|
||||
|
||||
impl CubeGraph {
|
||||
pub fn with_data(r: &mut dyn std::io::Read, health_map: std::collections::HashMap<u32, u32>, root_id: u32) -> std::io::Result<Self> {
|
||||
let parsed_cubes = super::parser::Cube::parse_list(r)?;
|
||||
let mut cube_graph = std::collections::HashMap::<CellPoint, CubeDescriptor>::with_capacity(parsed_cubes.len());
|
||||
let mut connection_graph = std::collections::HashMap::<CellPoint, Vec<Connection>>::with_capacity(parsed_cubes.len());
|
||||
let mut root_location = None;
|
||||
for cube in parsed_cubes {
|
||||
let location = CellPoint {
|
||||
x: cube.x,
|
||||
y: cube.y,
|
||||
z: cube.z,
|
||||
};
|
||||
if cube.id == root_id {
|
||||
root_location = Some(location);
|
||||
}
|
||||
let conns = super::CUBE_CONNECTIONS.iter().find(|x| x.id == cube.id).unwrap_or(&super::DEFAULT_CONNECTION);
|
||||
let rot = super::CUBE_ROTATIONS[(cube.orientation & 0b01111111) as usize];
|
||||
let unit_rot = rot.normalize().unwrap();
|
||||
let is_destroyed = (cube.orientation & 0b10000000) != 0;
|
||||
if is_destroyed && cube.id != root_id { continue; }
|
||||
let cube_health = if is_destroyed {
|
||||
0
|
||||
} else if let Some(health) = health_map.get(&cube.id) {
|
||||
*health
|
||||
} else {
|
||||
log::warn!("No health data for cube id {} in CubeGraph at ({}, {}, {})", cube.id, location.x, location.y, location.z);
|
||||
1
|
||||
};
|
||||
let mut abs_connections = Vec::with_capacity(conns.connections.len());
|
||||
for conn in conns.connections.iter() {
|
||||
let conn = Self::calculate_connection(
|
||||
(location.x, location.y, location.z),
|
||||
conn,
|
||||
&unit_rot,
|
||||
);
|
||||
abs_connections.push(conn);
|
||||
let connects_to = conn.position.connects_to(conn.direction);
|
||||
let graph_pointer = Connection {
|
||||
position: location,
|
||||
direction: conn.direction,
|
||||
};
|
||||
if let Some(connects_to_point_list) = connection_graph.get_mut(&connects_to) {
|
||||
connects_to_point_list.push(graph_pointer);
|
||||
} else {
|
||||
connection_graph.insert(connects_to, vec![graph_pointer]);
|
||||
}
|
||||
}
|
||||
cube_graph.insert(location, CubeDescriptor {
|
||||
connected_to: std::sync::RwLock::new(std::collections::HashMap::with_capacity(abs_connections.len())),
|
||||
connections: std::sync::Arc::new(abs_connections),
|
||||
//references: std::sync::atomic::AtomicU64::new(0),
|
||||
health: std::sync::atomic::AtomicU32::new(cube_health),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(root) = root_location {
|
||||
let this = Self {
|
||||
cubes: std::sync::RwLock::new(cube_graph),
|
||||
connects_to: std::sync::RwLock::new(connection_graph),
|
||||
base: std::sync::RwLock::new(None),
|
||||
};
|
||||
this.rebase_on(&root);
|
||||
Ok(this)
|
||||
} else {
|
||||
Err(std::io::Error::other("Graph root cube not found"))
|
||||
}
|
||||
}
|
||||
|
||||
/*fn reset(&self) {
|
||||
for cube in self.cubes.values() {
|
||||
cube.references.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}*/
|
||||
|
||||
fn rebase_on(&self, point: &CellPoint) {
|
||||
let cubes = self.cubes.read().unwrap();
|
||||
let connects_to = self.connects_to.read().unwrap();
|
||||
if cubes.is_empty() { return; }
|
||||
if !cubes.contains_key(point) {
|
||||
log::warn!("Cannot rebase CubeGraph at point ({}, {}, {}); no cube is there", point.x, point.y, point.z);
|
||||
return;
|
||||
}
|
||||
if cubes.len() == 1 {
|
||||
// trivial case, base is the only cube (no need to connect anything)
|
||||
*self.base.write().unwrap() = Some(*point);
|
||||
return;
|
||||
}
|
||||
let mut seen = std::collections::HashSet::with_capacity(cubes.len());
|
||||
seen.insert(*point);
|
||||
let mut to_be_processed = std::collections::HashSet::new();
|
||||
to_be_processed.insert(*point);
|
||||
while !to_be_processed.is_empty() {
|
||||
let to_be_processed_now = to_be_processed.clone();
|
||||
to_be_processed.clear();
|
||||
for loc in to_be_processed_now.iter() {
|
||||
let cube = cubes.get(loc).unwrap();
|
||||
//let src_ref_count = cube.references.load(std::sync::atomic::Ordering::Relaxed);
|
||||
for conn in cube.connections.iter() {
|
||||
if cube.connected_to.read().unwrap().contains_key(&conn.position) { continue; } // already connected
|
||||
let opposite_direction = conn.direction.opposite();
|
||||
if let Some(other_conns) = connects_to.get(&conn.position) {
|
||||
for other_conn in other_conns.iter() {
|
||||
if other_conn.direction != opposite_direction { continue; }
|
||||
let other_cube = cubes.get(&other_conn.position).unwrap();
|
||||
cube.connected_to.write().unwrap().insert(conn.position, other_conn.position);
|
||||
let connected_to = conn.position.connects_to(conn.direction);
|
||||
other_cube.connected_to.write().unwrap().insert(connected_to, *loc);
|
||||
//cubes.get(&other_conn.position).unwrap().references.fetch_add(src_ref_count, std::sync::atomic::Ordering::Relaxed);
|
||||
if seen.insert(other_conn.position) {
|
||||
to_be_processed.insert(other_conn.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*self.base.write().unwrap() = Some(*point);
|
||||
}
|
||||
|
||||
pub fn add_cube(&self, point: &CellPoint, cube_id: u32, health: u32, extras: u8) {
|
||||
let conns = super::CUBE_CONNECTIONS.iter().find(|x| x.id == cube_id).unwrap_or(&super::DEFAULT_CONNECTION);
|
||||
let rot = super::CUBE_ROTATIONS[(extras & 0b01111111) as usize];
|
||||
let unit_rot = rot.normalize().unwrap();
|
||||
//let is_destroyed = health == 0 || (extras & 0b10000000) != 0;
|
||||
let mut abs_connections = Vec::with_capacity(conns.connections.len());
|
||||
//let mut ref_count = 0;
|
||||
let mut connects_to_lock = self.connects_to.write().unwrap();
|
||||
let mut connected_to = std::collections::HashMap::with_capacity(conns.connections.len());
|
||||
for conn in conns.connections.iter() {
|
||||
let conn = Self::calculate_connection(
|
||||
(point.x, point.y, point.z),
|
||||
conn,
|
||||
&unit_rot,
|
||||
);
|
||||
abs_connections.push(conn);
|
||||
let connects_to = conn.position.connects_to(conn.direction);
|
||||
let opposite_direction = conn.direction.opposite();
|
||||
let graph_pointer = Connection {
|
||||
position: *point,
|
||||
direction: conn.direction,
|
||||
};
|
||||
if let Some(connects_to_point_list) = connects_to_lock.get_mut(&connects_to) {
|
||||
connects_to_point_list.push(graph_pointer);
|
||||
} else {
|
||||
connects_to_lock.insert(connects_to, vec![graph_pointer]);
|
||||
}
|
||||
if let Some(connects_to_point_list) = connects_to_lock.get_mut(&conn.position) {
|
||||
let cubes_lock = self.cubes.read().unwrap();
|
||||
for other_cube_loc in connects_to_point_list.iter() {
|
||||
if other_cube_loc.direction != opposite_direction { continue; }
|
||||
let other_cube = cubes_lock.get(&other_cube_loc.position).unwrap();
|
||||
//ref_count += other_cube.references.load(std::sync::atomic::Ordering::Relaxed);
|
||||
other_cube.connected_to.write().unwrap().insert(connects_to, *point);
|
||||
connected_to.insert(conn.position, other_cube_loc.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(connects_to_lock);
|
||||
self.cubes.write().unwrap().insert(*point, CubeDescriptor {
|
||||
connected_to: std::sync::RwLock::new(connected_to),
|
||||
connections: std::sync::Arc::new(abs_connections),
|
||||
//references: std::sync::atomic::AtomicU64::new(ref_count),
|
||||
health: std::sync::atomic::AtomicU32::new(health),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove_cube(&self, point: &CellPoint) -> std::collections::HashSet<CellPoint> {
|
||||
self.remove_cubes_and_disconnects(&[*point])
|
||||
}
|
||||
|
||||
fn remove_cube_only(&self, point: &CellPoint, cubes: &mut std::collections::HashMap<CellPoint, CubeDescriptor>) {
|
||||
if let Some(cube) = cubes.remove(point) {
|
||||
let mut connects_to = self.connects_to.write().unwrap();
|
||||
let connected_to = cube.connected_to.read().unwrap();
|
||||
for conn in cube.connections.iter() {
|
||||
let my_connects_to = conn.position.connects_to(conn.direction);
|
||||
connects_to.get_mut(&my_connects_to).unwrap().retain(|x| !(&x.position == point && x.direction == conn.direction));
|
||||
}
|
||||
for other_conn in connected_to.values() {
|
||||
let other_cube = cubes.get(other_conn).unwrap();
|
||||
other_cube.connected_to.write().unwrap().retain(|_, conn| conn != point);
|
||||
}
|
||||
} else {
|
||||
log::warn!("Cannot remove cube at point ({}, {}, {}); no cube is there", point.x, point.y, point.z);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_cubes_and_disconnects(&self, points: &[CellPoint]) -> std::collections::HashSet<CellPoint> {
|
||||
let mut cubes = self.cubes.write().unwrap();
|
||||
for to_remove in points.iter() {
|
||||
self.remove_cube_only(to_remove, &mut cubes);
|
||||
}
|
||||
let mut chunks = ChunkTracker::with_capacity(6);
|
||||
for (point, cube) in cubes.iter() {
|
||||
chunks.add_cube(*point, cube.connected_to.read().unwrap().values());
|
||||
}
|
||||
if let Some(root) = *self.base.read().unwrap() {
|
||||
let disconnected_cubes = chunks.cubes_not_in_chunk(root);
|
||||
for to_remove in disconnected_cubes.iter() {
|
||||
if !cubes.contains_key(to_remove) { continue; }
|
||||
self.remove_cube_only(to_remove, &mut cubes);
|
||||
}
|
||||
disconnected_cubes
|
||||
} else {
|
||||
log::warn!("Cannot calculate disconnections without base cube of CubeGraph");
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn damage_cube(&self, point: &CellPoint, damage: u32) {
|
||||
let cubes = self.cubes.read().unwrap();
|
||||
if let Some(cube) = cubes.get(point) {
|
||||
cube.health.fetch_sub(damage, std::sync::atomic::Ordering::Relaxed);
|
||||
} else {
|
||||
log::warn!("Cannot damage cube at point ({}, {}, {}); no cube is there", point.x, point.y, point.z);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_connection(location: (u8, u8, u8), relative_conn: &crate::cubes::connections::CubeConnection, unit_rot: &num_quaternion::UnitQuaternion<f32>) -> Connection {
|
||||
let rotated_pos = unit_rot.rotate_vector([
|
||||
relative_conn.position.0 as f32,
|
||||
relative_conn.position.1 as f32,
|
||||
relative_conn.position.2 as f32
|
||||
]);
|
||||
let rotated_dir = unit_rot.rotate_vector([
|
||||
relative_conn.direction.0 as f32,
|
||||
relative_conn.direction.1 as f32,
|
||||
relative_conn.direction.2 as f32
|
||||
]);
|
||||
let abs_point = CellPoint {
|
||||
x: location.0.saturating_add_signed(rotated_pos[0] as i8),
|
||||
y: location.1.saturating_add_signed(rotated_pos[1] as i8),
|
||||
z: location.2.saturating_add_signed(rotated_pos[2] as i8),
|
||||
};
|
||||
let direction = AxisDirection::from_i8_direction((rotated_dir[0] as i8, rotated_dir[1] as i8, rotated_dir[2] as i8));
|
||||
Connection {
|
||||
position: abs_point,
|
||||
direction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::clone::Clone for CubeGraph {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
cubes: std::sync::RwLock::new(self.cubes.read().unwrap().to_owned()),
|
||||
connects_to: std::sync::RwLock::new(self.connects_to.read().unwrap().to_owned()),
|
||||
base: std::sync::RwLock::new(self.base.read().unwrap().to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct CellPoint {
|
||||
pub x: u8,
|
||||
pub y: u8,
|
||||
pub z: u8,
|
||||
}
|
||||
|
||||
impl CellPoint {
|
||||
#[inline]
|
||||
fn connects_to(&self, direction: AxisDirection) -> Self {
|
||||
match direction {
|
||||
AxisDirection::Up => Self {
|
||||
x: self.x,
|
||||
y: self.y + 1,
|
||||
z: self.z,
|
||||
},
|
||||
AxisDirection::Down => Self {
|
||||
x: self.x,
|
||||
y: self.y - 1,
|
||||
z: self.z,
|
||||
},
|
||||
AxisDirection::Right => Self {
|
||||
x: self.x + 1,
|
||||
y: self.y,
|
||||
z: self.z,
|
||||
},
|
||||
AxisDirection::Left => Self {
|
||||
x: self.x - 1,
|
||||
y: self.y,
|
||||
z: self.z,
|
||||
},
|
||||
AxisDirection::Back => Self {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
z: self.z + 1,
|
||||
},
|
||||
AxisDirection::Front => Self {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
z: self.z - 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<(u8, u8, u8)> for CellPoint {
|
||||
fn from(value: (u8, u8, u8)) -> Self {
|
||||
Self {
|
||||
x: value.0,
|
||||
y: value.1,
|
||||
z: value.2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChunkTracker {
|
||||
chunks: Vec<std::collections::HashSet<CellPoint>>,
|
||||
}
|
||||
|
||||
impl ChunkTracker {
|
||||
fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
chunks: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_cube<'a>(&mut self, new_cube: CellPoint, connects_to: impl std::iter::Iterator<Item=&'a CellPoint>) {
|
||||
let connections: Vec<_> = connects_to.copied().collect();
|
||||
let mut found_in_chunks = Vec::with_capacity(self.chunks.len());
|
||||
for (i, chunk) in self.chunks.iter_mut().enumerate() {
|
||||
for connected_to in connections.iter() {
|
||||
if chunk.contains(connected_to) && !found_in_chunks.contains(&i) {
|
||||
chunk.insert(new_cube);
|
||||
found_in_chunks.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if found_in_chunks.is_empty() {
|
||||
let mut new_chunk = std::collections::HashSet::<CellPoint>::new();
|
||||
new_chunk.insert(new_cube);
|
||||
for connected_to in connections.iter() {
|
||||
new_chunk.insert(*connected_to);
|
||||
}
|
||||
self.chunks.push(new_chunk);
|
||||
} else {
|
||||
self.merge_chunks(found_in_chunks, &connections);
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_chunks(&mut self, mut chunks_to_merge: Vec<usize>, connections: &[CellPoint]) {
|
||||
if chunks_to_merge.is_empty() || chunks_to_merge.len() == 1 { return; }
|
||||
// by sorting and then reversing the order it is guaranteed that
|
||||
// self.chunks.swap_remove(chunks_to_merge[i]) will not affect the index of other chunks
|
||||
chunks_to_merge.sort();
|
||||
chunks_to_merge.reverse();
|
||||
let mut super_chunk = self.chunks.swap_remove(chunks_to_merge[0]);
|
||||
for chunk_i in chunks_to_merge[1..].iter() {
|
||||
let to_merge = self.chunks.swap_remove(*chunk_i);
|
||||
for point in to_merge {
|
||||
super_chunk.insert(point);
|
||||
}
|
||||
}
|
||||
for connected_to in connections.iter() {
|
||||
super_chunk.insert(*connected_to);
|
||||
}
|
||||
self.chunks.push(super_chunk);
|
||||
}
|
||||
|
||||
fn cubes_not_in_chunk(mut self, point: CellPoint) -> std::collections::HashSet<CellPoint> {
|
||||
let chunk_indices: Vec<_> = self.chunks.iter().enumerate()
|
||||
.filter(|(_, chunk)| !chunk.contains(&point))
|
||||
.map(|(i, _)| i)
|
||||
.collect();
|
||||
self.merge_chunks(chunk_indices, &[]);
|
||||
if self.chunks[0].contains(&point) {
|
||||
if self.chunks.len() == 1 {
|
||||
Default::default()
|
||||
} else {
|
||||
self.chunks.swap_remove(1)
|
||||
}
|
||||
} else {
|
||||
self.chunks.swap_remove(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
const CRYSTAL_ID: u32 = 3950293873;
|
||||
const CLASP_ID: u32 = 606866102;
|
||||
use super::{CLASP_ID, CRYSTAL_ID};
|
||||
|
||||
pub struct CubeLocationsParser;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CubeLocationInfo {
|
||||
pub x: u8,
|
||||
pub y: u8,
|
||||
@@ -193,19 +193,12 @@ impl CubeLocationsParser {
|
||||
};
|
||||
// calculate connect target connection points
|
||||
let mut abs_connected_to = None;
|
||||
let mut target_cube_loc = None;
|
||||
for cube in cubes.iter() {
|
||||
if cube.id == CLASP_ID {
|
||||
let rot_index = cube.orientation & 0b01111111;
|
||||
let rot = &CUBE_ROTATIONS[rot_index as usize];
|
||||
log::trace!("Calculating target connection points");
|
||||
abs_connected_to = Some(calculate_absolute_connections(connected_to_connections, rot, (cube.x, cube.y, cube.z)));
|
||||
target_cube_loc = Some(CubeLocationInfo {
|
||||
x: cube.x,
|
||||
y: cube.y,
|
||||
z: cube.z,
|
||||
extras: cube.orientation,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -215,7 +208,6 @@ impl CubeLocationsParser {
|
||||
log::error!("Failed to find cube with id {} to calculate connections to", CLASP_ID);
|
||||
return Vec::default();
|
||||
};
|
||||
let target_cube_loc = target_cube_loc.unwrap();
|
||||
// find cubes connected to target (or to another relevant cube connected to the target)
|
||||
let mut to_be_sorted = std::collections::HashMap::new();
|
||||
for (i, cube) in cubes.iter().enumerate() {
|
||||
@@ -228,7 +220,6 @@ impl CubeLocationsParser {
|
||||
}
|
||||
}
|
||||
let mut sorted = Vec::with_capacity(to_be_sorted.len() + 1);
|
||||
sorted.push(target_cube_loc);
|
||||
let mut iteration = 0;
|
||||
let mut random = rand::rng();
|
||||
let mut to_be_released = Vec::new();
|
||||
|
||||
@@ -13,11 +13,17 @@ mod offsetter;
|
||||
pub use offsetter::OffsetParser;
|
||||
|
||||
mod connections;
|
||||
pub use connections::CUBE_CONNECTIONS;
|
||||
pub use connections::{CUBE_CONNECTIONS, DEFAULT_CONNECTION};
|
||||
|
||||
mod rotations;
|
||||
pub use rotations::CUBE_ROTATIONS;
|
||||
|
||||
mod graph;
|
||||
pub use graph::{CubeGraph, CellPoint};
|
||||
|
||||
pub const CRYSTAL_ID: u32 = 3950293873;
|
||||
pub const CLASP_ID: u32 = 606866102;
|
||||
|
||||
//pub mod prefabs;
|
||||
|
||||
pub struct CubeParsers {
|
||||
|
||||
@@ -723,10 +723,10 @@ struct BaseTickInfo {
|
||||
impl BaseTracker {
|
||||
const DOMINATING_MULT: f32 = 4.0;
|
||||
|
||||
fn new<'a>(bases_iter: impl std::iter::Iterator<Item=&'a u8>, crystals: &[oj_rc_core::cubes::CubeLocationInfo], ba_config: &oj_rc_core::data::battle_arena_config::BattleArenaData) -> Self {
|
||||
fn new<'a>(bases_iter: impl std::iter::Iterator<Item=&'a u8>, crystals: &[oj_rc_core::cubes::CubeLocationInfo], ba_config: &oj_rc_core::data::battle_arena_config::BattleArenaData, base_graph: &oj_rc_core::cubes::CubeGraph) -> Self {
|
||||
let mut bases = std::collections::HashMap::new();
|
||||
for base_id in bases_iter {
|
||||
bases.insert(*base_id, BaseInfo::new(crystals));
|
||||
bases.insert(*base_id, BaseInfo::new(crystals, base_graph));
|
||||
}
|
||||
Self {
|
||||
bases,
|
||||
@@ -735,30 +735,6 @@ impl BaseTracker {
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_partial_base_heal(&self, crystals: &[oj_rc_core::cubes::CubeLocationInfo], crystal_health: u32, base_id: u8, base: &BaseInfo, old_index: usize, new_index: usize) -> rlnl::events::HealedCubes {
|
||||
//let base = self.bases.get(&base_id).unwrap();
|
||||
let target_crystals = &crystals[old_index..new_index];
|
||||
for crystal_i in old_index..new_index {
|
||||
base.crystals_healths[crystal_i].store(crystal_health, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
rlnl::events::HealedCubes {
|
||||
healed_machine: base_id as u16,
|
||||
type_performing_healing: rlnl::types::TargetType::TeamBase,
|
||||
target_type: rlnl::types::TargetType::TeamBase,
|
||||
num_healed_cubes: target_crystals.len() as _,
|
||||
hit_cubes: target_crystals
|
||||
.iter()
|
||||
.map(|loc| {
|
||||
log::trace!("Healing base cube at grid ({}, {}, {})", loc.x, loc.y, loc.z);
|
||||
rlnl::types::HitCubeInfo {
|
||||
pos: rlnl::types::Byte3 { x: loc.x, y: loc.y, z: loc.z, },
|
||||
damage: crystal_health as i32,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn tick(&self, tick_info: &PointTickInfo, crystals: &[oj_rc_core::cubes::CubeLocationInfo], generic: &crate::matches::GenericGamemodeEngine<BattleArenaLogic>, capture_points_count: usize, crystal_health: u32, ba_config: &oj_rc_core::data::battle_arena_config::BattleArenaData) -> BaseTickInfo {
|
||||
let multiplier = if let Some(dominant_team) = tick_info.dominating {
|
||||
if !self.dominating.swap(true, std::sync::atomic::Ordering::Relaxed) {
|
||||
@@ -790,24 +766,26 @@ impl BaseTracker {
|
||||
* ((PointTracker::TICK_MS as f32) / (generic.game_duration.as_millis() as f32))
|
||||
* ((self.bases.len() as f32) / (capture_points_count as f32));
|
||||
let increment = tick_info.delta as f32 * (*owned_points as f32) * one_tick * multiplier;
|
||||
#[cfg(debug_assertions)]
|
||||
let increment = increment + 0.1;
|
||||
let old_float_index = tracked_base.cube_index.fetch_add(increment, std::sync::atomic::Ordering::SeqCst);
|
||||
let new_float_index = old_float_index + increment;
|
||||
let old_index = (old_float_index.ceil() as usize).clamp(0, crystals.len());
|
||||
let new_index = (new_float_index.ceil() as usize).clamp(0, crystals.len());
|
||||
if new_index != old_index {
|
||||
log::trace!("Base {} increment passed a crystal index barrier", base_id);
|
||||
log::trace!("Base {} increment passed a crystal index barrier in game {}", base_id, generic.game_guid());
|
||||
let first_damaged = tracked_base.first_damaged(old_index, crystal_health);
|
||||
let payload = if new_index - old_index == 1 && first_damaged.is_some() {
|
||||
// undo cube_index update
|
||||
tracked_base.cube_index.fetch_sub(increment, std::sync::atomic::Ordering::SeqCst);
|
||||
log::trace!("Skipping increment in favour of healing damaged/destroyed cube");
|
||||
tracked_base.cube_index.fetch_sub(1.0, std::sync::atomic::Ordering::SeqCst);
|
||||
//log::info!("Skipping increment in favour of healing damaged/destroyed cube");
|
||||
#[allow(clippy::unnecessary_unwrap)] // have you seen the mess this would be with another if statement?
|
||||
let first_damaged = first_damaged.unwrap();
|
||||
//let healing = crystal_health - tracked_base.calculate_crystal_health(first_damaged, crystal_health);
|
||||
let existing_health = tracked_base.calculate_crystal_health(first_damaged);
|
||||
let healing = crystal_health - existing_health;
|
||||
tracked_base.crystals_healths[first_damaged].store(crystal_health, std::sync::atomic::Ordering::Relaxed);
|
||||
let target_crystal = &crystals[first_damaged];
|
||||
if existing_health == 0 {
|
||||
tracked_base.add_crystals_update_graph(vec![target_crystal.to_owned()], crystal_health);
|
||||
}
|
||||
rlnl::events::HealedCubes {
|
||||
healed_machine: *base_id as u16,
|
||||
type_performing_healing: rlnl::types::TargetType::TeamBase,
|
||||
@@ -816,30 +794,13 @@ impl BaseTracker {
|
||||
hit_cubes: vec![
|
||||
rlnl::types::HitCubeInfo {
|
||||
pos: rlnl::types::Byte3 { x: target_crystal.x, y: target_crystal.y, z: target_crystal.z, },
|
||||
//damage: healing as i32,
|
||||
damage: crystal_health.saturating_sub(2) as i32,
|
||||
damage: healing as i32,
|
||||
}
|
||||
],
|
||||
}
|
||||
} else {
|
||||
self.apply_partial_base_heal(crystals, crystal_health, *base_id, tracked_base, old_index, new_index)
|
||||
/*let target_crystals = &crystals[old_index..new_index];
|
||||
for crystal_i in old_index..new_index {
|
||||
tracked_base.crystals_healths[crystal_i].store(u8::MAX, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
rlnl::events::HealedCubes {
|
||||
healed_machine: *base_id as u16,
|
||||
type_performing_healing: rlnl::types::TargetType::TeamBase,
|
||||
target_type: rlnl::types::TargetType::TeamBase,
|
||||
num_healed_cubes: target_crystals.len() as _,
|
||||
hit_cubes: target_crystals
|
||||
.iter()
|
||||
.map(|loc| rlnl::types::HitCubeInfo {
|
||||
pos: rlnl::types::Byte3 { x: loc.x, y: loc.y, z: loc.z, },
|
||||
damage: crystal_health as i32,
|
||||
})
|
||||
.collect(),
|
||||
}*/
|
||||
//log::info!("Doing base heal tick");
|
||||
tracked_base.apply_partial_base_heal(crystals, crystal_health, *base_id, old_index, new_index)
|
||||
};
|
||||
|
||||
generic.broadcast(
|
||||
@@ -859,7 +820,6 @@ impl BaseTracker {
|
||||
|
||||
if new_index == crystals.len() {
|
||||
// team base is charged to 100%
|
||||
//self.do_win(*base_id, WinMode::BaseFull, generic).await;
|
||||
return BaseTickInfo {
|
||||
win: Some((*base_id, WinMode::BaseFull)),
|
||||
};
|
||||
@@ -876,16 +836,18 @@ impl BaseTracker {
|
||||
|
||||
struct BaseInfo {
|
||||
cube_index: atomic_float::AtomicF32,
|
||||
crystals_healths: Vec<std::sync::atomic::AtomicU32>,
|
||||
crystals_healths: std::sync::Arc<Vec<std::sync::atomic::AtomicU32>>,
|
||||
base_graph: std::sync::Arc<oj_rc_core::cubes::CubeGraph>,
|
||||
}
|
||||
|
||||
impl BaseInfo {
|
||||
fn new(crystals: &[oj_rc_core::cubes::CubeLocationInfo]) -> Self {
|
||||
fn new(crystals: &[oj_rc_core::cubes::CubeLocationInfo], base_graph: &oj_rc_core::cubes::CubeGraph) -> Self {
|
||||
Self {
|
||||
cube_index: atomic_float::AtomicF32::new(0.0),
|
||||
crystals_healths: (0..crystals.len())
|
||||
crystals_healths: std::sync::Arc::new((0..crystals.len())
|
||||
.map(|_| std::sync::atomic::AtomicU32::new(0))
|
||||
.collect()
|
||||
.collect()),
|
||||
base_graph: std::sync::Arc::new(base_graph.to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -912,7 +874,7 @@ impl BaseInfo {
|
||||
fn first_damaged(&self, old_index: usize, max_health: u32) -> Option<usize> {
|
||||
for i in 0..old_index {
|
||||
let health = self.calculate_crystal_health(i);
|
||||
if health != 0 && health != max_health {
|
||||
if health < max_health {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
@@ -935,7 +897,7 @@ impl BaseInfo {
|
||||
fn damage_crystal(&self, i: usize, damage: i32) -> bool {
|
||||
let damage = damage as u32;
|
||||
let old_health = self.crystals_healths[i].fetch_sub(damage, std::sync::atomic::Ordering::Relaxed);
|
||||
log::info!("Crystal {} damaged (was {}, now {}, delta {})", i, old_health, old_health.saturating_sub(damage), damage);
|
||||
//log::info!("Crystal {} damaged (was {}, now {}, delta {})", i, old_health, old_health.saturating_sub(damage), damage);
|
||||
if damage > old_health {
|
||||
// guarantee underflow behaviour
|
||||
self.crystals_healths[i].store(0, std::sync::atomic::Ordering::Relaxed);
|
||||
@@ -943,8 +905,60 @@ impl BaseInfo {
|
||||
old_health != 0 && damage >= old_health
|
||||
}
|
||||
|
||||
fn destroy_crystals_update_graph(
|
||||
&self,
|
||||
positions: Vec<rlnl::types::Byte3>,
|
||||
crystal_positions: &std::sync::Arc<Vec<oj_rc_core::cubes::CubeLocationInfo>>,
|
||||
) {
|
||||
let base_graph = self.base_graph.clone();
|
||||
let crystal_healths = self.crystals_healths.clone();
|
||||
let crystal_positions = crystal_positions.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
for pos in positions {
|
||||
let actual_destroyed = base_graph.remove_cube(&oj_rc_core::cubes::CellPoint {
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
z: pos.z,
|
||||
});
|
||||
for (i, loc) in crystal_positions.iter().enumerate() {
|
||||
let point = oj_rc_core::cubes::CellPoint {
|
||||
x: loc.x,
|
||||
y: loc.y,
|
||||
z: loc.z,
|
||||
};
|
||||
if actual_destroyed.contains(&point) {
|
||||
crystal_healths[i].store(0, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn add_crystals_update_graph(
|
||||
&self,
|
||||
positions: Vec<oj_rc_core::cubes::CubeLocationInfo>,
|
||||
health: u32,
|
||||
) {
|
||||
let base_graph = self.base_graph.clone();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
for pos in positions {
|
||||
let point = oj_rc_core::cubes::CellPoint {
|
||||
x: pos.x,
|
||||
y: pos.y,
|
||||
z: pos.z,
|
||||
};
|
||||
base_graph.add_cube(
|
||||
&point,
|
||||
oj_rc_core::cubes::CRYSTAL_ID,
|
||||
health,
|
||||
pos.orientation(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// returns whether crystal exists
|
||||
fn destroy_crystal_at_pos(&self, pos: rlnl::types::Byte3, crystals: &[oj_rc_core::cubes::CubeLocationInfo]) -> bool {
|
||||
fn destroy_crystal_at_pos(&self, pos: rlnl::types::Byte3, crystals: &std::sync::Arc<Vec<oj_rc_core::cubes::CubeLocationInfo>>) -> bool {
|
||||
if let Some((index, _)) = crystals.iter().enumerate().find(|(_i, crystal)| crystal.x == pos.x && crystal.y == pos.y && crystal.z == pos.z) {
|
||||
let max_index = self.max_index();
|
||||
if index > max_index { return false; }
|
||||
@@ -980,6 +994,34 @@ impl BaseInfo {
|
||||
hit_cubes: target_crystals,
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_partial_base_heal(&self, crystals: &[oj_rc_core::cubes::CubeLocationInfo], crystal_health: u32, base_id: u8, old_index: usize, new_index: usize) -> rlnl::events::HealedCubes {
|
||||
//let base = self.bases.get(&base_id).unwrap();
|
||||
let target_crystals = &crystals[old_index..new_index];
|
||||
let mut healed = Vec::with_capacity(target_crystals.len());
|
||||
#[allow(clippy::needless_range_loop)] // this suggestion is deranged and way too long
|
||||
for crystal_i in old_index..new_index {
|
||||
self.crystals_healths[crystal_i].store(crystal_health, std::sync::atomic::Ordering::Relaxed);
|
||||
healed.push(crystals[crystal_i].clone());
|
||||
}
|
||||
self.add_crystals_update_graph(healed, crystal_health);
|
||||
rlnl::events::HealedCubes {
|
||||
healed_machine: base_id as u16,
|
||||
type_performing_healing: rlnl::types::TargetType::TeamBase,
|
||||
target_type: rlnl::types::TargetType::TeamBase,
|
||||
num_healed_cubes: target_crystals.len() as _,
|
||||
hit_cubes: target_crystals
|
||||
.iter()
|
||||
.map(|loc| {
|
||||
log::trace!("Healing base cube at grid ({}, {}, {})", loc.x, loc.y, loc.z);
|
||||
rlnl::types::HitCubeInfo {
|
||||
pos: rlnl::types::Byte3 { x: loc.x, y: loc.y, z: loc.z, },
|
||||
damage: crystal_health as i32,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum WinMode {
|
||||
@@ -989,6 +1031,13 @@ enum WinMode {
|
||||
Surrender,
|
||||
}
|
||||
|
||||
fn crystal_health_map(crystal_health: u32) -> std::collections::HashMap<u32, u32> {
|
||||
let mut map = std::collections::HashMap::with_capacity(2);
|
||||
map.insert(oj_rc_core::cubes::CLASP_ID, 1);
|
||||
map.insert(oj_rc_core::cubes::CRYSTAL_ID, crystal_health);
|
||||
map
|
||||
}
|
||||
|
||||
pub struct BattleArenaLogic {
|
||||
respawn_full_heal_duration: f32,
|
||||
respawn_heal_duration: f32,
|
||||
@@ -1006,6 +1055,11 @@ impl BattleArenaLogic {
|
||||
pub fn new(config: &oj_rc_core::data::game_mode::GameModeConfig, map: &oj_rc_core::persist::config::MapConfig, players: &[oj_rc_core::persist::user::PlayerDescriptor], ba_config: oj_rc_core::data::battle_arena_config::BattleArenaData, crystals: std::sync::Arc<Vec<oj_rc_core::cubes::CubeLocationInfo>>) -> Self {
|
||||
//let min_y = crystals.iter().min_by_key(|x| x.y).unwrap();
|
||||
//log::warn!("First crystal is as ({}, {}, {})", min_y.x, min_y.y, min_y.z);
|
||||
let base_graph = oj_rc_core::cubes::CubeGraph::with_data(
|
||||
&mut std::io::Cursor::new(&ba_config.base_machine_map),
|
||||
crystal_health_map(ba_config.protonium_health as u32),
|
||||
oj_rc_core::cubes::CLASP_ID,
|
||||
).expect("Invalid Battle Arena base cube data");
|
||||
Self {
|
||||
respawn_full_heal_duration: config.respawn_full_heal_duration,
|
||||
respawn_heal_duration: config.respawn_heal_duration,
|
||||
@@ -1013,7 +1067,7 @@ impl BattleArenaLogic {
|
||||
player_tracking: PlayerTracker::new(players),
|
||||
capture_tracking: PointTracker::new(map.capture_points.iter().map(|(_, speed)| *speed)),
|
||||
surrender_tracking: super::trackers::SurrenderGameTracker::new(),
|
||||
base_tracking: BaseTracker::new(map.bases.keys(), &crystals, &ba_config),
|
||||
base_tracking: BaseTracker::new(map.bases.keys(), &crystals, &ba_config, &base_graph),
|
||||
//cube_parser,
|
||||
//ba_base: teambase,
|
||||
//ba_equalizer: equalizer,
|
||||
@@ -1184,6 +1238,7 @@ impl BattleArenaLogic {
|
||||
let mut total_destroyed = 0;
|
||||
let mut total_damaged = 0;
|
||||
let mut actual_cube_damage = Vec::with_capacity(cube_damage.hit_cubes.len());
|
||||
let mut destroyed_cubes_positions = Vec::with_capacity(cube_damage.hit_cubes.len());
|
||||
if let Some(base) = self.base_tracking.bases.get(&base_id) {
|
||||
for hit_cube in cube_damage.hit_cubes.iter() {
|
||||
if let Some(damage) = hit_cube.status.damage {
|
||||
@@ -1195,15 +1250,16 @@ impl BattleArenaLogic {
|
||||
}
|
||||
} else if matches!(hit_cube.status.ty, rlnl::types::CubeHistoryEventType::Destroy) {
|
||||
if base.destroy_crystal_at_pos(hit_cube.loc, &self.crystals) {
|
||||
// TODO handle cube graph disconnects
|
||||
actual_cube_damage.push(hit_cube.to_owned());
|
||||
destroyed_cubes_positions.push(hit_cube.loc);
|
||||
total_destroyed += 1;
|
||||
} else {
|
||||
log::warn!("Could not destroy cube with destroy status");
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("Destroyed {} ({} damaged) base cubes; {}/{} valid", total_destroyed, total_damaged, actual_cube_damage.len(), cube_damage.hit_cubes.len());
|
||||
base.destroy_crystals_update_graph(destroyed_cubes_positions, &self.crystals);
|
||||
log::debug!("Destroyed {} ({} damaged) base cubes; {}/{} valid, game {}", total_destroyed, total_damaged, actual_cube_damage.len(), cube_damage.hit_cubes.len(), generic.game_guid());
|
||||
let actual_damage_data = actual_damage_data(actual_cube_damage);
|
||||
generic.broadcast(
|
||||
actual_damage_data.event,
|
||||
@@ -1221,11 +1277,10 @@ impl BattleArenaLogic {
|
||||
let new_float_index = old_float_index + total_destroyed_f32;
|
||||
let old_index = (old_float_index.ceil() as usize).clamp(0, self.crystals.len());
|
||||
let new_index = (new_float_index.ceil() as usize).clamp(0, self.crystals.len());
|
||||
let payload = self.base_tracking.apply_partial_base_heal(
|
||||
let payload = base.apply_partial_base_heal(
|
||||
&self.crystals,
|
||||
self.config.protonium_health as u32,
|
||||
team_id,
|
||||
base,
|
||||
old_index,
|
||||
new_index,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user