Skip to content

Command Helpers

API reference for the routines defined in core.command.

Command(bot, init_handler=False)

Facade that exposes quest, item, map, combat, player, and utility helpers.

Source code in core\command.py
89
90
91
92
93
94
def __init__(self, bot, init_handler = False):
    from core.bot import Bot
    self.bot: Bot = bot

    if init_handler:
        self.bot.subscribe(self._message_handler)

accept_quest(quest_id) async

Send a single quest accept packet and wait briefly for the response.

Parameters:

Name Type Description Default
quest_id int

Identifier of the quest to accept.

required

Returns:

Name Type Description
None None

The coroutine simply delays to allow server processing.

Source code in core\command.py
142
143
144
145
146
147
148
149
150
151
152
153
@check_alive
async def accept_quest(self, quest_id: int) -> None:
    """Send a single quest accept packet and wait briefly for the response.

    Args:
        quest_id (int): Identifier of the quest to accept.

    Returns:
        None: The coroutine simply delays to allow server processing."""
    self.bot.accept_quest(quest_id)
    print("trying accept quest:", quest_id)
    await asyncio.sleep(1)

accept_quest_bulk(quest_id, increment, ensure=False) async

Accept a range of quests sequentially.

Parameters:

Name Type Description Default
quest_id int

Starting quest identifier.

required
increment int

Number of consecutive quest ids to process.

required
ensure bool

Use ensure-accept logic for each quest when True.

False
Source code in core\command.py
225
226
227
228
229
230
231
232
233
234
235
236
237
238
@check_alive
async def accept_quest_bulk(self, quest_id: int, increment: int, ensure:bool = False):
    """Accept a range of quests sequentially.

    Args:
        quest_id (int): Starting quest identifier.
        increment (int): Number of consecutive quest ids to process.
        ensure (bool): Use ensure-accept logic for each quest when True."""
    print(f"accepting quest from {quest_id} to {quest_id + increment}")
    for i in range(increment):
        if ensure:
            await self.ensure_accept_quest(quest_id + i)
        elif not ensure:
            await self.accept_quest(quest_id + i)

add_drop(itemName)

Add items to the drop whitelist handled by the bot.

Parameters:

Name Type Description Default
itemName Union[str, List[str]]

Single name or list of names to whitelist.

required

Returns:

Name Type Description
None None

Extends the whitelist in place.

Source code in core\command.py
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
def add_drop(self, itemName: Union[str, List[str]]) -> None:
    """Add items to the drop whitelist handled by the bot.

    Args:
        itemName (Union[str, List[str]]): Single name or list of names to whitelist.

    Returns:
        None: Extends the whitelist in place.
    """
    if isinstance(itemName, str):
        itemName = [itemName]

    for item in itemName:
        if item not in self.bot.items_drop_whitelist:
            self.bot.items_drop_whitelist.append(item)

bank_to_inv(itemNames) async

Move items from the bank to the inventory.

Parameters:

Name Type Description Default
itemNames Union[str, List[str]]

Single name or list of names to transfer.

required

Returns:

Name Type Description
None None

The coroutine issues move requests for each item.

Source code in core\command.py
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
@check_alive
async def bank_to_inv(self, itemNames: Union[str, List[str]]) -> None:
    """Move items from the bank to the inventory.

    Args:
        itemNames (Union[str, List[str]]): Single name or list of names to transfer.

    Returns:
        None: The coroutine issues move requests for each item.
    """
    itemNames = itemNames if isinstance(itemNames, list) else [itemNames]
    for item in itemNames:
        if not self.is_still_connected():
            return
        item = self.bot.player.get_item_bank(item)        
        if item:
            packet = f"%xt%zm%bankToInv%{self.bot.areaId}%{item.item_id}%{item.char_item_id}%"
            self.bot.write_message(packet)
            is_exist = False
            for itemInv in self.bot.player.INVENTORY:
                if itemInv.item_name == item.item_name:
                    self.bot.player.INVENTORY.remove(itemInv)
                    self.bot.player.INVENTORY.append(item)
                    is_exist = True
                    break
            if not is_exist:
                self.bot.player.INVENTORY.append(item)
            for itemBank in self.bot.player.BANK:
                if itemBank.item_name == item.item_name:
                    self.bot.player.BANK.remove(itemBank)
                    break
            await asyncio.sleep(1)

buy_item(shop_id, item_name, qty=1) async

Buy an item, loading the shop if it is not cached.

Parameters:

Name Type Description Default
shop_id int

Identifier of the shop containing the item.

required
item_name str

Name of the item to purchase.

required
qty int

Quantity to purchase in a single request.

1

Returns:

Name Type Description
None None

Sends the buy packet once the shop data is available.

Source code in core\command.py
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
@check_alive
async def buy_item(self, shop_id: int, item_name: str, qty: int = 1) -> None:
    """Buy an item, loading the shop if it is not cached.

    Args:
        shop_id (int): Identifier of the shop containing the item.
        item_name (str): Name of the item to purchase.
        qty (int): Quantity to purchase in a single request.

    Returns:
        None: Sends the buy packet once the shop data is available.
    """
    print(f"buying {qty} {item_name}")
    shop: Optional[Shop] = None
    for loaded_shop in self.bot.loaded_shop_datas:
        if str(loaded_shop.shop_id) == str(shop_id):
            shop = loaded_shop
            break
    if shop:
        for shop_item in shop.items:
            if shop_item.item_name.lower() == item_name.lower():
                packet = f"%xt%zm%buyItem%{self.bot.areaId}%{shop_item.item_id}%{shop.shop_id}%{shop_item.shop_item_id}%{qty}%"
                self.bot.write_message(packet)
                await asyncio.sleep(1)
                break
    else:
        packet = f"%xt%zm%loadShop%{self.bot.areaId}%{shop_id}%"
        self.bot.write_message(packet)
        await asyncio.sleep(1)
        await self.buy_item(shop_id, item_name, qty)

buy_item_cmd(item_name, shop_id, qty=1) async

Buy an item from a shop, loading data when necessary.

Parameters:

Name Type Description Default
item_name str

Name of the shop item to purchase.

required
shop_id int

Identifier of the shop to query.

required
qty int

Quantity to purchase in a single request.

1

Returns:

Name Type Description
None None

The coroutine sends the purchase packet asynchronously.

Source code in core\command.py
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
async def buy_item_cmd(self, item_name: str, shop_id: int, qty: int = 1) -> None:
    """Buy an item from a shop, loading data when necessary.

    Args:
        item_name (str): Name of the shop item to purchase.
        shop_id (int): Identifier of the shop to query.
        qty (int): Quantity to purchase in a single request.

    Returns:
        None: The coroutine sends the purchase packet asynchronously.
    """
    await self.bot.ensure_leave_from_combat()
    shop = None
    for loaded_shop in self.bot.loaded_shop_datas:
        if str(loaded_shop.shop_id) == str(shop_id):
            shop = loaded_shop
            break
    if shop:
        for shop_item in shop.items:
            if shop_item.item_name == item_name.lower():
                packet = f"%xt%zm%buyItem%{self.bot.areaId}%{shop_item.item_id}%{shop.shop_id}%{shop_item.shop_item_id}%{qty}%"
                self.bot.write_message(packet)
                await asyncio.sleep(0.5)
                break
    else:
        packet = f"%xt%zm%loadShop%{self.bot.areaId}%{shop_id}%"
        self.bot.write_message(packet)
        await asyncio.sleep(1)
        self.bot.index -= 1

