---
# System prepended metadata

title: Chrono API Docs

---

# 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()
```