diff --git a/classes/esp.py b/classes/esp.py index 0916fc1..bf9ec82 100644 --- a/classes/esp.py +++ b/classes/esp.py @@ -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: @@ -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.""" @@ -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) @@ -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}") @@ -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: @@ -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: @@ -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 @@ -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() @@ -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) @@ -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 @@ -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.") \ No newline at end of file