can_turnin_quest(questId)

Delegate to the bot helper that checks quest completion requirements.

Source code in core\command.py
181
182
183
def can_turnin_quest(self, questId: int) -> bool:
    """Delegate to the bot helper that checks quest completion requirements."""
    return self.bot.can_turn_in_quest(questId)

check_is_skill_safe(skill)

Return whether a skill is safe to use at the current HP threshold.

Parameters:

Name Type Description Default
skill int

Skill slot that is about to be executed.

required

Returns:

Name Type Description
bool bool

True when the skill can be used safely for the equipped class.

Source code in core\command.py
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
def check_is_skill_safe(self, skill: int) -> bool:
    """Return whether a skill is safe to use at the current HP threshold.

    Args:
        skill (int): Skill slot that is about to be executed.

    Returns:
        bool: True when the skill can be used safely for the equipped class.
    """
    conditions = {
        "void highlord": {
            "hp_threshold": 50, # in percentage of current hp from max hp
            "skills_to_check": [1, 3],
            "condition": lambda hp, threshold: hp < threshold
        },
        "scarlet sorceress": {
            "hp_threshold": 50,
            "skills_to_check": [1, 4],
            "condition": lambda hp, threshold: hp < threshold
        },
        "dragon of time": {
            "hp_threshold": 40,
            "skills_to_check": [1, 3],
            "condition": lambda hp, threshold: hp < threshold
        },
        # "archpaladin": {
        #     "hp_threshold": 70,
        #     "skills_to_check": [2],
        #     "condition": lambda hp, threshold: hp > threshold
        # },
    }
    # Get the class and its conditions
    equipped_class = self.bot.player.get_equipped_item(ItemType.CLASS)
    if equipped_class:
        if equipped_class.item_name in conditions:
            condition = conditions[equipped_class.item_name]
            current_hp = self.bot.player.CURRENT_HP
            max_hp = self.bot.player.MAX_HP
            # Check if the current conditions match
            if skill in condition["skills_to_check"] and condition["condition"]((current_hp / max_hp) * 100, condition["hp_threshold"]):
                return False
    return True

do_pwd(monster_id)

Send a raw PWD packet to the server for a specific monster.

Parameters:

Name Type Description Default
monster_id str

Monster identifier to include in the packet payload.

required

Returns:

Name Type Description
None None

The message is sent and no value is returned.

Source code in core\command.py
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
@check_alive
def do_pwd(self, monster_id: str) -> None:
    """Send a raw PWD packet to the server for a specific monster.

    Args:
        monster_id (str): Monster identifier to include in the packet payload.

    Returns:
        None: The message is sent and no value is returned.
    """
    # %xt%zm%gar%1%3%p6>m:1%wvz%
    self.bot.write_message(f"%xt%zm%gar%1%3%p6>m:{monster_id}%wvz%")

ensure_accept_quest(quest_id) async

Keep accepting a quest until it is in progress or the client disconnects.

Parameters:

Name Type Description Default
quest_id int

Identifier of the quest to accept.

required

Returns:

Name Type Description
None None

Exits once the quest is tracked or a failure is recorded.

Source code in core\command.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
@check_alive
async def ensure_accept_quest(self, quest_id: int) -> None:
    """Keep accepting a quest until it is in progress or the client disconnects.

    Args:
        quest_id (int): Identifier of the quest to accept.

    Returns:
        None: Exits once the quest is tracked or a failure is recorded."""
    while self.quest_not_in_progress(quest_id) and self.is_still_connected():
        await self.accept_quest(quest_id)
        await self.sleep(1000)
        if quest_id in self.bot.failed_get_quest_datas:
            return

ensure_load_shop(shop_id) async

Keep loading a shop until it is present in the cache.

Parameters:

Name Type Description Default
shop_id int

Identifier of the shop to ensure.

required
Source code in core\command.py
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
@check_alive
async def ensure_load_shop(self, shop_id: int) -> None:
    """Keep loading a shop until it is present in the cache.

    Args:
        shop_id (int): Identifier of the shop to ensure.
    """
    await self.leave_combat()
    while True:
        for loaded_shop in self.bot.loaded_shop_datas:
            if str(loaded_shop.shop_id) == str(shop_id): 
                print("loaded_Shop", loaded_shop.shop_id)
                return
        packet = f"%xt%zm%loadShop%{self.bot.areaId}%{shop_id}%"
        self.bot.write_message(packet)
        await asyncio.sleep(1)

ensure_turn_in_quest(quest_id, item_id=-1, amount=1) async

Attempt to turn in a quest until it completes or the client disconnects.

Parameters:

Name Type Description Default
quest_id int

Identifier of the quest to complete.

required
item_id int

Required item id for turn-in when applicable.

-1
amount int

Quantity of the required item.

1

Returns:

Name Type Description
None None

Stops when the quest leaves the progress list or fails.

Source code in core\command.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
@check_alive
async def ensure_turn_in_quest(self, quest_id: int, item_id = -1, amount = 1) -> None:
    """Attempt to turn in a quest until it completes or the client disconnects.

    Args:
        quest_id (int): Identifier of the quest to complete.
        item_id (int): Required item id for turn-in when applicable.
        amount (int): Quantity of the required item.

    Returns:
        None: Stops when the quest leaves the progress list or fails."""
    while self.quest_in_progress(quest_id) and self.is_still_connected():
        await self.turn_in_quest(quest_id, item_id,amount)
        await self.sleep(1000)
        if quest_id in self.bot.failed_get_quest_datas:
            return
    print("quest turned in:", quest_id, item_id)

equip_item(item_name) async

Equip an inventory item if it is present and not already equipped.

Parameters:

Name Type Description Default
item_name str

Name of the item to equip.

required

Returns:

Name Type Description
None None

Updates the equipped state and sends the equip packet.

Source code in core\command.py
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
@check_alive
async def equip_item(self, item_name: str) -> None:
    """Equip an inventory item if it is present and not already equipped.

    Args:
        item_name (str): Name of the item to equip.

    Returns:
        None: Updates the equipped state and sends the equip packet.
    """
    await self.bot.ensure_leave_from_combat()

    is_equipped = False
    s_type = None
    for item in self.bot.player.INVENTORY:
        if normalize(item.item_name.lower()) == normalize(item_name.lower()):
            if item.is_equipped:
                return
            print(f"equipping {item_name}")
            packet = f"%xt%zm%equipItem%{self.bot.areaId}%{item.item_id}%"
            self.bot.write_message(packet)
            is_equipped = True
            s_type = item.s_type
            item.is_equipped = is_equipped
            await asyncio.sleep(1)
            break
    # Update unequip previous item
    if is_equipped and s_type:
        for item in self.bot.player.INVENTORY:
            if item.s_type == s_type and item.is_equipped and not item.item_name == item_name.lower():
                item.is_equipped = False
                break

