diff --git a/src/cli.rs b/src/cli.rs
index aff9fcb..15625f7 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -11,27 +11,27 @@ const POLL_RANGE: std::ops::RangeInclusive<usize> = 1..=1000;
 #[derive(Parser)]
 #[command(author, version = version(), about, long_about = None)]
 pub struct Cli {
-    /// Connector to use for DMA
+    /// Specifies the connector type for DMA
     #[clap(value_enum, short, long, ignore_case = true, default_value_t = Connector::Qemu)]
     pub connector: Connector,
 
-    /// Pcileech device name
+    /// Name of the Pcileech device
     #[clap(long, default_value_t = String::from("FPGA"))]
     pub pcileech_device: String,
 
-    /// Port to run Webserver on
+    /// Port number for the Webserver to run on
     #[arg(short, long, default_value_t = 8000, value_parser = port_in_range)]
     pub port: u16,
 
-    /// Path to serve on webserver
+    /// Path to the directory served by the Webserver
     #[arg(short, long, default_value = "./web", value_parser = valid_path)]
     pub web_path: PathBuf,
 
-    /// How often per second the DMA thread should poll for data
+    /// Polling frequency in times per second for the DMA thread
     #[arg(short = 'r', long, default_value_t = 60, value_parser = poll_in_range)]
     pub poll_rate: u16,
 
-    /// Loglevel verbosity
+    /// Verbosity level for logging to the console
     #[arg(value_enum, long, short,  ignore_case = true, default_value_t = Loglevel::Warn)]
     pub loglevel: Loglevel,
 }
diff --git a/src/dma/cache.rs b/src/dma/cache.rs
new file mode 100644
index 0000000..c9ca099
--- /dev/null
+++ b/src/dma/cache.rs
@@ -0,0 +1,87 @@
+use memflow::types::Address;
+
+use crate::structs::communication::PlayerType;
+
+#[derive(Clone, Copy)]
+pub enum CachedEntity {
+    Bomb {ptr: Address},
+    Player {ptr: Address, player_type: PlayerType},
+}
+
+pub struct Cache {
+    timestamp: std::time::Instant,
+    entity_cache: Vec<CachedEntity>,
+    map_name: String,
+    entity_list: Address,
+}
+
+impl Cache {
+    pub fn is_valid(&self) -> bool {
+        if self.timestamp.elapsed() > std::time::Duration::from_millis(250) {
+            return false;
+        }
+
+        true
+    }
+
+    pub fn new_invalid() -> Cache {
+        Cache {
+            timestamp: std::time::Instant::now().checked_sub(std::time::Duration::from_millis(500)).unwrap(),
+            entity_cache: Vec::new(),
+            map_name: String::new(),
+            entity_list: Address::null(),
+        }
+    }
+
+    pub fn entity_cache(&mut self) -> Vec<CachedEntity> {
+        self.entity_cache.clone()
+    }
+
+    pub fn map_name(&self) -> String {
+        self.map_name.clone()
+    }
+
+    pub fn entity_list(&self) -> Address {
+        self.entity_list
+    }
+}
+
+pub struct CacheBuilder {
+    entity_cache: Option<Vec<CachedEntity>>,
+    map_name: Option<String>,
+    entity_list: Option<Address>
+}
+
+impl CacheBuilder {
+    pub fn new() -> CacheBuilder {
+        CacheBuilder {
+            entity_cache: None,
+            map_name: None,
+            entity_list: None,
+        }
+    }
+
+    pub fn entity_cache(mut self, entity_cache: Vec<CachedEntity>) -> CacheBuilder {
+        self.entity_cache = Some(entity_cache);
+        self
+    }
+
+    pub fn map_name(mut self, map_name: String) -> CacheBuilder {
+        self.map_name = Some(map_name);
+        self
+    }
+
+    pub fn entity_list(mut self, entity_list: Address) -> CacheBuilder {
+        self.entity_list = Some(entity_list);
+        self
+    }
+
+    pub fn build(self) -> anyhow::Result<Cache> {
+        Ok(Cache {
+            timestamp: std::time::Instant::now(),
+            entity_cache: self.entity_cache.ok_or(anyhow::anyhow!("entity_cache not set on builder"))?,
+            map_name: self.map_name.ok_or(anyhow::anyhow!("map_name not set on builder"))?,
+            entity_list: self.entity_list.ok_or(anyhow::anyhow!("entity_list not set on builder"))?,
+        })
+    }
+}
\ No newline at end of file
diff --git a/src/dma/mod.rs b/src/dma/mod.rs
index b6ff52f..0baddf2 100644
--- a/src/dma/mod.rs
+++ b/src/dma/mod.rs
@@ -1,9 +1,14 @@
+mod cache;
+
 use ::std::sync::Arc;
 
 use memflow::prelude::v1::*;
 use tokio::{sync::RwLock, time::{Duration, Instant}};
 
