diff --git a/src/comms.rs b/src/comms.rs index e979c61..6e58715 100755 --- a/src/comms.rs +++ b/src/comms.rs @@ -10,12 +10,18 @@ pub struct PlayerData { player_type: PlayerType, #[serde(rename = "hasBomb")] - has_bomb: bool + has_bomb: bool, + + #[serde(rename = "hasAwp")] + has_awp: bool, + + #[serde(rename = "isScoped")] + is_scoped: bool } impl PlayerData { - pub fn new(pos: Vec3, yaw: f32, player_type: PlayerType, has_bomb: bool) -> PlayerData { - PlayerData { pos, yaw, player_type, has_bomb } + pub fn new(pos: Vec3, yaw: f32, player_type: PlayerType, has_bomb: bool, has_awp: bool, is_scoped: bool) -> PlayerData { + PlayerData { pos, yaw, player_type, has_bomb, has_awp, is_scoped } } } @@ -44,6 +50,24 @@ pub struct RadarData { freq: usize, ingame: bool, + #[serde(rename = "bombPlanted")] + bomb_planted: bool, + + #[serde(rename = "bombExploded")] + bomb_exploded: bool, + + #[serde(rename = "bombBeingDefused")] + bomb_being_defused: bool, + + #[serde(rename = "bombCanDefuse")] + bomb_can_defuse: bool, + + #[serde(rename = "bombDefuseLength")] + bomb_defuse_length: f32, + + #[serde(rename = "bombDefuseTimeleft")] + bomb_defuse_timeleft: f32, + #[serde(rename = "mapName")] map_name: String, @@ -55,8 +79,8 @@ pub struct RadarData { } impl RadarData { - pub fn new(ingame: bool, map_name: String, player_data: Vec<EntityData>, freq: usize) -> RadarData { - RadarData { ingame, map_name, player_data, freq } + pub fn new(ingame: bool, map_name: String, player_data: Vec<EntityData>, freq: usize, bomb_planted: bool, bomb_cannot_defuse: bool, bomb_defuse_timeleft: f32, bomb_exploded: bool, bomb_being_defused: bool, bomb_defuse_length: f32) -> RadarData { + RadarData { ingame, map_name, player_data, freq, bomb_planted, bomb_can_defuse: bomb_cannot_defuse, bomb_defuse_timeleft, bomb_exploded, bomb_being_defused, bomb_defuse_length } } /// Returns empty RadarData, it's also the same data that gets sent to clients when not ingame @@ -65,7 +89,13 @@ impl RadarData { ingame: false, map_name: String::new(), player_data: Vec::new(), - freq + freq, + bomb_planted: false, + bomb_can_defuse: false, + bomb_defuse_timeleft: 0.0, + bomb_exploded: false, + bomb_being_defused: false, + bomb_defuse_length: 0.0 } } } diff --git a/src/dma/context/mod.rs b/src/dma/context/mod.rs index 0449841..d404fbb 100755 --- a/src/dma/context/mod.rs +++ b/src/dma/context/mod.rs @@ -91,6 +91,8 @@ impl DmaCtx { let mut yaw = 0f32; let mut health = 0u32; let mut team = 0i32; + let mut clipping_weapon = 0u64; + let mut is_scoped = 0u8; { let mut batcher = MemoryViewBatcher::new(&mut self.process); @@ -98,15 +100,29 @@ impl DmaCtx { batcher.read_into(pawn + cs2dumper::client::C_CSPlayerPawnBase::m_angEyeAngles + 4, &mut yaw); batcher.read_into(pawn + cs2dumper::client::C_BaseEntity::m_iHealth, &mut health); batcher.read_into(controller + cs2dumper::client::C_BaseEntity::m_iTeamNum, &mut team); + batcher.read_into(pawn + cs2dumper::client::C_CSPlayerPawnBase::m_pClippingWeapon, &mut clipping_weapon); + batcher.read_into(pawn + cs2dumper::client::C_CSPlayerPawnBase::m_bIsScoped, &mut is_scoped); } let team = TeamID::from_i32(team); + + let has_awp = { + let clipping_weapon: Address = clipping_weapon.into(); + let items_def_idx_addr = clipping_weapon + cs2dumper::client::C_EconEntity::m_AttributeManager + + cs2dumper::client::C_AttributeContainer::m_Item + cs2dumper::client::C_EconItemView::m_iItemDefinitionIndex; + let items_def_idx: i16 = self.process.read(items_def_idx_addr)?; + + items_def_idx == 9 + }; + Ok(BatchedPlayerData { pos, yaw, team, - health + health, + has_awp, + is_scoped: is_scoped != 0 }) } @@ -174,9 +190,12 @@ impl DmaCtx { } +#[derive(Debug)] pub struct BatchedPlayerData { pub pos: Vec3, pub yaw: f32, pub team: Option<TeamID>, pub health: u32, + pub has_awp: bool, + pub is_scoped: bool, } \ No newline at end of file diff --git a/src/dma/mod.rs b/src/dma/mod.rs index d4b4d5d..7f2c5a0 100755 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -54,6 +54,33 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ data.recheck_bomb_holder = true; } + + let bomb_defuse_timeleft: f32 = { + if data.bomb_planted && !data.bomb_exploded && !data.bomb_defused { + if let Some(bomb_stamp) = data.bomb_planted_stamp { + data.bomb_plant_timer - bomb_stamp.elapsed().as_secs_f32() + } else { + 0.0 + } + } else { + 0.0 + } + }; + + let bomb_can_defuse: bool = { + if data.bomb_planted && !data.bomb_exploded && !data.bomb_defused { + if let (Some(bomb_stamp), Some(defuse_stamp)) = (data.bomb_planted_stamp, data.bomb_defuse_stamp) { + let time_left = data.bomb_plant_timer - bomb_stamp.elapsed().as_secs_f32(); + let defuse_left = data.bomb_defuse_length - defuse_stamp.elapsed().as_secs_f32(); + time_left - defuse_left > 0.0 + } else { + false + } + } else { + false + } + }; + last_bomb_dropped = data.bomb_dropped; last_bomb_planted = data.bomb_planted; @@ -109,7 +136,9 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ local_data.pos, local_data.yaw, PlayerType::Local, - has_bomb + has_bomb, + local_data.has_awp, + local_data.is_scoped ) ) ); @@ -156,7 +185,9 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ player_data.pos, player_data.yaw, player_type, - has_bomb + has_bomb, + player_data.has_awp, + player_data.is_scoped ) ) ); @@ -167,7 +198,13 @@ pub async fn run(radar_data: ArcRwlockRadarData, connector: Connector, pcileech_ true, data.map.clone(), entity_data, - freq + freq, + data.bomb_planted, + bomb_can_defuse, + bomb_defuse_timeleft, + data.bomb_exploded, + data.bomb_being_defused, + data.bomb_defuse_length ); } else { let mut radar = radar_data.write().await; diff --git a/src/dma/threaddata/mod.rs b/src/dma/threaddata/mod.rs index 5264716..f08deac 100755 --- a/src/dma/threaddata/mod.rs +++ b/src/dma/threaddata/mod.rs @@ -1,5 +1,6 @@ use itertools::Itertools; use memflow::{mem::MemoryView, types::Address}; +use tokio::time::Instant; use super::{context::DmaCtx, cs2dumper}; @@ -24,6 +25,13 @@ pub struct CsData { pub tick_count: i32, pub bomb_dropped: bool, pub bomb_planted: bool, + pub bomb_planted_stamp: Option<Instant>, + pub bomb_plant_timer: f32, + pub bomb_being_defused: bool, + pub bomb_defuse_stamp: Option<Instant>, + pub bomb_defuse_length: f32, + pub bomb_exploded: bool, + pub bomb_defused: bool, pub highest_index: i32, pub map: String } @@ -129,6 +137,9 @@ impl CsData { let mut bomb_dropped = 0u8; let mut bomb_planted = 0u8; let mut map_ptr = 0u64; + let mut bomb_being_defused = 0u8; + let mut bomb_exploded = 0u8; + let mut bomb_defused = 0u8; { // Globals @@ -159,11 +170,52 @@ impl CsData { batcher.read_into(map_addr, &mut map_ptr); } + { + let mut batcher = ctx.process.batcher(); + if self.bomb_planted { + + batcher.read_into(self.bomb + cs2dumper::client::C_PlantedC4::m_flTimerLength , &mut self.bomb_plant_timer); + batcher.read_into(self.bomb + cs2dumper::client::C_PlantedC4::m_bBombDefused, &mut bomb_defused); + batcher.read_into(self.bomb + cs2dumper::client::C_PlantedC4::m_flDefuseLength, &mut self.bomb_defuse_length); + batcher.read_into(self.bomb + cs2dumper::client::C_PlantedC4::m_bHasExploded, &mut bomb_exploded); + batcher.read_into(self.bomb + cs2dumper::client::C_PlantedC4::m_bBeingDefused, &mut bomb_being_defused); + + drop(batcher); + + if self.bomb_planted_stamp.is_none() && !self.bomb.is_null() { + self.bomb_planted_stamp = Some(Instant::now()); + } + + if self.bomb_being_defused { + if self.bomb_defuse_stamp.is_none() { + self.bomb_defuse_stamp = Some(Instant::now()) + } + } else { + if self.bomb_defuse_stamp.is_some() { + self.bomb_defuse_stamp = None; + } + } + + } else { + if self.bomb_planted_stamp.is_some() { + self.bomb_planted_stamp = None; + } + + if self.bomb_defuse_stamp.is_some() { + self.bomb_defuse_stamp = None; + } + } + } + + let map_string = ctx.process.read_char_string_n(map_ptr.into(), 32).unwrap_or(String::from("<empty>")); self.map = map_string; self.bomb_dropped = bomb_dropped != 0; self.bomb_planted = bomb_planted != 0; + self.bomb_exploded = bomb_exploded != 0; + self.bomb_being_defused = bomb_being_defused != 0; + self.bomb_defused = bomb_defused != 0; } pub fn update_pointers(&mut self, ctx: &mut DmaCtx) { diff --git a/webradar/script.js b/webradar/script.js index 4e6ddea..ee33678 100755 --- a/webradar/script.js +++ b/webradar/script.js @@ -14,6 +14,7 @@ canvas = null ctx = null // radarflow specific +radarData = null freq = 0 image = null map = null @@ -170,11 +171,63 @@ function render() { fillStyle, data.Player.isDormant, data.Player.hasBomb, - data.Player.yaw + data.Player.yaw, + data.Player.hasAwp, + data.Player.playerType, + data.Player.isScoped ) } }); } + + if (radarData != null) { + if (radarData.bombPlanted && !radarData.bombExploded && radarData.bombDefuseTimeleft >= 0) { + + let maxWidth = 1024-128-128; + let timeleft = radarData.bombDefuseTimeleft; + //let canDefuse = (timeleft - radarData.bombDefuseLength) > 0 + + // Base bar + ctx.fillStyle = "black" + ctx.fillRect(128, 16, maxWidth, 16) + + // Bomb timer + if (radarData.bombBeingDefused) { + if (radarData.bombCanDefuse) { + ctx.fillStyle = teamColor + } else { + ctx.fillStyle = enemyColor + } + } else { + ctx.fillStyle = bombColor + } + + ctx.fillRect(130, 18, (maxWidth-2) * (timeleft / 40), 12) + + ctx.font = "24px Arial"; + ctx.textAlign = "center" + ctx.textBaseline = "middle" + ctx.fillStyle = textColor + ctx.fillText(`${timeleft.toFixed(1)}s`, 1024/2, 28+24); + + // Defuse time lines + ctx.strokeStyle = "black" + ctx.lineWidth = 2 + + // Kit defuse + ctx.beginPath() + ctx.moveTo(128 + (maxWidth * (5 / 40)), 16) + ctx.lineTo(128 + (maxWidth * (5 / 40)), 32) + ctx.stroke() + + // Normal defuse + ctx.beginPath() + ctx.moveTo(130 + (maxWidth-2) * (10 / 40), 16) + ctx.lineTo(130 + (maxWidth-2) * (10 / 40), 32) + ctx.stroke() + } + } + } else { if (websocket != null) { ctx.font = "100px Arial"; @@ -247,7 +300,7 @@ function drawBomb(pos, planted) { } } -function drawEntity(pos, fillStyle, dormant, hasBomb, yaw) { +function drawEntity(pos, fillStyle, dormant, hasBomb, yaw, hasAwp, playerType, isScoped) { if (map == null) return @@ -272,12 +325,34 @@ function drawEntity(pos, fillStyle, dormant, hasBomb, yaw) { ctx.fillText("?", pos.x, pos.y); } else { + if (hasAwp) { + ctx.beginPath(); + ctx.arc(pos.x, pos.y, circleRadius, 0, 2 * Math.PI); + ctx.fillStyle = "orange"; + ctx.fill(); + circleRadius -= 2; + } + // Draw circle ctx.beginPath(); ctx.arc(pos.x, pos.y, circleRadius, 0, 2 * Math.PI); ctx.fillStyle = fillStyle; ctx.fill(); + if (hasAwp && false) { + + let style = "yellow" + + if (playerType == "Enemy") { + style = "orange" + } + + ctx.beginPath(); + ctx.arc(pos.x, pos.y, circleRadius / 1.5, 0, 2 * Math.PI); + ctx.fillStyle = style; + ctx.fill(); + } + if (hasBomb) { ctx.beginPath(); ctx.arc(pos.x, pos.y, circleRadius / 2, 0, 2 * Math.PI); @@ -319,6 +394,22 @@ function drawEntity(pos, fillStyle, dormant, hasBomb, yaw) { ctx.fillStyle = 'white' ctx.fill(); + + if (isScoped) { + const lineOfSightX = arrowHeadX + 1024 * Math.cos(yaw * (Math.PI / 180)) + const lineOfSightY = arrowHeadY - 1024 * Math.sin(yaw * (Math.PI / 180)) + ctx.beginPath(); + ctx.moveTo(arrowHeadX, arrowHeadY); + ctx.lineTo(lineOfSightX, lineOfSightY); + + if (playerType == "Enemy") + ctx.strokeStyle = enemyColor + else + ctx.strokeStyle = teamColor + + ctx.strokeWidth = 1; + ctx.stroke(); + } } } @@ -367,7 +458,7 @@ function connect() { console.log("[radarflow] Server had an unknown error") } else { let data = JSON.parse(event.data); - + radarData = data; freq = data.freq; if (data.ingame == false) {