equip_item_by_enhancement(enh_id) async

Equip the item that matches a specific enhancement identifier.

Parameters:

Name Type Description Default
enh_id int

Enhancement identifier bound to the desired item.

required

Returns:

Name Type Description
None None

Calls :meth:equip_item when a matching item exists.

Source code in core\command.py
489
490
491
492
493
494
495
496
497
498
499
500
501
502
@check_alive
async def equip_item_by_enhancement(self, enh_id: int) -> None:
    """Equip the item that matches a specific enhancement identifier.

    Args:
        enh_id (int): Enhancement identifier bound to the desired item.

    Returns:
        None: Calls :meth:`equip_item` when a matching item exists.
    """
    # TODO: should change the enhance_pattern_id to enhance name
    item = self.bot.player.get_item_inventory_by_enhance_id(enh_id)
    if item:
        await self.equip_item(item.item_name)

equip_scroll(item_name, item_type=ScrollType.SCROLL) async

Equip a scroll or potion from the player's inventory.

Parameters:

Name Type Description Default
item_name str

Name of the scroll or potion to equip.

required
item_type ScrollType

Scroll category to include in the packet.

SCROLL

Returns:

Name Type Description
None None

Sends the equip packet when the item is found.

Source code in core\command.py
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
@check_alive
async def equip_scroll(self, item_name: str, item_type: ScrollType = ScrollType.SCROLL) -> None:
    """Equip a scroll or potion from the player's inventory.

    Args:
        item_name (str): Name of the scroll or potion to equip.
        item_type (ScrollType): Scroll category to include in the packet.

    Returns:
        None: Sends the equip packet when the item is found.
    """
    for item in self.bot.player.INVENTORY:
        if item.item_name.lower() == item_name.lower():
            packet = f"%xt%zm%geia%{self.bot.areaId}%{item_type.value}%{item.s_meta}%{item.item_id}%"
            self.bot.scroll_id = item.item_id
            self.bot.write_message(packet)
            await asyncio.sleep(1)
            break

farming_logger(item_name, item_qty=1, is_temp=False)

Log farming progress for a specific item.

Parameters:

Name Type Description Default
item_name str

Name of the item being farmed.

required
item_qty int

Target quantity for the farming session.

1
is_temp bool

Whether to read from the temporary inventory.

False

Returns:

Name Type Description
None None

Prints progress information to the console.

Source code in core\command.py
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
def farming_logger(self, item_name: str, item_qty: int = 1, is_temp: bool = False) -> None:
    """Log farming progress for a specific item.

    Args:
        item_name (str): Name of the item being farmed.
        item_qty (int): Target quantity for the farming session.
        is_temp (bool): Whether to read from the temporary inventory.

    Returns:
        None: Prints progress information to the console.
    """
    # Determine inventory type and fetch the item
    inventory_type = "temp" if is_temp else "inv"
    get_inventory = (
        self.bot.player.get_item_temp_inventory
        if is_temp else self.bot.player.get_item_inventory
    )

    # Fetch the item
    item = get_inventory(item_name)
    inv_item_qty = item.qty if item else 0

    # Prepare log message
    current_time = datetime.now().strftime('%H:%M:%S')
    message = (
        f"{Fore.CYAN}[{current_time}] [{inventory_type}] {item_name} "
        f"{inv_item_qty}/{item_qty}{Fore.RESET}"
    )

    # Print log message
    print(message)

get_equipped_class()

Return the currently equipped class inventory item, or None.

Source code in core\command.py
1136
1137
1138
1139
def get_equipped_class(self) -> Optional[ItemInventory]:
    """Return the currently equipped class inventory item, or None."""
    equipped_class = self.bot.player.get_equipped_item(ItemType.CLASS)
    return equipped_class if equipped_class else None

get_farm_class()

Return the configured farming class name, or None when unset.

Source code in core\command.py
1128
1129
1130
def get_farm_class(self) -> Optional[str]:
    """Return the configured farming class name, or None when unset."""
    return None if self.bot.farmClass == "" else self.bot.farmClass

get_loaded_shop(shop_id)

Return a loaded shop instance when available.

Parameters:

Name Type Description Default
shop_id int

Identifier of the shop to look up.

required

Returns:

Type Description
Optional[Shop]

Shop | None: Cached shop instance, or None if it has not been loaded.

Source code in core\command.py
549
550
551
552
553
554
555
556
557
558
559
560
561
562
@check_alive
def get_loaded_shop(self, shop_id: int) -> Optional[Shop]:
    """Return a loaded shop instance when available.

    Args:
        shop_id (int): Identifier of the shop to look up.

    Returns:
        Shop | None: Cached shop instance, or None if it has not been loaded.
    """
    for loaded_shop in self.bot.loaded_shop_datas:
        if str(loaded_shop.shop_id) == str(shop_id): 
            return loaded_shop
    return None

get_map_item(map_item_id, qty=1) async

Collect a map item multiple times.

Parameters:

Name Type Description Default
map_item_id int

Map item identifier to pick up.

required
qty int

Number of pickup attempts.

1

Returns:

Name Type Description
None None

Sends the pickup packet for each requested iteration.

Source code in core\command.py
520
521
522
523
524
525
526
527
528
529
530
531
532
533
@check_alive
async def get_map_item(self, map_item_id: int, qty: int = 1) -> None:
    """Collect a map item multiple times.

    Args:
        map_item_id (int): Map item identifier to pick up.
        qty (int): Number of pickup attempts.

    Returns:
        None: Sends the pickup packet for each requested iteration.
    """
    for _ in range(qty):
        self.bot.write_message(f"%xt%zm%getMapItem%{self.bot.areaId}%{map_item_id}%")
        await asyncio.sleep(1)

get_monster(monster)

Return the monster object that matches the provided identifier.

Parameters:

Name Type Description Default
monster str

Name or id.X identifier of the monster.

required

Returns:

Type Description
Optional[Monster]

Monster or None: Monster instance when found, otherwise None.

Source code in core\command.py
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
def get_monster(self, monster: str) -> Optional[Monster]:
    """Return the monster object that matches the provided identifier.

    Args:
        monster (str): Name or ``id.X`` identifier of the monster.

    Returns:
        Monster or None: Monster instance when found, otherwise None.
    """
    if monster.startswith('id.'):
        monster = monster.split('.')[1]
    for mon in self.bot.monsters:
        if mon.mon_name.lower() == monster.lower() or mon.mon_map_id == monster:
            return mon
    return None

get_monster_hp(monster)

Get the current HP of the requested monster.

Parameters:

Name Type Description Default
monster str

Name or id.X identifier of the monster, * for any.

required

Returns:

Name Type Description
int int

Current HP, or -1 when the monster is not found.

Source code in core\command.py
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
@check_alive
def get_monster_hp(self, monster: str) -> int:
    """Get the current HP of the requested monster.

    Args:
        monster (str): Name or ``id.X`` identifier of the monster, ``*`` for any.

    Returns:
        int: Current HP, or -1 when the monster is not found.
    """
    if monster == None:
        return -1
    if monster.startswith('id.'):
        monster = monster.split('.')[1]
    for mon in self.bot.monsters:
        if mon.mon_name.lower() == monster.lower() or mon.mon_map_id == monster and mon.is_alive:
            return mon.current_hp
        elif monster == "*":
            return mon.current_hp
    # this mean not get the desired monster
    return -1

