diff --git a/__main__.py b/__main__.py index 041c511..32ccbbf 100644 --- a/__main__.py +++ b/__main__.py @@ -8,6 +8,8 @@ import player_service import threading import sys import replica_service +import game_message_service +import chat_command_service from importlib import reload @@ -30,12 +32,8 @@ if __name__ == "__main__": config = configparser.ConfigParser() config.read("config.ini") game_config = config["GAME_CONFIG"] - game.set_config("address", str(game_config["address"])) - game.set_config("auth_port", int(game_config["auth_port"])) - game.set_config("world_port", int(game_config["world_port"])) - game.set_config("auth_max_connections", int(game_config["auth_max_connections"])) - game.set_config("world_max_connections", int(game_config["world_max_connections"])) - game.set_config("accept_custom_names", bool(game_config["accept_custom_names"])) + for config_option in game_config: + game.set_config(config_option, eval(game_config[config_option])) #Append all game scripts to Game for file in os.listdir("./game_scripts"): @@ -58,12 +56,18 @@ if __name__ == "__main__": world_server = services.WorldServerService(game) game.register_service(world_server) + game_message = game_message_service.GameMessageService(game) + game.register_service(game_message) + replica = replica_service.ReplicaService(game) game.register_service(replica) world = services.WorldService(game) game.register_service(world) + chat_command = chat_command_service.ChatCommandService(game) + game.register_service(chat_command) + game.start() console_thread = threading.Thread(target=console_loop) diff --git a/chat_command_service.py b/chat_command_service.py new file mode 100644 index 0000000..c0f1740 --- /dev/null +++ b/chat_command_service.py @@ -0,0 +1,42 @@ +import services +from pyraknet.bitstream import * +import copy +import game_enums + + +class ChatCommandService(services.GameService): + def __init__(self, parent): + super().__init__(parent) + self._name = "Chat Command" + self._commands = {} + global game + game = self.get_parent() + + def initialize(self): + self.register_command("/testmap", self.testmap) + game.register_event_handler("GM_{}".format(game_enums.GameMessages.PARSE_CHAT_MSG.value))(self.handle_command) + super().initialize() + + def handle_command(self, object_id, stream, address): + client_state = stream.read(c_int) + command = stream.read(str, length_type=c_ulong) + args = command.split(" ") + if(game.get_config("allow_commands") == True or bool(game.get_service("Player").get_account_by_player_id(object_id)["is_admin"]) == True): + if(args[0] in self._commands): + copy_args = copy.deepcopy(args) + del copy_args[0] + self._commands[args[0]](object_id, address, copy_args, client_state) + + def register_command(self, command_name, handler): + self._commands[command_name] = handler + + def testmap(self, object_id, address, args, client_state): + if(game.get_service("World").get_zone_by_id(int(args[0])) is not None): + world_server = game.get_service("World Server").server + world_server.load_world(object_id, int(args[0]), address, True) + + + + + + diff --git a/components.py b/components.py index 86cd8cd..efd78dd 100644 --- a/components.py +++ b/components.py @@ -1,5 +1,6 @@ import game_types import typing +import time import copy ''' @@ -33,6 +34,20 @@ class Transform(Component): self.on_ground : bool = True self.angular_velocity : game_types.Vector3 = game_types.Vector3() + if(self.get_parent().lot == 1): + self.player_sync_thread = game_types.GameThread(target=self.player_sync) + self.player_sync_thread.start() + def player_sync(self): + player_id = self.get_parent().get_object_id() + game = self.get_parent().zone.get_parent().get_parent() + player_service = game.get_service("Player") + while self.get_parent().player_sync == True: + player = player_service.get_player_by_id(player_id) + player["Data"]["position"] = self.position + player["Data"]["rotation"] = self.rotation + time.sleep(.1) + + class Collectible(Component): def __init__(self, parent): super().__init__(parent) @@ -199,6 +214,23 @@ class Stats(Component): self.is_smashable : bool = False self.level : int = 1 + if(self.get_parent().lot == 1): + self.player_sync_thread = game_types.GameThread(target=self.player_sync) + self.player_sync_thread.start() + def player_sync(self): + player_id = self.get_parent().get_object_id() + game = self.get_parent().zone.get_parent().get_parent() + player_service = game.get_service("Player") + while self.get_parent().player_sync == True: + player = player_service.get_player_by_id(player_id) + player["Data"]["health"] = self.health + player["Data"]["max_health"] = self.max_health + player["Data"]["armor"] = self.armor + player["Data"]["max_armor"] = self.max_armor + player["Data"]["imagination"] = self.imagination + player["Data"]["max_imagination"] = self.max_imagination + time.sleep(.1) + #TODO: Implement This Component, It Doesn't Do Anything Now class Destructible(Component): def __init__(self, parent): diff --git a/config.ini b/config.ini index b6bf362..928f830 100644 --- a/config.ini +++ b/config.ini @@ -1,7 +1,9 @@ [GAME_CONFIG] -address=127.0.0.1 +address="127.0.0.1" auth_port=1001 world_port=2002 +allow_commands=True auth_max_connections=10 world_max_connections=10 -accept_custom_names=0 \ No newline at end of file +accept_custom_names=False +custom_config={"JonnysDumbEffect":True} \ No newline at end of file diff --git a/game_enums.py b/game_enums.py index db8cd7c..bc08bd9 100644 --- a/game_enums.py +++ b/game_enums.py @@ -79,9 +79,12 @@ class ItemLOTs(IntEnum): PANTS_DARK_RED = 2527 class GameMessages(IntEnum): - SERVER_DONE_LOADING_OBJECTS = 0x066a - PLAYER_READY = 0x01fd - READY_FOR_UPDATES = 0x0378 + SERVER_DONE_LOADING_OBJECTS = 1642 + PLAYER_READY = 509 + READY_FOR_UPDATES = 888 + PLAY_FX_EFFECT = 154 + STOP_FX_EFFECT = 155 + PARSE_CHAT_MSG = 850 class MinifigureCreationResponseEnum(IntEnum): SUCCESS = 0x00 diff --git a/game_message_service.py b/game_message_service.py new file mode 100644 index 0000000..8c6ee8f --- /dev/null +++ b/game_message_service.py @@ -0,0 +1,70 @@ +import services +from pyraknet.bitstream import * +import game_enums +import game_types +import copy + + +class GameMessageService(services.GameService): + def __init__(self, parent): + super().__init__(parent) + self._name = "Game Message" + global game + game = self.get_parent() + + def initialize(self): + self.world_server = game.get_service("World Server").server + super().initialize() + + def send_game_msg(self, object_id, msg_id, recipients: list, additional_parameters: list = None): + msg = WriteStream() + msg.write(game_enums.PacketHeaderEnum.SERVER_GAME_MESSAGE.value) + msg.write(c_longlong(object_id)) + msg.write(c_ushort(msg_id)) + self.world_server.send(msg, recipients) + + def play_fx_effect(self, object_id : int, recipients : list, effect_id : int, effect_type : str, scale : float, name : str, priority : float = 1.0, secondary : int = -1, serialize : bool = True): + msg = WriteStream() + msg.write(game_enums.PacketHeaderEnum.SERVER_GAME_MESSAGE.value) + msg.write(c_longlong(object_id)) + msg.write(c_ushort(game_enums.GameMessages.PLAY_FX_EFFECT.value)) + + msg.write(c_bit(effect_id != -1)) + if(effect_id != -1): + msg.write(c_long(effect_id)) + + msg.write(effect_type, length_type=c_ulong) + + msg.write(c_bit(scale != 1.0)) + if(scale != 1.0): + msg.write(c_float(scale)) + + msg.write(game_types.String(name, length_type=c_ulong)) + + msg.write(c_bit(priority != 1.0)) + if(priority != 1.0): + msg.write(c_float(priority)) + + msg.write(c_bit(secondary > -1)) + if(secondary > -1): + msg.write(c_longlong(secondary)) + + msg.write(c_bit(serialize)) + + self.world_server.send(msg, recipients) + + def stop_fx_effect(self, object_id : int, recipients : list, kill_immediately : bool, name : str): + msg = WriteStream() + msg.write(game_enums.PacketHeaderEnum.SERVER_GAME_MESSAGE.value) + msg.write(c_longlong(object_id)) + msg.write(c_ushort(game_enums.GameMessages.PLAY_FX_EFFECT.value)) + msg.write(c_bit(kill_immediately)) + msg.write(game_types.String(name, length_type=c_ulong)) + self.world_server.send(msg, recipients) + + + + + + + diff --git a/game_objects.py b/game_objects.py index 4fef6b9..6372851 100644 --- a/game_objects.py +++ b/game_objects.py @@ -58,15 +58,24 @@ class ReplicaObject(GameObject): self.world_state = None self.gm_level = None self.json = {} - transform = components.Transform(self) - self.add_component(transform) if("gm_level" in config): self.gm_level = config["gm_level"] if("world_state" in config): self.world_state = config["world_state"] if("lot" in config): self.lot = config["lot"] + if(self.lot == 1): + self.player_sync = True if("spawner_id" in config): self.spawner_id = config["spawner_id"] if("spawner_node_id" in config): self.spawner_node_id = config["spawner_node_id"] + transform = components.Transform(self) + self.add_component(transform) + + def on_destruction(self): + for component in self._components: + if (hasattr(component, "player_sync_thread")): + if(self.lot == 1): + self.player_sync = False + component.player_sync_thread.stop() diff --git a/game_scripts/jonnys_dumb_effect.py b/game_scripts/jonnys_dumb_effect.py new file mode 100644 index 0000000..99650f0 --- /dev/null +++ b/game_scripts/jonnys_dumb_effect.py @@ -0,0 +1,30 @@ +import sys +sys.path.append("..") +import scripts +import time + +'''Big thanks to Tracer (the other Wesley), for providing me with the function from JALUS that does this thing''' + +class Main(scripts.Script): + def __init__(self, parent): + super().__init__(parent, "Jonny's Dumb Effect") + global game + game = self.get_parent() + + def run(self): + game.wait_for_event("ServiceInitialized", '''args[0].get_name() == "World"''') + custom_config = game.get_config("custom_config") + if("JonnysDumbEffect" in custom_config and custom_config["JonnysDumbEffect"] == True): + game_message_service = game.get_service("Game Message") + while True: + zones = game.get_service("World").get_zones() + for zone in zones: + players = zone.get_players() + for player_id in players: + game_message_service.stop_fx_effect(player_id, zone.get_connections(), False, "wisp_hands") + game_message_service.play_fx_effect(player_id, zone.get_connections(), 1573, "on-anim", 1.0, "wisp_hands") + + game_message_service.stop_fx_effect(player_id, zone.get_connections(), False, "wisp_hands_left") + game_message_service.play_fx_effect(player_id, zone.get_connections(), 1579, "on-anim", 1.0, "wisp_hands_left") + + time.sleep(.5) diff --git a/game_types.py b/game_types.py index f8c85ea..a09d6d6 100644 --- a/game_types.py +++ b/game_types.py @@ -45,6 +45,7 @@ class BaseObject(): class GameThread(threading.Thread): def stop(self): + self._tstate_lock = None self._stop() class String(Serializable): diff --git a/server.sqlite b/server.sqlite index b41f7f1..8ab0975 100644 Binary files a/server.sqlite and b/server.sqlite differ diff --git a/services.py b/services.py index 549bd27..eaf4e52 100644 --- a/services.py +++ b/services.py @@ -23,6 +23,9 @@ class GameService(game_types.BaseObject): self.get_parent().trigger_event("ServiceInitialized", args=(self,), debug=False) print("Initializied {} Service".format(self._name)) + def get_name(self): + return self._name + class WorldService(GameService): def __init__(self, parent): super().__init__(parent) @@ -40,7 +43,7 @@ class WorldService(GameService): return zone return None - def get_scenes(self): + def get_zones(self): return self._zones class AuthServerService(GameService): @@ -66,9 +69,9 @@ class AuthServerService(GameService): self.server = auth_server.AuthServer((self._address, self._port), max_connections=self._max_connections, incoming_password=b"3.25 ND1", auth_server_service=self) def initialize(self): - super().initialize() for handler in self.server.default_handlers: self.get_parent().register_event_handler(self.server.default_handlers[handler][0])(self.server.default_handlers[handler][1]) + super().initialize() def validate_login(self, username : str, password : str): server_db : database.GameDB = self._parent.get_service("Database").server_db @@ -112,6 +115,6 @@ class WorldServerService(GameService): self.server = world_server.WorldServer((self._address, self._port), max_connections=self._max_connections, incoming_password=b"3.25 ND1", world_server_service=self) def initialize(self): - super().initialize() for handler in self.server.default_handlers: self.get_parent().register_event_handler(self.server.default_handlers[handler][0])(self.server.default_handlers[handler][1]) + super().initialize() diff --git a/world_server.py b/world_server.py index 722fdef..aabe75d 100644 --- a/world_server.py +++ b/world_server.py @@ -2,10 +2,12 @@ import pyraknet.server import pyraknet.messages import time import game_enums +import components import os import game_types from pyraknet.bitstream import * import zlib +import typing from xml.etree import ElementTree @@ -24,16 +26,8 @@ class WorldServer(pyraknet.server.Server): "world_minifig_deletion:":["OnPacket_World_{}".format(game_enums.PacketHeaderEnum.CLIENT_DELETE_MINIFIGURE_REQUEST.value), self.handle_minifig_deletion], "world_join_world":["OnPacket_World_{}".format(game_enums.PacketHeaderEnum.CLINET_ENTER_WORLD.value), self.handle_join_world], "world_detailed_user_info":["OnPacket_World_{}".format(game_enums.PacketHeaderEnum.CLIENT_LOAD_COMPLETE.value), self.handle_detailed_user_info], - "world_game_message":["OnPacket_World_{}".format(game_enums.PacketHeaderEnum.CLIENT_GAME_MESSAGE.value), self.handle_game_msg]} - - def send_game_msg(self, object_id, msg_id, recipients : list, additional_parameters : list = None): - msg = WriteStream() - msg.write(c_longlong(object_id)) - msg.write(c_ushort(msg_id)) - if(additional_parameters is not None): - for param in additional_parameters: - msg.write(param) - self.send(msg, recipients) + "world_game_message":["OnPacket_World_{}".format(game_enums.PacketHeaderEnum.CLIENT_GAME_MESSAGE.value), self.handle_game_msg], + "world_position_updates":["OnPacket_World_{}".format(game_enums.PacketHeaderEnum.CLIENT_POSITION_UPDATES.value), self.handle_position_updates]} def handle_game_msg(self, data: bytes, address): stream = ReadStream(data) @@ -138,6 +132,7 @@ class WorldServer(pyraknet.server.Server): else: world.get_zone_by_id(session.zone_id).remove_player(player_id) zone.add_player(player_id) + player["zone"] = level_id session.zone_id = level_id self.send(packet, session.address) game.trigger_event("LoadWorld", args=[player_id, level_id]) @@ -222,10 +217,11 @@ class WorldServer(pyraknet.server.Server): "armor":player_data["armor"], "max_armor":player_data["max_armor"], "imagination":player_data["imagination"], "max_imagination":player_data["max_imagination"]} zone.create_object(zone, player_config) - game.wait_for_event("GM_888", '''args[0] == {}'''.format(player["player_id"])) + game.wait_for_event("GM_{}".format(game_enums.GameMessages.READY_FOR_UPDATES.value), '''args[0] == {}'''.format(player["player_id"]))#Wait for client to load player entirely (May not be necessary) - self.send_game_msg(player["player_id"], game_enums.GameMessages.SERVER_DONE_LOADING_OBJECTS.value, recipients=[address]) - self.send_game_msg(player["player_id"], game_enums.GameMessages.PLAYER_READY.value, recipients=[address]) + game_message_service = game.get_service("Game Message") + game_message_service.send_game_msg(player["player_id"], game_enums.GameMessages.SERVER_DONE_LOADING_OBJECTS.value, recipients=[address]) + game_message_service.send_game_msg(player["player_id"], game_enums.GameMessages.PLAYER_READY.value, recipients=[address]) def handle_join_world(self, data: bytes, address): stream = ReadStream(data) @@ -305,6 +301,27 @@ class WorldServer(pyraknet.server.Server): except: pass + + def handle_position_updates(self, data: bytes, address): + stream = ReadStream(data) + x_pos = stream.read(c_float) + y_pos = stream.read(c_float) + z_pos = stream.read(c_float) + x_rot = stream.read(c_float) + y_rot = stream.read(c_float) + z_rot = stream.read(c_float) + w_rot = stream.read(c_float) + + try: + session = game.get_service("Session").get_session_by_address(address) + zone = game.get_service("World").get_zone_by_id(session.zone_id) + player_object = zone.get_object_by_id(session.player_id) + transform = player_object.get_component(components.Transform) + transform.position = game_types.Vector3(x_pos, y_pos, z_pos) + transform.rotation = game_types.Vector4(x_rot, y_rot, z_rot, w_rot) + except Exception as e: + print(f"Error {e}") + def handle_minifig_deletion(self, data: bytes, address): stream = ReadStream(data) player_id = stream.read(c_longlong) diff --git a/zone.py b/zone.py index cd10b80..ceacc48 100644 --- a/zone.py +++ b/zone.py @@ -78,6 +78,11 @@ class Zone(game_types.BaseObject): self.destroy_object(self.get_object_by_id(player_id)) game.trigger_event("RemovedPlayerFromZone", args=(player_id, self)) + def get_players(self): + return self._players + + def get_connections(self): + return self._replica_manager.get_participants() class ZoneManager(pyraknet.replicamanager.ReplicaManager): def __init__(self, server): @@ -96,6 +101,9 @@ class ZoneManager(pyraknet.replicamanager.ReplicaManager): else: return None + def get_participants(self): + return self._participants + def serialize(self, obj: game_objects.ReplicaObject) -> None: out = WriteStream() out.write(c_ubyte(Message.ReplicaManagerSerialize)) @@ -124,7 +132,4 @@ class ZoneManager(pyraknet.replicamanager.ReplicaManager): out.write(c_ushort(self._network_ids[obj])) replica_service.write_to_stream(obj, out, game_enums.ReplicaTypes.CONSTRUCTION) - # file = open("39_2007-51995_2_[24]_[23-2d].bin", "wb") - # file.write(bytes(copy.deepcopy(out))) - self._server.send(out, recipients) \ No newline at end of file