summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/Aircraft.py1
-rw-r--r--lib/plugin_manager.py46
-rw-r--r--plugins/allcaps.py23
-rw-r--r--plugins/discolights.py22
-rw-r--r--plugins/over_g_damage.py28
-rw-r--r--proxy.py54
6 files changed, 142 insertions, 32 deletions
diff --git a/lib/Aircraft.py b/lib/Aircraft.py
index ecbecff..dd2bbed 100644
--- a/lib/Aircraft.py
+++ b/lib/Aircraft.py
@@ -17,6 +17,7 @@ class Aircraft:
self.id = -1
self.last_packet = None
self.damage_engine_warn_sent = False
+ self.last_over_g_message = 0
self.just_repaired = False
def reset(self):
diff --git a/lib/plugin_manager.py b/lib/plugin_manager.py
new file mode 100644
index 0000000..113c47a
--- /dev/null
+++ b/lib/plugin_manager.py
@@ -0,0 +1,46 @@
+import importlib
+import os
+import sys
+from logging import info
+
+PLUGIN_DIR = os.path.join(os.path.dirname(__file__), '../plugins')
+sys.path.append(PLUGIN_DIR)
+
+class PluginManager:
+ def __init__(self):
+ self.plugins = {}
+ self.hooks= {}
+ self.load_plugins()
+
+ def load_plugins(self):
+ for plugin in os.listdir(PLUGIN_DIR):
+ if plugin.endswith('.py') and not plugin.startswith('__'):
+ plugin_name = plugin[:-3]
+ plugin_module = importlib.import_module(plugin_name)
+ if hasattr(plugin_module, 'Plugin'):
+ if plugin_module.ENABLED:
+ plugin_instance = plugin_module.Plugin()
+ self.register_plugin(plugin_instance)
+ info(f"Loaded plugin {plugin_name}")
+ self.plugins[plugin_name] = plugin_instance
+
+ def register_plugin(self, plugin):
+ """Registers the plugin with the plugin manager"""
+ plugin.register(self)
+
+ def register_hook(self, hook_name, callback):
+ """Registers the hook with the plugin manager"""
+ if hook_name not in self.hooks:
+ self.hooks[hook_name] = []
+ self.hooks[hook_name].append(callback)
+
+ def triggar_hook(self, hook_name, data, *args, **kwargs):
+ """Triggars the callbacks for the hook.
+ If a callback returns False, then the original data is set to None
+ and not forwarded to the destination. This is useful for modifying
+ packets."""
+ keep_orignal = True
+ if hook_name in self.hooks:
+ for callback in self.hooks[hook_name]:
+ keep_orignal = callback(data, *args, **kwargs)
+ return keep_orignal
diff --git a/plugins/allcaps.py b/plugins/allcaps.py
new file mode 100644
index 0000000..83b5aec
--- /dev/null
+++ b/plugins/allcaps.py
@@ -0,0 +1,23 @@
+"""This plugin will convert all chat messages to all caps.
+It can be enabled here by changing the value of ENABLED to True."""
+from lib.PacketManager.packets import FSNETCMD_TEXTMESSAGE
+from logging import info
+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_hook('on_chat', self.on_chat)
+
+ def on_chat(self, data, player,message_to_client, message_to_server):
+ if ENABLED:
+ message = FSNETCMD_TEXTMESSAGE(data, should_decode=True)
+ message.message = message.message.upper()
+ message = FSNETCMD_TEXTMESSAGE.encode(f"({message.user}){message.message}", with_size=True)
+ message_to_server.append(message)
+
+
+ return False
diff --git a/plugins/discolights.py b/plugins/discolights.py
new file mode 100644
index 0000000..db441e7
--- /dev/null
+++ b/plugins/discolights.py
@@ -0,0 +1,22 @@
+"""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 = False
+
+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):
+ if ENABLED:
+ 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
diff --git a/plugins/over_g_damage.py b/plugins/over_g_damage.py
new file mode 100644
index 0000000..aa44ca8
--- /dev/null
+++ b/plugins/over_g_damage.py
@@ -0,0 +1,28 @@
+"""This plugin will cause damage to the aircraft if
+it exceeds the g-force limit set in the config file."""
+from lib.PacketManager.packets import FSNETCMD_GETDAMAGE, FSNETCMD_TEXTMESSAGE
+from config import G_LIM
+import time
+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):
+ if ENABLED:
+ if abs(player.aircraft.last_packet.g_value)> G_LIM:
+ if time.time() - player.aircraft.last_over_g_message > 1: # Only send every second.
+ player.aircraft.last_over_g_message = time.time()
+ damage_packet = FSNETCMD_GETDAMAGE.encode(player.aircraft.id,
+ 1, 1,
+ player.aircraft.id,
+ 1, 11,0, True)
+ warning_message = FSNETCMD_TEXTMESSAGE.encode(f"You are exceeding the G Limit for the aircraft!, gValue = {player.aircraft.last_packet.g_value}!",True)
+ messages_to_client.append(damage_packet)
+ messages_to_client.append(warning_message)
+ return True
diff --git a/proxy.py b/proxy.py
index 2ce6d47..5d3e02a 100644
--- a/proxy.py
+++ b/proxy.py
@@ -7,6 +7,7 @@ import asyncio
from struct import unpack, pack
from lib.parseFlightData import parseFlightData
from lib import YSchat, YSplayer, YSendFlight, YSundead, YSviaversion, Player, Aircraft
+from lib.plugin_manager import PluginManager
from lib.PacketManager.PacketManager import PacketManager
from lib.PacketManager.packets import *
import logging
@@ -55,6 +56,9 @@ info("Perfect and Elegant Proxy for your YSFlight Server")
info("Lisenced under GPLv3")
info("Press CTRL+C to stop the proxy")
+#Load the plugins
+plugin_manager = PluginManager()
+
# Handle client connections
async def handle_client(client_reader, client_writer):
message_to_client = []
@@ -76,7 +80,8 @@ async def handle_client(client_reader, client_writer):
while True:
try:
- #Test if there are any unsent messages to the client or server from other processes.
+ #Test if there are any unsent messages to the client or
+ # server from other processes.
if len(message_to_client) > 0:
client_writer.write(message_to_client.pop(0))
await client_writer.drain()
@@ -114,34 +119,23 @@ async def handle_client(client_reader, client_writer):
try:
if packet_type == "FSNETCMD_LOGON":
+ keep_message = plugin_manager.triggar_hook('on_login', packet, player, message_to_client, message_to_server)
+ if not keep_message:
+ data = None
player.login(FSNETCMD_LOGON(packet))
if player.version != YSF_VERSION and VIA_VERSION:
info(f"ViaVersion enabled : Porting {player.username} from {player.version} to {YSF_VERSION}")
message_to_client.append(YSchat.message(f"Porting you to YSFlight {YSF_VERSION}, This is currently Experimental"))
message_to_client.append(YSchat.message(f"Please report any bugs to the server admin or join with the correct version"))
- data = YSviaversion.genViaVersion(player.username, YSF_VERSION)
+ data = YSviaversion.genViaVersion(player.username, YSF_VERSION) #TODO: Refactor using the FSNETCMD packet.
writer.write(data)
continue
elif packet_type == "FSNETCMD_AIRPLANESTATE":
- packet = player.aircraft.add_state(FSNETCMD_AIRPLANESTATE(packet))
-
- # Disco lights
- # Just for fun remove in production
- #skycolorPacket = FSNETCMD_SKYCOLOR.encode(randint(0,255), randint(0,255), randint(0,255), True)
- #fogcolorpacket = FSNETCMD_FOGCOLOR.encode(randint(0,255), randint(0,255), randint(0,255), True)
- #message_to_server.append(skycolorPacket)
- #message_to_client.append(skycolorPacket)
- #message_to_server.append(fogcolorpacket)
- #message_to_client.append(fogcolorpacket)
-
- if abs(player.aircraft.last_packet.g_value) > G_LIM:
- debug("G Value exceeded : ", player.aircraft.last_packet.g_value)
- # We make a packet which damages the aircraft using the same pilot ID, using a gun
- damageData = FSNETCMD_GETDAMAGE.encode(player.aircraft.id, 1, 1, player.aircraft.id, 1, 11, 0, True)
- warnMsg = YSchat.message(f"You are exceeding the G Limit for the aircraft!, gValue = {player.aircraft.last_packet.g_value}!")
- message_to_client.append(damageData)
- message_to_client.append(warnMsg)
+ packet = player.aircraft.add_state(FSNETCMD_AIRPLANESTATE(packet)) #TODO: Do we want to convert all this to plugins? Probably not, but there is duplicated functionality
+ keep_message = plugin_manager.triggar_hook('on_flight_data', packet, player, message_to_client, message_to_server)
+ if not keep_message:
+ data = None
if player.aircraft.life == -1: #Uninitialised
player.aircraft.prev_life = player.aircraft.life
@@ -174,16 +168,11 @@ async def handle_client(client_reader, client_writer):
elif packet_type == "FSNETCMD_TEXTMESSAGE":
msg = FSNETCMD_TEXTMESSAGE(packet)
+ keep_message = plugin_manager.triggar_hook('on_chat', packet, player, message_to_client, message_to_server)
+ if not keep_message:
+ data = None
finalMsg = (f"{player.username} : {msg.message}")
- # TODO: Remove in production
- # skyColor = FSNETCMD_SKYCOLOR.encode(136, 34, 34, True) # Top Gradient
- # fogColor = FSNETCMD_FOGCOLOR.encode(237, 156, 21, True) # Bottom Gradient
- # fogColor = FSNETCMD_FOGCOLOR.encode(136, 34, 34, True) # Dark Orange
- fogColor = FSNETCMD_FOGCOLOR.encode(120, 30, 30, True) # Magenta Tint
- # message_to_client.append(skyColor)
- message_to_client.append(fogColor)
- # message_to_server.append(skyColor)
- message_to_server.append(fogColor)
+
if DISCORD_ENABLED:
# Make it non blocking!
@@ -222,9 +211,10 @@ async def handle_client(client_reader, client_writer):
pck = FSNETCMD_ENVIRONMENT.setTime(packet, True, False)
data = pack("I", len(pck)) + pck
- # Forward the packet to the other endpoint
- writer.write(data)
- await writer.drain()
+ # Forward the packet to the other endpoint if the data packet still exists.
+ if data:
+ writer.write(data)
+ await writer.drain()
except (asyncio.CancelledError, ConnectionResetError, BrokenPipeError) as e:
if e == BrokenPipeError or ConnectionResetError or asyncio.CancelledError:
info(f"Connection closed by {player.username} : {player.ip}")