get_monster_hp_percentage(monster)

Get the remaining HP of a monster as a percentage.

Parameters:

Name Type Description Default
monster str

Name or id.X identifier of the monster, * for any.

required

Returns:

Name Type Description
int int

Rounded HP percentage, or -1 when the monster is missing.

Source code in core\command.py
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
def get_monster_hp_percentage(self, monster: str) -> int:
    """Get the remaining HP of a monster as a percentage.

    Args:
        monster (str): Name or ``id.X`` identifier of the monster, ``*`` for any.

    Returns:
        int: Rounded HP percentage, or -1 when the monster is missing.
    """
    if monster.startswith("id."):
        monster = monster.split(".")[1]

    for mon in self.bot.monsters:
        if mon.mon_name.lower() == monster.lower() or mon.mon_map_id == monster or monster == "*":
            return round((mon.current_hp / mon.max_hp) * 100)
    # this mean not get the desired monster
    return -1

get_player()

Return the bot's active player instance.

Source code in core\command.py
1120
1121
1122
def get_player(self) -> Player:
    """Return the bot's active player instance."""
    return self.bot.player

get_player_cell()

Return the local player's current cell name.

Source code in core\command.py
1141
1142
1143
def get_player_cell(self) -> str:
    """Return the local player's current cell name."""
    return self.bot.player.getPlayerCell()[0]

get_player_in_map(name)

Return the area record for a player in the current map, if present.

Source code in core\command.py
645
646
647
648
649
650
def get_player_in_map(self, name: str) -> Optional[PlayerArea]:
    """Return the area record for a player in the current map, if present."""
    for player in self.bot.player_in_area:
        if player.str_username.lower() == name.lower():
            return player
    return None

get_player_pad()

Return the local player's current pad identifier.

Source code in core\command.py
1145
1146
1147
def get_player_pad(self) -> str:
    """Return the local player's current pad identifier."""
    return self.bot.player.getPlayerCell()[1]

get_player_position_xy()

Return the local player's map coordinates as an [x, y] list.

Source code in core\command.py
1149
1150
1151
def get_player_position_xy(self) -> list[int]:
    """Return the local player's map coordinates as an [x, y] list."""
    return self.bot.player.getPlayerPositionXY()

get_quant_item(itemName)

Return the current quantity of an item in the inventory.

Parameters:

Name Type Description Default
itemName str

Name of the inventory item.

required

Returns:

Name Type Description
int int

Quantity of the item, or 0 when the item is missing.

Source code in core\command.py
323
324
325
326
327
328
329
330
331
332
333
334
335
336
def get_quant_item(self, itemName: str) -> int:
    """Return the current quantity of an item in the inventory.

    Args:
        itemName (str): Name of the inventory item.

    Returns:
        int: Quantity of the item, or 0 when the item is missing.
    """
    # get item quant from inventory
    item_inventory = self.bot.player.get_item_inventory(itemName)
    if item_inventory:
        return item_inventory.qty
    return 0

get_solo_class()

Return the configured solo class name, or None when unset.

Source code in core\command.py
1132
1133
1134
def get_solo_class(self) -> Optional[str]:
    """Return the configured solo class name, or None when unset."""
    return None if self.bot.soloClass == "" else self.bot.soloClass

goto_player(player_name) async

Jump to another player on the current server.

Parameters:

Name Type Description Default
player_name str

Target player name to follow.

required
Source code in core\command.py
659
660
661
662
663
664
665
666
667
668
669
670
671
672
@check_alive
async def goto_player(self, player_name: str) -> None:
    """Jump to another player on the current server.

    Args:
        player_name (str): Target player name to follow.
    """
    player_in_map = self.get_player_in_map(player_name)
    if player_in_map:
        self.bot.jump_cell(player_in_map.str_frame, player_in_map.str_pad)
    else:
        await self.bot.ensure_leave_from_combat(always=True)
        self.bot.write_message(f"%xt%zm%cmd%1%goto%{player_name}%")
        await self.sleep(1000)

hp_below_percentage(percent)

Check if the player HP is below the requested percentage.

Parameters:

Name Type Description Default
percent int

HP threshold to compare against.

required

Returns:

Name Type Description
bool bool

True when the player HP percentage is lower than the threshold.

Source code in core\command.py
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
@check_alive
def hp_below_percentage(self, percent: int) -> bool:
    """Check if the player HP is below the requested percentage.

    Args:
        percent (int): HP threshold to compare against.

    Returns:
        bool: True when the player HP percentage is lower than the threshold.
    """
    return ((self.bot.player.CURRENT_HP / self.bot.player.MAX_HP) * 100) < percent

inv_to_bank(itemNames) async

Move items from the inventory to the bank.

Parameters:

Name Type Description Default
itemNames Union[str, List[str]]

Single name or list of names to transfer.

required

Returns:

Name Type Description
None None

The coroutine issues transfer packets for each item.

Source code in core\command.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
@check_alive
async def inv_to_bank(self, itemNames: Union[str, List[str]]) -> None:
    """Move items from the inventory to the bank.

    Args:
        itemNames (Union[str, List[str]]): Single name or list of names to transfer.

    Returns:
        None: The coroutine issues transfer packets for each item.
    """
    await self.leave_combat()
    itemNames = itemNames if isinstance(itemNames, list) else [itemNames]
    for item in itemNames:
        if not self.is_still_connected():
            return
        item = self.bot.player.get_item_inventory(item)        
        if item:
            packet = f"%xt%zm%bankFromInv%{self.bot.areaId}%{item.item_id}%{item.char_item_id}%"
            self.bot.write_message(packet)
            is_exist = False
            for itemBank in self.bot.player.BANK:
                if itemBank.item_name == item.item_name:
                    self.bot.player.BANK.remove(itemBank)
                    self.bot.player.BANK.append(item)
                    is_exist = True
                    break
            if not is_exist:
                self.bot.player.BANK.append(item)
            for itemInv in self.bot.player.INVENTORY:
                if itemInv.item_name == item.item_name:
                    self.bot.player.INVENTORY.remove(itemInv)
                    break
            await asyncio.sleep(1)

is_completed_before(quest_id) async

Determine whether the quest has been completed previously.

Parameters:

Name Type Description Default
quest_id int

Identifier of the quest to inspect.

required

Returns:

Name Type Description
bool bool

True when the server indicates prior completion.

Source code in core\command.py
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
@check_alive
async def is_completed_before(self, quest_id: int) -> bool:
    """Determine whether the quest has been completed previously.

    Args:
        quest_id (int): Identifier of the quest to inspect.

    Returns:
        bool: True when the server indicates prior completion."""
    await self.turn_in_quest(quest_id)
    while(self.is_still_connected()):
        if self.is_completed_before_var is not None:
            output = self.is_completed_before_var
            # print(f"{quest_id} is {self.is_green_quest_var}")
            self.is_completed_before_var = None
            return output
        else:
            await self.sleep(100)
    return False

