# Chrono API Docs Chrono Bot offers a basic python API, that allows you to control bots and clients. ###### Simple Bot example ```python= from __future__ import annotations import chrono_api.core as core from chrono_api.utils import get_nearest_monster from chrono_api.script import ScriptBase class Script(ScriptBase): # the name of the class HAS to be Script async def run(self) -> None: # check if there is at least one client if not (clients := core.get_clients()): self.log('There is no running client') # you can see logged messages in the Script window return # get the first client client = clients[0] # we're gonna loop until the client get's disconnected while client.is_connected: # find the nearest monster monster = get_nearest_monster(client) self.log(f'Current target: { monster.name }') # kill him! >:( while monster.is_alive: self.log(f'Using a auto-attack on { monster.name } with { monster.hp }% HP') await client.attack(monster) ``` ## Client API Client API allows you to interact directly with the nostale client, such as moving with your character, attacking, and checking near monsters/players. Of course, not everything is implemented right now in this API, like collecting items, but I'll try to add those things progressively. (anyways almost everything can be implemented by sending packets) :::spoiler {state="closed"} ###### Code that represents Client API ```python= class Entity: @property def entity_id(self) -> int: ... @property def client_id(self) -> int: ... @property def entity_type(self) -> str: ... @property def x(self) -> int: ... @property def y(self) -> int: ... @property def name(self) -> str: ... class LivingEntity(Entity): @property def hp(self) -> int: ... @property def max_hp(self) -> int: ... @property def mp(self) -> int: ... @property def max_mp(self) -> int: ... @property def can_attack(self) -> bool: ... @property def can_move(self) -> bool: ... @property def is_alive(self) -> bool: """ Does not raise an error when the monster is not present on the map, in that case it returns False """ class Player(LivingEntity): ... class Monster(LivingEntity): ... class Npc(LivingEntity): ... class Item(Entity): ... class Portal: @property def entity_id(self) -> int: ... @property def x(self) -> int: ... @property def y(self) -> int: ... @property def destination_map(self) -> int: ... class Skill: @property def id(self) -> int: ... @property def name(self) -> str: ... @property def cast_id(self) -> int: ... @property def is_on_cooldown(self) -> bool: ... class InventoryItems: @property def client_id(self) -> str: ... @property def name(self) -> str: ... @property def item_id(self) -> int: ... @property def inventory_type(self) -> str: ... @property def slot(self) -> int: ... @property def amount(self) -> int: ... class Client: @property def is_connected(self) -> bool: ... @property def client_id(self) -> str: ... @property def character(self) -> Player: ... @property def monsters(self) -> list[Monster]: ... @property def players(self) -> list[Player]: ... @property def items(self) -> list[Item]: ... @property def npcs(self) -> list[Npc]: ... @property def portals(self) -> list[Portal]: ... @property def skills(self) -> list[Skill]: ... @property def inventory_items(self) -> list[InventoryItems]: ... async def walk(self, x: int, y: int): ... async def walk_in_range(self, x: int, y: int, range: float): ... async def use_skill(self, skill: Skill, target: Entity): ... async def attack(self, target: Entity): """ Uses auto-attack on the target, if the auto-attack is on cooldown, it waits for it to finish """ def send_packet(self, packet: str) -> None: ... def get_clients() -> list[Client]: ... ``` ::: --- As you can see the only way of getting client object is using the `get_clients` function, that gives you the `list[Client]`. Almost all methods are properties, which means that you can't modify the values of Entities/Client. ###### Print names of all players on the map ```python= from __future__ import annotations import chrono_api.core as core from chrono_api.script import ScriptBase class Script(ScriptBase): # the name of the class HAS to be Script async def run(self) -> None: # check if there is at least one client if not (clients := core.get_clients()): self.log('There is no running client') # you can see logged messages in the Script window return # get the first client client = clients[0] for player in client.players: self.log(f'Player with name: { player.name }') ``` ## Loot Bot API Loot Bot API allows you to interact directly with the Loot Bot instances. For now there are only methods to start, stop, change the configuration and client. This can be great if you want to have for example IC script that automtically joins the IC (stops to bot, joins the IC, changes Loot Bot config to IC, does IC, stops the bot, selects the old config, and continues botting). :::spoiler {state="closed"} ###### Code that represents Loot Bot API ```python= class LootBot: @property def is_running(self) -> bool: ... def start(self) -> None: ... def stop(self) -> None: ... @property def bot_id(self) -> str: ... @property def client_id(self) -> Optional[str]: ... @client_id.setter def client_id(self, client_id: str) -> None: ... @property def config_name(self) -> str: ... @config_name.setter def config_name(self, config_name: str) -> None: ... def get_loot_bots() -> list[LootBot]: ... def add_loot_bot() -> LootBot: ... ``` ::: --- ## Event listening API This bot also provides API for listening all incomming Client Events, (client events can be for example received packet, sent packet, entity moved packet etc., all events are gonna be posted later.). The easiest way how to describe this API is probably example. ```python= from __future__ import annotations from typing import Any from chrono_api.script import ScriptBase from chrono_api.script import GLOBAL_EVENT_SUBSCRIBER import chrono_api.core as core class Script(ScriptBase): async def handle_event(self, event: Any): event = event['event'] if event['kind'] == 'ReceivePacket' \ and not event['packet'].startswith('mv'): self.log(event['packet']) async def run(self) -> None: self.log('Script is running') self.subscribe_client(GLOBAL_EVENT_SUBSCRIBER, self.handle_event) await self.wait_forever() ``` In this example we're listing for events from all the clients and then we're logging all received packets that doesn't start with `mv`. ## IC Bot Example ```python= from __future__ import annotations from typing import Any from chrono_api.script import ScriptBase from chrono_api.script import GLOBAL_EVENT_SUBSCRIBER from chrono_api.utils import get_client_by_id from chrono_api.utils import random_sleep from chrono_api.core import Client from chrono_api.events import EventKind class Script(ScriptBase): """ IC Bot, that joins IC and goes to the bottom of the map """ async def join_ic(self, client: Client): client.send_packet('guri 508') client.send_packet('#guri^506') async def handle_events(self, event: Any): client_id = event['client_id'] event = event['event'] if event['kind'] == EventKind.RECEIVE_PACKET \ and event['packet'] == 'qnamli 1 #guri^506 379 0 0 0': if client := get_client_by_id(client_id): await random_sleep(1000, 2000) await self.join_ic(client) elif event['kind'] == EventKind.MAP_CHANGE \ and event['map_id'] == 2004: if client := get_client_by_id(client_id): await random_sleep(2000, 6000) await client.walk(37, 66) async def run(self) -> None: self.subscribe_client(GLOBAL_EVENT_SUBSCRIBER, self.handle_events) await self.wait_forever() ```