diff options
Diffstat (limited to 'docs/api')
| -rw-r--r-- | docs/api/README.md | 92 | ||||
| -rw-r--r-- | docs/api/_sidebar.md | 7 | ||||
| -rw-r--r-- | docs/api/hooks.md | 110 | ||||
| -rw-r--r-- | docs/api/objects.md | 272 | ||||
| -rw-r--r-- | docs/api/packets.md | 1 | ||||
| -rw-r--r-- | docs/api/plugin.md | 76 |
6 files changed, 558 insertions, 0 deletions
diff --git a/docs/api/README.md b/docs/api/README.md new file mode 100644 index 0000000..736c0aa --- /dev/null +++ b/docs/api/README.md @@ -0,0 +1,92 @@ +# Sakuya AC API Documentation + +Sakuya AC : The perfect and elegant YSFlight Proxy Software. It is written in Python. Uses +asyncio so that it doesn't lag. This documentation will guide you through creating your +own Plugin for Sakuya AC. + +## Getting Started + +To get started with Sakuya AC, you need to have Python 3.9 or higher installed on your system. + +### Basic Structure of a Plugin + +There are two ways to work with the proxy: +1. Hooks : These actually modify the incoming/outgoing packets from YSF Server, these are blocking +and can be used to modify the packets. +- Example : G Limiter, Chat Filter; As these need to modify the packets at that instant + +2. Commands : These are non essential and do not modify the incoming/outgong packets, you can send your +own packets to server/client. These are non blocking. +- Example : Fog color changer, Ban command; these do not need to modify any commands at that instant + +- All plugins are saved in plugins directory in the root of the project. + +### Simple Command Plugin + +```python +""" +This is an example test command! +""" + +from lib import YSchat +from time import sleep + +ENABLED = False + +class Plugin: + def __init__(self): + self.plugin_manager = None + + def register(self, plugin_manager): + self.plugin_manager = plugin_manager + self.plugin_manager.register_command('test', self.test) + self.plugin_manager.register_command('timer', self.timer) + + def test(self, full_message, player, message_to_client, message_to_server): + message_to_client.append(YSchat.message("Test command received")) + return True + + def timer(self, full_message, player, message_to_client, message_to_server): + sleep(5) + message_to_client.append(YSchat.message("Timer ended")) + return True +``` +- You start the plugin by giving it a description, +- You must have a global ENABLED variable, which is set by the user + if they wish to enable the plugin or not +- You must have a class Plugin, which has a register method + use ``register_command`` to register a command. + -- ``self.plugin_manager.register_command('command_name', self.function_name)`` +- now self.function_name must take the shown parameters + +### Simple Hooks Plugin + +```python +"""This plugin will flash the lights/fog colour whenever a flight status update +is sent +It can be enabled here by changing the value of ENABLED to True.""" +from lib.PacketManager.packets import FSNETCMD_SKYCOLOR, FSNETCMD_FOGCOLOR +from random import randint +ENABLED = True + +class Plugin: + def __init__(self): + self.plugin_manager = None + + def register(self, plugin_manager): + self.plugin_manager = plugin_manager + self.plugin_manager.register_hook('on_flight_data', self.on_receive) + + def on_receive(self, data, player, messages_to_client, *args): + sky_colour_packet = FSNETCMD_SKYCOLOR.encode(randint(0, 255), randint(0, 255), randint(0, 255), True) + fog_colour_packet = FSNETCMD_FOGCOLOR.encode(randint(0, 255), randint(0, 255), randint(0, 255), True) + messages_to_client.append(sky_colour_packet) + messages_to_client.append(fog_colour_packet) + return True +``` +- Unlike the previous example, this uses a hook which modifies the packet from the server at that instant +- You must return True at the end of the function to indicate that the original packet will be sent + -- (In this case, the orginal packet is the flight data, not sending will cause the client to + not receive the flight data) +- returning False, means the orginal packet which triggered the hook will not be sent to the YSF server, +this is useful for chat filters etc. diff --git a/docs/api/_sidebar.md b/docs/api/_sidebar.md new file mode 100644 index 0000000..a8f77cf --- /dev/null +++ b/docs/api/_sidebar.md @@ -0,0 +1,7 @@ +* [Home](/api/README.md) +* [Plugin Structure](/api/plugin.md) +* [Class Reference](/api/objects.md) + - [Aircraft](/api/objects.md#aircraft-class) + - [Player](/api/objects.md#player-class) +* [Packet Reference](/api/packets.md) +* [Hooks Reference](/api/hooks.md) diff --git a/docs/api/hooks.md b/docs/api/hooks.md new file mode 100644 index 0000000..373b42f --- /dev/null +++ b/docs/api/hooks.md @@ -0,0 +1,110 @@ +# Introduction + +Hooks are blocking functions that are called when a specific event occurs. +They are used to modify the behavior of the proxy. Every hook function +must return a `bool` value, `True` if the packet that triggered the hook +should be sent to the client/server or `False` if the packet should be +dropped and not sent. + +# Hook Structure + +In main `Plugin` Class, there must be a `register` method, under which +you must declare your hooks. +> eg. +> ```python +> def register(self, plugin_manager): +> self.plugin_manager = plugin_manager +> self.plugin_manager.register_hook('on_flight_data', self.on_receive) +>``` +> This will trigger the `on_receive` method of your plugin when the `on_flight_data` hook is triggered. + +# Hook Function Structure + +Every hook function must have the following structure: +```python +def on_receive(self, data, player, message_to_client, message_to_server) +``` +Where: +- `data` is the packet object that triggered the hook, It is a `bytes` object. +The data is sent without the header.(that is the size of the packet) + +- `player` is the `Player` object that triggered the hook. + +- `message_to_client` is the list that contains the packets that will be sent to the client. + you must append to it and return a value for the packet to be sent. + +- `message_to_server` is the list that contains the packets that will be sent to the server. + you must append to it and return a value for the packet to be sent. + +> You must return a `bool` value, `True` if the packet that triggered the hook should be +> sent to the client/server or `False` if the packet should be dropped and not sent. + +# Hook List + +## Client to Server Side + +| Hook | Packet Object | +|-------------------------|-----------------------| +| `on_login` | `FSNETCMD_LOGON` | +| `on_logout` | `FSNETCMD_LOGOFF` | +| `on_error` | `FSNETCMD_ERROR` | +| `on_load_field` | `FSNETCMD_LOADFIELD` | +| `on_add_object` | `FSNETCMD_ADDOBJECT` | +| `on_readback` | `FSNETCMD_READBACK` | +| `on_smoke_color` | `FSNETCMD_SMOKECOLOR` | +| `on_join_request` | `FSNETCMD_JOINREQUEST`| +| `on_join_approval` | `FSNETCMD_JOINAPPROVAL`| +| `on_reject_join_request`| `FSNETCMD_REJECTJOINREQ`| +| `on_flight_data` | `FSNETCMD_AIRPLANESTATE`| +| `on_unjoin` | `FSNETCMD_UNJOIN` | +| `on_remove_airplane` | `FSNETCMD_REMOVEAIRPLANE`| +| `on_request_test_airplane`| `FSNETCMD_REQUESTTESTAIRPLANE`| +| `on_kill_server` | `FSNETCMD_KILLSERVER` | +| `on_prepare_simulation` | `FSNETCMD_PREPARESIMULATION`| +| `on_test_packet` | `FSNETCMD_TESTPACKET` | +| `on_lockon` | `FSNETCMD_LOCKON` | +| `on_remove_ground` | `FSNETCMD_REMOVEGROUND`| +| `on_missile_launch` | `FSNETCMD_MISSILELAUNCH`| +| `on_get_damage` | `FSNETCMD_GETDAMAGE` | +| `on_weapon_config` | `FSNETCMD_WEAPONCONFIG`| +| `on_air_cmd` | `FSNETCMD_AIRCMD` | +| `on_chat` | `FSNETCMD_TEXTMESSAGE`| +| `on_environment` | `FSNETCMD_ENVIRONMENT`| +| `on_sky_color` | `FSNETCMD_SKYCOLOR` | +| `on_fog_color` | `FSNETCMD_FOGCOLOR` | +| `on_list` | `FSNETCMD_LIST` | + +<br> + +## Server to Client Side + +| Hook | Packet Object | +|-------------------------------|-----------------------| +| `on_login_server` | `FSNETCMD_LOGON` | +| `on_logout_server` | `FSNETCMD_LOGOFF` | +| `on_error_server` | `FSNETCMD_ERROR` | +| `on_load_field_server` | `FSNETCMD_LOADFIELD` | +| `on_add_object_server` | `FSNETCMD_ADDOBJECT` | +| `on_readback_server` | `FSNETCMD_READBACK` | +| `on_smoke_color_server` | `FSNETCMD_SMOKECOLOR` | +| `on_join_request_server` | `FSNETCMD_JOINREQUEST`| +| `on_join_approval_server` | `FSNETCMD_JOINAPPROVAL`| +| `on_reject_join_request_server`| `FSNETCMD_REJECTJOINREQ`| +| `on_flight_data_server` | `FSNETCMD_AIRPLANESTATE`| +| `on_unjoin_server` | `FSNETCMD_UNJOIN` | +| `on_remove_airplane_server` | `FSNETCMD_REMOVEAIRPLANE`| +| `on_request_test_airplane_server`| `FSNETCMD_REQUESTTESTAIRPLANE`| +| `on_kill_server_server` | `FSNETCMD_KILLSERVER` | +| `on_prepare_simulation_server`| `FSNETCMD_PREPARESIMULATION`| +| `on_test_packet_server` | `FSNETCMD_TESTPACKET` | +| `on_lockon_server` | `FSNETCMD_LOCKON` | +| `on_remove_ground_server` | `FSNETCMD_REMOVEGROUND`| +| `on_missile_launch_server` | `FSNETCMD_MISSILELAUNCH`| +| `on_get_damage_server` | `FSNETCMD_GETDAMAGE` | +| `on_weapon_config_server` | `FSNETCMD_WEAPONCONFIG`| +| `on_air_cmd_server` | `FSNETCMD_AIRCMD` | +| `on_chat_server` | `FSNETCMD_TEXTMESSAGE`| +| `on_environment_server` | `FSNETCMD_ENVIRONMENT`| +| `on_sky_color_server` | `FSNETCMD_SKYCOLOR` | +| `on_fog_color_server` | `FSNETCMD_FOGCOLOR` | +| `on_list_server` | `FSNETCMD_LIST` | diff --git a/docs/api/objects.md b/docs/api/objects.md new file mode 100644 index 0000000..bdd5d49 --- /dev/null +++ b/docs/api/objects.md @@ -0,0 +1,272 @@ +## Object Descriptions + +### ``Aircraft`` Class + +An aircraft class designed to hold information from airplane state and related packets within a flight simulation environment. This class manages aircraft properties such as position, attitude, +life, configuration etc. + +#### Attributes + +* `parent`: Reference to the parent object. +* `name` (*str*): Aircraft name (empty initially). +* `position` (*list[float]*): 3D position [x, y, z] (initially `[0, 0, 0]`). +* `attitude` (*list[float]*): Attitude angles (initially `[0, 0, 0]`). +* `initial_config` (*dict*): Initial configuration parameters (empty initially). +* `custom_config` (*dict*): Custom configuration parameters (empty initially). +* `life` (*int*): Current life/health (initially `-1`). +* `prev_life` (*int*): Previous life value (initially `-1`). +* `id` (*int*): Unique identifier (initially `-1`). +* `last_packet`: Last received packet (initially `None`). +* `damage_engine_warn_sent` (*bool*): Damage engine warning flag (initially `False`). +* `last_over_g_message` (*int*): Last over-G message timestamp (initially `0`). +* `just_repaired` (*bool*): Just repaired flag (initially `False`). + +#### Methods + +##### `reset(self)` + +**Description:** + +Resets all aircraft attributes to their initial defaults. + +**Parameters:** + +* `self`: The `Aircraft` instance. + +**Returns:** + +* `None` + +**Resets attributes:** `name`, `position`, `attitude`, `initial_config`, `custom_config`, `life`, `prev_life`, `id`, `last_packet`, `damage_engine_warn_sent`, `just_repaired`. + +##### `set_position(self, position: list)` + +**Description:** + +Sets the aircraft's 3D position. + +**Parameters:** + +* `self`: The `Aircraft` instance. +* `position` (*list[float]*): [x, y, z] coordinates. + +**Returns:** + +* `None` + +##### `set_attitude(self, attitude: list)` + +**Description:** + +Sets the aircraft's attitude. + +**Parameters:** + +* `self`: The `Aircraft` instance. +* `attitude` (*list[float]*): Attitude angles. + +**Returns:** + +* `None` + +##### `get_position(self)` + +**Description:** + +Returns the aircraft's 3D position. + +**Parameters:** + +* `self`: The `Aircraft` instance. + +**Returns:** + +* *list[float]*: [x, y, z] coordinates. + +##### `get_altitude(self)` + +**Description:** + +Returns the aircraft's altitude (Z-coordinate) in meters. + +**Parameters:** + +* `self`: The `Aircraft` instance. + +**Returns:** + +* *float*: Altitude in meters. + +##### `get_attitude(self)` + +**Description:** + +Returns the aircraft's attitude. + +**Parameters:** + +* `self`: The `Aircraft` instance. + +**Returns:** + +* *list[float]*: Attitude angles. + +##### `set_initial_config(self, config: dict)` + +**Description:** + +Sets the aircraft's initial configuration. + +**Parameters:** + +* `self`: The `Aircraft` instance. +* `config` (*dict*): Initial configuration key-value pairs. + +**Returns:** + +* `None` + +##### `get_initial_config_value(self, key: str)` + +**Description:** + +Retrieves a value from `initial_config`. + +**Parameters:** + +* `self`: The `Aircraft` instance. +* `key` (*str*): Configuration key. + +**Returns:** + +* *Any*: Configuration value or `None` if key not found. + +##### `set_custom_config_value(self, key: str, value)` + +**Description:** + +Sets a custom configuration value. + +**Parameters:** + +* `self`: The `Aircraft` instance. +* `key` (*str*): Configuration key. +* `value` (*Any*): Configuration value. + +**Returns:** + +* `None` + +##### `add_state(self, packet: FSNETCMD_AIRPLANESTATE)` + +**Description:** + +Updates aircraft state from an `FSNETCMD_AIRPLANESTATE` packet. + +**Parameters:** + +* `self`: The `Aircraft` instance. +* `packet` (*FSNETCMD_AIRPLANESTATE*): Airplane state packet. + +**Returns:** + +* *FSNETCMD_AIRPLANESTATE* or *None*: Input `packet` if processed, `None` if ID mismatch. + +**Functionality:** Updates `life`, `position`, `attitude` and stores the `last_packet`. + +##### `check_command(self, command: FSNETCMD_AIRCMD)` + +**Description:** + +Processes an `FSNETCMD_AIRCMD` packet for configuration commands. + +**Parameters:** + +* `self`: The `Aircraft` instance. +* `command` (*FSNETCMD_AIRCMD*): Air command packet. + +**Returns:** + +* `None` + +**Functionality:** Updates `initial_config` based on the command. Logs the command in debug. + +##### `set_afterburner(self, enabled: bool)` + +**Description:** + +Toggles the afterburner if available. + +**Parameters:** + +* `self`: The `Aircraft` instance. +* `enabled` (*bool*): `True` to enable, `False` to disable. + +**Returns:** + +* *FSNETCMD_AIRCMD* or *None*: Result of `FSNETCMD_AIRCMD.set_afterburner` if afterburner available, else `None`. + +**Functionality:** Checks for "AFTBURNR" in `initial_config` and sends command if available. + +### `Player` Class + +The `Player` class represents a connected client. It stores key information such as their username, alias, IP address, + and the `Aircraft` object they are currently piloting. + +#### Attributes + +* **`username`**: The player's username (string). Set via the `login` method. +* **`alias`**: The player's alias (string). Set via the `login` method. +* **`aircraft`**: An `Aircraft` object instance representing the aircraft the player is currently flying. Initially an empty `Aircraft` object and populated through `check_add_object` or `set_aircraft`. +* **`version`**: The client version (integer). Set via the `login` method. +* **`ip`**: The player's IP address (string). Set via the `set_ip` method. +* **`streamWriterObject`**: Object for handling network communication with the player's client. +* **`is_a_bot`**: A boolean flag indicating if the player is considered a bot. Initially `True`, and is intended to be set to `False` after a successful `LOGIN` packet is processed, to differentiate real players from initial bot-like states. + +#### Methods + +##### `set_aircraft(aircraft: Aircraft)` + +```python +set_aircraft(aircraft: Aircraft) +``` +Assigns a specific `Aircraft` object to this player, representing the aircraft they are currently flying. Useful when you need to manually set or update the player's aircraft. + +* **`aircraft`**: An `Aircraft` object instance. + +##### `login(packet: FSNETCMD_LOGON)` + +```python +login(packet: FSNETCMD_LOGON) +``` +Processes a login packet (`FSNETCMD_LOGON`) to extract and set the player's `username`, `alias`, and client `version`. This is typically called upon receiving a successful login packet from the client. + +* **`packet`**: An `FSNETCMD_LOGON` packet instance containing login details. + +##### `set_ip(ip)` + +```python +set_ip(ip) +``` +Sets the IP address associated with this player's connection. + +* **`ip`**: A string representing the player's IP address. + +##### `check_add_object(packet: FSNETCMD_ADDOBJECT)` + +```python +check_add_object(packet: FSNETCMD_ADDOBJECT) +``` +Checks if an `ADDOBJECT` packet (`FSNETCMD_ADDOBJECT`) pertains to this player based on the pilot's username in the packet. If it does, it initializes a new `Aircraft` object for the player using data from the packet, effectively setting the aircraft they are flying. Returns `True` if the aircraft was initialized, `False` otherwise. + +* **`packet`**: An `FSNETCMD_ADDOBJECT` packet instance containing aircraft creation details. +* **Returns**: `True` if the packet was for this player and the aircraft was initialized, `False` otherwise. + +##### `__str__()` + +```python +__str__() +``` +Returns a user-friendly string representation of the `Player` object. This string includes the player's `username`, the name of their `aircraft`, and its current `position`. Useful for logging and debugging purposes. + +* **Returns**: A descriptive string of the `Player` object. diff --git a/docs/api/packets.md b/docs/api/packets.md new file mode 100644 index 0000000..f10c9a1 --- /dev/null +++ b/docs/api/packets.md @@ -0,0 +1 @@ +Incomplete! diff --git a/docs/api/plugin.md b/docs/api/plugin.md new file mode 100644 index 0000000..2d05ece --- /dev/null +++ b/docs/api/plugin.md @@ -0,0 +1,76 @@ +# Single File Plugins + +These plugins are contained in a single `.py` files, recomemded for small plugins. + +```python +""" +This is an example test command! +""" + +from lib import YSchat + +# ENABLED variable must be present in your plugin otherwise it will +# fail to load. This is used by user to change the state of the plugin + +ENABLED = True + +class Plugin: + def __init__(self): + # Intialise the plugin here + self.plugin_manager = None + + def register(self, plugin_manager): + # Here you declare functions of your plugin + # Bind to plugin manager + self.plugin_manager = plugin_manager + # Register your plugin commands + self.plugin_manager.register_command('test', self.test) + # Register your plugin hooks + self.plugin_manager.register_hook('on_flight_data', self.on_receive) + + # Command Function + def test(self, full_message, player, message_to_client, message_to_server): + message_to_client.append(YSchat.message("Test command received")) + return True + + # Hook Function + def on_receive(self, data, player, message_to_client, message_to_server): + print(f"Received flight data of {player.username}") + return True +``` + +# Multi File Plugins + +These plugins are contained in a directory with multiple files, recomemded for large plugins. + +```bash +plugin/ +├── Plugin.py +└── __init__.py +``` + +```python +# __init__.py +from .Plugin import Plugin +from .Plugin import ENABLED +``` + +```python +# Plugin.py +""" +This is a multi file plugin example +""" +ENABLED = True + +class Plugin: + def __init__(self): + self.plugin_manager = None + + def register(self, plugin_manager): + self.plugin_manager = plugin_manager + self.plugin_manager.register_hook('on_flight_data', self.on_flight_data) + + def on_flight_data(self, data, player,message_to_client, message_to_server): + print(f"Received flight data of {player.username}") + return True +``` |