is_green_quest(quest_id) async

Check whether a quest is marked green (ready to turn in).

Parameters:

Name Type Description Default
quest_id int

Identifier of the quest to inspect.

required

Returns:

Name Type Description
bool bool

True when the server reports the quest as green.

Source code in core\command.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
@check_alive
async def is_green_quest(self, quest_id: int) -> bool:
    """Check whether a quest is marked green (ready to turn in).

    Args:
        quest_id (int): Identifier of the quest to inspect.

    Returns:
        bool: True when the server reports the quest as green."""
    await self.turn_in_quest(quest_id)
    while(self.is_still_connected()):
        if self.is_green_quest_var is not None:
            output = self.is_green_quest_var
            # print(f"{quest_id} is {self.is_green_quest_var}")
            self.is_green_quest_var = None
            return output
        else:
            await self.sleep(100)
    return False

is_in_bank(itemName, itemQty=1, operator='>=')

Check whether the bank holds a given quantity of an item.

Parameters:

Name Type Description Default
itemName str

Name of the item to inspect.

required
itemQty int

Quantity threshold to test.

1
operator str

Comparison operator understood by the bot API.

'>='

Returns:

Name Type Description
bool bool

True when the bank fulfils the requested quantity test.

Source code in core\command.py
280
281
282
283
284
285
286
287
288
289
290
291
292
def is_in_bank(self, itemName: str, itemQty: int = 1, operator: str = ">=") -> bool:
    """Check whether the bank holds a given quantity of an item.

    Args:
        itemName (str): Name of the item to inspect.
        itemQty (int): Quantity threshold to test.
        operator (str): Comparison operator understood by the bot API.

    Returns:
        bool: True when the bank fulfils the requested quantity test.
    """
    inBank = self.bot.player.isInBank(itemName, itemQty, operator)
    return inBank[0]

is_in_inventory(itemName, itemQty=1, operator='>=', isTemp=False)

Check whether the inventory (temp or permanent) has enough items.

Parameters:

Name Type Description Default
itemName str

Name of the item to inspect.

required
itemQty int

Quantity threshold to test.

1
operator str

Comparison operator understood by the bot API.

'>='
isTemp bool

When True, inspect the temporary inventory.

False

Returns:

Name Type Description
bool bool

True when the inventory satisfies the quantity requirement.

Source code in core\command.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
def is_in_inventory(self, itemName: str, itemQty: int = 1, operator: str = ">=", isTemp: bool = False) -> bool:
    """Check whether the inventory (temp or permanent) has enough items.

    Args:
        itemName (str): Name of the item to inspect.
        itemQty (int): Quantity threshold to test.
        operator (str): Comparison operator understood by the bot API.
        isTemp (bool): When True, inspect the temporary inventory.

    Returns:
        bool: True when the inventory satisfies the quantity requirement.
    """
    inInv = self.bot.player.isInInventory(itemName, itemQty, operator, isTemp)
    return inInv[0]

is_in_inventory_or_bank(itemName, itemQty=1, operator='>=', isTemp=False)

Check whether an item is available in bank or inventory.

Parameters:

Name Type Description Default
itemName str

Name of the item to inspect.

required
itemQty int

Quantity threshold to test.

1
operator str

Comparison operator understood by the bot API.

'>='
isTemp bool

When True, include the temporary inventory.

False

Returns:

Name Type Description
bool bool

True when either storage location satisfies the check.

Source code in core\command.py
309
310
311
312
313
314
315
316
317
318
319
320
321
def is_in_inventory_or_bank(self, itemName: str, itemQty: int = 1, operator: str = ">=", isTemp: bool = False) -> bool:
    """Check whether an item is available in bank or inventory.

    Args:
        itemName (str): Name of the item to inspect.
        itemQty (int): Quantity threshold to test.
        operator (str): Comparison operator understood by the bot API.
        isTemp (bool): When True, include the temporary inventory.

    Returns:
        bool: True when either storage location satisfies the check.
    """
    return self.is_in_bank(itemName, itemQty, operator) or self.is_in_inventory(itemName, itemQty, operator, isTemp)

is_monster_alive(monster='*')

Check whether a monster is alive in the player's current cell.

Parameters:

Name Type Description Default
monster str

Name or id.X identifier of the monster, * for any.

'*'

Returns:

Name Type Description
bool bool

True when a matching live monster is found in the cell.

Source code in core\command.py
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
def is_monster_alive(self, monster: str = "*") -> bool:
    """Check whether a monster is alive in the player's current cell.

    Args:
        monster (str): Name or ``id.X`` identifier of the monster, ``*`` for any.

    Returns:
        bool: True when a matching live monster is found in the cell.
    """
    if monster.startswith('id.'):
        monster = monster.split('.')[1]
    for mon in self.bot.monsters:
        if mon.is_alive and mon.frame == self.bot.player.CELL:
            if mon.mon_name.lower() == monster.lower() or mon.mon_map_id == monster:
                return True
            elif monster == "*":
                return True
    return False

is_not_in_cell(cell)

Check whether the player is standing in a different cell.

Parameters:

Name Type Description Default
cell str

Cell name to compare with the current position.

required

Returns:

Name Type Description
bool bool

True when the active cell does not match cell.

Source code in core\command.py
743
744
745
746
747
748
749
750
751
752
def is_not_in_cell(self, cell: str) -> bool:
    """Check whether the player is standing in a different cell.

    Args:
        cell (str): Cell name to compare with the current position.

    Returns:
        bool: True when the active cell does not match ``cell``.
    """
    return self.bot.player.CELL.lower() != cell.lower()

is_not_in_map(mapName)

Return True when the player is not currently in the given map.

Parameters:

Name Type Description Default
mapName str

Map identifier to compare.

required

Returns:

Name Type Description
bool bool

True when the current map differs from mapName.

Source code in core\command.py
716
717
718
719
720
721
722
723
724
725
def is_not_in_map(self, mapName: str) -> bool:
    """Return True when the player is not currently in the given map.

    Args:
        mapName (str): Map identifier to compare.

    Returns:
        bool: True when the current map differs from ``mapName``.
    """
    return mapName.lower() != self.bot.strMapName.lower()

is_player_alive()

Return True when the local player is not dead.

Source code in core\command.py
1124
1125
1126
def is_player_alive(self) -> bool:
    """Return True when the local player is not dead."""
    return not self.bot.player.ISDEAD

is_player_in_cell(name, cell)

Return True when a named player is currently in the given cell.

Source code in core\command.py
652
653
654
655
656
657
def is_player_in_cell(self, name: str, cell: str) -> bool:
    """Return True when a named player is currently in the given cell."""
    player = self.get_player_in_map(name)
    if player and player.str_frame and player.str_frame.lower() == cell.lower():
        return True
    return False

is_still_connected()

Check whether the client connection is still active.

Source code in core\command.py
96
97
98
99
def is_still_connected(self) -> bool:
    """Check whether the client connection is still active."""
    self.bot.accept_quest
    return self.bot.is_client_connected

is_valid_json(s)