-use crate::{structs::{Connector, communication::{RadarData, PlayerType, EntityData, PlayerData, BombData}}, sdk::{self, structs::{PlayerController, Cache, MemoryClass}}};
+use crate::{structs::{Connector, communication::{RadarData, PlayerType, EntityData, PlayerData, BombData}}, sdk::{self, structs::{MemoryClass, BaseEntity, CBaseEntity, CPlayerController}}, dma::cache::CacheBuilder};
+
+use self::cache::Cache;
+
 
 pub struct CheatCtx {
     pub process: IntoProcessInstanceArcBox<'static>,
@@ -67,48 +72,45 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
     let mut last_iteration_time = Instant::now();
     let mut missmatch_count = 0;
 
-    let mut cache = Cache::new();
+    let mut cache = Cache::new_invalid();
 
     loop {
         if ctx.process.state().is_dead() {
             break;
         }
 
-        if cache.is_outdated() {
-            cache.clean();
+        if !cache.is_valid() {
+            let mut cached_entities = Vec::new();
 
             let globals = sdk::get_globals(&mut ctx)?;
             let highest_index = sdk::highest_entity_index(&mut ctx)?;
             let map_name = sdk::map_name(globals, &mut ctx)?;
             let entity_list = sdk::get_entity_list(&mut ctx)?;
 
-            cache.common().update(
-                map_name,
-                entity_list
-            );
-
             let local = sdk::get_local(&mut ctx)?;
             
-            if local.pawn(&mut ctx, entity_list)?.is_some() {
-                cache.push_data(sdk::structs::CachedEntityData::Player {
+            if local.get_pawn(&mut ctx, entity_list)?.is_some() {
+                cached_entities.push(cache::CachedEntity::Player {
                     ptr: local.ptr(),
                     player_type: PlayerType::Local 
                 });
 
                 for idx in 1..highest_index {
-                    if let Some(entity) = PlayerController::from_entity_list_v2(&mut ctx, entity_list, idx)? {
+                    if let Some(entity) = CBaseEntity::from_index(&mut ctx, entity_list, idx)? {
 
                         let class_name = entity.class_name(&mut ctx)?;
 
                         match class_name.as_str() {
                             "weapon_c4" => {
-                                cache.push_data(sdk::structs::CachedEntityData::Bomb {
+                                cached_entities.push(cache::CachedEntity::Bomb {
                                     ptr: entity.ptr()
                                 })
                             },
                             "cs_player_controller" => {
+                                let controller = entity.to_player_controller();
+
                                 let player_type = {
-                                    match entity.get_player_type(&mut ctx, &local)? {
+                                    match controller.get_player_type(&mut ctx, &local)? {
                                         Some(t) => {
                                             if t == PlayerType::Spectator { continue } else { t }
                                         },
@@ -116,7 +118,7 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
                                     }
                                 };
 
-                                cache.push_data(sdk::structs::CachedEntityData::Player {
+                                cached_entities.push(cache::CachedEntity::Player {
                                     ptr: entity.ptr(),
                                     player_type,
                                 })
@@ -127,9 +129,13 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
                 }
             }
 
-            log::debug!("Rebuilt cache.");
+            cache = CacheBuilder::new()
+                .entity_cache(cached_entities)
+                .entity_list(entity_list)
+                .map_name(map_name)
+                .build()?;
 
-            cache.new_time();
+            log::debug!("Rebuilt cache.");
         }
 
         if sdk::is_ingame(&mut ctx)? {
@@ -146,12 +152,12 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
                 );
             }
 
-            for cached_data in cache.data() {
+            for cached_data in cache.entity_cache() {
                 match cached_data {
-                    sdk::structs::CachedEntityData::Bomb { ptr } => {
+                    cache::CachedEntity::Bomb { ptr } => {
                         if sdk::is_bomb_dropped(&mut ctx)?  {
-                            let controller = PlayerController::new(ptr);
-                            let pos = controller.pos(&mut ctx)?;
+                            let bomb_entity = CBaseEntity::new(ptr);
+                            let pos = bomb_entity.pos(&mut ctx)?;
     
                             radar_data.push(
                                 EntityData::Bomb(
@@ -163,13 +169,13 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
                             );
                         }
                     },
-                    sdk::structs::CachedEntityData::Player { ptr, player_type } => {
-                        let controller = PlayerController::new(ptr);
-                        if let Some(pawn) = controller.pawn(&mut ctx, cache.common().entity_list())? {
+                    cache::CachedEntity::Player { ptr, player_type } => {
+                        let controller = CPlayerController::new(ptr);
+                        if let Some(pawn) = controller.get_pawn(&mut ctx, cache.entity_list())? {
                             if pawn.is_alive(&mut ctx)? {
                                 let pos = pawn.pos(&mut ctx)?;
                                 let yaw = pawn.angles(&mut ctx)?.y;
-                                let has_bomb = pawn.has_c4(&mut ctx, cache.common().entity_list())?;
+                                let has_bomb = pawn.has_c4(&mut ctx, cache.entity_list())?;
                     
                                 radar_data.push(
                                     EntityData::Player(
@@ -188,7 +194,11 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
             }
 
             let mut data = data_lock.write().await;
-            *data = RadarData::new(true, cache.common().map_name(), radar_data)
+            if cache.map_name() == "<empty>" || cache.map_name().is_empty() {
+                *data = RadarData::empty()
+            } else {
+                *data = RadarData::new(true, cache.map_name(), radar_data)
+            }
         } else {
             let mut data = data_lock.write().await;
             *data = RadarData::empty()
@@ -221,8 +231,8 @@ pub async fn run(connector: Connector, pcileech_device: String, poll_rate: u16,
             tokio::time::sleep(remaining).await;
     
             log::info!("poll rate: {:.2}Hz", SECOND_AS_NANO as f64 / last_iteration_time.elapsed().as_nanos() as f64);
-            log::trace!("elapsed: {}", elapsed.as_nanos());
-            log::trace!("target: {}", target_interval.as_nanos());
+            log::trace!("elapsed: {}ns", elapsed.as_nanos());
+            log::trace!("target: {}ns", target_interval.as_nanos());
             log::trace!("missmatch count: {}", missmatch_count);
     
             last_iteration_time = Instant::now();
diff --git a/src/main.rs b/src/main.rs
index ca51150..9fc1969 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -15,10 +15,6 @@ mod webserver;
 async fn main() -> anyhow::Result<()> {
     let cli = Cli::parse();
 
-    if std::env::var("RADARFLOW_LOG").is_err() {
-        std::env::set_var("RADARFLOW_LOG", "warn")
-    }
-
     simple_logger::SimpleLogger::new()
         .with_level(cli.loglevel.into())
         .init()
@@ -35,7 +31,6 @@ async fn main() -> anyhow::Result<()> {
         if let Err(err) = dma::run(cli.connector, cli.pcileech_device, cli.poll_rate, rwlock_clone).await {
             log::error!("Error in dma thread: {}", err.to_string());
         }
-
     });
 
     tokio::spawn(async move {
diff --git a/src/sdk/mod.rs b/src/sdk/mod.rs
index 7bf4726..ae91d48 100644
--- a/src/sdk/mod.rs
+++ b/src/sdk/mod.rs
@@ -5,17 +5,17 @@ use crate::dma::CheatCtx;
 use memflow::prelude::v1::*;
 use anyhow::Result;
 
-use self::structs::{PlayerController, PlayerPawn, MemoryClass, Bomb};
+use self::structs::{CPlayerController, CBaseEntity, MemoryClass, CPlayerPawn};
 
-pub fn get_local(ctx: &mut CheatCtx) -> Result<PlayerController> {
+pub fn get_local(ctx: &mut CheatCtx) -> Result<CPlayerController> {
     let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerController)?;
-    Ok(PlayerController::new(ptr))
+    Ok(CPlayerController::new(ptr))
 }
 
-pub fn get_plantedc4(ctx: &mut CheatCtx) -> Result<Bomb> {
+pub fn get_plantedc4(ctx: &mut CheatCtx) -> Result<CBaseEntity> {
     let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwPlantedC4)?;
     let ptr2 = ctx.process.read_addr64(ptr)?;
-    Ok(Bomb::new(ptr2))
+    Ok(CBaseEntity::new(ptr2))
 }
 
 pub fn is_bomb_planted(ctx: &mut CheatCtx) -> Result<bool> {
@@ -30,10 +30,16 @@ pub fn is_bomb_dropped(ctx: &mut CheatCtx) -> Result<bool> {
     Ok(data != 0)
 }
 
+pub fn is_ingame(ctx: &mut CheatCtx) -> Result<bool> {
+    let game_rules = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwGameRules)?;
+    let data: i32 = ctx.process.read(game_rules + cs2dumper::client::C_CSGameRules::m_gamePhase)?;
+    Ok(data != 1)
+}
+
 #[allow(dead_code)]
-pub fn get_local_pawn(ctx: &mut CheatCtx) -> Result<PlayerPawn> {
+pub fn get_local_pawn(ctx: &mut CheatCtx) -> Result<CPlayerPawn> {
     let ptr = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwLocalPlayerPawn)?;
-    Ok(PlayerPawn::new(ptr))
+    Ok(CPlayerPawn::new(ptr))
 }
 
 pub fn get_entity_list(ctx: &mut CheatCtx) -> Result<Address> {
@@ -51,20 +57,16 @@ pub fn map_name(global_vars: Address, ctx: &mut CheatCtx) -> Result<String> {
     Ok(ctx.process.read_char_string_n(ptr, 32)?)
 }
 
-/* 
-pub fn max_clients(global_vars: Address, ctx: &mut CheatCtx) -> Result<i32> {
-    Ok(ctx.process.read(global_vars + 0x10)?)
-}
-*/
-
 pub fn highest_entity_index(ctx: &mut CheatCtx) -> Result<i32> {
     let game_entity_system = ctx.process.read_addr64(ctx.client_module.base + cs2dumper::offsets::client_dll::dwGameEntitySystem)?;
     let highest_index = ctx.process.read(game_entity_system + cs2dumper::offsets::client_dll::dwGameEntitySystem_getHighestEntityIndex)?;
     Ok(highest_index)
 }
 
-pub fn is_ingame(ctx: &mut CheatCtx) -> Result<bool> {
+/*
+pub fn network_is_ingame(ctx: &mut CheatCtx) -> Result<bool> {
     let ptr = ctx.process.read_addr64(ctx.engine_module.base + cs2dumper::offsets::engine2_dll::dwNetworkGameClient)?;
     let signonstate: u64 = ctx.process.read(ptr + cs2dumper::offsets::engine2_dll::dwNetworkGameClient_signOnState)?;
     Ok(signonstate == 6)
-}
\ No newline at end of file
+}
+*/
\ No newline at end of file
diff --git a/src/sdk/structs/entity.rs b/src/sdk/structs/entity.rs
deleted file mode 100644
index ea6699e..0000000
--- a/src/sdk/structs/entity.rs
+++ /dev/null
@@ -1,212 +0,0 @@
-use crate::{dma::CheatCtx, sdk::cs2dumper, structs::{Vec3, communication::PlayerType}};
-use enum_primitive_derive::Primitive;
-use memflow::{prelude::MemoryView, types::Address};
-use anyhow::Result;
-use num_traits::FromPrimitive;
-
-pub trait MemoryClass {
-    fn ptr(&self) -> Address;
-    fn new(ptr: Address) -> Self;
-}
-
-#[repr(i32)]
-#[derive(Debug, Eq, PartialEq, Primitive)]
-pub enum TeamID {
-    Spectator = 1,
-    T = 2,
-    CT = 3
-}
-
-pub struct PlayerController(Address);
-
-impl PlayerController {
-
-    /*
-    pub fn from_entity_list(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<Self>> {
-        let list_entry = ctx.process.read_addr64(entity_list + ((8 * (index & 0x7FFF)) >> 9) + 16)?;
-        if list_entry.is_null() && !list_entry.is_valid() {
-            return Ok(None);
-        }
-
-        let player_ptr = ctx.process.read_addr64(list_entry + 120 * (index & 0x1FF))?;
-        if player_ptr.is_null() && !player_ptr.is_valid() {
-            return Ok(None);
-        }
-
-        Ok(Some(Self::new(player_ptr)))
-    }
-    */
-
-    pub fn from_entity_list_v2(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<Self>> {
-        let list_entry = ctx.process.read_addr64(entity_list + 8 * (index >> 9) + 16)?;
-        if list_entry.is_null() && !list_entry.is_valid() {
-            return Ok(None);
-        }
-
-        let player_ptr = ctx.process.read_addr64(list_entry + 120 * (index & 0x1FF))?;
-        if player_ptr.is_null() && !player_ptr.is_valid() {
-            return Ok(None);
-        }
-
-        Ok(Some(Self::new(player_ptr)))
-    }
-
-    pub fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
-        let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
-        Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
-    }
-
-    pub fn class_name(&self, ctx: &mut CheatCtx) -> Result<String> {
-        let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
-        let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?;
-        Ok(ctx.process.read_char_string_n(class_name_ptr, 32)?)
-    }
-
-    pub fn get_team(&self, ctx: &mut CheatCtx) -> Result<Option<TeamID>> {
-        let team_num: i32 = ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iTeamNum)?;
-        Ok(TeamID::from_i32(team_num))
-    }
-
-    pub fn get_player_type(&self, ctx: &mut CheatCtx, local: &PlayerController) -> Result<Option<PlayerType>> {
-        if self.0 == local.0 {
-            return Ok(Some(PlayerType::Local))
-        }
-
-        let team = {
-            match self.get_team(ctx)? {
-                Some(t) => t,
-                None => { return Ok(None) },
-            }
-        };
-        
-        let local_team = {
-            match local.get_team(ctx)? {
-                Some(t) => t,
-                None => { return Ok(None) },
-            }
-        };
-
-        let player_type = {
-            if team == TeamID::Spectator { 
-                PlayerType::Spectator 
-            } else if team != local_team {
-                PlayerType::Enemy
-            } else {
-                PlayerType::Team
-            }
-        };
-
-        Ok(Some(player_type))
-    }
-
-    pub fn pawn(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<Option<PlayerPawn>> {
-        let uhandle = ctx.process.read(self.0 + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?;
-        PlayerPawn::from_uhandle(ctx, entity_list, uhandle)
-    }
-
-}
-
-impl MemoryClass for PlayerController {
-    fn ptr(&self) -> Address {
-        self.0
-    }
-
-    fn new(ptr: Address) -> Self {
-        Self(ptr)
-    }
-}
-
-pub struct PlayerPawn(Address);
-
-impl PlayerPawn {
-    pub fn from_uhandle(ctx: &mut CheatCtx, entity_list: Address, uhandle: u32) -> Result<Option<Self>> {
-        let list_entry = ctx.process.read_addr64(entity_list + 0x8 * ((uhandle & 0x7FFF) >> 9) + 16)?;
-        
-        if list_entry.is_null() || !list_entry.is_valid() {
-            Ok(None)
-        } else {
-            let ptr = ctx.process.read_addr64(list_entry + 120 * (uhandle & 0x1FF))?;
-            Ok(Some(Self(ptr)))
-        }
-    }
-
-    pub fn has_c4(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<bool> {
-        let mut has_c4 = false;
-        let wep_services = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BasePlayerPawn::m_pWeaponServices)?;
-        let wep_count: i32  = ctx.process.read(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons)?;
-        let wep_base = ctx.process.read_addr64(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons + 0x8)?;
-
-        for wep_idx in 0..wep_count {
-            let handle: i32 = ctx.process.read(wep_base + wep_idx * 0x4)?;
-            if handle == -1 {
-                continue;
-            }
-
-            let list_entry = ctx.process.read_addr64(entity_list + 0x8 * ((handle & 0x7FFF) >> 9) + 16)?;
-            if let Some(wep_ptr) = {
-                if list_entry.is_null() || !list_entry.is_valid() {
-                    None
-                } else {
-                    let ptr = ctx.process.read_addr64(list_entry + 120 * (handle & 0x1FF))?;
-                    Some(ptr)
-                }
-            } {
-                let wep_data = ctx.process.read_addr64(wep_ptr + cs2dumper::client::C_BaseEntity::m_nSubclassID + 0x8)?;
-                let id: i32 = ctx.process.read(wep_data + cs2dumper::client::CCSWeaponBaseVData::m_WeaponType)?;
-
-                if id == 7 {
-                    has_c4 = true;
-                    break;
-                }
-            }
-        }
-
-        Ok(has_c4)
-    }
-
-    pub fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
-        Ok(ctx.process.read(self.0 + cs2dumper::client::C_BasePlayerPawn::m_vOldOrigin)?)
-    }
-
-    pub fn angles(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
-        Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSPlayerPawnBase::m_angEyeAngles)?)
-    }
-
-    pub fn health(&self, ctx: &mut CheatCtx) -> Result<u32> {
-        Ok(ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iHealth)?)
-    }
-
-    /// Same as ::get_health > 0
-    pub fn is_alive(&self, ctx: &mut CheatCtx) -> Result<bool> {
-        Ok(self.health(ctx)? > 0)
-    }
-}
-
-impl MemoryClass for PlayerPawn {
-    fn ptr(&self) -> Address {
-        self.0
-    }
-
-    fn new(ptr: Address) -> Self {
-        Self(ptr)
-    }
-}
-
-pub struct Bomb(Address);
-
-impl MemoryClass for Bomb {
-    fn ptr(&self) -> Address {
-        self.0
-    }
-
-    fn new(ptr: Address) -> Self {
-        Self(ptr)
-    }
-}
-
-impl Bomb {
-    pub fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
-        let c4_node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
-        Ok(ctx.process.read(c4_node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
-    }
-}
\ No newline at end of file
diff --git a/src/sdk/structs/entity_classes/base_entity.rs b/src/sdk/structs/entity_classes/base_entity.rs
new file mode 100644
index 0000000..6a68247
--- /dev/null
+++ b/src/sdk/structs/entity_classes/base_entity.rs
@@ -0,0 +1,50 @@
+use memflow::{types::Address, mem::MemoryView};
+use anyhow::Result;
+
+use crate::{dma::CheatCtx, structs::Vec3, sdk::{cs2dumper, structs::{BaseEntity, MemoryClass}}};
+use super::CPlayerController;
+
+pub struct CBaseEntity(Address);
+
+impl MemoryClass for CBaseEntity {
+    fn ptr(&self) -> Address {
+        self.0
+    }
+
+    fn new(ptr: Address) -> Self {
+        Self(ptr)
+    }
+}
+
+impl BaseEntity for CBaseEntity {
+    fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<CBaseEntity>> {
+        let list_entry = ctx.process.read_addr64(entity_list + 8 * (index >> 9) + 16)?;
+        if list_entry.is_null() && !list_entry.is_valid() {
+            return Ok(None);
+        }
+
+        let player_ptr = ctx.process.read_addr64(list_entry + 120 * (index & 0x1FF))?;
+        if player_ptr.is_null() && !player_ptr.is_valid() {
+            return Ok(None);
+        }
+
+        Ok(Some(Self::new(player_ptr)))
+    }
+
+    fn pos(&self, ctx: &mut CheatCtx) -> anyhow::Result<Vec3> {
+        let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
+        Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
+    }
+
+    fn class_name(&self, ctx: &mut CheatCtx) -> anyhow::Result<String> {
+        let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
+        let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?;
+        Ok(ctx.process.read_char_string_n(class_name_ptr, 32)?)
+    }
+}
+
+impl CBaseEntity {
+    pub fn to_player_controller(&self) -> CPlayerController {
+        CPlayerController::new(self.0)
+    }
+}
\ No newline at end of file
diff --git a/src/sdk/structs/entity_classes/mod.rs b/src/sdk/structs/entity_classes/mod.rs
new file mode 100644
index 0000000..a2f9d62
--- /dev/null
+++ b/src/sdk/structs/entity_classes/mod.rs
@@ -0,0 +1,26 @@
+mod base_entity;
+mod player_controller;
+mod player_pawn;
+
+pub use base_entity::CBaseEntity;
+pub use player_controller::CPlayerController;
+pub use player_pawn::CPlayerPawn;
+
+use crate::{dma::CheatCtx, structs::Vec3};
+
+use memflow::types::Address;
+use anyhow::Result;
+
+/// A trait that implements basic functions from C_BaseEntity
+/// CCSPlayerController inherits C_BaseEntity, which is why this trait exists.
+pub trait BaseEntity {
+    fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<Self>> where Self: std::marker::Sized;
+    fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3>;
+    fn class_name(&self, ctx: &mut CheatCtx) -> Result<String>;
+}
+
+/// A trait that implements basic functions for an class represented by a single pointer
+pub trait MemoryClass {
+    fn ptr(&self) -> Address;
+    fn new(ptr: Address) -> Self;
+}
\ No newline at end of file
diff --git a/src/sdk/structs/entity_classes/player_controller.rs b/src/sdk/structs/entity_classes/player_controller.rs
new file mode 100644
index 0000000..4dbae41
--- /dev/null
+++ b/src/sdk/structs/entity_classes/player_controller.rs
@@ -0,0 +1,91 @@
+use memflow::{types::Address, mem::MemoryView};
+use anyhow::Result;
+use num_traits::FromPrimitive;
+
+use crate::{dma::CheatCtx, structs::{Vec3, communication::PlayerType}, sdk::{cs2dumper, structs::{BaseEntity, MemoryClass, TeamID}}};
+
+use super::CPlayerPawn;
+
+pub struct CPlayerController(Address);
+
+impl MemoryClass for CPlayerController {
+    fn ptr(&self) -> Address {
+        self.0
+    }
+
+    fn new(ptr: Address) -> Self {
+        Self(ptr)
+    }
+}
+
+impl BaseEntity for CPlayerController {
+    fn from_index(ctx: &mut CheatCtx, entity_list: Address, index: i32) -> Result<Option<CPlayerController>> {
+        let list_entry = ctx.process.read_addr64(entity_list + 8 * (index >> 9) + 16)?;
+        if list_entry.is_null() && !list_entry.is_valid() {
+            return Ok(None);
+        }
+
+        let player_ptr = ctx.process.read_addr64(list_entry + 120 * (index & 0x1FF))?;
+        if player_ptr.is_null() && !player_ptr.is_valid() {
+            return Ok(None);
+        }
+
+        Ok(Some(Self::new(player_ptr)))
+    }
+
+    fn pos(&self, ctx: &mut CheatCtx) -> anyhow::Result<Vec3> {
+        let node = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BaseEntity::m_pGameSceneNode)?;
+        Ok(ctx.process.read(node + cs2dumper::client::CGameSceneNode::m_vecAbsOrigin)?)
+    }
+
+    fn class_name(&self, ctx: &mut CheatCtx) -> anyhow::Result<String> {
+        let entity_identity_ptr = ctx.process.read_addr64(self.0 + cs2dumper::client::CEntityInstance::m_pEntity)?;
+        let class_name_ptr = ctx.process.read_addr64(entity_identity_ptr + cs2dumper::client::CEntityIdentity::m_designerName)?;
+        Ok(ctx.process.read_char_string_n(class_name_ptr, 32)?)
+    }
+}
+
+impl CPlayerController {
+    pub fn get_pawn(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<Option<CPlayerPawn>> {
+        let uhandle = ctx.process.read(self.0 + cs2dumper::client::CCSPlayerController::m_hPlayerPawn)?;
+        CPlayerPawn::from_uhandle(ctx, entity_list, uhandle)
+    }
+
+    // Technically something that should be in the BaseEntity trait, but we are only gonna use it on CPlayerController
+    pub fn get_team(&self, ctx: &mut CheatCtx) -> Result<Option<TeamID>> {
+        let team_num: i32 = ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iTeamNum)?;
+        Ok(TeamID::from_i32(team_num))
+    }
+
+    pub fn get_player_type(&self, ctx: &mut CheatCtx, local: &CPlayerController) -> Result<Option<PlayerType>> {
+        if self.0 == local.0 {
+            return Ok(Some(PlayerType::Local))
+        }
+
+        let team = {
+            match self.get_team(ctx)? {
+                Some(t) => t,
+                None => { return Ok(None) },
+            }
+        };
+        
+        let local_team = {
+            match local.get_team(ctx)? {
+                Some(t) => t,
+                None => { return Ok(None) },
+            }
+        };
+
+        let player_type = {
+            if team == TeamID::Spectator { 
+                PlayerType::Spectator 
+            } else if team != local_team {
+                PlayerType::Enemy
+            } else {
+                PlayerType::Team
+            }
+        };
+
+        Ok(Some(player_type))
+    }
+}
\ No newline at end of file
diff --git a/src/sdk/structs/entity_classes/player_pawn.rs b/src/sdk/structs/entity_classes/player_pawn.rs
new file mode 100644
index 0000000..183875a
--- /dev/null
+++ b/src/sdk/structs/entity_classes/player_pawn.rs
@@ -0,0 +1,81 @@
+use memflow::{types::Address, mem::MemoryView};
+use anyhow::Result;
+
+use crate::{dma::CheatCtx, structs::Vec3, sdk::{cs2dumper, structs::MemoryClass}};
+
+pub struct CPlayerPawn(Address);
+
+impl CPlayerPawn {
+    pub fn from_uhandle(ctx: &mut CheatCtx, entity_list: Address, uhandle: u32) -> Result<Option<Self>> {
+        let list_entry = ctx.process.read_addr64(entity_list + 0x8 * ((uhandle & 0x7FFF) >> 9) + 16)?;
+        
+        if list_entry.is_null() || !list_entry.is_valid() {
+            Ok(None)
+        } else {
+            let ptr = ctx.process.read_addr64(list_entry + 120 * (uhandle & 0x1FF))?;
+            Ok(Some(Self(ptr)))
+        }
+    }
+
+    // Todo: Optimize this function: find another way to do this
+    pub fn has_c4(&self, ctx: &mut CheatCtx, entity_list: Address) -> Result<bool> {
+        let mut has_c4 = false;
+        let wep_services = ctx.process.read_addr64(self.0 + cs2dumper::client::C_BasePlayerPawn::m_pWeaponServices)?;
+        let wep_count: i32  = ctx.process.read(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons)?;
+        let wep_base = ctx.process.read_addr64(wep_services + cs2dumper::client::CPlayer_WeaponServices::m_hMyWeapons + 0x8)?;
+
+        for wep_idx in 0..wep_count {
+            let handle: i32 = ctx.process.read(wep_base + wep_idx * 0x4)?;
+            if handle == -1 {
+                continue;
+            }
+
+            let list_entry = ctx.process.read_addr64(entity_list + 0x8 * ((handle & 0x7FFF) >> 9) + 16)?;
+            if let Some(wep_ptr) = {
+                if list_entry.is_null() || !list_entry.is_valid() {
+                    None
+                } else {
+                    let ptr = ctx.process.read_addr64(list_entry + 120 * (handle & 0x1FF))?;
+                    Some(ptr)
+                }
+            } {
+                let wep_data = ctx.process.read_addr64(wep_ptr + cs2dumper::client::C_BaseEntity::m_nSubclassID + 0x8)?;
+                let id: i32 = ctx.process.read(wep_data + cs2dumper::client::CCSWeaponBaseVData::m_WeaponType)?;
+
+                if id == 7 {
+                    has_c4 = true;
+                    break;
+                }
+            }
+        }
+
+        Ok(has_c4)
+    }
+
+    pub fn pos(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
+        Ok(ctx.process.read(self.0 + cs2dumper::client::C_BasePlayerPawn::m_vOldOrigin)?)
+    }
+
+    pub fn angles(&self, ctx: &mut CheatCtx) -> Result<Vec3> {
+        Ok(ctx.process.read(self.0 + cs2dumper::client::C_CSPlayerPawnBase::m_angEyeAngles)?)
+    }
+
+    pub fn health(&self, ctx: &mut CheatCtx) -> Result<u32> {
+        Ok(ctx.process.read(self.0 + cs2dumper::client::C_BaseEntity::m_iHealth)?)
+    }
+
+    /// Same as ::get_health > 0
+    pub fn is_alive(&self, ctx: &mut CheatCtx) -> Result<bool> {
+        Ok(self.health(ctx)? > 0)
+    }
+}
+
+impl MemoryClass for CPlayerPawn {
+    fn ptr(&self) -> Address {
+        self.0
+    }
+
+    fn new(ptr: Address) -> Self {
+        Self(ptr)
+    }
+}
\ No newline at end of file
diff --git a/src/sdk/structs/mod.rs b/src/sdk/structs/mod.rs
index ab1e9a9..792961d 100644
--- a/src/sdk/structs/mod.rs
+++ b/src/sdk/structs/mod.rs
@@ -1,82 +1,13 @@
-mod entity;
-pub use entity::*;
-use memflow::types::Address;
+mod entity_classes;
 
-use crate::structs::communication::PlayerType;
+pub use entity_classes::*;
 
-#[derive(Clone, Copy)]
-pub enum CachedEntityData {
-    Bomb {ptr: Address},
-    Player {ptr: Address, player_type: PlayerType},
-}
+use enum_primitive_derive::Primitive;
 
-pub struct CommonCache {
-    map_name: String,
-    entity_list: Address,
-}
-
-impl CommonCache {
-    pub fn new() -> CommonCache {
-        CommonCache {
-            map_name: String::from("unknown"),
-            entity_list:  Address::null(),
-        }
-    }
-
-    pub fn update(&mut self, map_name: String, entity_list: Address) {
-        self.map_name = map_name;
-        self.entity_list = entity_list;
-    }
-
-    pub fn map_name(&self) -> String {
-        self.map_name.clone()
-    }
-
-    pub fn entity_list(&self) -> Address {
-        self.entity_list
-    }
-}
-
-pub struct Cache {
-    last_cached: std::time::Instant,
-    data: Vec<CachedEntityData>,
-    common: CommonCache
-}
-
-impl Cache {
-    pub fn new() -> Cache {
-        Cache {
-            last_cached: std::time::Instant::now().checked_sub(std::time::Duration::from_millis(500)).unwrap(),
-            data: Vec::new(),
-            common: CommonCache::new(),
-        }
-    }
-
-    pub fn is_outdated(&self) -> bool {
-        if self.last_cached.elapsed() > std::time::Duration::from_millis(250) {
-            return true;
-        }
-
-        false
-    }
-
-    pub fn new_time(&mut self) {
-        self.last_cached = std::time::Instant::now();
-    }
-
-    pub fn clean(&mut self) {
-        self.data.clear();
-    }
-
-    pub fn data(&self) -> Vec<CachedEntityData> {
-        self.data.clone()
-    }
-
-    pub fn push_data(&mut self, data: CachedEntityData) {
-        self.data.push(data);
-    }
-
-    pub fn common(&mut self) -> &mut CommonCache {
-        &mut self.common
-    }
+#[repr(i32)]
+#[derive(Debug, Eq, PartialEq, Primitive)]
+pub enum TeamID {
+    Spectator = 1,
+    T = 2,
+    CT = 3
 }
\ No newline at end of file