diff --git a/.vscode/launch.json b/.vscode/launch.json index 83497e5..532bc93 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -899,6 +899,27 @@ "RUST_BACKTRACE": "1" } }, + { + "type": "lldb", + "request": "launch", + "name": "face_to_face1", + "cargo": { + "args": [ + "build", + "--bin=face_to_face1", + "--package=examples", + ], + "filter": { + "name": "face_to_face1", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, { "type": "lldb", "request": "launch", @@ -943,5 +964,266 @@ "RUST_BACKTRACE": "1" } }, + { + "type": "lldb", + "request": "launch", + "name": "face_to_face_with_one_side_brickwall1 --release", + "cargo": { + "args": [ + "build", + "--bin=face_to_face_with_one_side_brickwall1", + "--package=examples", + "--release" + ], + "filter": { + "name": "face_to_face_with_one_side_brickwall1", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "face_to_face_with_one_side_brickwall2 --release", + "cargo": { + "args": [ + "build", + "--bin=face_to_face_with_one_side_brickwall2", + "--package=examples", + "--release" + ], + "filter": { + "name": "face_to_face_with_one_side_brickwall2", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "face_to_face_with_one_side_brickwall3 --release", + "cargo": { + "args": [ + "build", + "--bin=face_to_face_with_one_side_brickwall3", + "--package=examples", + "--release" + ], + "filter": { + "name": "face_to_face_with_one_side_brickwall3", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "face_to_face_opaque1 --release", + "cargo": { + "args": [ + "build", + "--bin=face_to_face_opaque1", + "--package=examples", + "--release" + ], + "filter": { + "name": "face_to_face_opaque1", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "face_to_face_opaque2 --release", + "cargo": { + "args": [ + "build", + "--bin=face_to_face_opaque2", + "--package=examples", + "--release" + ], + "filter": { + "name": "face_to_face_opaque2", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "running_with_one_side_brickwall1 --release", + "cargo": { + "args": [ + "build", + "--bin=running_with_one_side_brickwall1", + "--package=examples", + "--release" + ], + "filter": { + "name": "running_with_one_side_brickwall1", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "walking_with_one_side_brickwall1 --release", + "cargo": { + "args": [ + "build", + "--bin=walking_with_one_side_brickwall1", + "--package=examples", + "--release" + ], + "filter": { + "name": "walking_with_one_side_brickwall1", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "walking_with_one_side_brickwall1", + "cargo": { + "args": [ + "build", + "--bin=walking_with_one_side_brickwall1", + "--package=examples", + ], + "filter": { + "name": "walking_with_one_side_brickwall1", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "walking_with_one_side_brickwall2 --release", + "cargo": { + "args": [ + "build", + "--bin=walking_with_one_side_brickwall2", + "--package=examples", + "--release" + ], + "filter": { + "name": "walking_with_one_side_brickwall2", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "walking_with_one_side_brickwall2", + "cargo": { + "args": [ + "build", + "--bin=walking_with_one_side_brickwall2", + "--package=examples" + ], + "filter": { + "name": "walking_with_one_side_brickwall2", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "walking_with_one_side_brickwall3 --release", + "cargo": { + "args": [ + "build", + "--bin=walking_with_one_side_brickwall3", + "--package=examples", + "--release" + ], + "filter": { + "name": "walking_with_one_side_brickwall3", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, + { + "type": "lldb", + "request": "launch", + "name": "walking_with_one_side_brickwall3", + "cargo": { + "args": [ + "build", + "--bin=walking_with_one_side_brickwall3", + "--package=examples" + ], + "filter": { + "name": "walking_with_one_side_brickwall3", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "env": { + "RUST_BACKTRACE": "1" + } + }, ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index bced448..2cd2e73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1482,6 +1482,7 @@ dependencies = [ "battle_gui", "battle_server", "battle_tools", + "glam 0.22.0", "oc_core", "thiserror", ] diff --git a/battle_core/src/behavior/mod.rs b/battle_core/src/behavior/mod.rs index dd6b243..ca07f41 100644 --- a/battle_core/src/behavior/mod.rs +++ b/battle_core/src/behavior/mod.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; pub mod feeling; pub mod gesture; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub enum Body { StandUp, Crouched, @@ -148,6 +148,7 @@ impl Behavior { pub fn posture(&self) -> Posture { // TODO : posture can be different on same behavior (like with SuppressFire, EngageSoldier) + // FIXME: Clarify which usage with `Body` !! match self { Behavior::MoveTo(_) | Behavior::MoveFastTo(_) | Behavior::Idle(_) => Posture::StandUp, Behavior::Defend(_) diff --git a/battle_core/src/config.rs b/battle_core/src/config.rs index 7830f6c..bbf28c7 100644 --- a/battle_core/src/config.rs +++ b/battle_core/src/config.rs @@ -12,6 +12,7 @@ use strum::IntoEnumIterator; pub const DEFAULT_SERVER_REP_ADDRESS: &str = "tcp://0.0.0.0:4255"; pub const DEFAULT_SERVER_PUB_ADDRESS: &str = "tcp://0.0.0.0:4256"; /// +pub const TARGET_CYCLE_DURATION_US: u64 = 16666; pub const TARGET_FPS: u64 = 60; pub const SOLDIER_UPDATE_FREQ: u64 = 1; pub const FLAGS_UPDATE_FREQ: u64 = 120; @@ -41,7 +42,7 @@ pub const VISIBILITY_DEAD_MODIFIER: f32 = 0.0; pub const VISIBILITY_UNCONSCIOUS_MODIFIER: f32 = 0.0; /// pub const TILE_TYPE_OPACITY_SHORT_GRASS: f32 = 0.0; -pub const TILE_TYPE_OPACITY_MIDDLE_GRASS: f32 = 0.008; +pub const TILE_TYPE_OPACITY_MIDDLE_GRASS: f32 = 0.002; pub const TILE_TYPE_OPACITY_HIGH_GRASS: f32 = 0.1; pub const TILE_TYPE_OPACITY_DIRT: f32 = 0.0; pub const TILE_TYPE_OPACITY_CONCRETE: f32 = 0.0; @@ -87,6 +88,7 @@ pub const COVER_DISTANCE: i32 = 6; // Visibility computing must consider firsts tiles differently pub const VISIBILITY_FIRSTS: usize = 6; pub const VISIBLE_STARTS_AT: f32 = 0.5; +pub const TARGET_ALTERATION_BY_OPACITY_FACTOR: f32 = 8.; // When compute visibility, configure here each pixels step of line which me considered pub const VISIBILITY_PIXEL_STEPS: usize = 5; // When compute coverage, configure here each pixels step of line which me considered @@ -103,6 +105,7 @@ pub const CAN_STANDUP_AFTER: u64 = TARGET_FPS * 60 * 10; #[derive(Debug, Clone)] pub struct ServerConfig { pub send_debug_points: bool, + pub target_cycle_duration_us: u64, pub flags_update_freq: u64, pub soldier_update_freq: u64, pub soldier_animate_freq: u64, @@ -115,6 +118,7 @@ pub struct ServerConfig { pub feeling_decreasing_freq: u64, pub visibility_firsts: usize, pub visible_starts_at: f32, + pub target_alteration_by_opacity_factor: f32, pub visibility_idle_standup_modifier: f32, pub visibility_idle_crouch_modifier: f32, pub visibility_idle_lying_modifier: f32, @@ -167,6 +171,7 @@ impl Default for ServerConfig { Self { send_debug_points: false, + target_cycle_duration_us: TARGET_CYCLE_DURATION_US, /// Frequency of flags update flags_update_freq: FLAGS_UPDATE_FREQ, /// Frequency of soldier update : @@ -194,6 +199,7 @@ impl Default for ServerConfig { /// visibility_firsts: VISIBILITY_FIRSTS, visible_starts_at: VISIBLE_STARTS_AT, + target_alteration_by_opacity_factor: TARGET_ALTERATION_BY_OPACITY_FACTOR, visibility_idle_standup_modifier: VISIBILITY_IDLE_STANDUP_MODIFIER, visibility_idle_crouch_modifier: VISIBILITY_IDLE_CROUCH_MODIFIER, @@ -317,7 +323,7 @@ impl ServerConfig { match tile_type { TileType::ShortGrass => self.tile_type_opacity_short_grass, TileType::MiddleGrass => self.tile_type_opacity_middle_grass, - TileType::HighGrass => self.tile_type_opacity_middle_grass, + TileType::HighGrass => self.tile_type_opacity_high_grass, TileType::Dirt => self.tile_type_opacity_dirt, TileType::Concrete => self.tile_type_opacity_concrete, TileType::Mud => self.tile_type_opacity_mud, @@ -337,6 +343,7 @@ impl ServerConfig { pub fn react(&mut self, message: &ChangeConfigMessage) { match message { ChangeConfigMessage::SendDebugPoints(v) => self.send_debug_points = *v, + ChangeConfigMessage::TargetCycleDuration(v) => self.target_cycle_duration_us = *v, ChangeConfigMessage::SoldierUpdateFreq(v) => self.soldier_update_freq = *v, ChangeConfigMessage::SoldierAnimateFreq(v) => self.soldier_animate_freq = *v, ChangeConfigMessage::InteriorsUpdateFreq(v) => self.interiors_update_freq = *v, @@ -410,6 +417,7 @@ impl Default for GuiConfig { #[derive(Debug, Serialize, Deserialize, Clone)] pub enum ChangeConfigMessage { SendDebugPoints(bool), + TargetCycleDuration(u64), SoldierUpdateFreq(u64), SoldierAnimateFreq(u64), InteriorsUpdateFreq(u64), diff --git a/battle_core/src/entity/soldier.rs b/battle_core/src/entity/soldier.rs index 29fadfd..936e729 100644 --- a/battle_core/src/entity/soldier.rs +++ b/battle_core/src/entity/soldier.rs @@ -296,6 +296,23 @@ impl Soldier { let weapon_animation_type = WeaponAnimationType::from(&animation_type); (animation_type, weapon_animation_type) } + + pub fn body(&self) -> Body { + match self.behavior { + Behavior::MoveTo(_) => Body::StandUp, + Behavior::MoveFastTo(_) => Body::StandUp, + Behavior::SneakTo(_) => Body::Lying, + Behavior::DriveTo(_) => Body::Crouched, + Behavior::RotateTo(_) => Body::Crouched, + Behavior::Idle(body) => body, + Behavior::Defend(_) => Body::Lying, + Behavior::Hide(_) => Body::Lying, + Behavior::Dead => Body::Lying, + Behavior::Unconscious => Body::Lying, + Behavior::SuppressFire(_) => Body::Lying, + Behavior::EngageSoldier(_) => Body::Lying, + } + } } impl From<&SoldierDeployment> for Soldier { diff --git a/battle_core/src/map/terrain.rs b/battle_core/src/map/terrain.rs index 77625df..2fdaa50 100644 --- a/battle_core/src/map/terrain.rs +++ b/battle_core/src/map/terrain.rs @@ -2,7 +2,7 @@ use std::{fmt::Display, str::FromStr}; use crate::{game::posture::Posture, types::Coverage}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum TileType { ShortGrass, MiddleGrass, @@ -113,17 +113,37 @@ impl TileType { TileType::Mud => Some(Coverage(0.3)), TileType::Concrete => None, TileType::BrickWall => Some(Coverage(0.8)), - TileType::Trunk => Some(Coverage(0.9)), + TileType::Trunk => Some(Coverage(0.7)), TileType::Water => None, TileType::DeepWater => None, TileType::Underbrush => None, TileType::LightUnderbrush => None, TileType::MiddleWoodLogs => Some(Coverage(0.7)), TileType::Hedge => Some(Coverage(0.15)), - TileType::MiddleRock => Some(Coverage(0.9)), + TileType::MiddleRock => Some(Coverage(0.75)), }, } } + + pub fn block_bullet(&self) -> bool { + match self { + TileType::ShortGrass => false, + TileType::MiddleGrass => false, + TileType::HighGrass => false, + TileType::Dirt => false, + TileType::Mud => false, + TileType::Concrete => false, + TileType::BrickWall => true, + TileType::Trunk => true, + TileType::Water => false, + TileType::DeepWater => false, + TileType::Underbrush => false, + TileType::LightUnderbrush => false, + TileType::MiddleWoodLogs => false, // true ? + TileType::Hedge => false, + TileType::MiddleRock => false, // true ? + } + } } #[derive(Debug)] diff --git a/battle_core/src/physics/coverage.rs b/battle_core/src/physics/coverage.rs index ff7eb38..ee64906 100644 --- a/battle_core/src/physics/coverage.rs +++ b/battle_core/src/physics/coverage.rs @@ -25,18 +25,37 @@ impl<'a> SoldierCovered<'a> { } } - pub fn compute(&self) -> bool { + pub fn compute(&self, force_target_tile: bool) -> bool { + // Make bullet path from the end to get target soldier tiles let pixels = Bresenham::new( - ( - self.bullet_fire.from().x as isize, - self.bullet_fire.from().y as isize, - ), ( self.bullet_fire.to().x as isize, self.bullet_fire.to().y as isize, ), + ( + self.bullet_fire.from().x as isize, + self.bullet_fire.from().y as isize, + ), ); + if force_target_tile { + let target_grid_point = self + .map + .grid_point_from_world_point(&self.soldier.world_point()); + + if let Some(tile) = self + .map + .terrain_tiles() + .get((target_grid_point.y * self.map.width() as i32 + target_grid_point.x) as usize) + { + if let Some(coverage) = tile.type_().coverage(&self.soldier.behavior().posture()) { + let mut rng = rand::thread_rng(); + let value: f32 = rng.gen(); + return value <= coverage.0; + } + } + } + let mut visited_grid_points = vec![]; for (pixel_x, pixel_y) in pixels.step_by(COVERAGE_PIXEL_STEPS) { let grid_point = self diff --git a/battle_core/src/physics/visibility.rs b/battle_core/src/physics/visibility.rs index 3dc3fec..a886c61 100644 --- a/battle_core/src/physics/visibility.rs +++ b/battle_core/src/physics/visibility.rs @@ -1,19 +1,20 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use bresenham::Bresenham; +use glam::Vec2; +use rand::Rng; use serde::{Deserialize, Serialize}; use crate::{ config::{ServerConfig, VISIBILITY_FIRSTS, VISIBILITY_PIXEL_STEPS}, entity::soldier::Soldier, + game::Side, map::Map, types::{Distance, GridPath, SoldierIndex, WorldPoint}, }; use super::utils::distance_between_points; -pub const VISIBLE_OPACITY_LIMIT: f32 = 0.5; - #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct Visibilities { visibilities: HashMap<(SoldierIndex, SoldierIndex), Visibility>, @@ -30,15 +31,29 @@ impl Visibilities { self.visibilities.get(soldiers) } - pub fn visibles_soldiers_by_soldier(&self, soldier: &Soldier) -> Vec<&Visibility> { + pub fn visibles_soldiers_by_side(&self, side: &Side) -> Vec { self.visibilities .values() - .filter(|v| { - v.from_soldier == Some(soldier.uuid()) && v.to_soldier.is_some() && v.visible + .filter(|v| v.from_side == Some(*side) && v.to_soldier.is_some() && v.visible) + .map(|v| { + v.to_soldier + .expect("Previous line must test v.to_soldier.is_some()") }) .collect() } + pub fn visibles_soldiers_by_soldiers(&self, soldiers: Vec) -> Vec { + self.visibilities + .values() + .filter(|v| v.from_soldier.is_some() && v.to_soldier.is_some()) + .filter(|v| soldiers.contains(&v.from_soldier.expect("Must be filtered previous line"))) + .filter(|v| v.visible) + .map(|v| v.to_soldier.expect("Must be filtered previous line")) + .collect::>() + .into_iter() + .collect() + } + pub fn visibles_soldiers(&self) -> Vec<&Visibility> { self.visibilities .values() @@ -59,12 +74,17 @@ impl Visibilities { pub struct Visibility { pub from: WorldPoint, pub from_soldier: Option, + pub from_side: Option, pub to: WorldPoint, pub to_soldier: Option, + pub altered_to: WorldPoint, pub path_final_opacity: f32, pub to_scene_item_opacity: f32, pub opacity_segments: Vec<(WorldPoint, f32)>, pub visible: bool, + /// true if something will (probably) intercept bullets + /// before final point (wall, trunk, etc) + pub blocked: bool, pub distance: Distance, pub break_point: Option, } @@ -78,12 +98,15 @@ impl Visibility { Self { from: from_point, from_soldier: Some(from_soldier.uuid()), + from_side: Some(*from_soldier.side()), to: to_point, + altered_to: to_point, to_soldier: Some(to_soldier.uuid()), opacity_segments: vec![], path_final_opacity: 999., to_scene_item_opacity: 999., visible: false, + blocked: false, distance, break_point: Some(from_point), } @@ -101,7 +124,6 @@ impl Visibility { let last_shoot_frame_i = to_soldier.last_shoot_frame_i(); let by_behavior_modifier: f32 = config.visibility_behavior_modifier(to_soldier.behavior()); - let exclude_lasts = if last_shoot_frame_i + config.visibility_by_last_frame_shoot >= frame_i { config.visibility_by_last_frame_shoot_distance @@ -109,15 +131,21 @@ impl Visibility { 0 }; - let (mut to_soldier_item_opacity, opacity_segments, path_final_opacity, break_point) = - Self::between_points_raw( - config, - &from_point, - &to_point, - map, - config.visibility_firsts, - exclude_lasts, - ); + let ( + mut to_soldier_item_opacity, + opacity_segments, + path_final_opacity, + break_point, + blocked, + altered_to, + ) = Self::between_points_raw( + config, + &from_point, + &to_point, + map, + config.visibility_firsts, + exclude_lasts, + ); to_soldier_item_opacity -= by_behavior_modifier; let visible = to_soldier_item_opacity < config.visible_starts_at; @@ -127,12 +155,15 @@ impl Visibility { Self { from: from_point, from_soldier: Some(from_soldier.uuid()), + from_side: Some(*from_soldier.side()), to: to_point, to_soldier: Some(to_soldier.uuid()), + altered_to, opacity_segments, path_final_opacity, to_scene_item_opacity: to_soldier_item_opacity, visible, + blocked, distance, break_point, } @@ -147,27 +178,36 @@ impl Visibility { ) -> Self { let from_point = from_soldier.world_point(); - let (to_soldier_item_opacity, opacity_segments, path_final_opacity, break_point) = - Self::between_points_raw( - config, - &from_point, - to_point, - map, - VISIBILITY_FIRSTS, - exclude_lasts, - ); + let ( + to_soldier_item_opacity, + opacity_segments, + path_final_opacity, + break_point, + blocked, + altered_to, + ) = Self::between_points_raw( + config, + &from_point, + to_point, + map, + VISIBILITY_FIRSTS, + exclude_lasts, + ); - let visible = to_soldier_item_opacity < VISIBLE_OPACITY_LIMIT; + let visible = to_soldier_item_opacity < config.visible_starts_at; let distance = distance_between_points(&from_point, to_point); Self { from: from_point, from_soldier: Some(from_soldier.uuid()), + from_side: Some(*from_soldier.side()), to: *to_point, to_soldier: None, + altered_to, opacity_segments, path_final_opacity, to_scene_item_opacity: to_soldier_item_opacity, visible, + blocked, distance, break_point, } @@ -179,20 +219,29 @@ impl Visibility { to_point: &WorldPoint, map: &Map, ) -> Self { - let (to_soldier_item_opacity, opacity_segments, path_final_opacity, break_point) = - Self::between_points_raw(config, from_point, to_point, map, VISIBILITY_FIRSTS, 0); + let ( + to_soldier_item_opacity, + opacity_segments, + path_final_opacity, + break_point, + blocked, + altered_to, + ) = Self::between_points_raw(config, from_point, to_point, map, VISIBILITY_FIRSTS, 0); - let visible = to_soldier_item_opacity < 0.5; + let visible = to_soldier_item_opacity < config.visible_starts_at; let distance = distance_between_points(from_point, to_point); Self { from: *from_point, from_soldier: None, + from_side: None, to: *to_point, to_soldier: None, + altered_to, opacity_segments, path_final_opacity, to_scene_item_opacity: to_soldier_item_opacity, visible, + blocked, distance, break_point, } @@ -206,11 +255,20 @@ impl Visibility { map: &Map, exclude_firsts: usize, exclude_lasts: usize, - ) -> (f32, Vec<(WorldPoint, f32)>, f32, Option) { + ) -> ( + f32, + Vec<(WorldPoint, f32)>, + f32, + Option, + bool, + WorldPoint, + ) { + let mut rng = rand::thread_rng(); let mut opacity_segments: Vec<(WorldPoint, f32)> = vec![]; let mut path_final_opacity: f32 = 0.0; let mut to_opacity: f32 = 0.0; let mut break_point = None; + let mut blocked = false; let _visible_by_bullet_fire = false; // Compute line pixels @@ -221,7 +279,7 @@ impl Visibility { let mut grid_path: GridPath = GridPath::new(); let mut other: Vec<(WorldPoint, f32)> = vec![]; - for (pixel_x, pixel_y) in pixels.step_by(VISIBILITY_PIXEL_STEPS) { + for (i, (pixel_x, pixel_y)) in pixels.step_by(VISIBILITY_PIXEL_STEPS).enumerate() { let grid_point = map.grid_point_from_world_point(&WorldPoint::new(pixel_x as f32, pixel_y as f32)); if !grid_path.contains(&grid_point) { @@ -239,6 +297,10 @@ impl Visibility { } else { config.terrain_tile_opacity(&terrain_tile.type_) }; + if i >= exclude_firsts && terrain_tile.type_().block_bullet() { + // FIXME BS NOW: defend and move etc. must change their order only if visible and not !blocked ! + blocked = true + } grid_path.push(grid_point); other.push(( WorldPoint::new(pixel_x as f32, pixel_y as f32), @@ -247,6 +309,11 @@ impl Visibility { } } + let exclude_lasts = if grid_path.len() < exclude_lasts { + grid_path.len() + } else { + exclude_lasts + }; let exclude_opacity_starts_at = grid_path.len() - exclude_lasts; for (i, (_, (world_point, opacity))) in grid_path.points.iter().zip(other).enumerate() { // Disable to_scene_item firsts if seen because firing @@ -258,16 +325,30 @@ impl Visibility { path_final_opacity += opacity; to_opacity += opacity; opacity_segments.push((world_point, path_final_opacity)); - if path_final_opacity > VISIBLE_OPACITY_LIMIT && break_point.is_none() { + if path_final_opacity > config.visible_starts_at && break_point.is_none() { break_point = Some(world_point); } } + // Compute a target point altered by opacity + let altered_to = { + let range = path_final_opacity * config.target_alteration_by_opacity_factor; + if range > 0. { + let x_change = rng.gen_range(-range..range); + let y_change = rng.gen_range(-range..range); + to_point.apply(Vec2::new(x_change, y_change)) + } else { + *to_point + } + }; + ( to_opacity, opacity_segments, path_final_opacity, break_point, + blocked, + altered_to, ) } } diff --git a/battle_core/src/state/battle/mod.rs b/battle_core/src/state/battle/mod.rs index 3e4573a..2ba4c8c 100644 --- a/battle_core/src/state/battle/mod.rs +++ b/battle_core/src/state/battle/mod.rs @@ -12,12 +12,13 @@ use crate::{ physics::{ event::{bullet::BulletFire, cannon_blast::CannonBlast, explosion::Explosion}, path::{Direction, PathMode}, + utils::distance_between_points, visibility::Visibilities, }, sync::BattleStateCopy, types::{ - SoldierBoard, SoldierIndex, SoldiersOnBoard, SquadComposition, SquadUuid, VehicleBoard, - VehicleIndex, + Distance, SoldierBoard, SoldierIndex, SoldiersOnBoard, SquadComposition, SquadUuid, + VehicleBoard, VehicleIndex, WorldPoint, }, utils::{vehicle_board_from_soldiers_on_board, WorldShape}, }; @@ -404,6 +405,23 @@ impl BattleState { pub fn b_morale(&self) -> &Morale { &self.b_morale } + + pub fn get_circle_side_soldiers_able_to_see( + &self, + side: &Side, + point: &WorldPoint, + distance: &Distance, + ) -> Vec<&Soldier> { + self.soldiers + .iter() + .filter(|s| s.can_seek()) + .filter(|s| s.side() == side) + .filter(|s| { + distance_between_points(&s.world_point(), &point).millimeters() + <= distance.millimeters() + }) + .collect() + } } #[derive(Debug)] diff --git a/battle_core/src/state/battle/visibility.rs b/battle_core/src/state/battle/visibility.rs index 13a3ad6..639559f 100644 --- a/battle_core/src/state/battle/visibility.rs +++ b/battle_core/src/state/battle/visibility.rs @@ -69,8 +69,7 @@ impl BattleState { soldier: &Soldier, point: &WorldPoint, exclude_lasts: usize, - ) -> bool { + ) -> Visibility { Visibility::between_soldier_and_point(config, soldier, point, self.map(), exclude_lasts) - .visible } } diff --git a/battle_gui/src/engine/debug/gui/config.rs b/battle_gui/src/engine/debug/gui/config.rs index d1dc4e5..595c8ba 100644 --- a/battle_gui/src/engine/debug/gui/config.rs +++ b/battle_gui/src/engine/debug/gui/config.rs @@ -3,9 +3,9 @@ use ggez::Context; use battle_core::config::{ ChangeConfigMessage, FEELING_DECREASING_FREQ, INTERIORS_UPDATE_FREQ, SOLDIER_ANIMATE_FREQ, - SOLDIER_UPDATE_FREQ, TARGET_FPS, TILE_TYPE_OPACITY_BRICK_WALL, TILE_TYPE_OPACITY_CONCRETE, - TILE_TYPE_OPACITY_DEEP_WATER, TILE_TYPE_OPACITY_DIRT, TILE_TYPE_OPACITY_HEDGE, - TILE_TYPE_OPACITY_HIGH_GRASS, TILE_TYPE_OPACITY_LIGHT_UNDERBRUSH, + SOLDIER_UPDATE_FREQ, TARGET_CYCLE_DURATION_US, TARGET_FPS, TILE_TYPE_OPACITY_BRICK_WALL, + TILE_TYPE_OPACITY_CONCRETE, TILE_TYPE_OPACITY_DEEP_WATER, TILE_TYPE_OPACITY_DIRT, + TILE_TYPE_OPACITY_HEDGE, TILE_TYPE_OPACITY_HIGH_GRASS, TILE_TYPE_OPACITY_LIGHT_UNDERBRUSH, TILE_TYPE_OPACITY_MIDDLE_GRASS, TILE_TYPE_OPACITY_MIDDLE_ROCK, TILE_TYPE_OPACITY_MIDDLE_WOOD_LOGS, TILE_TYPE_OPACITY_MUD, TILE_TYPE_OPACITY_SHORT_GRASS, TILE_TYPE_OPACITY_TRUNK, TILE_TYPE_OPACITY_UNDERBRUSH, TILE_TYPE_OPACITY_WATER, @@ -38,6 +38,14 @@ impl Engine { .striped(true) .show(ui, |ui| { for (name, value, min, max, default, message) in [ + ( + "TARGET_CYCLE_DURATION_US", + &mut self.server_config.target_cycle_duration_us, + 0, + TARGET_CYCLE_DURATION_US * 5, + TARGET_CYCLE_DURATION_US, + ChangeConfigMessage::TargetCycleDuration, + ), ( "SOLDIER_UPDATE_FREQ", &mut self.server_config.soldier_update_freq, @@ -79,7 +87,7 @@ impl Engine { ChangeConfigMessage::FeelingDecreasingFreq, ), ] - as [(_, _, _, _, _, fn(_) -> _); 5] + as [(_, _, _, _, _, fn(_) -> _); 6] { ui.label(name); if ui.button("reset").clicked() { diff --git a/battle_gui/src/engine/mod.rs b/battle_gui/src/engine/mod.rs index 2404c1a..9ec923b 100644 --- a/battle_gui/src/engine/mod.rs +++ b/battle_gui/src/engine/mod.rs @@ -69,6 +69,9 @@ pub struct Engine { hud: Hud, a_control: MapControl, b_control: MapControl, + // + first_copy_loaded: bool, + when_first_copy_messages: Vec, } impl Engine { @@ -87,6 +90,7 @@ impl Engine { a_control: MapControl, b_control: MapControl, apply: Vec, + when_first_copy_apply: Vec, ) -> GameResult { let mut gui_state = GuiState::new(*side, battle_state.map()); gui_state.set_saves( @@ -112,6 +116,8 @@ impl Engine { hud, a_control, b_control, + first_copy_loaded: false, + when_first_copy_messages: when_first_copy_apply, }; engine.react(apply, ctx)?; diff --git a/battle_gui/src/engine/network.rs b/battle_gui/src/engine/network.rs index 5d48e6f..f4913b2 100644 --- a/battle_gui/src/engine/network.rs +++ b/battle_gui/src/engine/network.rs @@ -36,6 +36,12 @@ impl Engine { self.sync_required.swap(false, Ordering::Relaxed); self.battle_state = battle_state; + + if !self.first_copy_loaded { + self.first_copy_loaded = true; + self.react(self.when_first_copy_messages.clone(), ctx)?; + self.when_first_copy_messages = vec![]; + } } OutputMessage::BattleState(battle_state_message) => { if self.gui_state.debug_physics_areas { diff --git a/battle_gui/src/graphics/map.rs b/battle_gui/src/graphics/map.rs index 0c331d4..62cc4a8 100644 --- a/battle_gui/src/graphics/map.rs +++ b/battle_gui/src/graphics/map.rs @@ -1,4 +1,7 @@ -use std::path::{Path, PathBuf}; +use std::{ + any::Any, + path::{Path, PathBuf}, +}; use battle_core::{config::ServerConfig, map::Map, types::WorldPoint}; use ggez::{ diff --git a/battle_gui/src/main.rs b/battle_gui/src/main.rs index 0a76257..a7fd943 100644 --- a/battle_gui/src/main.rs +++ b/battle_gui/src/main.rs @@ -44,5 +44,6 @@ fn main() -> Result<(), GuiError> { false, vec![], vec![], + vec![], ) } diff --git a/battle_gui/src/run.rs b/battle_gui/src/run.rs index e2031e7..5561018 100644 --- a/battle_gui/src/run.rs +++ b/battle_gui/src/run.rs @@ -117,6 +117,7 @@ pub fn run( force_server_map: bool, inputs: Vec, engine_apply: Vec, + engine_when_first_copy_apply: Vec, ) -> Result<(), GuiError> { let sync_required = Arc::new(AtomicBool::new(true)); let stop_required: Arc = Arc::new(AtomicBool::new(false)); @@ -132,6 +133,7 @@ pub fn run( let (output_sender, output_receiver) = unbounded(); if let Err(error) = EmbeddedServer::new( + server_config.clone(), &resources.lib(), input_receiver, output_sender, @@ -219,6 +221,7 @@ pub fn run( a_control, b_control, engine_apply, + engine_when_first_copy_apply, )?; // FIXME BS NOW : Closing GUI don't close thread correctly and keep process running diff --git a/battle_gui/src/server/mod.rs b/battle_gui/src/server/mod.rs index 9f4762f..84ff8f9 100644 --- a/battle_gui/src/server/mod.rs +++ b/battle_gui/src/server/mod.rs @@ -55,6 +55,7 @@ impl Display for EmbeddedServerError { } pub struct EmbeddedServer { + config: ServerConfig, resources: PathBuf, map_name: Option, force_map: Option, @@ -67,12 +68,14 @@ pub struct EmbeddedServer { impl EmbeddedServer { pub fn new( + config: ServerConfig, resources: &Path, gui_input_receiver: Receiver>, gui_output_sender: Sender>, stop_required: Arc, ) -> Self { Self { + config, resources: resources.to_path_buf(), map_name: None, force_map: None, @@ -112,7 +115,7 @@ impl EmbeddedServer { .map_name .as_ref() .ok_or(EmbeddedServerError::MissingMapName)?; - let config = ServerConfig::default(); + let config = self.config.clone(); let map = if let Some(map) = &self.force_map { map.clone() diff --git a/battle_server/src/runner/fight/choose.rs b/battle_server/src/runner/fight/choose.rs index 33fafac..3767070 100644 --- a/battle_server/src/runner/fight/choose.rs +++ b/battle_server/src/runner/fight/choose.rs @@ -15,36 +15,21 @@ pub enum ChooseMethod { RandomFromNearest, } impl ChooseMethod { - fn choose( - &self, - battle_state: &BattleState, - visibles: Vec<&Visibility>, - ) -> Option { + fn choose(&self, battle_state: &BattleState, soldiers: Vec<&Soldier>) -> Option { match self { - Self::RandomFromNearest => self.choose_random_from_nearest(battle_state, visibles), + Self::RandomFromNearest => self.choose_random_from_nearest(battle_state, soldiers), } } fn choose_random_from_nearest( &self, - battle_state: &BattleState, - visibles: Vec<&Visibility>, + _battle_state: &BattleState, + soldiers: Vec<&Soldier>, ) -> Option { - if let Some(visibility) = visibles.first() { - let soldier = battle_state.soldier( - visibility - .to_soldier - .expect("visibles_soldiers_by must returned with to_soldier"), - ); + if let Some(soldier) = soldiers.first() { let soldier_position = soldier.world_point(); - let near_soldiers: Vec<&Soldier> = visibles - .iter() - .map(|v| { - battle_state.soldier( - v.to_soldier - .expect("visibles_soldiers_by must returned with to_soldier"), - ) - }) + let near_soldiers: Vec<&Soldier> = soldiers + .into_iter() .filter(|s| { distance_between_points(&soldier_position, &s.world_point()) < Distance::from_meters(NEAR_SOLDIERS_DISTANCE_METERS) @@ -68,40 +53,49 @@ impl Runner { &self, soldier: &Soldier, squad_index: Option<&SquadUuid>, - method: &ChooseMethod, + choose_method: &ChooseMethod, ) -> Option<&Soldier> { - let mut visibles = self + let around_soldiers: Vec = self + .battle_state + .get_circle_side_soldiers_able_to_see( + soldier.side(), + &soldier.world_point(), + &Distance::from_meters(20), + ) + .iter() + .map(|s| s.uuid()) + .collect(); + let mut visibles: Vec<&Soldier> = self .battle_state .visibilities() - .visibles_soldiers_by_soldier(soldier); + // FIXME BS NOW: !!! visible by near soldiers instead of all side + .visibles_soldiers_by_soldiers(around_soldiers) + .iter() + .map(|s| self.battle_state.soldier(*s)) + .collect(); - visibles.retain(|v| { - self.battle_state - .soldier(v.to_soldier.expect("filtered previously")) - .can_be_designed_as_target() - }); + visibles.retain(|s| s.can_be_designed_as_target()); if let Some(squad_index) = squad_index { - visibles.retain(|v| { - self.battle_state - .soldier(v.to_soldier.expect("filtered previously")) - .squad_uuid() - == *squad_index - }) + visibles.retain(|s| s.squad_uuid() == *squad_index) } - visibles.sort_by(|a, b| { - a.distance - .millimeters() - .partial_cmp(&b.distance.millimeters()) - .expect("Must be i64") - }); + // Why this sort ? + // visibles.sort_by(|a, b| { + // a.distance + // .millimeters() + // .partial_cmp(&b.distance.millimeters()) + // .expect("Must be i64") + // }); if soldier.behavior().is_hide() { - visibles.retain(|v| v.distance <= self.config.hide_maximum_rayon) + visibles.retain(|s| { + distance_between_points(&soldier.world_point(), &s.world_point()) + <= self.config.hide_maximum_rayon + }) } - method + choose_method .choose(&self.battle_state, visibles) .map(|i| self.battle_state.soldier(i)) } diff --git a/battle_server/src/runner/gesture/engage.rs b/battle_server/src/runner/gesture/engage.rs index 4e25700..30fa4e5 100644 --- a/battle_server/src/runner/gesture/engage.rs +++ b/battle_server/src/runner/gesture/engage.rs @@ -13,9 +13,12 @@ impl Runner { let target_soldier = self.battle_state.soldier(*engaged_soldier_index); if target_soldier.can_be_designed_as_target() { - let point = target_soldier.world_point(); - if let Some(weapon) = self.soldier_able_to_fire_on_point(soldier, &point) { - let (gesture_context, gesture) = self.engage_point_gesture(soldier, &point, weapon); + let target_soldier_point = target_soldier.world_point(); + + if let Some(engagement) = + self.soldier_able_to_fire_on_point(soldier, &target_soldier_point) + { + let (gesture_context, gesture) = self.engage_point_gesture(soldier, engagement); return GestureResult::Handled(gesture_context, gesture); } } diff --git a/battle_server/src/runner/gesture/fire.rs b/battle_server/src/runner/gesture/fire.rs index d707dcd..83cec01 100644 --- a/battle_server/src/runner/gesture/fire.rs +++ b/battle_server/src/runner/gesture/fire.rs @@ -15,24 +15,32 @@ impl Runner { &'a self, soldier: &'a Soldier, point: &WorldPoint, - ) -> Option<(WeaponClass, &Weapon)> { - if !self.battle_state.point_is_visible_by_soldier( + ) -> Option<(WeaponClass, &Weapon, WorldPoint)> { + // FIXME BS NOW: aut. tir si: + // - visible + // - ou visible par soldat a proximité + // - FIXME BS NOW: pk path_final_opacity differents de '2 see {}' + // Algo pas le même ... ajouter une notion que on shoot un soldat vu donc c bon + // ne bloquer le tir que si obstacle avant la fin ? + let visibility = self.battle_state.point_is_visible_by_soldier( &self.config, soldier, point, // Shoot a hidden point is possible (like fire through a wall) self.config.visibility_by_last_frame_shoot_distance, - ) { + ); + + if visibility.blocked { return None; } if let Some((weapon_class, weapon)) = self.soldier_weapon_for_point(soldier, point) { if weapon.can_fire() || weapon.can_reload() { - return Some((weapon_class, weapon)); + return Some((weapon_class, weapon, visibility.altered_to)); } if self.soldier_can_reload_with(soldier, weapon).is_some() { - return Some((weapon_class, weapon)); + return Some((weapon_class, weapon, visibility.altered_to)); } } @@ -42,31 +50,33 @@ impl Runner { pub fn engage_point_gesture( &self, soldier: &Soldier, - point: &WorldPoint, - weapon: (WeaponClass, &Weapon), + engagement: (WeaponClass, &Weapon, WorldPoint), ) -> (GestureContext, Gesture) { let frame_i = self.battle_state.frame_i(); let current = soldier.gesture(); - + let (weapon_class, weapon, point) = engagement; let gesture = match current { Gesture::Idle => { // Gesture::Reloading( - self.soldier_reloading_end(soldier, weapon.1), - weapon.0.clone(), + self.soldier_reloading_end(soldier, weapon), + weapon_class.clone(), ) } Gesture::Reloading(_, _) => { // current.next( *frame_i, - Gesture::Aiming(self.soldier_aiming_end(soldier, weapon.1), weapon.0.clone()), + Gesture::Aiming( + self.soldier_aiming_end(soldier, weapon), + weapon_class.clone(), + ), ) } Gesture::Aiming(_, _) => { // - let end = self.soldier_firing_end(soldier, weapon.1); - current.next(*frame_i, Gesture::Firing(end, weapon.0.clone())) + let end = self.soldier_firing_end(soldier, weapon); + current.next(*frame_i, Gesture::Firing(end, weapon_class.clone())) } Gesture::Firing(_, _) => { // @@ -74,7 +84,7 @@ impl Runner { } }; - let final_point = self.soldier_fire_point(soldier, &weapon.0, point); + let final_point = self.soldier_fire_point(soldier, &weapon_class, &point); (GestureContext::Firing(final_point, None), gesture) } @@ -87,9 +97,9 @@ impl Runner { ) -> WorldPoint { let mut rng = rand::thread_rng(); // TODO : change precision according to weapon, stress, distance, etc - let range = 1.5 - * (distance_between_points(&soldier.world_point(), target_point).meters() as f32 - / 500.); + let factor_by_meter = 0.075; + let distance = distance_between_points(&soldier.world_point(), target_point); + let range = distance.meters() as f32 * factor_by_meter; if range == 0. { eprintln!( @@ -101,8 +111,7 @@ impl Runner { let x_change = rng.gen_range(-range..range); let y_change = rng.gen_range(-range..range); - let final_point = target_point.apply(Vec2::new(x_change, y_change)); - final_point + target_point.apply(Vec2::new(x_change, y_change)) } } diff --git a/battle_server/src/runner/gesture/suppress.rs b/battle_server/src/runner/gesture/suppress.rs index ff3871e..30c58f0 100644 --- a/battle_server/src/runner/gesture/suppress.rs +++ b/battle_server/src/runner/gesture/suppress.rs @@ -7,7 +7,7 @@ use super::GestureResult; impl Runner { pub fn suppress_fire_gesture(&self, soldier: &Soldier, point: &WorldPoint) -> GestureResult { if let Some(weapon) = self.soldier_able_to_fire_on_point(soldier, point) { - let (gesture_context, gesture) = self.engage_point_gesture(soldier, point, weapon); + let (gesture_context, gesture) = self.engage_point_gesture(soldier, weapon); return GestureResult::Handled(gesture_context, gesture); } diff --git a/battle_server/src/runner/mod.rs b/battle_server/src/runner/mod.rs index 0cac69d..bba28d7 100644 --- a/battle_server/src/runner/mod.rs +++ b/battle_server/src/runner/mod.rs @@ -35,8 +35,6 @@ mod vehicle; mod victory; mod visibility; -const TARGET_CYCLE_DURATION_US: u64 = 16666; - pub struct Runner { config: ServerConfig, input: Receiver>, @@ -87,10 +85,10 @@ impl Runner { fn sleep_duration(&self) -> Duration { let elapsed = self.last.elapsed().as_micros() as u64; - if elapsed > TARGET_CYCLE_DURATION_US { + if elapsed > self.config.target_cycle_duration_us { Duration::from_micros(0) } else { - Duration::from_micros(TARGET_CYCLE_DURATION_US - elapsed) + Duration::from_micros(self.config.target_cycle_duration_us - elapsed) } } } diff --git a/battle_server/src/runner/physics/bullet.rs b/battle_server/src/runner/physics/bullet.rs index 57ea82b..4c3c13a 100644 --- a/battle_server/src/runner/physics/bullet.rs +++ b/battle_server/src/runner/physics/bullet.rs @@ -1,5 +1,6 @@ use battle_core::{ audio::Sound, + behavior::Body, entity::soldier::Soldier, physics::{ coverage::SoldierCovered, event::bullet::BulletFire, utils::distance_between_points, @@ -8,7 +9,6 @@ use battle_core::{ types::Distance, }; use rand::seq::SliceRandom; -use rand::Rng; use crate::runner::{message::RunnerMessage, Runner}; @@ -49,25 +49,25 @@ impl Runner { continue; } + let cover = SoldierCovered::new(self.battle_state.map(), bullet_fire, soldier); let from = &soldier.world_point(); let distance = distance_between_points(from, point); - - if distance.meters() < 1 - && SoldierCovered::new(self.battle_state.map(), bullet_fire, soldier).compute() - { + // FIXME these values in config + let body_surface = match soldier.body() { + Body::StandUp => 1000, + Body::Crouched => 700, + Body::Lying => 600, + }; + let body_impact = distance.millimeters() <= body_surface; + let covered = distance.meters() < 1 && cover.compute(body_impact); + let proximity = !body_impact && distance.meters() < 30; + + if covered { messages.extend(self.covered_bullet_effects(soldier)); messages.extend(self.proximity_bullet_effects(soldier, &distance)) - } else if distance.millimeters() < 500 { - let mut rng = rand::thread_rng(); - let value: u8 = rng.gen(); - if value < 10 { - messages.extend(self.killing_bullet_effects(soldier)) - } else if value < 50 { - messages.extend(self.injuring_bullet_effects(soldier)) - } else { - messages.extend(self.proximity_bullet_effects(soldier, &distance)) - } - } else if distance.meters() < 30 { + } else if body_impact { + messages.extend(self.killing_bullet_effects(soldier)) + } else if proximity { messages.extend(self.proximity_bullet_effects(soldier, &distance)) } } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 4a0e496..dcf84de 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -11,3 +11,4 @@ battle_server = { path = "../battle_server" } battle_tools = { path = "../battle_tools" } battle_gui = { path = "../battle_gui" } thiserror = "1.0.39" +glam = { version = "0.22.0", features = ["mint"]} diff --git a/examples/src/bin/face_to_face1.rs b/examples/src/bin/face_to_face1.rs index 78f13e4..0a5016f 100644 --- a/examples/src/bin/face_to_face1.rs +++ b/examples/src/bin/face_to_face1.rs @@ -1,17 +1,19 @@ +use battle_core::{config::TARGET_CYCLE_DURATION_US, map::terrain::TileType}; +use battle_gui::debug::DebugTerrain; use examples::{ runner::{Runner, RunnerError}, scenarios::face_to_face::face_to_face, }; fn main() -> Result<(), RunnerError> { - let (map, deployment) = face_to_face(300.); + let (map, deployment) = face_to_face(TileType::ShortGrass, 1500., None); - // FIXME BS NOW disable victory by morale Runner::new(map) - .expire(Some(60 * 60)) // FIXME BS NOW implement .deployment(deployment) .begin(true) .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) .run()?; Ok(()) diff --git a/examples/src/bin/face_to_face2.rs b/examples/src/bin/face_to_face2.rs index b1f2e64..2f062a4 100644 --- a/examples/src/bin/face_to_face2.rs +++ b/examples/src/bin/face_to_face2.rs @@ -1,17 +1,19 @@ +use battle_core::{config::TARGET_CYCLE_DURATION_US, map::terrain::TileType}; +use battle_gui::debug::DebugTerrain; use examples::{ runner::{Runner, RunnerError}, scenarios::face_to_face::face_to_face, }; fn main() -> Result<(), RunnerError> { - let (map, deployment) = face_to_face(100.); + let (map, deployment) = face_to_face(TileType::ShortGrass, 500., None); - // FIXME BS NOW disable victory by morale Runner::new(map) - .expire(Some(60 * 60)) // FIXME BS NOW implement .deployment(deployment) .begin(true) .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) .run()?; Ok(()) diff --git a/examples/src/bin/face_to_face3.rs b/examples/src/bin/face_to_face3.rs index d67b968..d9af423 100644 --- a/examples/src/bin/face_to_face3.rs +++ b/examples/src/bin/face_to_face3.rs @@ -1,17 +1,19 @@ +use battle_core::{config::TARGET_CYCLE_DURATION_US, map::terrain::TileType}; +use battle_gui::debug::DebugTerrain; use examples::{ runner::{Runner, RunnerError}, scenarios::face_to_face::face_to_face, }; fn main() -> Result<(), RunnerError> { - let (map, deployment) = face_to_face(50.); + let (map, deployment) = face_to_face(TileType::ShortGrass, 75., None); - // FIXME BS NOW disable victory by morale Runner::new(map) - .expire(Some(60 * 60)) // FIXME BS NOW implement .deployment(deployment) .begin(true) .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) .run()?; Ok(()) diff --git a/examples/src/bin/face_to_face_opaque1.rs b/examples/src/bin/face_to_face_opaque1.rs new file mode 100644 index 0000000..efb6383 --- /dev/null +++ b/examples/src/bin/face_to_face_opaque1.rs @@ -0,0 +1,20 @@ +use battle_core::{config::TARGET_CYCLE_DURATION_US, map::terrain::TileType}; +use battle_gui::debug::DebugTerrain; +use examples::{ + runner::{Runner, RunnerError}, + scenarios::face_to_face::face_to_face, +}; + +fn main() -> Result<(), RunnerError> { + let (map, deployment) = face_to_face(TileType::MiddleGrass, 1500., None); + + Runner::new(map) + .deployment(deployment) + .begin(true) + .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) + .run()?; + + Ok(()) +} diff --git a/examples/src/bin/face_to_face_opaque2.rs b/examples/src/bin/face_to_face_opaque2.rs new file mode 100644 index 0000000..0c370ab --- /dev/null +++ b/examples/src/bin/face_to_face_opaque2.rs @@ -0,0 +1,20 @@ +use battle_core::{config::TARGET_CYCLE_DURATION_US, map::terrain::TileType}; +use battle_gui::debug::DebugTerrain; +use examples::{ + runner::{Runner, RunnerError}, + scenarios::face_to_face::face_to_face, +}; + +fn main() -> Result<(), RunnerError> { + let (map, deployment) = face_to_face(TileType::MiddleGrass, 750., None); + + Runner::new(map) + .deployment(deployment) + .begin(true) + .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) + .run()?; + + Ok(()) +} diff --git a/examples/src/bin/face_to_face_with_one_side_brickwall1.rs b/examples/src/bin/face_to_face_with_one_side_brickwall1.rs new file mode 100644 index 0000000..766136b --- /dev/null +++ b/examples/src/bin/face_to_face_with_one_side_brickwall1.rs @@ -0,0 +1,20 @@ +use battle_core::{config::TARGET_CYCLE_DURATION_US, map::terrain::TileType}; +use battle_gui::debug::DebugTerrain; +use examples::{ + runner::{Runner, RunnerError}, + scenarios::face_to_face::face_to_face, +}; + +fn main() -> Result<(), RunnerError> { + let (map, deployment) = face_to_face(TileType::ShortGrass, 1500., Some(TileType::BrickWall)); + + Runner::new(map) + .deployment(deployment) + .begin(true) + .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) + .run()?; + + Ok(()) +} diff --git a/examples/src/bin/face_to_face_with_one_side_brickwall2.rs b/examples/src/bin/face_to_face_with_one_side_brickwall2.rs new file mode 100644 index 0000000..f7171de --- /dev/null +++ b/examples/src/bin/face_to_face_with_one_side_brickwall2.rs @@ -0,0 +1,20 @@ +use battle_core::{config::TARGET_CYCLE_DURATION_US, map::terrain::TileType}; +use battle_gui::debug::DebugTerrain; +use examples::{ + runner::{Runner, RunnerError}, + scenarios::face_to_face::face_to_face, +}; + +fn main() -> Result<(), RunnerError> { + let (map, deployment) = face_to_face(TileType::ShortGrass, 500., Some(TileType::BrickWall)); + + Runner::new(map) + .deployment(deployment) + .begin(true) + .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) + .run()?; + + Ok(()) +} diff --git a/examples/src/bin/face_to_face_with_one_side_brickwall3.rs b/examples/src/bin/face_to_face_with_one_side_brickwall3.rs new file mode 100644 index 0000000..7320015 --- /dev/null +++ b/examples/src/bin/face_to_face_with_one_side_brickwall3.rs @@ -0,0 +1,20 @@ +use battle_core::{config::TARGET_CYCLE_DURATION_US, map::terrain::TileType}; +use battle_gui::debug::DebugTerrain; +use examples::{ + runner::{Runner, RunnerError}, + scenarios::face_to_face::face_to_face, +}; + +fn main() -> Result<(), RunnerError> { + let (map, deployment) = face_to_face(TileType::ShortGrass, 75., Some(TileType::BrickWall)); + + Runner::new(map) + .deployment(deployment) + .begin(true) + .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) + .run()?; + + Ok(()) +} diff --git a/examples/src/bin/running_with_one_side_brickwall1.rs b/examples/src/bin/running_with_one_side_brickwall1.rs new file mode 100644 index 0000000..2a0a3df --- /dev/null +++ b/examples/src/bin/running_with_one_side_brickwall1.rs @@ -0,0 +1,41 @@ +use battle_core::{ + config::TARGET_CYCLE_DURATION_US, + map::terrain::TileType, + order::Order, + state::battle::message::{BattleStateMessage, SoldierMessage}, + types::{Angle, SoldierIndex, WorldPath, WorldPaths, WorldPoint}, +}; +use battle_gui::{debug::DebugTerrain, engine::message::EngineMessage}; +use examples::{ + runner::{Runner, RunnerError}, + scenarios::face_to_face::face_to_face, +}; + +fn main() -> Result<(), RunnerError> { + let (map, deployment) = face_to_face(TileType::ShortGrass, 500., Some(TileType::BrickWall)); + + let messages = vec![ + EngineMessage::BattleState(BattleStateMessage::Soldier( + SoldierIndex(0), + SoldierMessage::SetOrder(Order::MoveFastTo( + WorldPaths::new(vec![WorldPath::new(vec![WorldPoint::new(500., 50.)])]), + None, + )), + )), + EngineMessage::BattleState(BattleStateMessage::Soldier( + SoldierIndex(5), + SoldierMessage::SetOrder(Order::Hide(Angle(0.75))), + )), + ]; + + Runner::new(map) + .deployment(deployment) + .begin(true) + .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) + .when_first_copy_apply(messages) + .run()?; + + Ok(()) +} diff --git a/examples/src/bin/walking_with_one_side_brickwall1.rs b/examples/src/bin/walking_with_one_side_brickwall1.rs new file mode 100644 index 0000000..4754a1d --- /dev/null +++ b/examples/src/bin/walking_with_one_side_brickwall1.rs @@ -0,0 +1,42 @@ +use battle_core::{ + config::TARGET_CYCLE_DURATION_US, + map::terrain::TileType, + order::Order, + state::battle::message::{BattleStateMessage, SoldierMessage}, + types::{Angle, SoldierIndex, WorldPath, WorldPaths, WorldPoint}, +}; +use battle_gui::{debug::DebugTerrain, engine::message::EngineMessage}; +use examples::{ + runner::{Runner, RunnerError}, + scenarios::face_to_face::face_to_face, +}; + +fn main() -> Result<(), RunnerError> { + let (map, deployment) = face_to_face(TileType::MiddleGrass, 1200., Some(TileType::BrickWall)); + + let messages = vec![ + EngineMessage::BattleState(BattleStateMessage::Soldier( + SoldierIndex(0), + SoldierMessage::SetOrder(Order::MoveTo( + WorldPaths::new(vec![WorldPath::new(vec![WorldPoint::new(1250., 50.)])]), + None, + )), + )), + EngineMessage::BattleState(BattleStateMessage::Soldier( + SoldierIndex(5), + SoldierMessage::SetOrder(Order::Hide(Angle(0.75))), + )), + ]; + + // FIXME BS NOW: side B is not hided at start, why ? + Runner::new(map) + .deployment(deployment) + .begin(true) + .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) + .when_first_copy_apply(messages) + .run()?; + + Ok(()) +} diff --git a/examples/src/bin/walking_with_one_side_brickwall2.rs b/examples/src/bin/walking_with_one_side_brickwall2.rs new file mode 100644 index 0000000..430a031 --- /dev/null +++ b/examples/src/bin/walking_with_one_side_brickwall2.rs @@ -0,0 +1,42 @@ +use battle_core::{ + config::TARGET_CYCLE_DURATION_US, + map::terrain::TileType, + order::Order, + state::battle::message::{BattleStateMessage, SoldierMessage}, + types::{Angle, SoldierIndex, WorldPath, WorldPaths, WorldPoint}, +}; +use battle_gui::{debug::DebugTerrain, engine::message::EngineMessage}; +use examples::{ + runner::{Runner, RunnerError}, + scenarios::face_to_face::face_to_face, +}; + +fn main() -> Result<(), RunnerError> { + let (map, deployment) = face_to_face(TileType::MiddleGrass, 700., Some(TileType::BrickWall)); + + let messages = vec![ + EngineMessage::BattleState(BattleStateMessage::Soldier( + SoldierIndex(0), + SoldierMessage::SetOrder(Order::MoveTo( + WorldPaths::new(vec![WorldPath::new(vec![WorldPoint::new(750., 50.)])]), + None, + )), + )), + EngineMessage::BattleState(BattleStateMessage::Soldier( + SoldierIndex(5), + SoldierMessage::SetOrder(Order::Hide(Angle(0.75))), + )), + ]; + + // FIXME BS NOW: side B is not hided at start, why ? + Runner::new(map) + .deployment(deployment) + .begin(true) + .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) + .when_first_copy_apply(messages) + .run()?; + + Ok(()) +} diff --git a/examples/src/bin/walking_with_one_side_brickwall3.rs b/examples/src/bin/walking_with_one_side_brickwall3.rs new file mode 100644 index 0000000..4cd293f --- /dev/null +++ b/examples/src/bin/walking_with_one_side_brickwall3.rs @@ -0,0 +1,46 @@ +use battle_core::{ + config::TARGET_CYCLE_DURATION_US, + map::terrain::TileType, + order::Order, + state::battle::message::{BattleStateMessage, SoldierMessage}, + types::{Angle, SoldierIndex, WorldPath, WorldPaths, WorldPoint}, +}; +use battle_gui::{debug::DebugTerrain, engine::message::EngineMessage}; +use examples::{ + runner::{Runner, RunnerError}, + scenarios::face_to_face::face_to_face, +}; +use glam::Vec2; + +fn main() -> Result<(), RunnerError> { + let (map, deployment) = face_to_face(TileType::HighGrass, 300., Some(TileType::BrickWall)); + + let messages = vec![ + EngineMessage::BattleState(BattleStateMessage::Soldier( + SoldierIndex(0), + SoldierMessage::SetOrder(Order::MoveTo( + WorldPaths::new(vec![WorldPath::new(vec![WorldPoint::new(350., 50.)])]), + None, + )), + )), + EngineMessage::BattleState(BattleStateMessage::Soldier( + SoldierIndex(5), + SoldierMessage::SetOrder(Order::Hide(Angle::from_points( + &Vec2::new(0.0, 0.0), + &Vec2::new(1.0, 0.0), + ))), + )), + ]; + + // FIXME BS NOW: side B is not hided at start, why ? + Runner::new(map) + .deployment(deployment) + .begin(true) + .debug_physics(true) + .target_cycle_duration(TARGET_CYCLE_DURATION_US / 3) + .debug_terrain(DebugTerrain::Opacity) + .when_first_copy_apply(messages) + .run()?; + + Ok(()) +} diff --git a/examples/src/map/flat.rs b/examples/src/map/flat.rs index 86e2a65..952a215 100644 --- a/examples/src/map/flat.rs +++ b/examples/src/map/flat.rs @@ -1,10 +1,13 @@ -use battle_core::map::terrain::{TerrainTile, TileType}; +use battle_core::{ + map::terrain::{TerrainTile, TileType}, + types::GridPoint, +}; use super::MapModel; -pub struct FlatAndEmpty; +pub struct Flat; -impl MapModel for FlatAndEmpty { +impl MapModel for Flat { fn terrain_tile_size(&self) -> u32 { 5 } @@ -13,24 +16,33 @@ impl MapModel for FlatAndEmpty { &self, width: u32, height: u32, + default_tile_type: TileType, + placed: &[(GridPoint, TileType)], ) -> Vec { let mut terrain_tiles = vec![]; let terrain_tile_size: u32 = 5; let columns = width / terrain_tile_size; let lines = height / terrain_tile_size; - for x in 0..lines { - for y in 0..columns { - let tile_x = x; // TODO: not sure at all here ... - let tile_y = y; // TODO: not sure at all here ... + for line in 0..lines { + for column in 0..columns { + let tile_x = column; // TODO: not sure at all here ... + let tile_y = line; // TODO: not sure at all here ... + + let tile_type = placed + .iter() + .find(|x| x.0 == GridPoint::new(column as i32, line as i32)) + .map(|x| x.1.clone()) + .unwrap_or(default_tile_type.clone()); + terrain_tiles.push(TerrainTile::new( - TileType::ShortGrass, + tile_type, terrain_tile_size, terrain_tile_size, 1.0, 1.0, - x, - y, + column, + line, tile_x, tile_y, )) diff --git a/examples/src/map/generator.rs b/examples/src/map/generator.rs index 1e95557..3e30c2e 100644 --- a/examples/src/map/generator.rs +++ b/examples/src/map/generator.rs @@ -1,8 +1,8 @@ use std::path::Path; use battle_core::{ - map::{decor::Decor, Map}, - types::Offset, + map::{decor::Decor, terrain::TileType, Map}, + types::{GridPoint, Offset}, }; use super::MapModel; @@ -11,6 +11,8 @@ pub struct MapGenerator { model: T, width: u32, height: u32, + default_tile_type: TileType, + placed: Vec<(GridPoint, TileType)>, } impl MapGenerator { @@ -19,6 +21,8 @@ impl MapGenerator { model, width: Default::default(), height: Default::default(), + default_tile_type: TileType::ShortGrass, + placed: vec![], } } @@ -32,8 +36,23 @@ impl MapGenerator { self } + pub fn place(mut self, value: Vec<(GridPoint, TileType)>) -> Self { + self.placed.extend(value); + self + } + + pub fn default_tile_type(mut self, value: TileType) -> Self { + self.default_tile_type = value; + self + } + pub fn generate(&self) -> Map { - let terrain_tiles = self.model.terrain_tiles(self.width, self.height); + let terrain_tiles = self.model.terrain_tiles( + self.width, + self.height, + self.default_tile_type.clone(), + &self.placed, + ); let terrain_tile_size = self.model.terrain_tile_size(); Map::new( diff --git a/examples/src/map/mod.rs b/examples/src/map/mod.rs index 826a5f3..aba89de 100644 --- a/examples/src/map/mod.rs +++ b/examples/src/map/mod.rs @@ -1,9 +1,18 @@ -use battle_core::map::terrain::TerrainTile; +use battle_core::{ + map::terrain::{TerrainTile, TileType}, + types::GridPoint, +}; pub mod flat; pub mod generator; pub trait MapModel { - fn terrain_tiles(&self, width: u32, height: u32) -> Vec; + fn terrain_tiles( + &self, + width: u32, + height: u32, + default_tile_type: TileType, + placed: &[(GridPoint, TileType)], + ) -> Vec; fn terrain_tile_size(&self) -> u32; } diff --git a/examples/src/runner.rs b/examples/src/runner.rs index 44f9ce4..9a54d38 100644 --- a/examples/src/runner.rs +++ b/examples/src/runner.rs @@ -1,5 +1,8 @@ use battle_core::{ - config::{GuiConfig, ServerConfig, DEFAULT_SERVER_PUB_ADDRESS, DEFAULT_SERVER_REP_ADDRESS}, + config::{ + GuiConfig, ServerConfig, DEFAULT_SERVER_PUB_ADDRESS, DEFAULT_SERVER_REP_ADDRESS, + TARGET_CYCLE_DURATION_US, + }, deployment::Deployment, game::{control::MapControl, Side}, map::Map, @@ -7,6 +10,7 @@ use battle_core::{ state::battle::{message::BattleStateMessage, BattleState}, }; use battle_gui::{ + debug::DebugTerrain, engine::message::{EngineMessage, GuiStateMessage}, run::{run, RunSettings}, GuiError, @@ -20,10 +24,12 @@ use thiserror::Error; pub struct Runner { map: Map, - expire: Option, // FIXME BS NOW: use it deployment: Deployment, begin: bool, + target_cycle_duration: u64, debug_physics: bool, + debug_terrain: DebugTerrain, + when_first_copy_apply: Vec, } impl Runner { @@ -39,7 +45,8 @@ impl Runner { target_fps: 60, interiors_update_freq: 60, }; - let server_config = ServerConfig::default(); + let mut server_config = ServerConfig::default(); + server_config.target_cycle_duration_us = self.target_cycle_duration; let (a_control, b_control) = ( MapControl::new(vec![SpawnZoneName::All]), MapControl::new(vec![SpawnZoneName::All]), @@ -66,6 +73,10 @@ impl Runner { )]) } + engine_apply.push(EngineMessage::GuiState(GuiStateMessage::SetDebugTerrain( + self.debug_terrain.clone(), + ))); + run( settings, config, @@ -79,6 +90,7 @@ impl Runner { true, inputs, engine_apply, + self.when_first_copy_apply, )?; Ok(()) @@ -87,18 +99,15 @@ impl Runner { pub fn new(map: Map) -> Self { Self { map, - expire: None, deployment: Deployment::empty(), begin: false, + target_cycle_duration: TARGET_CYCLE_DURATION_US, debug_physics: false, + debug_terrain: DebugTerrain::None, + when_first_copy_apply: vec![], } } - pub fn expire(mut self, value: Option) -> Self { - self.expire = value; - self - } - pub fn deployment(mut self, value: Deployment) -> Self { self.deployment = value; self @@ -113,6 +122,21 @@ impl Runner { self.debug_physics = value; self } + + pub fn debug_terrain(mut self, value: DebugTerrain) -> Self { + self.debug_terrain = value; + self + } + + pub fn target_cycle_duration(mut self, value: u64) -> Self { + self.target_cycle_duration = value; + self + } + + pub fn when_first_copy_apply(mut self, value: Vec) -> Self { + self.when_first_copy_apply = value; + self + } } #[derive(Error, Debug)] diff --git a/examples/src/scenarios/face_to_face.rs b/examples/src/scenarios/face_to_face.rs index 761de29..7be05af 100644 --- a/examples/src/scenarios/face_to_face.rs +++ b/examples/src/scenarios/face_to_face.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use crate::{ deployment::soldier::ManualSoldiersGenerator, - map::{flat::FlatAndEmpty, generator::MapGenerator}, + map::{flat::Flat, generator::MapGenerator, MapModel}, }; use battle_core::{ deployment::Deployment, @@ -10,8 +10,8 @@ use battle_core::{ weapon::{Magazine, Weapon}, Side, }, - map::Map, - types::{SquadUuid, WorldPoint}, + map::{terrain::TileType, Map}, + types::{GridPoint, SquadUuid, WorldPoint}, }; fn mosin_nagant() -> Weapon { @@ -37,27 +37,61 @@ fn mauser_magazines() -> Vec { Magazine::full(Magazine::Mauser(0)), Magazine::full(Magazine::Mauser(0)), Magazine::full(Magazine::Mauser(0)), + Magazine::full(Magazine::Mauser(0)), + Magazine::full(Magazine::Mauser(0)), ] } -pub fn face_to_face(distance: f32) -> (Map, Deployment) { + +pub fn face_to_face( + default_tile_type: TileType, + distance: f32, + hide: Option, +) -> (Map, Deployment) { let original_x = 75.; - let map = MapGenerator::new(FlatAndEmpty) - .width(500) + let original_y = 50.; + let y_increment = 5.; + let squad_members = 5; + + let place = if let Some(tile_type) = hide { + let mut place = vec![]; + for i in 0..squad_members { + let x = original_x + distance; + let y = original_y + y_increment * i as f32; + let column = x as u32 / Flat.terrain_tile_size(); + let line = y as u32 / Flat.terrain_tile_size(); + place.push(( + GridPoint::new(column as i32, line as i32), + tile_type.clone(), + )); + } + place + } else { + vec![] + }; + + let map = MapGenerator::new(Flat) + .width(1600) .height(150) + .default_tile_type(default_tile_type) + .place(place) .generate(); let soldiers = ManualSoldiersGenerator::default() .side(Side::A) .squad(SquadUuid(0)) .main_weapon(Some(mosin_nagant())) .magazines(mosin_nagant_magazines()) - .world_point(WorldPoint::new(original_x, 50.0)) - .place(5, |p: WorldPoint| p.apply(WorldPoint::new(0., 5.).into())) + .world_point(WorldPoint::new(original_x, original_y)) + .place(squad_members, |p: WorldPoint| { + p.apply(WorldPoint::new(0., y_increment).into()) + }) .side(Side::B) .squad(SquadUuid(1)) .main_weapon(Some(mauser())) .magazines(mauser_magazines()) - .world_point(WorldPoint::new(original_x + distance, 50.0)) - .place(5, |p: WorldPoint| p.apply(WorldPoint::new(0., 5.).into())) + .world_point(WorldPoint::new(original_x + distance, original_y)) + .place(squad_members, |p: WorldPoint| { + p.apply(WorldPoint::new(0., y_increment).into()) + }) .collect(); (map, Deployment::new(soldiers, vec![], HashMap::new())) } diff --git a/resources/ui.png b/resources/ui.png index d264893..e0b1513 100644 Binary files a/resources/ui.png and b/resources/ui.png differ diff --git a/resources/ui.xcf b/resources/ui.xcf index 9040f4b..ba51b67 100644 Binary files a/resources/ui.xcf and b/resources/ui.xcf differ