Return True when the provided string parses as JSON.

Source code in core\command.py
101
102
103
104
105
106
107
def is_valid_json(self, s: str) -> bool:
    """Return True when the provided string parses as JSON."""
    try:
        json.loads(s)
        return True
    except json.JSONDecodeError:
        return False

join_house(houseName, safeLeave=True) async

Join a player house while optionally leaving combat safely.

Parameters:

Name Type Description Default
houseName str

Name of the house to join.

required
safeLeave bool

Leave combat via spawn before issuing the join request.

True
Source code in core\command.py
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
@check_alive
async def join_house(self, houseName: str, safeLeave: bool = True) -> None:
    """Join a player house while optionally leaving combat safely.

    Args:
        houseName (str): Name of the house to join.
        safeLeave (bool): Leave combat via spawn before issuing the join request.
    """
    self.stop_aggro()
    if self.bot.strMapName.lower() == houseName.lower():
        return
    self.bot.is_joining_map = True
    await self.leave_combat(safeLeave)
    msg = f"%xt%zm%house%1%{houseName}%"
    self.bot.write_message(msg)

join_map(mapName, roomNumber=None, safeLeave=True) async

Join a map instance, picking the appropriate room.

Parameters:

Name Type Description Default
mapName str

Map identifier to join.

required
roomNumber int | None

Specific room number to target when provided.

None
safeLeave bool

Leave combat before transferring maps.

True

Returns:

Name Type Description
None None

Records join state and sends the transfer packet.

Source code in core\command.py
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
@check_alive
async def join_map(self, mapName: str, roomNumber: Optional[int] = None, safeLeave: bool = True) -> None:
    """Join a map instance, picking the appropriate room.

    Args:
        mapName (str): Map identifier to join.
        roomNumber (int | None): Specific room number to target when provided.
        safeLeave (bool): Leave combat before transferring maps.

    Returns:
        None: Records join state and sends the transfer packet.
    """
    self.stop_aggro()
    if self.bot.strMapName.lower() == mapName.lower():
        return
    self.bot.is_joining_map = True
    await self.leave_combat(safeLeave)

    if roomNumber != None:
        msg = f"%xt%zm%cmd%1%tfer%{self.bot.player.USER}%{mapName}-{roomNumber}%"
    elif self.bot.roomNumber != None:
        msg = f"%xt%zm%cmd%1%tfer%{self.bot.player.USER}%{mapName}-{self.bot.roomNumber}%"
    else:
        msg = f"%xt%zm%cmd%1%tfer%{self.bot.player.USER}%{mapName}%"
    self.bot.write_message(msg)

jump_cell(cell, pad) async

Jump to a specific cell and pad if not already positioned there.

Parameters:

Name Type Description Default
cell str

Cell name to move to.

required
pad str

Pad identifier within the cell.

required

Returns:

Name Type Description
None None

Executes a jump command and waits briefly for sync.

Source code in core\command.py
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
@check_alive
async def jump_cell(self, cell: str, pad: str) -> None:
    """Jump to a specific cell and pad if not already positioned there.

    Args:
        cell (str): Cell name to move to.
        pad (str): Pad identifier within the cell.

    Returns:
        None: Executes a jump command and waits briefly for sync.
    """
    if self.bot.player.CELL.lower() != cell.lower() or self.bot.player.PAD.lower() != pad.lower():
        self.bot.jump_cell(cell, pad)
        #print(f"jump cell: {cell} {pad}")
        await asyncio.sleep(1)

jump_to_monster(monsterName, byMostMonster=True, byAliveMonster=False) async

Jump to the cell that currently hosts the requested monster.

Parameters:

Name Type Description Default
monsterName str

Display name or id.X identifier for the monster.

required
byMostMonster bool

Prefer the cell with the highest monster population when True.

True
byAliveMonster bool

Prefer a cell that still has the monster alive when True.

False

Returns:

Name Type Description
None None

The coroutine adjusts player position and exits.

Source code in core\command.py
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
@check_alive
async def jump_to_monster(self, monsterName: str, byMostMonster: bool = True, byAliveMonster: bool = False) -> None:
    """Jump to the cell that currently hosts the requested monster.

    Args:
        monsterName (str): Display name or ``id.X`` identifier for the monster.
        byMostMonster (bool): Prefer the cell with the highest monster population when True.
        byAliveMonster (bool): Prefer a cell that still has the monster alive when True.

    Returns:
        None: The coroutine adjusts player position and exits.
    """
    if monsterName.startswith('id.'):
        monsterName = monsterName.split('.')[1]
    for monster in self.bot.monsters:
        if (monster.mon_name.lower() == monsterName.lower() or monster.mon_map_id == monsterName )\
                and monster.is_alive \
                and self.bot.player.CELL == monster.frame:
            return

    # Hunt monster in other cell
    if byMostMonster or byAliveMonster:
        cell = self.bot.find_best_cell(monsterName, byMostMonster, byAliveMonster)
        if cell:
            if cell == self.bot.player.CELL:
                return
            self.bot.jump_cell(cell, "Left")
            await asyncio.sleep(1)
            return
    for monster in self.bot.monsters:
        if (monster.mon_name.lower() == monsterName.lower() or monster.mon_map_id == monsterName )\
                and monster.is_alive \
                and self.bot.player.CELL != monster.frame:
            # TODO need to handle the rigth pad
            self.bot.jump_cell(monster.frame, "Left")
            await asyncio.sleep(1)
            return

leave_combat(safeLeave=True) async

Leave combat and optionally jump back to spawn.

Parameters:

Name Type Description Default
safeLeave bool

Jump to the Enter/Spawn cell after leaving combat when True.

True

Returns:

Name Type Description
None None

Always returns None; the bot actions run asynchronously.

Source code in core\command.py
810
811
812
813
814
815
816
817
818
819
820
821
822
@check_alive
async def leave_combat(self, safeLeave: bool = True) -> None:
    """Leave combat and optionally jump back to spawn.

    Args:
        safeLeave (bool): Jump to the Enter/Spawn cell after leaving combat when True.

    Returns:
        None: Always returns None; the bot actions run asynchronously.
    """
    await self.bot.ensure_leave_from_combat(always=True)
    if safeLeave:
        await self.jump_cell("Enter", "Spawn")

load_shop(shop_id) async

Request shop data from the server and wait for the response.

Parameters:

Name Type Description Default
shop_id int

Identifier of the shop to load.

required

Returns:

Name Type Description
None None

Awaits for a short delay to let the data load.

Source code in core\command.py
535
536
537
538
539
540
541
542
543
544
545
546
547
@check_alive
async def load_shop(self, shop_id: int) -> None:
    """Request shop data from the server and wait for the response.

    Args:
        shop_id (int): Identifier of the shop to load.

    Returns:
        None: Awaits for a short delay to let the data load.
    """
    msg = f"%xt%zm%loadShop%{self.bot.areaId}%{shop_id}%"
    self.bot.write_message(msg)
    await self.sleep(1000)

quest_in_progress(quest_id)

Return True when the quest is present in the in-progress list.

