Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 75 additions & 59 deletions classes/esp.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@
# Size of each entity entry in memory
ENTITY_ENTRY_SIZE = 120

SKELETON_BONES = {
6: [5],
5: [4],
4: [3, 8, 11],
3: [0],
8: [9],
9: [10],
11: [12],
12: [13],
0: [22, 25],
22: [23],
23: [24],
25: [26],
26: [27],
}
ALL_BONE_IDS = set(SKELETON_BONES.keys())
for _bones in SKELETON_BONES.values():
ALL_BONE_IDS.update(_bones)
MAX_BONE_ID = max(ALL_BONE_IDS) if ALL_BONE_IDS else 0

class Entity:
"""Represents a game entity with cached data for efficient access."""
def __init__(self, controller_ptr: int, pawn_ptr: int, memory_manager: MemoryManager) -> None:
Expand All @@ -30,6 +50,8 @@ def __init__(self, controller_ptr: int, pawn_ptr: int, memory_manager: MemoryMan
self.head_pos2d: Optional[Dict[str, float]] = None
self._cached_data = None
self._last_update = 0
self._cached_bones = None
self._last_bone_update = 0

def _update_cache(self) -> None:
"""Update cached data with a time interval."""
Expand Down Expand Up @@ -82,6 +104,8 @@ def dormant(self) -> bool:

def bone_pos(self, bone: int) -> Dict[str, float]:
"""Get the 3D position of a specific bone."""
if self._cached_bones and bone in self._cached_bones:
return self._cached_bones[bone]
try:
game_scene = self.memory_manager.read_longlong(self.pawn_ptr + self.memory_manager.m_pGameSceneNode)
bone_array_ptr = self.memory_manager.read_longlong(game_scene + self.memory_manager.m_pBoneArray)
Expand All @@ -91,21 +115,26 @@ def bone_pos(self, bone: int) -> Dict[str, float]:
return {"x": 0.0, "y": 0.0, "z": 0.0}

def all_bone_pos(self) -> Optional[Dict[int, Dict[str, float]]]:
"""Get all bone positions."""
"""Get all bone positions with caching."""
current_time = time.time()
if self._cached_bones and current_time - self._last_bone_update < 0.2: # Cache for 0.2 seconds
return self._cached_bones
try:
game_scene = self.memory_manager.read_longlong(self.pawn_ptr + self.memory_manager.m_pGameSceneNode)
bone_array_ptr = self.memory_manager.read_longlong(game_scene + self.memory_manager.m_pBoneArray)
if not bone_array_ptr:
return None

num_bones_to_read = 30
num_bones_to_read = MAX_BONE_ID + 1
data = self.memory_manager.pm.read_bytes(bone_array_ptr, num_bones_to_read * 32)

bone_positions = {}
for i in range(num_bones_to_read):
for i in ALL_BONE_IDS: # Only read relevant bones
offset = i * 32
x, y, z = struct.unpack_from('fff', data, offset)
bone_positions[i] = {"x": x, "y": y, "z": z}
self._cached_bones = bone_positions
self._last_bone_update = current_time
return bone_positions
except Exception as e:
logger.error(f"Failed to get all bone positions: {e}")
Expand All @@ -118,23 +147,6 @@ def validate_screen_position(pos: Dict[str, float]) -> bool:
screen_height = overlay.get_screen_height()
return 0 <= pos["x"] <= screen_width and 0 <= pos["y"] <= screen_height

def world_to_screen(self, view_matrix: list) -> bool:
"""Convert world coordinates to screen coordinates."""
try:
pos2d = overlay.world_to_screen(view_matrix, self.pos, 1)
head2d = overlay.world_to_screen(view_matrix, self.bone_pos(6), 1)
if not self.validate_screen_position(pos2d) or not self.validate_screen_position(head2d):
self.pos2d = None
self.head_pos2d = None
return False
self.pos2d = pos2d
self.head_pos2d = head2d
return True
except Exception:
self.pos2d = None
self.head_pos2d = None
return False

class CS2Overlay:
"""Manages the ESP overlay for Counter-Strike 2."""
def __init__(self, memory_manager: MemoryManager) -> None:
Expand Down Expand Up @@ -215,38 +227,35 @@ def iterate_entities(self) -> Iterator[Entity]:
pawn_ptr = self.memory_manager.read_longlong(list_entry_ptr + ENTITY_ENTRY_SIZE * (controller_pawn_ptr & 0x1FF))
if not pawn_ptr:
continue

# Early validation of dormant state
if bool(self.memory_manager.read_int(pawn_ptr + self.memory_manager.m_bDormant)):
continue

yield Entity(controller_ptr, pawn_ptr, self.memory_manager)
except Exception as e:
logger.error(f"Error iterating entity {i}: {e}")
continue

yield Entity(controller_ptr, pawn_ptr, self.memory_manager)

def draw_skeleton(self, entity: Entity, view_matrix: list, color: tuple) -> None:
def draw_skeleton(self, entity: Entity, view_matrix: list, color: tuple, all_bones_pos_3d: Dict[int, Dict[str, float]]) -> None:
"""Draw the skeleton of an entity."""
try:
all_bones_pos_3d = entity.all_bone_pos()
if not all_bones_pos_3d:
return

skeleton_bones = {
6: [5, 7], 5: [4], 7: [8], 8: [9], 9: [10], 4: [3], 3: [2],
2: [1], 1: [11, 12], 11: [13], 13: [15], 12: [14], 14: [16]
}

bone_positions_2d = {}

all_bone_ids = set(skeleton_bones.keys())
for bones in skeleton_bones.values():
all_bone_ids.update(bones)

for bone_id in all_bone_ids:
for bone_id in ALL_BONE_IDS:
if bone_id in all_bones_pos_3d:
pos_3d = all_bones_pos_3d[bone_id]
pos_2d = overlay.world_to_screen(view_matrix, pos_3d, 1)
if entity.validate_screen_position(pos_2d):
try:
pos_2d = overlay.world_to_screen(view_matrix, pos_3d, 1)
except Exception:
pos_2d = None

if pos_2d and entity.validate_screen_position(pos_2d):
bone_positions_2d[bone_id] = pos_2d

for start_bone, end_bones in skeleton_bones.items():
for start_bone, end_bones in SKELETON_BONES.items():
if start_bone in bone_positions_2d:
for end_bone in end_bones:
if end_bone in bone_positions_2d:
Expand All @@ -264,11 +273,30 @@ def draw_skeleton(self, entity: Entity, view_matrix: list, color: tuple) -> None
def draw_entity(self, entity: Entity, view_matrix: list, is_teammate: bool = False) -> None:
"""Render the ESP overlay for a given entity."""
try:
if not entity.world_to_screen(view_matrix) or not entity.pos2d or not entity.head_pos2d:
return
if entity.health <= 0 or entity.dormant:
return

# Only fetch bone positions if skeleton is enabled
all_bones_pos_3d = entity.all_bone_pos() if self.enable_skeleton else None

head_pos_3d = None
if all_bones_pos_3d and 6 in all_bones_pos_3d:
head_pos_3d = all_bones_pos_3d[6]
else:
head_pos_3d = entity.bone_pos(6)

try:
pos2d = overlay.world_to_screen(view_matrix, entity.pos, 1)
head_pos2d = overlay.world_to_screen(view_matrix, head_pos_3d, 1)
except Exception:
pos2d, head_pos2d = None, None

if not isinstance(pos2d, dict) or not isinstance(head_pos2d, dict) or not entity.validate_screen_position(pos2d) or not entity.validate_screen_position(head_pos2d):
return

entity.pos2d = pos2d
entity.head_pos2d = head_pos2d

head_y = entity.head_pos2d["y"]
pos_y = entity.pos2d["y"]
box_height = pos_y - head_y
Expand All @@ -278,8 +306,8 @@ def draw_entity(self, entity: Entity, view_matrix: list, is_teammate: bool = Fal
outline_color = overlay.get_color(self.teammate_color_hex if is_teammate else self.box_color_hex)
text_color = overlay.get_color(self.text_color_hex)

if self.enable_skeleton:
self.draw_skeleton(entity, view_matrix, outline_color)
if self.enable_skeleton and all_bones_pos_3d:
self.draw_skeleton(entity, view_matrix, outline_color, all_bones_pos_3d)

if self.draw_snaplines:
screen_width = overlay.get_screen_width()
Expand Down Expand Up @@ -372,16 +400,7 @@ def draw_minimap(self, entities: list[Entity], view_matrix: list) -> None:
map_size = {"x": map_max["x"] - map_min["x"], "y": map_max["y"] - map_min["y"]}

minimap_size = self.minimap_size
screen_width = overlay.get_screen_width()
screen_height = overlay.get_screen_height()

positions = {
"top_left": (10, 10),
"top_right": (screen_width - minimap_size - 10, 10),
"bottom_left": (10, screen_height - minimap_size - 10),
"bottom_right": (screen_width - minimap_size - 10, screen_height - minimap_size - 10)
}
minimap_x, minimap_y = positions[self.minimap_position]
minimap_x, minimap_y = self.minimap_positions[self.minimap_position]

overlay.draw_rectangle(minimap_x, minimap_y, minimap_size, minimap_size, Colors.grey)
overlay.draw_rectangle_lines(minimap_x, minimap_y, minimap_size, minimap_size, Colors.black, 2)
Expand Down Expand Up @@ -434,12 +453,9 @@ def start(self) -> None:
overlay.draw_fps(0, 0)
self.draw_minimap(entities, view_matrix)
for entity in entities:
is_teammate = False
if self.local_team is not None and entity.team == self.local_team:
if not self.draw_teammates:
continue
is_teammate = True
self.draw_entity(entity, view_matrix, is_teammate)
if self.local_team is not None and entity.team == self.local_team and not self.draw_teammates:
continue
self.draw_entity(entity, view_matrix, entity.team == self.local_team)
overlay.end_drawing()

elapsed_time = time.time() - start_time
Expand All @@ -460,4 +476,4 @@ def stop(self) -> None:
self.is_running = False
self.stop_event.set()
time.sleep(0.1)
logger.debug("Overlay stopped.")
logger.debug("Overlay stopped.")
Loading