February 4Feb 4 I definitely wrote this all myself and didnt use AIOSBOT API - Complete Function Reference & Use CasesTable of ContentsPlayers APINPCs APIObjects APIWorld & Map APIInteractions APIGround Items APIWalking & MovementUtilities & HelpersDisplay & Viewport ManagementCoordinate Projection SystemCamera SystemRendering & Screen CoordinatesMinimap RenderingGeometric UtilitiesPainting & Overlay ExamplesPlayers APILocation: src/api/scene/players.rsFunctions for accessing and managing player entities in the game world.Core Functionsplayers_get_local_player() -> Option<RSPlayer>Returns your own player character.Returns: Option<RSPlayer> - Your player if logged in, None otherwiseUse Cases:Getting your current position to calculate distancesChecking your current status (health, animation, etc.)Verifying game state before performing actionsSetting up path-finding from your locationExample:if let Some(local_player) = players_get_local_player() { let my_x = local_player.get_x(); let my_y = local_player.get_y(); println!("I am at ({}, {})", my_x, my_y); } players_get_local_player_index() -> i32Returns the index of your player in the players array.Returns: i32 - Array index of local playerUse Cases:Iterating through all players efficientlyFiltering your player from a list of all playersInternal API operationsplayers_get_at_index(index: i32) -> Option<RSPlayer>Retrieves a player at a specific array index.Returns: Option<RSPlayer> - Player at index if valid, None otherwiseUse Cases:Iterating through all players in the worldFollowing up on index-based queriesplayers_get_all() -> impl Iterator<Item = RSPlayer>Returns an iterator over all players currently visible.Returns: Iterator yielding all RSPlayer instancesUse Cases:Finding all players in a specific areaCounting players near a locationChecking for friends or clan membersDetecting multi-loggingExample:let player_count = players_get_all().count(); println!("There are {} players visible", player_count); // Find players near a specific position if let Some(local_player) = players_get_local_player() { let nearby = players_get_all() .filter(|p| { let distance = ((p.get_x() - local_player.get_x()).pow(2) + (p.get_y() - local_player.get_y()).pow(2)) .sqrt() as i32; distance < 50 }) .count(); } players_get_all_in_worldview(world_view: RSWorldView) -> impl Iterator<Item = RSPlayer>Returns players in a specific world view (instance/world).Parameters:world_view - The world view to queryReturns: Iterator yielding RSPlayer instances in that world viewUse Cases:Working with instanced content (dungeons, raids)Multi-world scenariosFiltering players by location groupNPCs APILocation: src/api/scene/npcs.rsComprehensive functions for finding, filtering, and interacting with NPCs.Finding Functionsnpcs_find_closest_by_id<T: Into<IdArgs>>(args) -> Option<RSNpc>Finds the nearest NPC matching a specific ID.Parameters:args - NPC ID(s) to search forReturns: Option<RSNpc> - Closest matching NPC or NoneUse Cases:Locating a specific NPC banker or shop keeperFinding the nearest vendorPathfinding to quest NPCsExample:// Using single ID if let Some(banker) = npcs_find_closest_by_id(1634) { println!("Banker at ({}, {})", banker.get_x(), banker.get_y()); } // Using multiple IDs (any match) if let Some(npc) = npcs_find_closest_by_id(vec![1, 2, 3]) { println!("Found NPC near me"); } npcs_find_closest_by_name<T: Into<NameArgs>>(args) -> Option<RSNpc>Finds the nearest NPC by name.Parameters:args - NPC name(s) to search forReturns: Option<RSNpc> - Closest matching NPC or NoneUse Cases:Finding specific NPCs by their display nameQuest NPCs by nameNamed vendors or quest giversExample:if let Some(guard) = npcs_find_closest_by_name("Guard") { // Interact with guard } npcs_find_closest_conditional(predicate) -> Option<RSNpc>Finds the nearest NPC matching a custom predicate.Parameters:predicate - Function returning true for desired NPCsReturns: Option<RSNpc> - Closest matching NPC or NoneUse Cases:Complex filtering logic (level, stats, actions available)Finding NPCs with specific propertiesAdvanced queriesExample:let mage = npcs_find_closest_conditional(|npc| { npc.get_name().contains("Mage") && npc.has_action("Talk-to") }); npcs_find_closest_to_by_id(to: Position, args) -> Option<RSNpc>Finds an NPC by ID closest to a specific position.Parameters:to - Target positionargs - NPC ID(s)Returns: Option<RSNpc> - Closest NPC to positionUse Cases:Finding NPCs near specific landmarksMulti-step navigationLocation-based logicnpcs_find_all_by_id<T: Into<IdArgs>>(args) -> impl Iterator<Item = RSNpc>Returns all NPCs matching given ID(s).Returns: Iterator yielding all matching RSNpc instancesUse Cases:Counting NPC spawnsArea analysisMonitoring NPC locationsnpcs_get_at_index(index: i32) -> Option<RSNpc>Gets NPC at specific array index.Use Cases:Iterator-based accessIndexed lookupsnpcs_get_all() -> impl Iterator<Item = RSNpc>Returns all NPCs currently visible.Use Cases:Scanning entire world for NPCsStatistical analysisDebug/monitoringnpcs_get_all_in_worldview(world_view: RSWorldView) -> impl Iterator<Item = RSNpc>Returns NPCs in a specific world view/instance.Use Cases:Instance-specific logicMulti-world supportObjects APILocation: src/api/scene/objects.rsComprehensive interface for locating and interacting with game objects.Object Type Flagsconst OBJECTS_GROUND_DECORATION_FLAG: i32 = 1 << 0; // Ground items const OBJECTS_WALL_DECORATION_FLAG: i32 = 1 << 1; // Wall decorations const OBJECTS_WALL_OBJECT_FLAG: i32 = 1 << 2; // Doors, walls const OBJECTS_INTERACTABLE_OBJECT_FLAG: i32 = 1 << 3; // Climbable, usable const OBJECTS_ALL_OBJECTS_FLAG: i32 = 0xF; // All types Finding Functionsobjects_find_closest_by_id(flags, args) -> Option<Box<dyn Object>>Finds nearest object by ID with type filtering.Parameters:flags - Object type filter (use macros for convenience)args - Object ID(s)Returns: Option<Box<dyn Object>> - Closest matching objectUse Cases:Finding doors, ladders, or staircasesLocating interactive furnitureNavigation obstaclesExample:// Find nearest door if let Some(door) = objects_find_closest_by_id( OBJECTS_WALL_OBJECT_FLAG, 1234 ) { // Interact with door if let Some(action_pos) = door.get_interaction_point() { // Navigate to and use door } } // Macro shorthand - searches all object types if let Some(obj) = objects_find_closest_by_id!(5678) { println!("Found object"); } objects_find_closest_by_name(flags, args) -> Option<Box<dyn Object>>Finds nearest object by name.Example:if let Some(ladder) = objects_find_closest_by_name( OBJECTS_INTERACTABLE_OBJECT_FLAG, "Ladder" ) { println!("Found ladder at ({}, {})", ladder.get_x(), ladder.get_y()); } objects_find_closest_conditional(flags, predicate) -> Option<Box<dyn Object>>Finds object matching custom logic.Use Cases:Complex object filteringSpecific object statesMultiple criteriaExample:let obj = objects_find_closest_conditional( OBJECTS_ALL_OBJECTS_FLAG, |obj| { obj.get_name().contains("Gate") && obj.has_action("Open") } ); objects_find_by_id(plane, flags, args) -> Option<Box<dyn Object>>Finds first object matching criteria on specific plane.Parameters:plane - Floor level (0-3)flags - Object type filterargs - Object ID(s)Returns: Option<Box<dyn Object>>Use Cases:Specific floor navigationMulti-level dungeon explorationobjects_find_all_by_id(plane, flags, args) -> Vec<Box<dyn Object>>Finds all objects matching ID on specific plane.Use Cases:Finding all doors/gatesCounting object instancesMapping obstacle locationsobjects_find_at_position_conditional(position, flags, predicate) -> Option<Box<dyn Object>>Finds object at exact tile position.Parameters:position - Target tile coordinateflags - Object type filterpredicate - Custom filter functionReturns: Option<Box<dyn Object>>Use Cases:Checking what's at a specific tileValidation before movementPre-interaction checksobjects_get_at(x, y, plane, flags) -> TileObjectsGets all objects at a tile (returns grouped by type).Returns: TileObjects struct containing:ground_decoration: Option<RSGroundDecoration>wall_decoration: Option<RSWallDecoration>wall_object: Option<RSWallObject>interactable_objects: Vec<RSInteractableObject>Example:let tile_objects = objects_get_at!(10, 20, 0); // All object types if let Some(wall) = tile_objects.get_wall_object() { println!("There's a wall here"); } objects_get_all(plane, flags) -> impl Iterator<Item = TileObjects>Returns all tiles with objects on specified plane.Use Cases:Mapping entire regionObstacle analysisPath validationWorld & Map APILocation: src/api/scene/world_view.rs, src/api/scene/map.rsWorld View Functionsworld_view_get_world_view() -> RSWorldViewGets the current active world view (viewport).Returns: RSWorldView - Current visible world stateUse Cases:Getting base coordinatesQuerying visible regionWorld state verificationExample:let world = world_view_get_world_view(); let base_x = world.get_base_x(); let base_y = world.get_base_y(); let plane = world.get_plane(); println!("Currently viewing region at ({}, {}), plane {}", base_x, base_y, plane); world_view_get_base_x() -> i32Gets base X coordinate of current world view.world_view_get_base_y() -> i32Gets base Y coordinate of current world view.world_view_get_plane() -> i32Gets current floor/plane (0-3).Use Cases:Floor-specific logicMulti-level navigationHeight-based decisionsworld_view_get_global_world_view() -> RSWorldViewGets the global/main world view.world_view_get_local_world_view() -> Option<RSWorldView>Gets local/instanced world view if in instance.Map Functionsmap_get_hint_arrow() -> RSHintArrowGets the hint arrow data (if active).Use Cases:Quest trackingFollowing game-provided guidanceNavigation assistancemap_get_destination() -> Option<Position>Gets player's current walking destination (if any).Returns: Option<Position> - Target position if walkingExample:if let Some(dest) = map_get_destination() { println!("Walking to ({}, {})", dest.x, dest.y); } else { println!("Not walking"); } Reachability Functionsmap_can_reach_local_position(position) -> boolChecks if a local position is reachable.Parameters:position - Position to checkReturns: bool - True if reachable without obstaclesUse Cases:Path validation before walkingObstacle detectionNavigation planningmap_can_reach_object(object) -> boolChecks if an object is reachable.Use Cases:Pre-interaction checksNavigation validationObstacle avoidancemap_can_reach_npc(npc) -> boolChecks if an NPC is reachable.map_can_reach_player(player) -> boolChecks if another player is reachable.map_can_reach_ground_item(item) -> boolChecks if a ground item is reachable.Distance Functionsmap_find_local_distance_to_position(position) -> i32Calculates distance to a position.Returns: i32 - Distance in tiles (or -1 if unreachable)Example:if let Some(target) = objects_find_closest_by_id!(1234) { let dist = map_find_local_distance_to_object(&target); if dist > 0 && dist < 20 { // Object is close and reachable } } map_find_distance_to_object(object) -> i32Gets distance to an object.map_find_distance_to_position(position) -> i32Gets distance to a position.Pathfinding Functionsmap_find_local_path_to_position(position) -> Option<Vec<Position>>Calculates a walkable path to position.Returns: Option<Vec<Position>> - Path if found, None if blockedUse Cases:Advanced navigationMulti-step movement planningObstacle navigationmap_find_path_to_object(object) -> Option<Vec<Position>>Calculates path to an object.map_find_local_path_position_to_position(from, to) -> Option<Vec<Position>>Finds path between two positions.Example:if let Some(path) = map_find_local_path_to_position(&target_pos) { println!("Found path with {} steps", path.len()); } map_get_wilderness_level() -> i32Gets current wilderness level (0 if not in wilderness).Use Cases:PvP content detectionRisk assessmentWilderness-specific logicInteractions APILocation: src/api/scene/interactable.rsInteractable TraitObjects implementing the Interactable trait provide methods for interaction:pub trait Interactable { fn contains(&self, point: (i32, i32)) -> bool; // Point in bounds? fn get_central_point(&self) -> (i32, i32); // Center position fn get_interaction_point(&self) -> (i32, i32); // Click position fn is_visible(&self) -> bool; // Currently visible } InteractArgs Configurationpub struct InteractArgs { min_camera_distance: i32, // Minimum distance from camera walk_to: bool, // Whether to walk to object move_camera: bool, // Whether to adjust camera } Use Cases:Controlling interaction behaviorCamera management during interactionsMovement prerequisitesExample:let mut args = InteractArgs::default(); args.set_min_camera_distance(5) .set_walk_to(true) .set_move_camera(true); Ground Items APILocation: src/api/scene/ground_items.rsFunctions for finding and managing items on the ground.Finding Functionsground_items_find_closest_by_id<T: Into<IdArgs>>(args) -> Option<RSGroundItem>Finds the nearest ground item by ID.Returns: Option<RSGroundItem> - Closest matching itemUse Cases:Finding dropped valuable itemsLooting specific itemsResource gatheringExample:if let Some(item) = ground_items_find_closest_by_id(1234) { println!("Found item at ({}, {})", item.get_x(), item.get_y()); } ground_items_find_closest_by_name(args) -> Option<RSGroundItem>Finds nearest ground item by name.ground_items_find_closest_conditional(predicate) -> Option<RSGroundItem>Finds nearest item matching predicate.Example:let valuable = ground_items_find_closest_conditional(|item| { item.get_quantity() > 1000 && item.get_name().contains("Ore") }); ground_items_find_closest_to_by_id(to, args) -> Option<RSGroundItem>Finds item by ID closest to position.ground_items_find_by_id(args) -> Option<RSGroundItem>Finds any item matching ID (not necessarily closest).ground_items_find_all_by_id(args) -> impl Iterator<Item = RSGroundItem>Returns all items matching ID.Use Cases:Counting dropped itemsTracking item distributionMass looting operationsground_items_get_all(plane) -> impl Iterator<Item = RSGroundItem>Gets all ground items on a plane.Example:let item_count = ground_items_get_all(plane).count(); println!("There are {} items on this floor", item_count); ground_items_get_all_at_local(local_x, local_y, plane) -> impl Iterator<Item = RSGroundItem>Gets all items at a specific tile.Use Cases:Pile checkingLoot verificationTile analysisWalking & MovementLocation: src/api/walking.rsCore Walking Functionswalking_get_run_energy() -> i32Gets current run energy percentage.Returns: i32 - 0-100 percentageUse Cases:Energy-aware movement decisionsStamina potions usagePerformance optimizationwalking_is_run_enabled() -> boolChecks if running is currently enabled.Returns: bool - True if runningwalking_set_run(enabled: bool) -> boolToggles run mode on/off.Parameters:enabled - True to enable run, false to disableReturns: bool - SuccessExample:if walking_get_run_energy() > 50 { walking_set_run(true); } else { walking_set_run(false); } walking_walk_path(path: &Vec<Position>) -> boolWalks along a predetermined path.Parameters:path - Vector of positions to walkReturns: bool - SuccessUse Cases:Following calculated pathsMulti-step navigationSafe route followingwalking_traverse(args: &mut WalkingArgs) -> boolAdvanced walking with configuration options.Parameters:args - Configuration struct (see below)Returns: bool - SuccessWalkingArgs Configurationpub struct WalkingArgs { path: Vec<Position>, area: Option<Area>, destination: Option<Position>, break_condition: Option<Box<dyn Fn() -> bool>>, min_destination_threshold: i32, min_minimap_threshold: i32, min_run_energy: i32, use_minimap: bool, } Methods:new_with_path(path) - Initialize with explicit pathnew_with_area(area) - Initialize with area to reachnew_with_destination(pos) - Initialize with target positionset_break_condition(fn) - Stop if condition becomes trueset_min_destination_threshold(i32) - Distance to destinationset_min_minimap_threshold(i32) - Minimap range thresholdset_min_run_energy(i32) - Minimum energy before runningset_use_minimap(bool) - Use minimap for movementExample:let mut args = WalkingArgs::new_with_destination(&target); args.set_use_minimap(true) .set_min_destination_threshold(5) .set_min_run_energy(30) .set_break_condition(|| { // Stop walking if we're under attack if let Some(player) = players_get_local_player() { player.get_interacting_entity().is_some() } else { false } }); walking_traverse(&mut args); Utilities & HelpersMenu & InteractionThe API provides comprehensive menu interaction for selecting game options.Common Functions:menu_is_open() - Check if menu is visiblemenu_get_count() - Get number of menu optionsmenu_get_items() - Get all menu itemsmenu_interact(vars, option, actions) - Click menu optionUse Cases:Handling context menusSelecting dialogue optionsConfirming actionsBankingLocation: src/api/ui/bank.rsCommon Functions:bank_is_open() - Check if bank is openbank_deposit_by_id(id, amount) - Deposit items by IDbank_withdraw_by_id(id, amount) - Withdraw itemsbank_get_amount_by_id(id) - Check item quantityExample:if bank_is_open() { bank_withdraw_by_id(1234, 10); // Withdraw 10 items bank_deposit_all_by_name("Ore"); } InventoryLocation: src/api/ui/tab/inventory.rsCommon Functions:inventory_contains_by_id(id) - Check for iteminventory_get_count() - Total itemsinventory_drop_by_id(id) - Drop iteminventory_interact(item, action) - Use itemComplete Example: Gathering Scenariouse osbot_api::api::scene::*; use osbot_api::api::ui::bank::*; fn gather_and_bank() -> bool { // 1. Get local player position let Some(player) = players_get_local_player() else { return false; }; // 2. Find nearest ore rock let Some(ore) = objects_find_closest_by_name( OBJECTS_INTERACTABLE_OBJECT_FLAG, "Ore" ) else { return false; }; // 3. Check if reachable if !map_can_reach_object(&ore) { return false; } // 4. Calculate path and walk if let Some(path) = map_find_local_path_to_object(&ore) { walking_walk_path(&path); } // 5. Interact with ore menu_interact( &ore.get_menu_vars(), &Some("Mine".to_string()), &None, ); // 6. Wait for mining loop { if inventory_is_full() { break; } } // 7. Find and open bank let Some(bank) = objects_find_closest_by_name( OBJECTS_INTERACTABLE_OBJECT_FLAG, "Bank" ) else { return false; }; bank_open(); // 8. Deposit ore bank_deposit_all_by_name("Ore"); true } OSBOT API - Rendering & Paint DocumentationOverviewThe rendering system in OSBOT API provides tools for visualizing game elements, converting world coordinates to screen coordinates, and managing the camera viewport. This is essential for creating overlays, debugging tools, and understanding the 3D-to-2D projection pipeline.Display & Viewport ManagementLocation: src/api/display.rsThe display system manages the game window, viewport dimensions, and coordinate transformations.Core Display Functionsdisplay_get_display_mode() -> DisplayModeRetrieves the current display mode setting.Returns: DisplayMode enumDisplayMode::Fixed - Fixed screen layout (1:1 pixel ratio)DisplayMode::ResizableClassic - Resizable classic interfaceDisplayMode::ResizableModern - Resizable modern interfaceUse Cases:Adapting UI rendering to interface styleAdjusting overlay positioningHandling different screen layoutsExample:match display_get_display_mode() { DisplayMode::Fixed => { // Fixed UI - known dimensions }, DisplayMode::ResizableClassic | DisplayMode::ResizableModern => { // Resizable UI - adapt positions dynamically } } display_is_modern_mode() -> boolQuick check if using modern resizable interface.Returns: bool - True if in modern resizable modedisplay_get_viewport() -> ViewportGets the current viewport information.Returns: Viewport struct containing:width: i32 - Viewport width in pixelsheight: i32 - Viewport height in pixelsdepth: i32 - Viewport depth (for 3D calculations)Use Cases:Calculating overlay positionsCentering UI elementsResponsive renderingExample:let viewport = display_get_viewport(); let center_x = viewport.get_width() / 2; let center_y = viewport.get_height() / 2; println!("Viewport: {}x{}", viewport.get_width(), viewport.get_height()); display_get_viewport_width() -> i32Gets viewport width in pixels.display_get_viewport_height() -> i32Gets viewport height in pixels.display_get_viewport_depth() -> i32Gets viewport depth for 3D projection calculations.display_get_canvas_width() -> i32Gets the full canvas width (including UI panels).display_get_canvas_height() -> i32Gets the full canvas height (including UI panels).Use Cases:Full-screen overlay renderingCalculating UI element positionsPositioning elements relative to game windowdisplay_get_root_widget_id() -> i32Gets the root UI widget ID for the current display mode.Returns: i32 - Widget ID (varies by mode)Fixed mode: 548Resizable classic: 161Resizable modern: 164Use Cases:Widget hierarchy traversalUI element identificationdisplay_get_minimap_widget() -> Option<RSWidget>Gets the minimap widget reference.Returns: Option<RSWidget> - Minimap widget if accessibleUse Cases:Getting minimap bounds for overlayMinimap-based renderingDisplay Utilitiesdisplay_is_point_in_polygon(point, vertices) -> boolChecks if a point is inside a polygon.Parameters:point: (i32, i32) - Point to checkvertices: &[(i32, i32)] - Polygon verticesReturns: bool - True if inside polygonUse Cases:Click detection on complex shapesCollision detection with objectsBoundary checkingExample:let vertices = vec![(0, 0), (100, 0), (100, 100), (0, 100)]; let mouse_pos = (50, 50); if display_is_point_in_polygon(mouse_pos, &vertices) { println!("Click is inside the rectangle"); } Coordinate Projection SystemLocation: src/api/util/projection/The projection system converts 3D world coordinates into 2D screen coordinates for rendering.ProjectionInfoprojection_info_get() -> &'static ProjectionInfoGets the cached projection information for the current frame.Returns: &ProjectionInfo - Immutable reference to projection dataUse Cases:World-to-screen conversionsCamera state queriesRendering calculationsProperties accessed via ProjectionInfo:get_plane() -> i32 - Current floor levelget_heightmap() -> &Heightmap - Elevation dataget_camera() -> &CameraInfo - Camera position/angleget_viewport() -> &Viewport - Screen dimensionsis_valid() -> bool - Check if data is currentExample:let projection = projection_info_get(); println!("Current plane: {}", projection.get_plane()); println!("Camera at: ({}, {})", projection.get_camera().get_x(), projection.get_camera().get_y()); Viewport Structpub struct Viewport { width: i32, height: i32, depth: i32, } Methods:get_width() -> i32 - Viewport widthget_height() -> i32 - Viewport heightget_depth() -> i32 - Viewport depthHeightmapThe heightmap contains elevation data for all tiles in the visible region.Location: src/api/scene/heightmap.rsHeightmap Methodspub fn get_width(&self) -> i32; pub fn get_height(&self) -> i32; pub fn get_tile_height(&self, x: i32, y: i32, plane: i32) -> i32; pub fn get_tile_setting(&self, x: i32, y: i32, plane: i32) -> i8; Use Cases:Getting ground elevation for NPCs/objectsWater level detectionSlope calculationsExample:let heightmap = projection.get_heightmap(); let ground_height = heightmap.get_tile_height(100, 100, 0); println!("Ground elevation at (100, 100): {}", ground_height); Camera SystemLocation: src/api/camera.rsThe camera system controls the 3D perspective and view orientation.Camera Informationcamera_get_camera_info() -> CameraInfoGets complete camera state information.Returns: CameraInfo struct containing:x: i32 - Camera X positiony: i32 - Camera Y positionz: i32 - Camera Z position (height)pitch: i32 - Vertical angle (0-2047, normalized)yaw: i32 - Horizontal angle (0-2047, normalized)scale_z: f32 - Vertical scaling factorExample:let camera = camera_get_camera_info(); println!("Camera position: ({}, {}, {})", camera.get_x(), camera.get_y(), camera.get_z()); println!("Camera angle: yaw={}, pitch={}", camera.get_yaw(), camera.get_pitch()); Camera State Queriescamera_get_pitch() -> i32Gets camera vertical angle (pitch).Returns: i32 - Pitch in game units (0-2047)Range: 22 (bottom) to 67 (top)camera_get_yaw() -> i32Gets camera horizontal angle (rotation).Returns: i32 - Yaw in game units (0-2047)Use Cases:Understanding current view directionCalculating relative angles to objectsDirection-aware renderingcamera_get_x() -> i32Camera world X coordinate.camera_get_y() -> i32Camera world Y coordinate.camera_get_z() -> i32Camera world Z coordinate (height).camera_get_scale_z() -> f32Zoom level scaling factor.Use Cases:Zoom-based rendering adjustmentsPerspective calculationscamera_get_pitch_angle() -> i32Gets pitch angle in degrees.camera_get_yaw_angle() -> i32Gets yaw angle in degrees.Use Cases:Human-readable angle valuesUI display of camera anglesCamera Angle Constantsconst CAMERA_MAX_PITCH: i32 = 67; // Maximum upward angle const CAMERA_MIN_PITCH: i32 = 22; // Maximum downward angle Camera Controlcamera_move_pitch_angle(pitch: i32) -> boolAdjusts camera vertical angle.Parameters:pitch: i32 - Change in pitch (positive = up, negative = down)Returns: bool - SuccessExample:// Rotate camera upward camera_move_pitch_angle(10); // Rotate camera downward camera_move_pitch_angle(-10); camera_move_yaw_angle(yaw: i32) -> boolRotates camera horizontally.Parameters:yaw: i32 - Change in yaw (positive = right, negative = left)camera_to_top() -> boolMoves camera to maximum height angle.camera_to_bottom() -> boolMoves camera to minimum height angle.camera_move_to_entity<T: Entity>(entity: &T) -> boolOrients camera to look at an entity.Parameters:entity - Target entity to look atReturns: bool - SuccessUse Cases:Focus on NPCs during interactionLooking at playersCombat monitoringExample:if let Some(npc) = npcs_find_closest_by_id(1234) { camera_move_to_entity(&npc); } camera_move_to_position(position: &Position) -> boolOrients camera toward a position.Parameters:position - Target world positioncamera_move_to_angle(yaw: i32, pitch: i32) -> boolSets camera to specific angles.Parameters:yaw - Target yaw anglepitch - Target pitch anglecamera_can_use_mouse_control() -> boolChecks if middle-mouse camera control is enabled.Use Cases:Detecting player-controlled cameraPreventing automation conflictscamera_get_yaw_to(origin: &Position, position: &Position) -> i32Calculates yaw angle from one position to another.Returns: i32 - Required yaw to face targetUse Cases:Calculating relative anglesPredictive camera positioningcamera_get_pitch_to(origin: &Position, position: &Position, height: i32) -> i32Calculates pitch angle from position to target.Parameters:origin - Starting positionposition - Target positionheight - Height differenceReturns: i32 - Required pitch to face targetRendering & Screen CoordinatesLocation: src/api/display.rsConverting world coordinates to screen coordinates is essential for all overlay rendering.Screen Coordinate Conversiondisplay_get_screen_coordinates(grid_x, grid_y, plane, elevation, projection_info) -> (i32, i32)Converts world coordinates to screen coordinates.Parameters:grid_x: i32 - World X coordinategrid_y: i32 - World Y coordinateplane: i32 - Floor level (0-3)elevation: i32 - Z height above groundprojection_info: &ProjectionInfo - Camera/viewport dataReturns: (i32, i32) - Screen X and Y coordinatesUse Cases:Placing overlays on entitiesDrawing lines to targetsRendering visual indicatorsExample:let projection = projection_info_get(); let screen_coords = display_get_screen_coordinates( 100, // world X 100, // world Y 0, // ground plane 64, // elevation (entity height) projection ); println!("Entity appears at screen: {:?}", screen_coords); display_get_model_screen_coordinates(model, grid_x, grid_y, plane, elevation, projection_info) -> Vec<(i32, i32)>Converts model vertices to screen coordinates.Parameters:model: &Model - 3D model datagrid_x, grid_y, plane, elevation - World positionprojection_info - Camera/viewport dataReturns: Vec<(i32, i32)> - Screen coordinates for all verticesUse Cases:Drawing bounding boxes around modelsRendering detailed object overlaysComplex shape renderingInteraction Point Calculationdisplay_get_interaction_point(bounds: (i32, i32, i32, i32)) -> (i32, i32)Calculates interaction point from screen bounds.Parameters:bounds - (x, y, width, height) screen rectangleReturns: (i32, i32) - Interaction point within boundsUse Cases:Finding click target within object boundsRandomizing click positionsGeometric Utilitiesdisplay_calculate_triangle_area(p1, p2, p3) -> f32Calculates area of a triangle.Parameters:p1, p2, p3: (i32, i32) - Triangle verticesReturns: f32 - Triangle areaUse Cases:Weighted random point selectionArea-based calculationsdisplay_point_in_triangle(point, a, b, c) -> boolChecks if point is inside triangle.Parameters:point - Test pointa, b, c - Triangle verticesReturns: bool - True if insideUse Cases:Click detection on triangular areasComplex shape testingdisplay_get_random_point_in_triangle(p1, p2, p3) -> (i32, i32)Generates random point within triangle.Returns: (i32, i32) - Random point inside triangleUse Cases:Randomized click positions on objectsArea-based mouse movementExample:let triangle_vertices = [(0, 0), (100, 0), (50, 100)]; let random_click = display_get_random_point_in_triangle( triangle_vertices[0], triangle_vertices[1], triangle_vertices[2] ); Polygon Utilitiesdisplay_is_inside_convex_polygon(polygon, point) -> boolChecks if point is inside convex polygon.Parameters:polygon: &[(i32, i32)] - Polygon verticespoint: (i32, i32) - Point to testReturns: bool - True if insidedisplay_calculate_convex_hull(points: Vec<(i32, i32)>) -> ConvexHullCalculates convex hull from points.Returns: ConvexHull - Resulting convex hull structureUse Cases:Generating object bounding shapesClick region optimizationClipping Spacedisplay_get_world_screen_area() -> ClippingSpaceGets the screen area containing world view.Returns: ClippingSpace - Screen clipping regionMethods:contains(x: i32, y: i32) -> bool - Check if point is visibleUse Cases:Clipping rendering to viewportVisibility cullingMinimap RenderingLocation: src/api/ui/minimap.rsThe minimap is a bird's-eye view of the game world. Converting between world and minimap coordinates is essential.Minimap Stateminimap_get_pitch() -> i32Gets minimap vertical rotation.minimap_get_yaw() -> i32Gets minimap horizontal rotation.Returns: i32 - Angle (0-2047)minimap_get_zoom() -> i32Gets minimap zoom level.Returns: i32 - Zoom factorminimap_get_angle() -> i32Gets effective minimap angle (accounting for rotation).Returns: i32 - Combined angleUse Cases:Calculating minimap point positionsAdjusting rendering for map rotationMinimap Coordinate Conversiondisplay_get_minimap_coordinates(local_x, local_y) -> (i32, i32)Converts world coordinates to minimap coordinates.Parameters:local_x: i32 - Local X coordinatelocal_y: i32 - Local Y coordinateReturns: (i32, i32) - Minimap screen coordinatesUse Cases:Drawing indicators on minimapMinimap-based overlaysExample:if let Some(player) = players_get_local_player() { let local_x = player.get_local_x(); let local_y = player.get_local_y(); let minimap_pos = display_get_minimap_coordinates(local_x, local_y); println!("Player on minimap: {:?}", minimap_pos); } display_get_minimap_coordinates_from_args(bounds, zoom, angle, player_x, player_y, target_x, target_y) -> (i32, i32)Advanced minimap coordinate calculation with explicit parameters.Parameters:minimap_bounds: (i32, i32, i32, i32) - Minimap screen areaminimap_zoom: i32 - Zoom levelminimap_angle: i32 - Rotation angleplayer_local_x, player_local_y - Player positionposition_local_x, position_local_y - Target positionReturns: (i32, i32) - Minimap screen coordinatesMinimap Range Queriesminimap_is_position_in_minimap_range(player_position, position) -> boolChecks if position is visible on minimap.Parameters:player_position - Player's positionposition - Position to checkReturns: bool - True if within minimap render distanceUse Cases:Determining if entities are map-visibleMinimap icon rendering decisionsminimap_get_max_range() -> i32Gets maximum minimap visibility range.Returns: i32 - Range in tilesminimap_is_position_on_map(position) -> boolChecks if position appears on minimap currently.Returns: bool - True if on visible map areaMinimap Interactionminimap_click_position(position: &Position) -> boolClicks a position via minimap (fast travel).Parameters:position - Position to clickReturns: bool - SuccessUse Cases:Quick navigation using minimapLong-distance movementExample:let target = Position::new(100, 100, 0); if minimap_is_position_in_minimap_range(&player_pos, &target) { minimap_click_position(&target); } minimap_hover_position(position: &Position) -> boolHovers mouse over a minimap position.Returns: bool - SuccessUse Cases:Previewing positions before clickingHover-based interactionsPainting & Overlay ExamplesConvexHull RenderingLocation: src/api/scene/convex_hull.rsThe ConvexHull structure represents a 2D object outline projected to screen coordinates and can be rendered.pub struct ConvexHull { points: Vec<(i32, i32)> // Screen coordinates } impl ConvexHull { pub fn render(&self, painter: &Painter, color: Color32); pub fn render_offset(&self, painter: &Painter, offset: (i32, i32), color: Color32); } Methods:contains(point) -> bool - Check if point inside hullget_central_point() -> (i32, i32) - Center of hullget_interaction_point() -> (i32, i32) - Interaction targetis_visible() -> bool - Currently on screenExample: Drawing Object Overlaysuse eframe::egui::{Color32, Painter}; fn render_entity_overlay( painter: &Painter, entity: &impl Entity, color: Color32 ) { let projection = projection_info_get(); // Get screen coordinates let screen_pos = display_get_screen_coordinates( entity.get_x(), entity.get_y(), projection.get_plane(), entity.get_height(), projection ); // Draw circle at position painter.circle( Pos2::new(screen_pos.0 as f32, screen_pos.1 as f32), 10.0, color, Stroke::new(2.0, Color32::WHITE) ); } Example: Rendering a Pathfn render_walking_path( painter: &Painter, path: &Vec<Position>, color: Color32 ) { let projection = projection_info_get(); let mut screen_points = Vec::new(); for position in path { let screen = display_get_screen_coordinates( position.x, position.y, projection.get_plane(), 0, projection ); screen_points.push(Pos2::new(screen.0 as f32, screen.1 as f32)); } // Draw line connecting all points for i in 0..screen_points.len() - 1 { painter.line_segment( [screen_points[i], screen_points[i + 1]], Stroke::new(2.0, color) ); } } Example: Minimap Indicatorsfn render_minimap_targets( painter: &Painter, targets: &Vec<Position>, color: Color32 ) { for target in targets { if minimap_is_position_in_minimap_range( &players_get_local_player().unwrap().get_position(), target ) { let minimap_pos = display_get_minimap_coordinates( target.x, target.y ); painter.circle( Pos2::new(minimap_pos.0 as f32, minimap_pos.1 as f32), 4.0, color, Stroke::new(1.0, Color32::WHITE) ); } } } Display Mode HandlingFixed vs Resizable LayoutThe display system supports three different UI layouts that affect coordinate calculations:match display_get_display_mode() { DisplayMode::Fixed => { // Coordinates are fixed (640x480 viewport) // UI elements have fixed positions // No scaling needed }, DisplayMode::ResizableClassic => { // Classic tabbed interface that can be resized // Use display_get_root_widget_id() = 161 // Calculate positions relative to viewport }, DisplayMode::ResizableModern => { // Modern side-panel interface (2015+ style) // Use display_get_root_widget_id() = 164 // More complex layout with docked widgets } } Performance ConsiderationsProjection CachingThe ProjectionInfo is cached and updated per frame:// Cached internally - cheap to call let projection = projection_info_get(); // Reuse for multiple conversions for entity in entities { let screen = display_get_screen_coordinates( entity.get_x(), entity.get_y(), projection.get_plane(), entity.get_height(), projection // Reuse cached projection ); } Viewport QueriesViewport information is queried frequently:// Cache these values if using multiple times let viewport_width = display_get_viewport_width(); let viewport_height = display_get_viewport_height(); // Use cached values for calculations let center_x = viewport_width / 2; let center_y = viewport_height / 2; Clipping and CullingAlways check visibility before rendering:let clipping = display_get_world_screen_area(); if clipping.contains(screen_x, screen_y) { // Only render if within visible area painter.circle(...); } Common Rendering PatternsPattern 1: Draw NPC at Positionlet npc = npcs_find_closest_by_id(1234).unwrap(); let projection = projection_info_get(); let screen = display_get_screen_coordinates( npc.get_x(), npc.get_y(), projection.get_plane(), 64, projection // NPC height ); painter.circle(Pos2::new(screen.0 as f32, screen.1 as f32), 5.0, Color32::RED, Stroke::new(1.0, Color32::WHITE)); Pattern 2: Minimap Markerif minimap_is_position_in_minimap_range(&player_pos, &target) { let minimap = display_get_minimap_coordinates(target.x, target.y); painter.circle(Pos2::new(minimap.0 as f32, minimap.1 as f32), 3.0, Color32::BLUE, Stroke::new(1.0, Color32::WHITE)); } Pattern 3: Distance-Based Coloringfn get_distance_color(distance: i32) -> Color32 { match distance { 0..=10 => Color32::RED, 11..=20 => Color32::YELLOW, 21..=50 => Color32::GREEN, _ => Color32::GRAY, } } OSBOT API - Text & Image Rendering GuideOverviewThe rendering documentation I provided covers geometric rendering (circles, lines, shapes) using the egui framework, but does not explicitly cover text and image rendering for overlays.However, the API provides the foundation through two key mechanisms:egui UI Framework - For text, images, and UI elementsScript Rendering Hooks - Where you can draw custom overlaysText & Image Rendering ArchitectureThe Rendering PipelineThe API uses egui (from the eframe crate) as its rendering backend. You get access to rendering through the Script trait's rendering callbacks.Script Trait Rendering MethodsLocation: src/api/script/script.rsThe Script trait provides two rendering entry points:pub trait Script { // ... other methods ... /// Called every frame to render custom UI fn on_render(&self, ui: &mut egui::Ui) { // Your overlay rendering goes here } /// Called every frame to render debug overlays fn on_debug_render(&self, ui: &mut egui::Ui) { // Debug-specific rendering } } These methods receive a mutable reference to egui::Ui, which is the primary interface for rendering text, images, and UI elements.Text RenderingBasic Text RenderingUsing egui::Ui:impl Script for MyScript { fn on_render(&self, ui: &mut egui::Ui) { // Display simple text label ui.label("Simple text overlay"); // Display colored text ui.colored_label(egui::Color32::RED, "Red text"); // Display text with custom font size ui.label(egui::RichText::new("Large text").size(20.0)); } } Text with Colorsuse eframe::egui::{Color32, RichText}; fn on_render(&self, ui: &mut egui::Ui) { // Red text ui.label(RichText::new("Health: Low").color(Color32::RED)); // Green text ui.label(RichText::new("Status: Safe").color(Color32::GREEN)); // Yellow text with larger font ui.label(RichText::new("Warning!").color(Color32::YELLOW).size(18.0)); } Monospace/Code Textfn on_render(&self, ui: &mut egui::Ui) { // Monospace font (good for coordinates, numbers) ui.label(RichText::new("X: 100, Y: 200").monospace()); // Combine styles ui.label(RichText::new("Position: 100, 200") .monospace() .color(Color32::CYAN) .size(14.0) ); } Heading Stylesfn on_render(&self, ui: &mut egui::Ui) { ui.heading("Main Title"); // Large heading ui.subheading("Subtitle"); // Medium heading } Text Layout & Positioninguse eframe::egui::{Rect, Vec2, Align2}; fn on_render(&self, ui: &mut egui::Ui) { // Position text at specific screen location let screen_rect = ui.available_rect_before_wrap(); // Top-left corner ui.painter().text( egui::Pos2::new(10.0, 10.0), Align2::LEFT_TOP, "Text at top-left", egui::FontId::default(), Color32::WHITE ); // Top-right corner ui.painter().text( egui::Pos2::new(screen_rect.right() - 10.0, 10.0), Align2::RIGHT_TOP, "Text at top-right", egui::FontId::default(), Color32::WHITE ); } Text Alignment Optionsuse eframe::egui::Align2; // Align2(horizontal, vertical) options: // LEFT_TOP, CENTER_TOP, RIGHT_TOP // LEFT_CENTER, CENTER, RIGHT_CENTER // LEFT_BOTTOM, CENTER_BOTTOM, RIGHT_BOTTOM Dynamic Text with Variablesfn on_render(&self, ui: &mut egui::Ui) { if let Some(player) = players_get_local_player() { let name = player.get_name(); let x = player.get_x(); let y = player.get_y(); ui.label(format!("Player: {}", name)); ui.label(format!("Position: ({}, {})", x, y)); } } Multi-line Textfn on_render(&self, ui: &mut egui::Ui) { let text = "Line 1\nLine 2\nLine 3"; ui.label(text); } Image RenderingScreen Capture as TextureThe API provides a screen capture function that converts game frames to textures:Location: src/api/util/screen_capture.rspub fn screen_capture_capture_game_image(title: String, ui: &egui::Ui) -> Option<TextureHandle> pub fn screen_capture_capture_game() -> Option<(i32, i32, Vec<u8>)> screen_capture_capture_game_image(title, ui) -> Option<TextureHandle>Captures the game screen and returns as an egui texture.Parameters:title: String - Label for the captured textureui: &egui::Ui - UI context for texture registrationReturns: Option<TextureHandle> - Handle to rendered texture, or None if capture failedUse Cases:Displaying game state snapshotsDebug visualization of game framesRecording/replay featuresscreen_capture_capture_game() -> Option<(i32, i32, Vec<u8>)>Captures raw game frame data.Returns: Option<(width, height, pixel_data)> - Image dimensions and RGBA pixel dataUse Cases:Processing game frames programmaticallyConverting to custom image formatsAnalysis and debuggingRendering Captured Imagesuse eframe::egui::TextureHandle; fn on_render(&self, ui: &mut egui::Ui) { // Capture the game screen if let Some(texture) = screen_capture_capture_game_image("game_view".to_string(), ui) { // Display as image ui.image(&texture, egui::Vec2::new(640.0, 480.0)); } } Using External Image Filesuse eframe::egui::{ColorImage, TextureHandle, TextureOptions}; use image::ImageReader; use std::io::Cursor; fn on_render(&self, ui: &mut egui::Ui) { // Load image from file if let Ok(img) = ImageReader::open("path/to/image.png") { if let Ok(rgba) = img.decode().map(|i| i.to_rgba8()) { let size = [rgba.width() as usize, rgba.height() as usize]; let pixels = rgba.into_raw(); // Convert to egui ColorImage let color_image = ColorImage::from_rgba_unmultiplied( size, &pixels ); // Register texture let texture = ui.ctx().load_texture( "my_image", color_image, TextureOptions::LINEAR ); // Display ui.image(&texture, egui::Vec2::new(200.0, 200.0)); } } } Advanced Rendering PatternsPattern 1: HUD Overlay with Textfn on_render(&self, ui: &mut egui::Ui) { // Top-left corner HUD egui::Window::new("HUD") .fixed_pos(egui::Pos2::new(10.0, 10.0)) .show(ui.ctx(), |ui| { if let Some(player) = players_get_local_player() { ui.label(format!("Position: ({}, {})", player.get_x(), player.get_y())); ui.label(format!("Health: {}", 64)); // Placeholder } }); } Pattern 2: Debug Overlayfn on_debug_render(&self, ui: &mut egui::Ui) { egui::Window::new("Debug") .resizable(true) .show(ui.ctx(), |ui| { ui.label(format!("FPS: {}", 60)); // Would be dynamic ui.label(format!("Entities: {}", players_get_all().count())); if let Some(player) = players_get_local_player() { ui.group(|ui| { ui.label("Player Info:"); ui.label(format!(" Position: ({}, {})", player.get_x(), player.get_y())); ui.label(format!(" Combat Level: {}", player.get_combat_level())); }); } }); } Pattern 3: Info Panel with Formattingfn on_render(&self, ui: &mut egui::Ui) { egui::Frame::dark_canvas(ui.style()) .margin(egui::Vec2::splat(10.0)) .show(ui, |ui| { ui.heading("Bot Status"); ui.separator(); ui.horizontal(|ui| { ui.label("Status:"); ui.colored_label(Color32::GREEN, "Running"); }); ui.horizontal(|ui| { ui.label("Progress:"); ui.colored_label(Color32::YELLOW, "50%"); }); }); } Pattern 4: Coordinate Display at World Positionfn on_render(&self, ui: &mut egui::Ui) { if let Some(player) = players_get_local_player() { let projection = projection_info_get(); // Convert world to screen coordinates let screen = display_get_screen_coordinates( player.get_x(), player.get_y(), projection.get_plane(), 64, projection ); // Draw text at that screen position ui.painter().text( egui::Pos2::new(screen.0 as f32, screen.1 as f32), egui::Align2::CENTER_CENTER, format!("{}, {}", player.get_x(), player.get_y()), egui::FontId::default(), Color32::WHITE ); } } Complete Example: Full-Featured Overlayuse osbot_api::prelude::*; use eframe::egui::{Color32, RichText, Pos2, Align2}; struct MyScript { // Script state } impl Script for MyScript { fn new() -> Self { MyScript {} } fn on_start(&mut self, params: Option<String>) { println!("Script started!"); } fn on_loop(&mut self) -> i32 { // Main loop logic here 100 // Sleep for 100ms } fn on_render(&self, ui: &mut egui::Ui) { // Main HUD window egui::Window::new("Overlay") .fixed_pos(Pos2::new(20.0, 20.0)) .default_width(300.0) .show(ui.ctx(), |ui| { ui.heading("Bot Control Panel"); ui.separator(); // Player info section if let Some(player) = players_get_local_player() { ui.group(|ui| { ui.label(RichText::new("Player Information") .color(Color32::CYAN).underline()); ui.label(format!("Name: {}", player.get_name())); ui.label(format!("Level: {}", player.get_combat_level())); ui.label(format!("Position: ({}, {})", player.get_x(), player.get_y())); }); } ui.separator(); // World info section let projection = projection_info_get(); ui.group(|ui| { ui.label(RichText::new("World Information") .color(Color32::CYAN).underline()); ui.label(format!("Plane: {}", projection.get_plane())); ui.label(format!("Viewport: {}x{}", display_get_viewport_width(), display_get_viewport_height() )); }); ui.separator(); // Entity counts ui.group(|ui| { ui.label(RichText::new("Entities") .color(Color32::CYAN).underline()); ui.label(format!("Players: {}", players_get_all().count())); ui.label(format!("NPCs: {}", npcs_get_all().count())); }); }); // Draw coordinates at specific screen positions if let Some(player) = players_get_local_player() { let projection = projection_info_get(); let screen = display_get_screen_coordinates( player.get_x(), player.get_y(), projection.get_plane(), 64, projection ); ui.painter().text( Pos2::new(screen.0 as f32, (screen.1 - 20) as f32), Align2::CENTER_TOP, player.get_name(), egui::FontId::default(), Color32::WHITE ); } } fn on_debug_render(&self, ui: &mut egui::Ui) { egui::Window::new("Debug Info") .fixed_pos(Pos2::new(20.0, 400.0)) .show(ui.ctx(), |ui| { ui.label("Camera Info:"); let camera = camera_get_camera_info(); ui.label(format!("Position: ({}, {}, {})", camera.get_x(), camera.get_y(), camera.get_z())); ui.label(format!("Pitch: {}, Yaw: {}", camera.get_pitch(), camera.get_yaw())); }); } fn can_start(&self) -> bool { true } fn is_running(&self) -> bool { true } fn is_alive(&self) -> bool { true } } Available egui ComponentsThe egui framework provides many components you can use in rendering:Text Componentsui.label() - Simple textui.heading() - Large headingui.subheading() - Medium headingui.monospace_multiline_text() - Code/data displayRichText - Styled text (colors, sizes, fonts)Layout Componentsui.horizontal() - Side-by-side layoutui.vertical() - Top-to-bottom layoutui.group() - Grouped content with backgroundegui::Window - Floating windowegui::Frame - Bordered frameui.separator() - Visual dividerInput Componentsui.button() - Clickable buttonui.checkbox() - Toggle checkboxui.slider() - Value sliderui.text_edit_singleline() - Text inputVisual Elementsui.painter().circle() - Draw circleui.painter().line_segment() - Draw lineui.painter().rect() - Draw rectangleui.painter().text() - Positioned textui.image() - Display image/textureColor32 Predefined Colorsuse eframe::egui::Color32; // Common colors Color32::WHITE Color32::BLACK Color32::RED Color32::GREEN Color32::BLUE Color32::CYAN Color32::MAGENTA Color32::YELLOW Color32::GRAY Color32::DARK_GRAY Color32::LIGHT_GRAY // Custom color (RGBA) Color32::from_rgba_unmultiplied(r, g, b, a) Painter & Advanced DrawingFor more complex rendering, use the Painter directly:fn on_render(&self, ui: &mut egui::Ui) { let painter = ui.painter(); // Draw shapes painter.circle_filled( egui::Pos2::new(100.0, 100.0), 10.0, Color32::RED ); // Draw text at arbitrary position painter.text( Pos2::new(50.0, 50.0), Align2::CENTER_CENTER, "Custom text", egui::FontId::default(), Color32::WHITE ); // Draw rectangle painter.rect_filled( egui::Rect::from_two_pos( Pos2::new(0.0, 0.0), Pos2::new(100.0, 100.0) ), 0.0, Color32::BLUE ); }
Create an account or sign in to comment