Source code in core\command.py
176
177
178
179
def quest_in_progress(self, quest_id: int) -> bool:
    """Return True when the quest is present in the in-progress list."""
    loaded_quest_ids = [loaded_quest["QuestID"] for loaded_quest in self.bot.loaded_quest_datas]
    return str(quest_id) in str(loaded_quest_ids)

quest_not_in_progress(quest_id)

Return True when the quest is not currently tracked in progress.

Source code in core\command.py
171
172
173
174
def quest_not_in_progress(self, quest_id: int) -> bool:
    """Return True when the quest is not currently tracked in progress."""
    loaded_quest_ids = [loaded_quest["QuestID"] for loaded_quest in self.bot.loaded_quest_datas]
    return str(quest_id) not in str(loaded_quest_ids)

register_quest(questId) async

Register a quest for auto accept and complete system.

Parameters:

Name Type Description Default
questId int

Identifier of the quest to register.

required
Source code in core\command.py
240
241
242
243
244
245
246
247
248
@check_alive
async def register_quest(self, questId: int):
    """Register a quest for auto accept and complete system.

    Args:
        questId (int): Identifier of the quest to register."""
    if questId not in self.bot.registered_auto_quest_ids:
        self.bot.registered_auto_quest_ids.append(questId)
        await self.ensure_accept_quest(questId)

rest() async

Request the rest action from the server.

Source code in core\command.py
1163
1164
1165
async def rest(self) -> None:
    """Request the rest action from the server."""
    await self.send_packet(f"%xt%zm%restRequest%1%%")

sell_item(item_name, qty=1) async

Sell an item from the inventory.

Parameters:

Name Type Description Default
item_name str

Name of the item to sell.

required
qty int

Quantity to sell in a single transaction.

1
Source code in core\command.py
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
@check_alive
async def sell_item(self, item_name: str, qty: int = 1) -> None:
    """Sell an item from the inventory.

    Args:
        item_name (str): Name of the item to sell.
        qty (int): Quantity to sell in a single transaction.

    """
    # %xt%zm%sellItem%374121%87406%1%950679343%
    item = self.bot.player.get_item_inventory(item_name)
    if item:
        self.bot.debug(f"Selling {qty}x {item_name}...")
        self.bot.write_message(f"%xt%zm%sellItem%{self.bot.areaId}%{item.item_id}%{qty}%{item.char_item_id}%")
        await self.sleep(500)

send_chat(message) async

Send a zone chat message through the server packet API.

Source code in core\command.py
1159
1160
1161
async def send_chat(self, message: str) -> None:
    """Send a zone chat message through the server packet API."""
    await self.send_packet(f"%xt%zm%message%{self.bot.areaId}%{message}%zone%")

send_packet(packet) async

Send a raw packet to the server after validating connectivity.

Source code in core\command.py
1172
1173
1174
1175
1176
1177
async def send_packet(self, packet: str) -> None:
    """Send a raw packet to the server after validating connectivity."""
    if not self.is_still_connected():
        return
    self.bot.write_message(packet)
    await asyncio.sleep(0.5)

sleep(milliseconds) async

Asynchronously sleep for the requested number of milliseconds.

Source code in core\command.py
1167
1168
1169
1170
@check_alive
async def sleep(self,  milliseconds: int) -> None:
    """Asynchronously sleep for the requested number of milliseconds."""
    await asyncio.sleep(milliseconds/1000)

start_aggro(mons_id, delay_ms=500)

Enable the aggro handler for the supplied monster identifiers.

Parameters:

Name Type Description Default
mons_id list[str]

Monster identifiers to keep aggroed.

required
delay_ms int

Delay between aggro ticks in milliseconds.

500

Returns:

Name Type Description
None None

Updates the bot state and starts the aggro task.

Source code in core\command.py
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
def start_aggro(self, mons_id: list[str], delay_ms: int = 500) -> None:
    """Enable the aggro handler for the supplied monster identifiers.

    Args:
        mons_id (list[str]): Monster identifiers to keep aggroed.
        delay_ms (int): Delay between aggro ticks in milliseconds.

    Returns:
        None: Updates the bot state and starts the aggro task.
    """
    self.stop_aggro()
    self.bot.is_aggro_handler_task_running = True
    self.bot.aggro_mons_id = mons_id
    self.bot.aggro_delay_ms = delay_ms
    self.bot.run_aggro_hadler_task()

start_aggro_by_cell(cells, delay_ms=500)

Start aggroing every monster found in the provided cells.

Parameters:

Name Type Description Default
cells list[str]

Cell names to scan for monsters.

required
delay_ms int

Delay between aggro commands in milliseconds.

500

Returns:

Name Type Description
None None

Delegates to start_aggro when monsters are present.

Source code in core\command.py
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
def start_aggro_by_cell(self, cells: list[str], delay_ms : int = 500) -> None:
    """Start aggroing every monster found in the provided cells.

    Args:
        cells (list[str]): Cell names to scan for monsters.
        delay_ms (int): Delay between aggro commands in milliseconds.

    Returns:
        None: Delegates to start_aggro when monsters are present.
    """
    mons_id: list[str] = []
    for monster in self.bot.monsters:
        if monster.frame in cells:
            mons_id.append(str(monster.mon_map_id))

    if len(mons_id) == 0:
        return

    self.start_aggro(mons_id, delay_ms)

stop_aggro()

Stop the aggro handler and clear tracked monsters.

Returns:

Name Type Description
None None

Clears aggro state without returning a value.

Source code in core\command.py
801
802
803
804
805
806
807
808
def stop_aggro(self) -> None:
    """Stop the aggro handler and clear tracked monsters.

    Returns:
        None: Clears aggro state without returning a value.
    """
    self.bot.is_aggro_handler_task_running = False
    self.bot.aggro_mons_id = []

stop_bot(msg='')

Print a stop message and terminate the bot session.

Source code in core\command.py
1153
1154
1155
1156
1157
def stop_bot(self, msg: str = "") -> None:
    """Print a stop message and terminate the bot session."""
    print(Fore.RED + msg + Fore.RESET)
    print(Fore.RED + "stop bot: " + self.bot.player.USER + Fore.RESET)
    self.bot.stop_bot()

turn_in_quest(quest_id, item_id=-1, qty=1) async

Submit quest completion requirements and leave combat if needed.

Parameters:

Name Type Description Default
quest_id int

Identifier of the quest to turn in.

required
item_id int

Required item id for the submission.

-1
qty int

Quantity of the required item.

1

Returns:

Name Type Description
None None

Updates quest tracking state and delays for server processing.

Source code in core\command.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
@check_alive
async def turn_in_quest(self, quest_id: int, item_id: int = -1, qty: int = 1) -> None:
    """Submit quest completion requirements and leave combat if needed.

    Args:
        quest_id (int): Identifier of the quest to turn in.
        item_id (int): Required item id for the submission.
        qty (int): Quantity of the required item.

    Returns:
        None: Updates quest tracking state and delays for server processing."""
    self.quest_to_check = quest_id
    await self.bot.ensure_leave_from_combat()
    self.bot.turn_in_quest(quest_id, item_id, qty)
    await asyncio.sleep(1)

