├── python ├── phoenixapi │ ├── __init__.py │ ├── clients │ │ ├── __init__.py │ │ ├── base_client.py │ │ ├── friend_manager.py │ │ ├── client_socket.py │ │ ├── bot_controller.py │ │ ├── packet_manager.py │ │ ├── player_manager.py │ │ ├── inventory_manager.py │ │ ├── pet_manager.py │ │ ├── scene_manager.py │ │ ├── skill_manager.py │ │ └── login_manager.py │ ├── api.py │ ├── entities.py │ └── finder.py ├── main.py ├── tests │ ├── __init__.py │ ├── friend_manager.py │ ├── bot_controller.py │ ├── skill_manager.py │ ├── pet_manager.py │ ├── inventory_manager.py │ ├── player_obj_manager.py │ ├── scene_manager.py │ ├── login.py │ └── packet_manager.py ├── pyproject.toml ├── examples │ ├── hello_world.py │ └── packetlogger.py ├── LICENSE └── README.md ├── package.ps1 ├── README.md └── .gitignore /python/phoenixapi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/phoenixapi/clients/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/main.py: -------------------------------------------------------------------------------- 1 | from tests import * 2 | 3 | if __name__ == "__main__": 4 | try: 5 | login.run() 6 | except RuntimeError as e: 7 | print(e) 8 | -------------------------------------------------------------------------------- /python/phoenixapi/clients/base_client.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | from .client_socket import ClientSocket 3 | 4 | class Client(ABC): 5 | def __init__(self, service_name: str, socket: ClientSocket): 6 | self._service_name = service_name 7 | self._socket = socket -------------------------------------------------------------------------------- /package.ps1: -------------------------------------------------------------------------------- 1 | # Script to upload a new version of the package 2 | 3 | # Move to the python dir 4 | Set-Location python 5 | 6 | # Delete old package files 7 | Remove-Item -r dist/* 8 | 9 | # Generate new files 10 | py -m build 11 | 12 | # Upload files 13 | py -m twine upload dist/* -------------------------------------------------------------------------------- /python/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from . import player_obj_manager 2 | from . import scene_manager 3 | from . import packet_manager 4 | from . import skill_manager 5 | from . import pet_manager 6 | from . import inventory_manager 7 | from . import bot_controller 8 | from . import friend_manager 9 | from . import login -------------------------------------------------------------------------------- /python/tests/friend_manager.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | import dotenv 4 | import os 5 | 6 | def run(): 7 | dotenv.load_dotenv() 8 | api: PhoenixApi = create_api_from_name(os.environ.get("CHAR_NAME")) 9 | 10 | friend = api.friend_manager.get_friend_list()[0] 11 | 12 | print(friend) 13 | 14 | api.friend_manager.join_miniland(friend["id"]) -------------------------------------------------------------------------------- /python/tests/bot_controller.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | import dotenv 4 | import os 5 | 6 | def run(): 7 | dotenv.load_dotenv() 8 | api: PhoenixApi = create_api_from_name(os.environ.get("CHAR_NAME")) 9 | 10 | #api.bot_controller.start_farming_bot() 11 | #api.bot_controller.stop_farming_bot() 12 | #api.bot_controller.continue_farming_bot() 13 | # api.bot_controller.start_minigame_bot() 14 | api.bot_controller.stop_minigame_bot() -------------------------------------------------------------------------------- /python/tests/skill_manager.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | import dotenv 4 | import os 5 | 6 | def run(): 7 | dotenv.load_dotenv() 8 | api: PhoenixApi = create_api_from_name(os.environ.get("CHAR_NAME")) 9 | 10 | #print(api.skill_manager.get_skills()) 11 | 12 | #print(api.skill_manager.find_skill_from_id(0)) 13 | 14 | #print(api.skill_manager.find_skill_from_vnum(276)) 15 | 16 | print(api.skill_manager.find_skill_from_id(11111110)) -------------------------------------------------------------------------------- /python/tests/pet_manager.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | from phoenixapi.entities import EntityType 4 | from phoenixapi.clients.pet_manager import PetState 5 | import dotenv 6 | import os 7 | 8 | def run(): 9 | dotenv.load_dotenv() 10 | api: PhoenixApi = create_api_from_name(os.environ.get("CHAR_NAME")) 11 | 12 | #print(api.pet_manager.get_pets()) 13 | 14 | #api.pet_manager.auto_attack(EntityType.MONSTER, 3070) 15 | 16 | api.pet_manager.set_pet_state(api.pet_manager.get_current_pet()["pet"]["id"], PetState.S) -------------------------------------------------------------------------------- /python/tests/inventory_manager.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | import dotenv 4 | import os 5 | 6 | def run(): 7 | dotenv.load_dotenv() 8 | api: PhoenixApi = create_api_from_name(os.environ.get("CHAR_NAME")) 9 | 10 | # print(api.inventory_manager.get_equip_tab()) 11 | # print(api.inventory_manager.get_main_tab()) 12 | # print(api.inventory_manager.get_etc_tab()) 13 | 14 | # print(api.inventory_manager.find_item(2083)) 15 | # print(api.inventory_manager.get_gold()) 16 | # print(api.inventory_manager.use_item(2071)) -------------------------------------------------------------------------------- /python/tests/player_obj_manager.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | import dotenv 4 | import os 5 | 6 | def run(): 7 | dotenv.load_dotenv() 8 | api: PhoenixApi = create_api_from_name(os.environ.get("CHAR_NAME")) 9 | 10 | player_obj_manager = api.player_obj_manager.get_player_obj_manager() 11 | print(player_obj_manager) 12 | 13 | #api.player_obj_manager.walk(9, 92) 14 | 15 | #api.player_obj_manager.attack(EntityType.MONSTER, 2685, 0) 16 | 17 | #api.player_obj_manager.target(EntityType.MONSTER, 2685) 18 | 19 | #api.player_obj_manager.pickup(4608121) 20 | 21 | api.player_obj_manager.collect(3121) -------------------------------------------------------------------------------- /python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling >= 1.26", "pywin32 >= 311"] 3 | build-backend = "hatchling.build" 4 | 5 | [tool.hatch.build.targets.wheel] 6 | packages = ["phoenixapi"] 7 | 8 | [project] 9 | name = "phoenix-bot-api" 10 | version = "1.0.6" 11 | authors = [{ name = "Hatz" }] 12 | description = "Python client for Phoenix API" 13 | readme = "README.md" 14 | requires-python = ">=3.11" 15 | classifiers = [ 16 | "Programming Language :: Python :: 3", 17 | "Operating System :: OS Independent", 18 | ] 19 | license = "MIT" 20 | license-files = ["LICEN[CS]E*"] 21 | 22 | dependencies = ["pywin32>=311"] 23 | 24 | [project.urls] 25 | GitHub = "https://github.com/hatz2/PhoenixAPI" 26 | -------------------------------------------------------------------------------- /python/tests/scene_manager.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | import dotenv 4 | import os 5 | 6 | def run(): 7 | dotenv.load_dotenv() 8 | api: PhoenixApi = create_api_from_name(os.environ.get("CHAR_NAME")) 9 | 10 | # print(api.scene_manager.get_players()) 11 | # print(api.scene_manager.get_monsters()) 12 | # print(api.scene_manager.get_items()) 13 | # print(api.scene_manager.get_npcs()) 14 | 15 | # print(api.scene_manager.find_player(1237518)) 16 | # print(api.scene_manager.find_monster(3108)) 17 | # print(api.scene_manager.find_npc(3125)) 18 | 19 | # print(api.scene_manager.get_all_bosses()) 20 | 21 | print(api.scene_manager.get_map_grid()) -------------------------------------------------------------------------------- /python/tests/login.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | import dotenv 4 | import os 5 | 6 | def run(): 7 | dotenv.load_dotenv() 8 | api: PhoenixApi = create_api_from_name(os.environ.get("CHAR_NAME")) 9 | #api = PhoenixApi(56325) 10 | 11 | #api.login_manager.select_language(5) 12 | #api.login_manager.select_server(0) 13 | # api.login_manager.select_channel(6) 14 | # api.login_manager.select_character(3) 15 | 16 | # print(api.login_manager.get_character_slots()) 17 | 18 | # print(api.login_manager.is_character_selection_visible()) 19 | # print(api.login_manager.is_server_selection_visible()) 20 | 21 | # print(api.login_manager.relog(1, 5, 3)) 22 | 23 | print(api.login_manager.logout()) -------------------------------------------------------------------------------- /python/tests/packet_manager.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | import dotenv 4 | import os 5 | 6 | def run(): 7 | dotenv.load_dotenv() 8 | api: PhoenixApi = create_api_from_name(os.environ.get("CHAR_NAME")) 9 | 10 | # api.packet_manager.send("u_s 0 3 3070") 11 | 12 | # api.packet_manager.subscribe() 13 | 14 | # try: 15 | # while True: 16 | # for packet in api.packet_manager.get_pending_send_packets(): 17 | # print(f"[SEND]: {packet}") 18 | 19 | # for packet in api.packet_manager.get_pending_recv_packets(): 20 | # print(f"[RECV]: {packet}") 21 | # except KeyboardInterrupt: 22 | # print(f"Interrupted by user") 23 | 24 | # api.packet_manager.unsubscribe() -------------------------------------------------------------------------------- /python/examples/hello_world.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.finder import create_api_from_name 2 | from phoenixapi.api import PhoenixApi 3 | from phoenixapi.clients.player_manager import PlayerObjManager 4 | 5 | def run(): 6 | CHAR_NAME = "Insert your character name here" 7 | client: PhoenixApi = create_api_from_name(CHAR_NAME) 8 | 9 | player_obj_manager: PlayerObjManager = client.player_obj_manager.get_player_obj_manager() 10 | 11 | name = player_obj_manager["player"]["name"] 12 | player_id = player_obj_manager["id"] 13 | family = player_obj_manager["player"]["family"] 14 | x = player_obj_manager['position']['x'] 15 | y = player_obj_manager['position']['y'] 16 | 17 | print(f"Name:\t\t{name}") 18 | print(f"ID:\t\t{player_id}") 19 | print(f"Family:\t\t{family}") 20 | print(f"Position:\t({x}, {y})") 21 | -------------------------------------------------------------------------------- /python/phoenixapi/clients/friend_manager.py: -------------------------------------------------------------------------------- 1 | from .client_socket import ClientSocket, Request, Response 2 | from .base_client import Client 3 | from typing import TypedDict 4 | 5 | class Friend(TypedDict): 6 | id: int 7 | name: str 8 | 9 | class FriendManagerClient(Client): 10 | 11 | def __init__(self, socket: ClientSocket): 12 | super().__init__("FriendManagerService", socket) 13 | 14 | def get_friend_list(self) -> list[Friend]: 15 | request: Request = { 16 | "service": self._service_name, 17 | "method": "getFriendList", 18 | "params": {} 19 | } 20 | response = self._socket.request(request) 21 | return list(response["result"]["friends"]) 22 | 23 | def join_miniland(self, friend_id: int) -> Response: 24 | request: Request = { 25 | "service": self._service_name, 26 | "method": "joinMiniland", 27 | "params": { 28 | "friend_id": friend_id 29 | } 30 | } 31 | return self._socket.request(request) -------------------------------------------------------------------------------- /python/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Hatz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # Phoenix API 2 | 3 | This is the Python client for the Phoenix API. 4 | 5 | ## Installation 6 | 7 | You can install the package using pip: 8 | 9 | ``` 10 | pip install phoenix-bot-api 11 | ``` 12 | 13 | ## Usage 14 | 15 | Here is a simple example of how to use the Phoenix API client: 16 | 17 | ```python 18 | from phoenixapi.finder import create_api_from_name 19 | from phoenixapi.api import PhoenixApi 20 | from phoenixapi.clients.player_manager import PlayerObjManager 21 | 22 | def run(): 23 | CHAR_NAME = "Insert your character name here" 24 | client: PhoenixApi = create_api_from_name(CHAR_NAME) 25 | 26 | player_obj_manager: PlayerObjManager = client.player_obj_manager.get_player_obj_manager() 27 | 28 | name = player_obj_manager["player"]["name"] 29 | player_id = player_obj_manager["id"] 30 | family = player_obj_manager["player"]["family"] 31 | x = player_obj_manager['position']['x'] 32 | y = player_obj_manager['position']['y'] 33 | 34 | print(f"Name:\t\t{name}") 35 | print(f"ID:\t\t{player_id}") 36 | print(f"Family:\t\t{family}") 37 | print(f"Position:\t({x}, {y})") 38 | ``` 39 | -------------------------------------------------------------------------------- /python/phoenixapi/api.py: -------------------------------------------------------------------------------- 1 | from .clients.client_socket import ClientSocket 2 | from .clients.player_manager import PlayerObjManagerClient 3 | from .clients.scene_manager import SceneManagerClient 4 | from .clients.packet_manager import PacketManagerClient 5 | from .clients.skill_manager import SkillManagerClient 6 | from .clients.pet_manager import PetManagerClient 7 | from .clients.inventory_manager import InventoryManagerClient 8 | from .clients.bot_controller import BotControllerClient 9 | from .clients.friend_manager import FriendManagerClient 10 | from .clients.login_manager import LoginClient 11 | 12 | class PhoenixApi: 13 | def __init__(self, port: int): 14 | socket = ClientSocket(port) 15 | 16 | self.player_obj_manager = PlayerObjManagerClient(socket) 17 | self.scene_manager = SceneManagerClient(socket) 18 | self.packet_manager = PacketManagerClient(socket) 19 | self.skill_manager = SkillManagerClient(socket) 20 | self.pet_manager = PetManagerClient(socket) 21 | self.inventory_manager = InventoryManagerClient(socket) 22 | self.bot_controller = BotControllerClient(socket) 23 | self.friend_manager = FriendManagerClient(socket) 24 | self.login_manager = LoginClient(socket) -------------------------------------------------------------------------------- /python/examples/packetlogger.py: -------------------------------------------------------------------------------- 1 | from phoenixapi.api import PhoenixApi 2 | from phoenixapi.finder import create_api_from_name 3 | 4 | def handle_send(client: PhoenixApi, packet: str): 5 | if packet == "say ping": 6 | client.packet_manager.recv("spk 1 1 5 Hatz pong") 7 | 8 | def handle_recv(client: PhoenixApi, packet: str): 9 | pass 10 | 11 | def run(): 12 | CHAR_NAME = "Insert your character name here" 13 | client: PhoenixApi = create_api_from_name(CHAR_NAME) 14 | 15 | client.packet_manager.subscribe() 16 | 17 | send_filter = ("ncif", "ptctl") 18 | recv_filter = ("mv", "eff", "pst", "st", "cond") 19 | 20 | try: 21 | while True: 22 | pending_send = client.packet_manager.get_pending_send_packets() 23 | 24 | for packet in pending_send: 25 | handle_send(client, packet) 26 | 27 | if packet.data.startswith(send_filter): 28 | continue 29 | 30 | print(f"[SEND]: {packet}") 31 | 32 | pending_recv = client.packet_manager.get_pending_recv_packets() 33 | 34 | for packet in pending_recv: 35 | handle_recv(client, packet) 36 | 37 | if packet.data.startswith(recv_filter): 38 | continue 39 | 40 | print(f"[RECV]: {packet}") 41 | except KeyboardInterrupt: 42 | print("Interrupted by user.") 43 | finally: 44 | client.packet_manager.unsubscribe() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phoenix API 2 | 3 | This API allows developers to interact with some of the bot functions and exposes a rich client interface to interact with the game without needing to do any reverse engineering by yourself. The goal is to allow developers extend the behaviour of the bot making quick and complex scripts with as little overhead as possible. 4 | 5 | ## Prerequisites 6 | The API is built upong TCP sockets and JSON so a good understanding of how TCP programming works is key if you want to make your own client. If this is the first time you hear about TCP we recommend you to visit [Socket Programming HOWTO](https://docs.python.org/3.13/howto/sockets.html). 7 | Phoenix Premium Membership is required in order to use the API. You can purchase it on the [official website](https://phoenix-bot.xyz/). 8 | 9 | ## Supported programming languages 10 | Any programming language that supports TCP sockets and JSON will be able to use the API. This includes: 11 | - C# / .NET 12 | - C / C++ 13 | - Go 14 | - Java 15 | - Kotlin 16 | - JavaScript / Node.js 17 | - PHP 18 | - Python 19 | - Ruby 20 | - Rust 21 | - Swift 22 | - Lua 23 | - Dart 24 | - R 25 | - Perl+ 26 | 27 | I recommend you to use Python as the client code is already developed and maintained so it will be easier for you to get started with. 28 | 29 | For more information on how to use the API, please refer to the [API Documentation](https://phoenix-13.gitbook.io/phoenix-docs). 30 | 31 | For more information about Phoenix Bot, please visit the [official website](https://phoenix-bot.xyz/) or join our [Discord community](https://discord.com/invite/phoenix-bot). 32 | 33 | If you are looking for the old API documentation, you can find it [here](https://github.com/hatz2/PhoenixAPI/tree/v1). 34 | -------------------------------------------------------------------------------- /python/phoenixapi/clients/client_socket.py: -------------------------------------------------------------------------------- 1 | from socket import socket, AF_INET, SOCK_STREAM 2 | from typing import TypedDict, NotRequired 3 | from threading import Lock 4 | import json 5 | 6 | class Request(TypedDict): 7 | service: str 8 | method: str 9 | params: dict 10 | 11 | class Response(TypedDict): 12 | status: str 13 | error_message: NotRequired[str] 14 | result: NotRequired[str] 15 | 16 | class ClientSocket: 17 | DELIM_CHAR = '\1' 18 | 19 | def __init__(self, port: int): 20 | self._socket = socket(AF_INET, SOCK_STREAM) 21 | self._socket.connect(("localhost", port)) 22 | self._mutex = Lock() 23 | 24 | def __del__(self): 25 | self._socket.close() 26 | 27 | def request(self, request_data: Request) -> Response: 28 | with self._mutex: 29 | self._send(json.dumps(request_data)) 30 | response = Response(json.loads(self._recv())) 31 | self._validate_response(response) 32 | return response 33 | 34 | def _send(self, data: str): 35 | total_bytes_sent = 0 36 | buffer = data + ClientSocket.DELIM_CHAR 37 | 38 | while total_bytes_sent < len(buffer): 39 | bytes_sent = self._socket.send(buffer[total_bytes_sent:].encode()) 40 | if bytes_sent == 0: 41 | raise RuntimeError("socket connection broken") 42 | total_bytes_sent += bytes_sent 43 | 44 | def _recv(self) -> str: 45 | chunks = [] 46 | finish = False 47 | 48 | while not finish: 49 | chunk = self._socket.recv(4096) 50 | if chunk == b'': 51 | raise RuntimeError("socket connection broken") 52 | chunks.append(chunk) 53 | finish = chunk.endswith(ClientSocket.DELIM_CHAR.encode()) 54 | 55 | return b''.join(chunks)[:-1].decode() 56 | 57 | def _validate_response(self, response: Response) -> None: 58 | if response["status"] == "error": 59 | raise RuntimeError(response["error_message"]) -------------------------------------------------------------------------------- /python/phoenixapi/entities.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict 2 | from enum import Enum 3 | 4 | class Direction(int, Enum): 5 | UP = 0 6 | RIGHT = 1 7 | DOWN = 2 8 | LEFT = 3 9 | UP_LEFT = 4 10 | UP_RIGHT = 5 11 | DOWN_RIGHT = 6 12 | DOWN_LEFT = 7 13 | 14 | class EntityType(int, Enum): 15 | PLAYER = 1 16 | MONSTER = 2 17 | NPC = 3 18 | ITEM = 4 19 | 20 | class Position(TypedDict): 21 | x: int 22 | y: int 23 | 24 | class Player(TypedDict): 25 | entity_type: EntityType 26 | id: int 27 | position: Position 28 | direction: Direction 29 | animation_status: int 30 | speed: int 31 | is_in_combat: bool 32 | health_percent: int 33 | mana_percent: int 34 | level: int 35 | champion_level: int 36 | current_map_id: int 37 | sp: int 38 | name: str 39 | title: str 40 | family: str 41 | is_gm: bool 42 | reputation_rank: int 43 | 44 | class Monster(TypedDict): 45 | entity_type: EntityType 46 | id: int 47 | position: Position 48 | direction: Direction 49 | animation_status: int 50 | speed: int 51 | is_in_combat: bool 52 | health_percent: int 53 | mana_percent: int 54 | level: int 55 | champion_level: int 56 | current_map_id: int 57 | vnum: int 58 | name: int 59 | race: int 60 | skin_id: int 61 | is_boss: bool 62 | 63 | class Item(TypedDict): 64 | entity_type: EntityType 65 | id: int 66 | position: Position 67 | vnum: int 68 | quantity: int 69 | owner_id: int 70 | is_quest_item: bool 71 | name: str 72 | 73 | class Npc(TypedDict): 74 | entity_type: EntityType 75 | id: int 76 | position: Position 77 | direction: Direction 78 | animation_status: int 79 | speed: int 80 | is_in_combat: bool 81 | health_percent: int 82 | mana_percent: int 83 | level: int 84 | champion_level: int 85 | current_map_id: int 86 | vnum: int 87 | name: int 88 | race: int 89 | skin_id: int 90 | is_boss: bool 91 | owner_id: int 92 | is_partner: bool -------------------------------------------------------------------------------- /python/phoenixapi/finder.py: -------------------------------------------------------------------------------- 1 | import json 2 | from win32gui import EnumWindows, GetWindowText 3 | from re import search 4 | from ctypes.wintypes import HWND, LPARAM 5 | from time import sleep 6 | from .api import PhoenixApi 7 | 8 | _ports: list[int] = [] 9 | 10 | def find_all_api_ports() -> list[int]: 11 | """Find all ports of the current bot windows.""" 12 | _ports.clear() 13 | EnumWindows(_enum_windows_callback, 0) 14 | return _ports.copy() 15 | 16 | def create_apis_from_names(character_names: list[str]) -> list[tuple[str, PhoenixApi]]: 17 | """ 18 | Create API instances from a list of character names. 19 | 20 | Returns: 21 | list[tuple[str, PhoenixApi]]: A list of tuples containing character names and their corresponding API instances. 22 | 23 | Raises: 24 | RuntimeError: If no bots are running or not all bots with the given character names are found. 25 | """ 26 | 27 | ports = find_all_api_ports() 28 | apis = [] 29 | 30 | if len(ports) == 0: 31 | raise RuntimeError("No bot windows found.") 32 | 33 | for port in ports: 34 | api = PhoenixApi(port) 35 | 36 | # Ask the bot to give us the player name 37 | player_obj_manager = api.player_obj_manager.get_player_obj_manager() 38 | name = player_obj_manager["player"]["name"] 39 | 40 | if name in character_names: 41 | character_names.remove(name) 42 | apis.append((name, api)) 43 | 44 | if (len(character_names) != 0): 45 | raise RuntimeError("Could not find all bots with the given character names.") 46 | 47 | return apis 48 | 49 | def create_api_from_name(character_name: str) -> PhoenixApi: 50 | """ 51 | Create an instance of the API class from the character's name. 52 | 53 | Returns: 54 | Phoenix: An instance of the API class. 55 | 56 | Raises: 57 | RuntimeError: If no bot with that name is found or no bots are running. 58 | """ 59 | 60 | ports = find_all_api_ports() 61 | 62 | if len(ports) == 0: 63 | raise RuntimeError("No bot windows found.") 64 | 65 | for port in ports: 66 | api = PhoenixApi(port) 67 | 68 | # Ask the bot to give us the player name 69 | player_obj_manager = api.player_obj_manager.get_player_obj_manager() 70 | name = player_obj_manager["player"]["name"] 71 | 72 | if name == character_name: 73 | return api 74 | 75 | raise RuntimeError(f"Could not find bot with character name: {character_name}") 76 | 77 | def _enum_windows_callback(hwnd: HWND, lparam: LPARAM) -> bool: 78 | """Callback function to enumerate windows and check if it is a bot window.""" 79 | window_title = GetWindowText(hwnd) 80 | 81 | if "- Phoenix Bot" in window_title: 82 | match: str = search(r"Bot:\d+ (\d+)", window_title) 83 | 84 | if match: 85 | port = (int)(match.group(1)) 86 | _ports.append(port) 87 | 88 | return True -------------------------------------------------------------------------------- /python/phoenixapi/clients/bot_controller.py: -------------------------------------------------------------------------------- 1 | from .client_socket import ClientSocket, Request, Response 2 | from .base_client import Client 3 | from ..entities import EntityType 4 | 5 | 6 | class BotControllerClient(Client): 7 | """This client allows you to interact with the bot directly. It has methods to start and stop the farming and minigame bot and also allows you to load custom settings.""" 8 | def __init__(self, socket: ClientSocket): 9 | super().__init__("BotControllerService", socket) 10 | 11 | def start_farming_bot(self) -> Response: 12 | """Starts the farming bot as if you press the Start button in the bot.""" 13 | request: Request = { 14 | "service": self._service_name, 15 | "method": "startFarmingBot", 16 | "params": {} 17 | } 18 | return self._socket.request(request) 19 | 20 | def stop_farming_bot(self) -> Response: 21 | """Stops the farming bot as if you press the Stop button in the bot.""" 22 | request: Request = { 23 | "service": self._service_name, 24 | "method": "stopFarmingBot", 25 | "params": {} 26 | } 27 | return self._socket.request(request) 28 | 29 | def continue_farming_bot(self) -> Response: 30 | """Continues the farming bot as if you press the Continue button in the bot.""" 31 | request: Request = { 32 | "service": self._service_name, 33 | "method": "continueFarmingBot", 34 | "params": {} 35 | } 36 | return self._socket.request(request) 37 | 38 | def start_minigame_bot(self) -> Response: 39 | request: Request = { 40 | "service": self._service_name, 41 | "method": "startMinigameBot", 42 | "params": {} 43 | } 44 | return self._socket.request(request) 45 | 46 | def stop_minigame_bot(self) -> Response: 47 | request: Request = { 48 | "service": self._service_name, 49 | "method": "stopMinigameBot", 50 | "params": {} 51 | } 52 | return self._socket.request(request) 53 | 54 | def load_settings(self, ini_file_path: str) -> Response: 55 | """Loads a specific settings file into the bot.""" 56 | request: Request = { 57 | "service": self._service_name, 58 | "method": "loadSettings", 59 | "params": { 60 | "ini_file_path": ini_file_path 61 | } 62 | } 63 | return self._socket.request(request) 64 | 65 | def attack(self, entity_type: EntityType, entity_id: int): 66 | """Attacks the desired entity using the skills configured in the bot.""" 67 | request: Request = { 68 | "service": self._service_name, 69 | "method": "attack", 70 | "params": { 71 | "entity_type": entity_type, 72 | "entity_id": entity_id, 73 | } 74 | } 75 | return self._socket.request(request) -------------------------------------------------------------------------------- /python/phoenixapi/clients/packet_manager.py: -------------------------------------------------------------------------------- 1 | from .client_socket import ClientSocket, Request, Response 2 | from .base_client import Client 3 | from uuid import uuid4 4 | 5 | class PacketManagerClient(Client): 6 | def __init__(self, socket: ClientSocket): 7 | """This service allows you to read the network traffic that is being exchanged between the game's client and the game's server. It also allows you to send your own packets and fake receive them.""" 8 | super().__init__("PacketManagerService", socket) 9 | self._id = str(uuid4()) 10 | self._subscribed = False 11 | 12 | def __del__(self): 13 | if self._subscribed: 14 | self.unsubscribe() 15 | 16 | def subscribe(self) -> Response: 17 | """This method lets the bot know that you want to start reading packets and allocates the resources needed for your client. It excpets an id which can be anything really but I recommend to use any kind of uuid. If you want to start reading packets you must call this function beforehand.""" 18 | request: Request = { 19 | "service": self._service_name, 20 | "method": "subscribe", 21 | "params": { 22 | "id": self._id 23 | } 24 | } 25 | response = self._socket.request(request) 26 | self._subscribed = response["status"] == "ok" 27 | return response 28 | 29 | def unsubscribe(self) -> Response: 30 | """This method lets the bot know that you don't want to read packets anymore and frees the resources previously allocated when you subscribed.""" 31 | request: Request = { 32 | "service": self._service_name, 33 | "method": "unsubscribe", 34 | "params": { 35 | "id": self._id 36 | } 37 | } 38 | return self._socket.request(request) 39 | 40 | def get_pending_send_packets(self) -> list[str]: 41 | """Returns a list with the pending packets that the game's client has sent to the game's server. Once called the bot will remove the packets from the allocated resources for your app.""" 42 | request: Request = { 43 | "service": self._service_name, 44 | "method": "getPendingSendPackets", 45 | "params": { 46 | "id": self._id 47 | } 48 | } 49 | response = self._socket.request(request) 50 | return list(response["result"]["packets"]) 51 | 52 | 53 | def get_pending_recv_packets(self) -> list[str]: 54 | """Returns a list with the pending packets that the game's client has received from the game's server to be processed by your application. Once called the bot will remove the packets from the allocated resources for your app.""" 55 | request: Request = { 56 | "service": self._service_name, 57 | "method": "getPendingRecvPackets", 58 | "params": { 59 | "id": self._id 60 | } 61 | } 62 | response = self._socket.request(request) 63 | return list(response["result"]["packets"]) 64 | 65 | def send(self, packet: str) -> Response: 66 | """Sends a packet to the game's server.""" 67 | request: Request = { 68 | "service": self._service_name, 69 | "method": "send", 70 | "params": { 71 | "packet": packet 72 | } 73 | } 74 | return self._socket.request(request) 75 | 76 | def recv(self, packet: str) -> Response: 77 | """Fake receives a packet in the game's client.""" 78 | request: Request = { 79 | "service": self._service_name, 80 | "method": "recv", 81 | "params": { 82 | "packet": packet 83 | } 84 | } 85 | return self._socket.request(request) -------------------------------------------------------------------------------- /python/phoenixapi/clients/player_manager.py: -------------------------------------------------------------------------------- 1 | from .client_socket import ClientSocket, Request, Response 2 | from .base_client import Client 3 | from typing import TypedDict 4 | from ..entities import Position, EntityType, Player 5 | 6 | class PlayerObjManager(TypedDict): 7 | position: Position 8 | dest_position: Position 9 | state: int 10 | player: Player 11 | id: int 12 | is_resting: bool 13 | 14 | 15 | class PlayerObjManagerClient(Client): 16 | def __init__(self, socket: ClientSocket): 17 | super().__init__("PlayerObjManagerService", socket) 18 | 19 | def get_player_obj_manager(self) -> PlayerObjManager: 20 | """Returns an object containing information about the player.""" 21 | request: Request = { 22 | "service": self._service_name, 23 | "method": "getPlayerObjManager", 24 | "params": {} 25 | } 26 | response = self._socket.request(request) 27 | return PlayerObjManager(response["result"]) 28 | 29 | 30 | def walk(self, x: int, y: int) -> Response: 31 | """Walks with your character to the specified coordinates.""" 32 | request: Request = { 33 | "service": self._service_name, 34 | "method": "walk", 35 | "params": { 36 | "x": x, 37 | "y": y 38 | } 39 | } 40 | return self._socket.request(request) 41 | 42 | def reset_player_state(self) -> Response: 43 | """This method allows you to reset the player state as if you are clicking in the ground. This is what makes the little arrow in the target's head go from red (attacking) to yellow (not attacking).""" 44 | request: Request = { 45 | "service": self._service_name, 46 | "method": "resetPlayerState", 47 | "params": {} 48 | } 49 | return self._socket.request(request) 50 | 51 | def attack(self, entity_type: EntityType, entity_id: int, skill_id: int) -> Response: 52 | """This method allows you to use skills. If you want to cast a skill that doesn't need a target you can pass an entity_type and entity_id values of 0.""" 53 | request: Request = { 54 | "service": self._service_name, 55 | "method": "attack", 56 | "params": { 57 | "entity_type": entity_type, 58 | "entity_id": entity_id, 59 | "skill_id": skill_id 60 | } 61 | } 62 | return self._socket.request(request) 63 | 64 | def pickup(self, item_id: int) -> Response: 65 | """Walks and pick up the specified item in the ground.""" 66 | request: Request = { 67 | "service": self._service_name, 68 | "method": "pickup", 69 | "params": { 70 | "item_id": item_id 71 | } 72 | } 73 | return self._socket.request(request) 74 | 75 | def collect(self, npc_id: int) -> Response: 76 | """Walks and collects the specified npc. This is used for stuff like fragant grass, ice flowers, lettuce, etc.""" 77 | request: Request = { 78 | "service": self._service_name, 79 | "method": "collect", 80 | "params": { 81 | "npc_id": npc_id 82 | } 83 | } 84 | return self._socket.request(request) 85 | 86 | def target(self, entity_type: EntityType, entity_id: int) -> Response: 87 | """Targets and entity to show the target widget in game. It is not strictly needed for attacking but it is recommended to use it when attacking a new entity as you would do while playing with your hands.""" 88 | request: Request = { 89 | "service": self._service_name, 90 | "method": "target", 91 | "params": { 92 | "entity_type": entity_type, 93 | "entity_id": entity_id 94 | } 95 | } 96 | return self._socket.request(request) -------------------------------------------------------------------------------- /python/phoenixapi/clients/inventory_manager.py: -------------------------------------------------------------------------------- 1 | from .client_socket import ClientSocket, Request, Response 2 | from .base_client import Client 3 | from enum import Enum 4 | from typing import TypedDict 5 | from ..entities import EntityType 6 | 7 | class InventoryTab(int, Enum): 8 | EQUIP = 0 9 | MAIN = 1 10 | ETC = 2 11 | 12 | 13 | class InvSlot(TypedDict): 14 | inv_tab: InventoryTab 15 | index: int 16 | vnum: int 17 | quantity: int 18 | name: str 19 | 20 | 21 | class InventoryManagerClient(Client): 22 | """This client allows you to get information about your inventory aswell as using items.""" 23 | 24 | def __init__(self, socket: ClientSocket): 25 | super().__init__("InventoryManagerService", socket) 26 | 27 | 28 | def get_gold(self) -> int: 29 | """Returns the gold you have in the inventory.""" 30 | request: Request = { 31 | "service": self._service_name, 32 | "method": "getGold", 33 | "params": {} 34 | } 35 | response = self._socket.request(request) 36 | return response["result"]["gold"] 37 | 38 | def get_equip_tab(self) -> list[InvSlot]: 39 | """Returns a list of the inventory slots from your EQUIP tab.""" 40 | request: Request = { 41 | "service": self._service_name, 42 | "method": "getEquipTab", 43 | "params": {} 44 | } 45 | response = self._socket.request(request) 46 | return response["result"]["inv_slots"] 47 | 48 | def get_main_tab(self) -> list[InvSlot]: 49 | """Returns a list of the inventory slots from your MAIN tab.""" 50 | request: Request = { 51 | "service": self._service_name, 52 | "method": "getMainTab", 53 | "params": {} 54 | } 55 | response = self._socket.request(request) 56 | return response["result"]["inv_slots"] 57 | 58 | def get_etc_tab(self) -> list[InvSlot]: 59 | """Returns a list of the inventory slots from your ETC tab.""" 60 | request: Request = { 61 | "service": self._service_name, 62 | "method": "getEtcTab", 63 | "params": {} 64 | } 65 | response = self._socket.request(request) 66 | return response["result"]["inv_slots"] 67 | 68 | def get_inventory_slot(self, inv_tab: InventoryTab, slot_index: int) -> InvSlot: 69 | """Returns an inventory slot from the specified tab and index.""" 70 | request: Request = { 71 | "service": self._service_name, 72 | "method": "getInventorySlot", 73 | "params": { 74 | "inv_tab": inv_tab, 75 | "slot_index": slot_index 76 | } 77 | } 78 | response = self._socket.request(request) 79 | return InvSlot(response["result"]) 80 | 81 | def find_item(self, vnum: int) -> InvSlot: 82 | """Searchs for the item with the given vnum. Returns the inventory slot if found, otherwise returns an error.""" 83 | request: Request = { 84 | "service": self._service_name, 85 | "method": "findItem", 86 | "params": { 87 | "vnum": vnum 88 | } 89 | } 90 | response = self._socket.request(request) 91 | return InvSlot(response["result"]) 92 | 93 | def use_item(self, vnum: int) -> Response: 94 | """Uses an item from the inventory with the given vnum. If the item is not found it returns an error.""" 95 | request: Request = { 96 | "service": self._service_name, 97 | "method": "useItem", 98 | "params": { 99 | "vnum": vnum 100 | } 101 | } 102 | return self._socket.request(request) 103 | 104 | def use_item_on_target(self, vnum: int, entity_type: EntityType, entity_id: int) -> Response: 105 | """Uses an item from the inventory with the given vnum on the specified target. If the item is not found or the target is not found it returns an error.""" 106 | request: Request = { 107 | "service": self._service_name, 108 | "method": "useItemOnTarget", 109 | "params": { 110 | "vnum": vnum, 111 | "entity_type": entity_type, 112 | "entity_id": entity_id 113 | } 114 | } 115 | return self._socket.request(request) -------------------------------------------------------------------------------- /python/phoenixapi/clients/pet_manager.py: -------------------------------------------------------------------------------- 1 | from .client_socket import ClientSocket, Request, Response 2 | from .base_client import Client 3 | from ..entities import Npc, Position, EntityType 4 | from enum import Enum 5 | from typing import TypedDict 6 | 7 | class PetState(int, Enum): 8 | D = 0 9 | S = 5 10 | F = 7 11 | WALK_AFTER_F = 8 12 | A = 12 13 | AFTER_A_CLICK = 15 14 | S_AFTER_A_F = 17 15 | 16 | 17 | class PetObjManager(TypedDict): 18 | position: Position 19 | dest_position: Position 20 | state: PetState 21 | pet: Npc 22 | 23 | 24 | class PetManagerClient(Client): 25 | """This client allows you to get information about your pets and perform actions with them like walking or attacking.""" 26 | 27 | def __init__(self, socket: ClientSocket): 28 | super().__init__("PetManagerService", socket) 29 | 30 | def get_pets(self) -> list[PetObjManager]: 31 | """Returns a list with your current pets.""" 32 | request: Request = { 33 | "service": self._service_name, 34 | "method": "getPets", 35 | "params": {} 36 | } 37 | response = self._socket.request(request) 38 | return list(response["result"]["pets"]) 39 | 40 | def get_current_pet(self) -> PetObjManager: 41 | """Returns your current pet information.""" 42 | request: Request = { 43 | "service": self._service_name, 44 | "method": "getCurrentPet", 45 | "params": {} 46 | } 47 | response = self._socket.request(request) 48 | return PetObjManager(response["result"]) 49 | 50 | def get_current_partner(self) -> PetObjManager: 51 | """Returns your current partner information.""" 52 | request: Request = { 53 | "service": self._service_name, 54 | "method": "getCurrentPartner", 55 | "params": {} 56 | } 57 | response = self._socket.request(request) 58 | return PetObjManager(response["result"]) 59 | 60 | def set_pet_state(self, pet_id: int, pet_state: PetState) -> Response: 61 | """Changes the pet state to the specified state.""" 62 | request: Request = { 63 | "service": self._service_name, 64 | "method": "setPetState", 65 | "params": { 66 | "pet_id": pet_id, 67 | "state": pet_state 68 | } 69 | } 70 | return self._socket.request(request) 71 | 72 | def walk(self, x: int, y: int) -> Response: 73 | """Walks with all your current pets unless you put them on S state.""" 74 | request: Request = { 75 | "service": self._service_name, 76 | "method": "walk", 77 | "params": { 78 | "x": x, 79 | "y": y 80 | } 81 | } 82 | return self._socket.request(request) 83 | 84 | def auto_attack(self, entity_type: EntityType, entity_id: int) -> Response: 85 | """Auto attack with all your current pets unless you put them on S state.""" 86 | request: Request = { 87 | "service": self._service_name, 88 | "method": "autoAttack", 89 | "params": { 90 | "entity_type": entity_type, 91 | "entity_id": entity_id 92 | } 93 | } 94 | return self._socket.request(request) 95 | 96 | def pet_attack(self, entity_type: EntityType, entity_id: int, skill_id: int) -> Response: 97 | """Uses a pet skill on the specified target. If you want to use a skill that doesn't need a target you can set entity_type and entity_id to 0.""" 98 | request: Request = { 99 | "service": self._service_name, 100 | "method": "petAttack", 101 | "params": { 102 | "entity_type": entity_type, 103 | "entity_id": entity_id, 104 | "skill_id": skill_id 105 | } 106 | } 107 | return self._socket.request(request) 108 | 109 | def partner_attack(self, entity_type: EntityType, entity_id: int, skill_id: int) -> Response: 110 | """Uses a partner skill on the specified target. If you want to use a skill that doesn't need a target you can set entity_type and entity_id to 0.""" 111 | request: Request = { 112 | "service": self._service_name, 113 | "method": "partnerAttack", 114 | "params": { 115 | "entity_type": entity_type, 116 | "entity_id": entity_id, 117 | "skill_id": skill_id 118 | } 119 | } 120 | return self._socket.request(request) -------------------------------------------------------------------------------- /python/phoenixapi/clients/scene_manager.py: -------------------------------------------------------------------------------- 1 | from .client_socket import ClientSocket, Request 2 | from .base_client import Client 3 | from typing import TypedDict 4 | from ..entities import Player, Npc, Monster, Item, Npc 5 | from enum import Enum 6 | 7 | class Cell(int, Enum): 8 | WALKABLE = 0 9 | OBSTACLE = 1 10 | OBSTACLE_2 = 2 11 | 12 | 13 | class MapGrid(TypedDict): 14 | width: int 15 | height: int 16 | grid: list[list[Cell]] 17 | 18 | 19 | class SceneManagerClient(Client): 20 | def __init__(self, socket: ClientSocket): 21 | super().__init__("SceneManagerService", socket) 22 | 23 | def get_players(self) -> list[Player]: 24 | """Returns a list with all the players in the map.""" 25 | request: Request = { 26 | "service": self._service_name, 27 | "method": "getPlayers", 28 | "params": {} 29 | } 30 | response = self._socket.request(request) 31 | return list(response["result"]["players"]) 32 | 33 | def get_monsters(self) -> list[Monster]: 34 | """Returns a list of all the monsters in the map.""" 35 | request: Request = { 36 | "service": self._service_name, 37 | "method": "getMonsters", 38 | "params": {} 39 | } 40 | response = self._socket.request(request) 41 | return list(response["result"]["monsters"]) 42 | 43 | def get_items(self) -> list[Item]: 44 | """Returns a list of all the items in the map.""" 45 | request: Request = { 46 | "service": self._service_name, 47 | "method": "getItems", 48 | "params": {} 49 | } 50 | response = self._socket.request(request) 51 | return list(response["result"]["items"]) 52 | 53 | def get_npcs(self) -> list[Npc]: 54 | """Returns a list of all the NPCs in the map.""" 55 | request: Request = { 56 | "service": self._service_name, 57 | "method": "getNpcs", 58 | "params": {} 59 | } 60 | response = self._socket.request(request) 61 | return list(response["result"]["npcs"]) 62 | 63 | def find_player(self, player_id: int) -> Npc: 64 | """Searchs for a player in the map. Returns the player object if found, otherwise returns an error.""" 65 | request: Request = { 66 | "service": self._service_name, 67 | "method": "findPlayer", 68 | "params": { 69 | "id": player_id 70 | } 71 | } 72 | response = self._socket.request(request) 73 | return Npc(response["result"]) 74 | 75 | def find_monster(self, monster_id: int) -> Monster: 76 | """Searchs for a monster in the map. Returns the monster object if found, otherwise returns an error.""" 77 | request: Request = { 78 | "service": self._service_name, 79 | "method": "findMonster", 80 | "params": { 81 | "id": monster_id 82 | } 83 | } 84 | response = self._socket.request(request) 85 | return Monster(response["result"]) 86 | 87 | def find_item(self, item_id: int) -> Item: 88 | """Searchs for an item in the map. Returns the item object if found, otherwise returns an error.""" 89 | request: Request = { 90 | "service": self._service_name, 91 | "method": "findItem", 92 | "params": { 93 | "id": item_id 94 | } 95 | } 96 | response = self._socket.request(request) 97 | return Item(response["result"]) 98 | 99 | def find_npc(self, npc_id: int) -> Npc: 100 | """Searchs for an NPC in the map. Returns the NPC object if found, otherwise returns an error.""" 101 | request: Request = { 102 | "service": self._service_name, 103 | "method": "findNpc", 104 | "params": { 105 | "id": npc_id 106 | } 107 | } 108 | response = self._socket.request(request) 109 | return Npc(response["result"]) 110 | 111 | def get_all_bosses(self) -> list[Monster]: 112 | """Returns a list with all the bosses in the map.""" 113 | request: Request = { 114 | "service": self._service_name, 115 | "method": "getAllBosses", 116 | "params": {} 117 | } 118 | response = self._socket.request(request) 119 | return list(response["result"]["bosses"]) 120 | 121 | def get_map_grid(self) -> MapGrid: 122 | """Returns the current map grid that tells you which cells are walkable and which ones are not.""" 123 | request: Request = { 124 | "service": self._service_name, 125 | "method": "getMapGrid", 126 | "params": {} 127 | } 128 | response = self._socket.request(request) 129 | return MapGrid(response["result"]) -------------------------------------------------------------------------------- /python/phoenixapi/clients/skill_manager.py: -------------------------------------------------------------------------------- 1 | from .client_socket import ClientSocket, Request, Response 2 | from .base_client import Client 3 | from enum import Enum 4 | from typing import TypedDict 5 | 6 | class SkillType(int, Enum): 7 | DAMAGE = 0 8 | DEBUFF = 1 9 | BUFF = 2 10 | 11 | 12 | class TargetType(int, Enum): 13 | TARGET = 0 14 | SELF = 1 15 | SELF_OR_TARGET = 2 16 | NO_TARGET = 3 17 | 18 | 19 | class Skill(TypedDict): 20 | vnum: int 21 | name: str 22 | id: int 23 | type: SkillType 24 | range: int 25 | area: int 26 | cast_time: int 27 | cool_time: int 28 | mana_cost: int 29 | is_ready: bool 30 | 31 | 32 | class SkillManagerClient(Client): 33 | """This client allows you to get information about your player, pet and partner skills.""" 34 | def __init__(self, socket: ClientSocket): 35 | super().__init__("SkillManagerService", socket) 36 | 37 | def get_skills(self) -> list[Skill]: 38 | """Returns a list with all your player skills.""" 39 | request: Request = { 40 | "service": self._service_name, 41 | "method": "getSkills", 42 | "params": {} 43 | } 44 | response = self._socket.request(request) 45 | return list(response["result"]["skills"]) 46 | 47 | def find_skill_from_id(self, skill_id: int) -> Skill: 48 | """Searchs for the skill with the given id. Returns the skill if found, otherwise returns an error.""" 49 | request: Request = { 50 | "service": self._service_name, 51 | "method": "findSkillFromId", 52 | "params": { 53 | "id": skill_id 54 | } 55 | } 56 | response = self._socket.request(request) 57 | return Skill(response["result"]) 58 | 59 | def find_skill_from_vnum(self, vnum: int) -> Skill: 60 | """Searchs for the skill with the given vnum. Returns the skill if found, otherwise returns an error.""" 61 | request: Request = { 62 | "service": self._service_name, 63 | "method": "findSkillFromVnum", 64 | "params": { 65 | "vnum": vnum 66 | } 67 | } 68 | response = self._socket.request(request) 69 | return Skill(response["result"]) 70 | 71 | def get_pet_skills(self) -> list[Skill]: 72 | """Returns a list with all your pet skills.""" 73 | request: Request = { 74 | "service": self._service_name, 75 | "method": "getPetSkills", 76 | "params": {} 77 | } 78 | response = self._socket.request(request) 79 | return list(response["result"]["skills"]) 80 | 81 | def find_pet_skill_from_id(self, skill_id: int) -> Skill: 82 | """Searchs for the pet skill with the given id. Returns the skill if found, otherwise returns an error.""" 83 | request: Request = { 84 | "service": self._service_name, 85 | "method": "findPetSkillFromId", 86 | "params": { 87 | "id": skill_id 88 | } 89 | } 90 | response = self._socket.request(request) 91 | return Skill(response["result"]) 92 | 93 | def find_pet_skill_from_vnum(self, vnum: int) -> Skill: 94 | """Searchs for the pet skill with the given vnum. Returns the skill if found, otherwise returns an error.""" 95 | request: Request = { 96 | "service": self._service_name, 97 | "method": "findPetSkillFromVnum", 98 | "params": { 99 | "vnum": vnum 100 | } 101 | } 102 | response = self._socket.request(request) 103 | return Skill(response["result"]) 104 | 105 | def get_partner_skills(self) -> list[Skill]: 106 | """Returns a list with all your partner skills.""" 107 | request: Request = { 108 | "service": self._service_name, 109 | "method": "getPartnerSkills", 110 | "params": {} 111 | } 112 | response = self._socket.request(request) 113 | return list(response["result"]["skills"]) 114 | 115 | def find_partner_skill_from_id(self, skill_id: int) -> Skill: 116 | """Searchs for the partner skill with the given id. Returns the skill if found, otherwise returns an error.""" 117 | request: Request = { 118 | "service": self._service_name, 119 | "method": "findPartnerSkillFromId", 120 | "params": { 121 | "id": skill_id 122 | } 123 | } 124 | response = self._socket.request(request) 125 | return Skill(response["result"]) 126 | 127 | def find_partner_skill_from_vnum(self, vnum: int) -> Skill: 128 | """Searchs for the partner skill with the given vnum. Returns the skill if found, otherwise returns an error.""" 129 | request: Request = { 130 | "service": self._service_name, 131 | "method": "findPartnerSkillFromVnum", 132 | "params": { 133 | "vnum": vnum 134 | } 135 | } 136 | response = self._socket.request(request) 137 | return Skill(response["result"]) -------------------------------------------------------------------------------- /python/phoenixapi/clients/login_manager.py: -------------------------------------------------------------------------------- 1 | from .client_socket import ClientSocket, Request, Response 2 | from .base_client import Client 3 | from typing import TypedDict 4 | from enum import Enum 5 | 6 | class Job(int, Enum): 7 | ADVENTURER = 0 8 | SWORDSMAN = 1 9 | ARCHER = 2 10 | MAGE = 3 11 | FIGHTER = 4 12 | 13 | 14 | class CharacterSelectSlot(TypedDict): 15 | is_created: bool 16 | name: str 17 | job: Job 18 | 19 | 20 | class LoginClient(Client): 21 | """This services allows you to logout from the game aswell as log back in.""" 22 | 23 | def __init__(self, socket: ClientSocket): 24 | super().__init__("LoginService", socket) 25 | 26 | def logout(self) -> Response: 27 | """Allows you to logout from the world server and go back to the server selection screen.""" 28 | request: Request = { 29 | "service": self._service_name, 30 | "method": "logout", 31 | "params": {} 32 | } 33 | return self._socket.request(request) 34 | 35 | def is_server_selection_visible(self) -> bool: 36 | """Returns true if the server selection screen is visible, false otherwise.""" 37 | request: Request = { 38 | "service": self._service_name, 39 | "method": "isServerSelectionVisible", 40 | "params": {} 41 | } 42 | response = self._socket.request(request) 43 | return response["result"]["visible"] 44 | 45 | def is_character_selection_visible(self) -> bool: 46 | """Returns true if the character selection screen is visible, false otherwise.""" 47 | request: Request = { 48 | "service": self._service_name, 49 | "method": "isCharacterSelectionVisible", 50 | "params": {} 51 | } 52 | response = self._socket.request(request) 53 | return response["result"]["visible"] 54 | 55 | def get_character_slots(self) -> list[CharacterSelectSlot]: 56 | """Returns a list with your character slots information.""" 57 | request: Request = { 58 | "service": self._service_name, 59 | "method": "getCharacterSlots", 60 | "params": {} 61 | } 62 | response = self._socket.request(request) 63 | return list(response["result"]["character_slots"]) 64 | 65 | def select_language(self, lang_index: int) -> Response: 66 | """Choose a language server from the server selection screen. The lang_index parameter starts from 0.""" 67 | request: Request = { 68 | "service": self._service_name, 69 | "method": "selectLanguage", 70 | "params": { 71 | "lang_index": lang_index 72 | } 73 | } 74 | return self._socket.request(request) 75 | 76 | def select_server(self, server_index: int) -> Response: 77 | """Choose a server from the server selection screen. The server_index parameter starts from 0""" 78 | request: Request = { 79 | "service": self._service_name, 80 | "method": "selectServer", 81 | "params": { 82 | "server_index": server_index 83 | } 84 | } 85 | return self._socket.request(request) 86 | 87 | def select_channel(self, channel_index: int) -> Response: 88 | """Choose a channel from the server selection screen. The channel_index parameter starts from 0.""" 89 | request: Request = { 90 | "service": self._service_name, 91 | "method": "selectChannel", 92 | "params": { 93 | "channel_index": channel_index 94 | } 95 | } 96 | return self._socket.request(request) 97 | 98 | def select_character(self, character_index: int) -> Response: 99 | """Choose a character from the character selection screen and starts the game. The character_index parameter starts from 0.""" 100 | request: Request = { 101 | "service": self._service_name, 102 | "method": "selectCharacter", 103 | "params": { 104 | "character_index": character_index 105 | } 106 | } 107 | return self._socket.request(request) 108 | 109 | def relog(self, server_index: int, channel_index: int, character_index: int = -1) -> Response: 110 | """ 111 | This method is an all in one, it will perform the following actions: 112 | 113 | 1. Logout your character 114 | 2. Selects the specified server 115 | 3. Selects the specified channel 116 | 4. Selects the specified character 117 | 118 | This method will block your current thread and will only return once the entire process has been completed. If the channel you are attempting to join is full the method will keep trying to join it indefinetly. If you want to stay at the character selection screen you can use a value of -1 for the character_index parameter. 119 | """ 120 | request: Request = { 121 | "service": self._service_name, 122 | "method": "relog", 123 | "params": { 124 | "server_index": server_index, 125 | "channel_index": channel_index, 126 | "character_index": character_index 127 | } 128 | } 129 | return self._socket.request(request) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015/2017 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # Visual Studio 2017 auto generated files 34 | Generated\ Files/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # Benchmark Results 50 | BenchmarkDotNet.Artifacts/ 51 | 52 | # .NET Core 53 | project.lock.json 54 | project.fragment.lock.json 55 | artifacts/ 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_h.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *_wpftmp.csproj 81 | *.log 82 | *.vspscc 83 | *.vssscc 84 | .builds 85 | *.pidb 86 | *.svclog 87 | *.scc 88 | 89 | # Chutzpah Test files 90 | _Chutzpah* 91 | 92 | # Visual C++ cache files 93 | ipch/ 94 | *.aps 95 | *.ncb 96 | *.opendb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | *.VC.db 101 | *.VC.VC.opendb 102 | 103 | # Visual Studio profiler 104 | *.psess 105 | *.vsp 106 | *.vspx 107 | *.sap 108 | 109 | # Visual Studio Trace Files 110 | *.e2e 111 | 112 | # TFS 2012 Local Workspace 113 | $tf/ 114 | 115 | # Guidance Automation Toolkit 116 | *.gpState 117 | 118 | # ReSharper is a .NET coding add-in 119 | _ReSharper*/ 120 | *.[Rr]e[Ss]harper 121 | *.DotSettings.user 122 | 123 | # JustCode is a .NET coding add-in 124 | .JustCode 125 | 126 | # TeamCity is a build add-in 127 | _TeamCity* 128 | 129 | # DotCover is a Code Coverage Tool 130 | *.dotCover 131 | 132 | # AxoCover is a Code Coverage Tool 133 | .axoCover/* 134 | !.axoCover/settings.json 135 | 136 | # Visual Studio code coverage results 137 | *.coverage 138 | *.coveragexml 139 | 140 | # NCrunch 141 | _NCrunch_* 142 | .*crunch*.local.xml 143 | nCrunchTemp_* 144 | 145 | # MightyMoose 146 | *.mm.* 147 | AutoTest.Net/ 148 | 149 | # Web workbench (sass) 150 | .sass-cache/ 151 | 152 | # Installshield output folder 153 | [Ee]xpress/ 154 | 155 | # DocProject is a documentation generator add-in 156 | DocProject/buildhelp/ 157 | DocProject/Help/*.HxT 158 | DocProject/Help/*.HxC 159 | DocProject/Help/*.hhc 160 | DocProject/Help/*.hhk 161 | DocProject/Help/*.hhp 162 | DocProject/Help/Html2 163 | DocProject/Help/html 164 | 165 | # Click-Once directory 166 | publish/ 167 | 168 | # Publish Web Output 169 | *.[Pp]ublish.xml 170 | *.azurePubxml 171 | # Note: Comment the next line if you want to checkin your web deploy settings, 172 | # but database connection strings (with potential passwords) will be unencrypted 173 | *.pubxml 174 | *.publishproj 175 | 176 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 177 | # checkin your Azure Web App publish settings, but sensitive information contained 178 | # in these scripts will be unencrypted 179 | PublishScripts/ 180 | 181 | # NuGet Packages 182 | *.nupkg 183 | # The packages folder can be ignored because of Package Restore 184 | **/[Pp]ackages/* 185 | # except build/, which is used as an MSBuild target. 186 | !**/[Pp]ackages/build/ 187 | # Uncomment if necessary however generally it will be regenerated when needed 188 | #!**/[Pp]ackages/repositories.config 189 | # NuGet v3's project.json files produces more ignorable files 190 | *.nuget.props 191 | *.nuget.targets 192 | 193 | # Microsoft Azure Build Output 194 | csx/ 195 | *.build.csdef 196 | 197 | # Microsoft Azure Emulator 198 | ecf/ 199 | rcf/ 200 | 201 | # Windows Store app package directories and files 202 | AppPackages/ 203 | BundleArtifacts/ 204 | Package.StoreAssociation.xml 205 | _pkginfo.txt 206 | *.appx 207 | 208 | # Visual Studio cache files 209 | # files ending in .cache can be ignored 210 | *.[Cc]ache 211 | # but keep track of directories ending in .cache 212 | !*.[Cc]ache/ 213 | 214 | # Others 215 | ClientBin/ 216 | ~$* 217 | *~ 218 | *.dbmdl 219 | *.dbproj.schemaview 220 | *.jfm 221 | *.pfx 222 | *.publishsettings 223 | orleans.codegen.cs 224 | 225 | # Including strong name files can present a security risk 226 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 227 | #*.snk 228 | 229 | # Since there are multiple workflows, uncomment next line to ignore bower_components 230 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 231 | #bower_components/ 232 | 233 | # RIA/Silverlight projects 234 | Generated_Code/ 235 | 236 | # Backup & report files from converting an old project file 237 | # to a newer Visual Studio version. Backup files are not needed, 238 | # because we have git ;-) 239 | _UpgradeReport_Files/ 240 | Backup*/ 241 | UpgradeLog*.XML 242 | UpgradeLog*.htm 243 | ServiceFabricBackup/ 244 | *.rptproj.bak 245 | 246 | # SQL Server files 247 | *.mdf 248 | *.ldf 249 | *.ndf 250 | 251 | # Business Intelligence projects 252 | *.rdl.data 253 | *.bim.layout 254 | *.bim_*.settings 255 | *.rptproj.rsuser 256 | 257 | # Microsoft Fakes 258 | FakesAssemblies/ 259 | 260 | # GhostDoc plugin setting file 261 | *.GhostDoc.xml 262 | 263 | # Node.js Tools for Visual Studio 264 | .ntvs_analysis.dat 265 | node_modules/ 266 | 267 | # Visual Studio 6 build log 268 | *.plg 269 | 270 | # Visual Studio 6 workspace options file 271 | *.opt 272 | 273 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 274 | *.vbw 275 | 276 | # Visual Studio LightSwitch build output 277 | **/*.HTMLClient/GeneratedArtifacts 278 | **/*.DesktopClient/GeneratedArtifacts 279 | **/*.DesktopClient/ModelManifest.xml 280 | **/*.Server/GeneratedArtifacts 281 | **/*.Server/ModelManifest.xml 282 | _Pvt_Extensions 283 | 284 | # Paket dependency manager 285 | .paket/paket.exe 286 | paket-files/ 287 | 288 | # FAKE - F# Make 289 | .fake/ 290 | 291 | # JetBrains Rider 292 | .idea/ 293 | *.sln.iml 294 | 295 | # CodeRush personal settings 296 | .cr/personal 297 | 298 | # Python Tools for Visual Studio (PTVS) 299 | __pycache__/ 300 | *.pyc 301 | 302 | # Cake - Uncomment if you are using it 303 | # tools/** 304 | # !tools/packages.config 305 | 306 | # Tabs Studio 307 | *.tss 308 | 309 | # Telerik's JustMock configuration file 310 | *.jmconfig 311 | 312 | # BizTalk build output 313 | *.btp.cs 314 | *.btm.cs 315 | *.odx.cs 316 | *.xsd.cs 317 | 318 | # OpenCover UI analysis results 319 | OpenCover/ 320 | 321 | # Azure Stream Analytics local run output 322 | ASALocalRun/ 323 | 324 | # MSBuild Binary and Structured Log 325 | *.binlog 326 | 327 | # NVidia Nsight GPU debugger configuration file 328 | *.nvuser 329 | 330 | # MFractors (Xamarin productivity tool) working folder 331 | .mfractor/ 332 | 333 | # Local History for Visual Studio 334 | .localhistory/ 335 | 336 | python/venv 337 | 338 | .vscode/ 339 | python/test.py 340 | 341 | .env 342 | 343 | python/dist 344 | --------------------------------------------------------------------------------