use_skill(index=0, target_monsters='*', hunt=False, buff_only=False, reload_delay=500) async

Execute a skill with optional hunting, targeting, and cooldown handling.

Parameters:

Name Type Description Default
index int

Skill slot that should be triggered.

0
target_monsters str

Target filter, * for any or comma-separated list.

'*'
hunt bool

When True, jump to the monster before casting.

False
buff_only bool

Prevent damaging skills from firing when True.

False
reload_delay int

Cooldown buffer in milliseconds after casting.

500

Returns:

Name Type Description
None None

The coroutine schedules the skill usage and exits.

Source code in core\command.py
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
@check_alive
async def use_skill(self,  
                    index: int = 0, 
                    target_monsters: str = "*", 
                    hunt: bool = False, 
                    buff_only: bool = False,
                    reload_delay: int = 500
    ) -> None:
    """Execute a skill with optional hunting, targeting, and cooldown handling.

    Args:
        index (int): Skill slot that should be triggered.
        target_monsters (str): Target filter, ``*`` for any or comma-separated list.
        hunt (bool): When True, jump to the monster before casting.
        buff_only (bool): Prevent damaging skills from firing when True.
        reload_delay (int): Cooldown buffer in milliseconds after casting.

    Returns:
        None: The coroutine schedules the skill usage and exits.
    """
    if not self.bot.player.canUseSkill(int(index)) or not self.check_is_skill_safe(int(index)):
        return

    skill = self.bot.player.SKILLS[int(index)]
    self.bot.skillAnim = skill.get("anim", None)
    max_target = int(skill.get("tgtMax", 1))

    wait_reload_s = (self.skill_reload_time - int(round(datetime.now().timestamp() * 1000))) / 1000
    if wait_reload_s > 0 and index != 0:
        # print(Fore.BLUE + f"[{datetime.now().strftime('%H:%M:%S')}] wait reload skill:{index} cd:{wait_reload_s:.2f} s" + Fore.RESET)
        await self.sleep(wait_reload_s*1000)

    if skill["tgt"] == "h": 
        priority_monsters_id = []
        if hunt and len(target_monsters.split(",")) == 1 and target_monsters != "*":
            await self.jump_to_monster(target_monsters, byAliveMonster=True)
        cell_monsters_id = [mon.mon_map_id for mon in self.bot.monsters if mon.frame == self.bot.player.CELL and mon.is_alive]
        cell_monsters = [mon for mon in self.bot.monsters if mon.frame == self.bot.player.CELL and mon.is_alive]
        final_ids = []
        if target_monsters != "*":
            # Mapping priority_monsters_id
            target_ids = []
            target_names = []
            for target_monster in target_monsters.split(','):
                if target_monster.startswith('id.'):
                    target_ids.append(target_monster.split('.')[1])
                else:
                    target_names.append(target_monster.lower())

            # Step 1: build a map of alive monsters in current cell
            alive_monsters = {mon.mon_map_id: mon for mon in self.bot.monsters if mon.frame == self.bot.player.CELL and mon.is_alive}

            priority_monsters_id = []

            # Step 2: follow *input* order strictly
            for target in target_monsters.split(','):
                if target.startswith("id."):
                    mon_id = target.split(".")[1]
                    if mon_id in alive_monsters:
                        priority_monsters_id.append(mon_id)
                else:
                    name = target.lower()
                    for mon in self.bot.monsters:
                        if mon.frame == self.bot.player.CELL and mon.is_alive and mon.mon_name.lower() == name:
                            priority_monsters_id.append(mon.mon_map_id)

            # Step 3: merge into cell_monsters_id (dedup, keep priority first)
            final_ids = []
            seen = set()

            # First: priority in order
            for mon_id in priority_monsters_id:
                if mon_id not in seen:
                    final_ids.append(mon_id)
                    seen.add(mon_id)

            # Then: the rest
            for mon_id in cell_monsters_id:
                if mon_id not in seen:
                    final_ids.append(mon_id)
                    seen.add(mon_id)

        else:
            cell_monsters.sort(key=lambda m: m.current_hp)
            final_ids = [mon.mon_map_id for mon in cell_monsters]
        if index == 5:
            self.bot.use_scroll(final_ids, max_target)
        if index < 5 and len(final_ids) > 0 and not buff_only:
            self.bot.use_skill_to_monster("a" if index == 0 else index, final_ids, max_target)
    elif skill["tgt"] == "f":
        self.bot.use_skill_to_player(index, max_target)
    elif skill["tgt"] == "s":
        self.bot.use_skill_to_myself(index)

    await self.sleep(200)
    self.bot.player.updateNextUse(index) # do this if skills is REALLY exetuced

    self.skill_reload_time = int(round(datetime.now().timestamp() * 1000)) + reload_delay

wait_count_player(player_count)

Check if the current map has at least the requested player count.

Source code in core\command.py
628
629
630
def wait_count_player(self, player_count: int) -> bool:
    """Check if the current map has at least the requested player count."""
    return len(self.bot.user_ids) >= player_count

wait_count_player_in_cell(cell, player_count)

Check if a cell hosts at least the requested number of players.

Source code in core\command.py
632
633
634
635
636
637
638
639
640
641
642
643
def wait_count_player_in_cell(self, cell: str, player_count: int) -> bool:
    """Check if a cell hosts at least the requested number of players."""
    count = 0
    cell = cell.lower()
    for player in self.bot.player_in_area:
        print(player.str_username, player.str_frame, cell)
        if player.str_frame.lower() == cell:
            count += 1

    if self.bot.player.CELL.lower() == cell:
        count += 1
    return count >= player_count

wait_use_skill(index, target_monsters='*') async

Wait until a skill is ready before casting it.

Parameters:

Name Type Description Default
index int

Skill slot to trigger.

required
target_monsters str

Comma-separated monster names or id.X identifiers to focus.

'*'

Returns:

Name Type Description
None None

The coroutine finishes once the skill has been used.

Source code in core\command.py
862
863
864
865
866
867
868
869
870
871
872
873
874
875
@check_alive
async def wait_use_skill(self, index: int, target_monsters: str = "*") -> None:
    """Wait until a skill is ready before casting it.

    Args:
        index (int): Skill slot to trigger.
        target_monsters (str): Comma-separated monster names or ``id.X`` identifiers to focus.

    Returns:
        None: The coroutine finishes once the skill has been used.
    """
    while not self.bot.player.canUseSkill(int(index)):
        await self.sleep(100)
    await self.use_skill(index, target_monsters)

walk_to(X, Y, speed=8) async

Walk to a coordinate within the current map.

Parameters:

Name Type Description Default
X int

Target X coordinate.

required
Y int

Target Y coordinate.

required
speed int

Movement speed for the walk animation.

8
Source code in core\command.py
754
755
756
757
758
759
760
761
762
763
@check_alive
async def walk_to(self, X: int, Y: int, speed: int = 8) -> None:
    """Walk to a coordinate within the current map.

    Args:
        X (int): Target X coordinate.
        Y (int): Target Y coordinate.
        speed (int): Movement speed for the walk animation."""
    await self.bot.walk_to(X, Y, speed)
    await self.sleep(200)