async def sendPacket(self, packet: Type[AbstractResponsePacket], *args, timeout: int = NET_TIMEOUT, **kwargs): try: # Generate Packet rawData = await packet.serialize(*args, **kwargs) # Send Packet Logger.verbose( f"SERVER -> CLIENT | CLIENT: {self.handler.ip} | ID: {packet.ID} {packet.NAME} | SIZE: {packet.SIZE} | DATA: {rawData}", module="network") if self.handler.isConnected: self.handler.writer.write(bytes(rawData)) await self.handler.writer.drain() else: Logger.debug( f"Packet {packet.NAME} Skipped Due To Closed Connection!", module="network") except Exception as e: # Making Sure These Errors Always Gets Raised (Ignore onError) if packet.CRITICAL or type(e) in CRITICAL_RESPONSE_ERRORS: raise e # Pass Down Exception To Lower Layer else: # TODO: Remove Hacky Type Ignore return packet.onError(e) # type: ignore
async def spawnCurrentPlayers(self, playerSelf: Player): # Update Joining Players of The Currently In-Game Players # Loop Through All Players for player in self.players: # Checking if Player Exists if player is None: continue # Checking if player is not self if player is playerSelf: continue # Attempting to Send Packet try: await playerSelf.networkHandler.dispacher.sendPacket( Packets.Response.SpawnPlayer, player.playerId, player.name, player.posX, player.posY, player.posZ, player.posYaw, player.posPitch, ) except Exception as e: if e not in CRITICAL_RESPONSE_ERRORS: # Something Broke! Logger.error(f"An Error Occurred While Sending World Packet {Packets.Response.SpawnPlayer.NAME} To {player.networkHandler.ip} - {type(e).__name__}: {e}", module="world-packet-dispatcher") else: # Bad Timing with Connection Closure. Ignoring Logger.verbose(f"Ignoring Error While Sending World Packet {Packets.Response.SpawnPlayer.NAME} To {player.networkHandler.ip}", module="world-packet-dispatcher")
async def sendWorldData(self, world: World): # Send Level Initialize Packet Logger.debug(f"{self.ip} | Sending Level Initialize Packet", module="network") await self.dispacher.sendPacket(Packets.Response.LevelInitialize) # Preparing To Send Map Logger.debug(f"{self.ip} | Preparing To Send Map", module="network") worldGzip = world.gzipMap(includeSizeHeader=True) # Generate GZIP # World Data Needs To Be Sent In Chunks Of 1024 Characters chunks = [ worldGzip[i:i + 1024] for i in range(0, len(worldGzip), 1024) ] # Looping Through All Chunks And Sending Data Logger.debug(f"{self.ip} | Sending Chunk Data", module="network") for chunkCount, chunk in enumerate(chunks): # Sending Chunk Data Logger.verbose( f"{self.ip} | Sending Chunk Data {chunkCount + 1} of {len(chunks)}", module="network") await self.dispacher.sendPacket( Packets.Response.LevelDataChunk, chunk, percentComplete=int((100 / len(chunks)) * chunkCount)) # Send Level Finalize Packet Logger.debug(f"{self.ip} | Sending Level Finalize Packet", module="network") await self.dispacher.sendPacket(Packets.Response.LevelFinalize, world.sizeX, world.sizeY, world.sizeZ)
def register(self, name: str, description: str, author: str, version: str, dependencies: Optional[list], module: Type[AbstractModule]): Logger.info(f"Discovered Module {name}.", module="module-import") Logger.debug(f"Registering Module {name}", module="module-import") # Lowercase Name name = name.lower() # Checking If Module Is Already In Modules List if name in self._module_list.keys(): raise InitRegisterError( f"Module {name} Has Already Been Registered!") # Check If Module Is Blacklisted if name in self._module_blacklist: return # Skip # Format Empty Dependencies if dependencies is None: dependencies = [] # Checking If Core Is Required if self._ensure_core: if "core" not in [m.NAME for m in dependencies] and name != "core": dependencies.append(Dependency("core")) # Attach Values As Attribute module.NAME = name module.DESCRIPTION = description module.AUTHOR = author module.VERSION = version module.DEPENDENCIES = dependencies self._module_list[name] = module
def closeWorlds(self): Logger.debug("Starting Attempt to Close All Worlds", module="world-close") # Loop through all worlds and attempt to close for worldName in list(self.worlds.keys( )): # Setting as list so dict size can change mid execution try: Logger.info(f"Closing World {worldName}", module="world-close") # Getting world obj world = self.worlds[worldName] # Removing world from dict Logger.debug("Removed world from dict", module="world-close") del self.worlds[worldName] # Checking if World and Server is Persistant if self.persistant and (self.server.config.worldSaveLocation is not None) and world.persistant: # Closing worlds fileIO Logger.debug("Closing World FileIO", module="world-close") world.fileIO.close() except Exception as e: Logger.error( f"Error While Closing World {worldName} - {type(e).__name__}: {e}", module="world-close")
def getHighestBlock(self, blockX: int, blockZ: int, start: Optional[int] = None): # Returns the highest block # Set and Verify Scan Start Value if start is None: scanY = self.sizeY - 1 else: if start > self.sizeY: Logger.warn( f"Trying To Get Heighest Block From Location Greater Than Map Size (MapHeight: {self.sizeY}, Given: {start})" ) scanY = self.sizeY - 1 else: scanY = start # Scan Downwards To Get Heighest Block while self.getBlock(blockX, scanY, blockZ) is Blocks.Air: if scanY == 0: break # Scan Downwards scanY -= 1 return scanY
def __init__(self, server: Server, blacklist: List[str] = []): self.server = server self.worlds = dict() self.blacklist = blacklist self.persistant = True self.worldFormat = None # Get worldFormat Using Given World Format Key # Loop Through All World Formats for worldFormat in WorldFormats._format_list.values(): # Check If key Matches With Config Key List if self.server.config.defaultSaveFormat.lower( ) in worldFormat.KEYS: # Set World Format self.worldFormat = worldFormat # Check If World Format Was Set if self.worldFormat is None: raise FatalError( f"Unknown World Format Key {self.server.config.defaultSaveFormat} Given In Server Config!" ) Logger.info(f"Using World Format {self.worldFormat.NAME}", module="init-world") # If World Location Was Not Given, Disable Persistance # (Don't Save / Load) if self.server.config.worldSaveLocation is None: Logger.warn( "World Save Location Was Not Defined. Creating Non-Persistant World!!!", module="init-world") self.persistant = False
def deallocateId(self, playerId: int): # Check If Id Is Already Deallocated if self.players[playerId] is None: Logger.error(f"Trying To Deallocate Non Allocated Id {playerId}", module="id-allocator", printTb=False) self.players[playerId] = None Logger.debug(f"Deallocated Id {playerId}", module="id-allocator")
async def setBlock(self, blockX: int, blockY: int, blockZ: int, blockId: int, player: Optional[Player] = None, sendPacket: bool = True): # Handles Block Updates In Server + Checks If Block Placement Is Allowed Logger.debug( f"Setting World Block {blockX}, {blockY}, {blockZ} to {blockId}", module="world") # Check If Block Is Out Of Range if blockX >= self.sizeX or blockY >= self.sizeY or blockZ >= self.sizeZ: raise BlockError( f"Block Placement Is Out Of Range ({blockX}, {blockY}, {blockZ})" ) # Setting Block in MapArray self.mapArray[blockX + self.sizeX * (blockZ + self.sizeZ * blockY)] = blockId if sendPacket: # Sending Block Update Update Packet To All Players await self.playerManager.sendWorldPacket( Packets.Response.SetBlock, blockX, blockY, blockZ, blockId, # not sending to self as that may cause some de-sync issues ignoreList=[player] if player is not None else [])
async def handlePlayerMovement(self, posX: int, posY: int, posZ: int, posYaw: int, posPitch: int): # Format, Process, and Handle incoming player movement requests. Logger.verbose(f"Handling Player Movement From Player {self.name}", module="player") # Checking If Player Is Joined To A World if self.worldPlayerManager is None: Logger.error(f"Player {self.name} Trying To handleBlockUpdate When No World Is Joined", module="player") return None # Skip Rest # Updating Current Player Position self.posX = posX self.posY = posY self.posZ = posZ self.posYaw = posYaw self.posPitch = posPitch # Sending Player Position Update Packet To All Players await self.worldPlayerManager.sendWorldPacket( Packets.Response.PlayerPositionUpdate, self.playerId, posX, posY, posZ, posYaw, posPitch, ignoreList=[self] # not sending to self as that may cause some de-sync issues )
def generateTable(self): try: table = PrettyTableLite() # Create Pretty List Class table.field_names = [ "Direction", "Packet", "Id", "Player Loop", "Module" ] # Loop Through All Request Modules And Add Value for _, packet in self.RequestManager._packet_list.items(): # Adding Row To Table table.add_row([ "Request", packet.NAME, packet.ID, packet.PLAYERLOOP, packet.MODULE.NAME ]) # Loop Through All Response Modules And Add Value for _, packet in self.ResponseManager._packet_list.items(): table.add_row([ "Response", packet.NAME, packet.ID, "N/A", packet.MODULE.NAME ]) return table except FatalError as e: # Pass Down Fatal Error To Base Server raise e except Exception as e: Logger.error( f"Error While Printing Table - {type(e).__name__}: {e}", module="table")
async def handleBlockUpdate(self, blockX: int, blockY: int, blockZ: int, blockType: AbstractBlock): # Format, Process, and Handle incoming block update requests. Logger.debug(f"Handling Block Placement From Player {self.name}", module="player") # Checking If Player Is Joined To A World if self.worldPlayerManager is None: Logger.error(f"Player {self.name} Trying To handleBlockUpdate When No World Is Joined", module="player") return None # Skip Rest # Trying To Update Block On Player World try: await blockType.placeBlock(self, blockX, blockY, blockZ) except ClientError as e: # Setting Player-Attempted Block Back To Original originalBlock = self.worldPlayerManager.world.getBlock(blockX, blockY, blockZ) await self.networkHandler.dispacher.sendPacket( Packets.Response.SetBlock, blockX, blockY, blockZ, originalBlock.ID ) # Send Error Message To await self.sendMessage(f"&c{e}&f")
def createWorldFile(self, savePath, worldName, worldFormat: AbstractWorldFormat = None): Logger.debug(f"Attempting to create world file with name {worldName}", module="world-gen") # Checking if World is Persistant if self.server.config.worldSaveLocation is None or not self.persistant: raise WorldSaveError( "Trying To Create World File When Server Is Not Persistant") # Checking if World Format was Passed In (Setting as default if not) if worldFormat is None: worldFormat = self.worldFormat # type: ignore # Generating File Path worldPath = os.path.join( SERVERPATH, savePath, worldName + "." + worldFormat.EXTENTIONS[ 0] # Gets the first value in the valid extentions list ) Logger.debug(f"File world path is {worldPath}", module="world-gen") # Check if file already exists if os.path.isfile(worldPath): raise WorldSaveError( f"Trying To Create World File {worldPath} That Already Exists") return open(worldPath, "wb+")
async def joinPlayer(self, player: Player): # Trying To Allocate Id # Fails If All Slots Are Taken try: playerId = self.allocateId() except WorldError: raise ClientError(f"World {self.world.name} Is Full") # Adding Player To Players List Using Id Logger.debug(f"Player {player.networkHandler.ip} Username {player.name} Id {playerId} Is Joining World {self.world.name}", module="world-player") player.playerId = playerId self.players[playerId] = player # Solve rare edge case where Spawn coords may not be set! if ( self.world.spawnX is not None and self.world.spawnY is not None and self.world.spawnZ is not None and self.world.spawnYaw is not None and self.world.spawnPitch is not None ): # Set Player Location # TODO saving player location await player.setLocation( self.world.spawnX, self.world.spawnY, self.world.spawnZ, self.world.spawnYaw, self.world.spawnPitch, notifyPlayers=False ) else: raise ServerError("Attempted To Spawn Player to a Location That Is Not Set!") # Send Player Join Packet To All Players (Except Joining User) await self.sendWorldPacket( Packets.Response.SpawnPlayer, player.playerId, player.name, player.posX, player.posY, player.posZ, player.posYaw, player.posPitch, ignoreList=[player] # Don't send packet to self! ) # Update User On Currently Connected Players await self.spawnCurrentPlayers(player) Logger.debug(f"Finished Handling Player Join For {player.name} Id {player.playerId} Joined World {self.world.name}", module="world-player") # Sending Join Chat Message await self.sendWorldMessage(f"&e{player.name} Joined The World &9(ID {player.playerId})&f") # Sending Warning If World Is Non-Persistant if not self.world.persistant: await player.sendMessage("&cWARNING: This world is Non-Persistant!&f") await player.sendMessage("&cAny changes WILL NOT be saved!!&f")
def saveMap(self): if self.persistant: Logger.info(f"Attempting To Save World {self.name}", module="world-save") self.worldManager.worldFormat.saveWorld(self, self.fileIO, self.worldManager) else: Logger.warn(f"World {self.name} Is Not Persistant! Not Saving.", module="world-save")
def allocateId(self): # Loop Through All Ids, Return Id That Is Not Free Logger.debug("Trying To Allocate Id", module="id-allocator") for idIndex, playerObj in enumerate(self.players): if playerObj is None: # Return Free ID return idIndex raise WorldError("Id Allocator Failed To Allocate Open Id")
async def sendMessage(self, message: Union[str, list]): Logger.debug(f"Sending Player {self.name} Message {message}", module="player") # If Message Is A List, Recursively Send All Messages Within if type(message) is list: Logger.debug("Sending List Of Messages To Player!") for msg in message: await self.sendMessage(msg) return None # Break Out of Function await self.networkHandler.dispacher.sendPacket(Packets.Response.SendMessage, str(message))
async def createPlayer(self, network: NetworkHandler, username: str, verificationKey: str): Logger.debug(f"Creating Player For Ip {network.ip}", module="player-manager") # Creating Player Class player = Player(self, network, username, verificationKey) # Checking if server is full if len(self.players) >= self.maxSize: raise ClientError("Server Is Full!") # Adding Player Class self.players.append(player) return player
def _ensureFileStructure(self, folders: Union[str, List[str]]): # Check Type, If Str Put In List if type(folders) is str: folders = [folders] Logger.debug(f"Ensuring Folders {folders}", module="init") # Ensure All Folders for folder in folders: folder = os.path.join(SERVERPATH, folder) if not os.path.exists(folder): Logger.debug(f"Creating Folder Structure {folder}", module="init") os.makedirs(folder)
async def serialize(self, message: str, playerId: int = 0): # <Player Message Packet> # (Byte) Packet ID # (Byte) Player ID (Seems to be unused?) # (64String) Message if len(message) > 64: Logger.warn( f"Trying to send message '{message}' over the 64 character limit!", module="packet-serializer") msg = struct.pack(self.FORMAT, self.ID, int(playerId), bytearray(packageString(message))) return msg
def getBlock(self, blockX: int, blockY: int, blockZ: int): # Gets Block Obj Of Requested Block Logger.verbose(f"Getting World Block {blockX}, {blockY}, {blockZ}", module="world") # Check If Block Is Out Of Range if blockX >= self.sizeX or blockY >= self.sizeY or blockZ >= self.sizeZ: raise BlockError( f"Requested Block Is Out Of Range ({blockX}, {blockY}, {blockZ})" ) return BlockManager.getBlockById( self.mapArray[blockX + self.sizeX * (blockZ + self.sizeZ * blockY)])
def _initSubmodule(self, submodule: Any, module: AbstractModule): Logger.debug( f"Initializing {submodule.MANAGER.NAME} {submodule.NAME} From Module {module.NAME}", module=f"{module.NAME}-submodule-init") # Create Object obj = submodule() # Initialize and Transfer Object Variables obj.NAME = submodule.NAME obj.DESCRIPTION = submodule.DESCRIPTION obj.VERSION = submodule.VERSION obj.OVERRIDE = submodule.OVERRIDE obj.MANAGER = submodule.MANAGER obj.MODULE = module return obj
def _convertArgs(name: str, param: inspect.Parameter, arg: str): Logger.verbose(f"Transforming Argument Data For Argument {name}", module="command") # If There Is No Type To Convert, Ignore if param.annotation == inspect._empty: # type: ignore return arg # Try to parse, if error, cancel try: return param.annotation(arg) except ValueError: raise CommandError( f"Argument '{name}' Expected {param.annotation.__name__} But Got '{type(arg).__name__}'" )
def internal(cls): Logger.verbose(f"Registered {submodule.NAME} {name} version {version}", module="submodule-import") # Set Class Variables cls.NAME = name cls.DESCRIPTION = description cls.VERSION = version cls.OVERRIDE = override cls.MANAGER = submodule # Set Obsidian Submodule to True -> Notifies Init that This Class IS a Submodule cls.obsidian_submodule = True # Return cls Obj for Decorator return cls
def _ensureNoCycles(current: Type[AbstractModule], previous: List[str]): Logger.verbose( f"Travelling Down Dependency Tree. CUR: {current} PREV: {previous}", module="cycle-check") # If Current Name Appears In Any Previous Dependency, There Is An Infinite Cycle if current.NAME in previous: raise DependencyError( f"Circular dependency Detected: {' -> '.join([*previous, current.NAME])}" ) Logger.verbose( f"Current Modules Has Dependencies {current.DEPENDENCIES}", module="cycle-check") # Run DFS through All Dependencies for dependency in current.DEPENDENCIES: _ensureNoCycles(dependency.MODULE, [*previous, current.NAME])
def _load(self, fileIO: io.TextIOWrapper): Logger.debug(f"Loading Config With FileIO {fileIO}", "config-load") # Set Internal Attributes If It Exists configData = json.load(fileIO) Logger.verbose(f"Config data Is {configData}", "config-load") for configKey, configValue in configData.items(): Logger.verbose(f"Checking Config Attribute {configKey}", "config-load") # Checking If Key Exists In Config AND If Its Not Part Of The Overrides if hasattr(self, configKey) and configKey not in self.configOverrides: Logger.verbose( f"Setting Config Attribute {configKey} to {configValue}", "config-load") setattr(self, configKey, configValue) else: Logger.warn(f"Ignoring Unknown Config Attribute {configKey}", "config-load")
async def removePlayer(self, player: Player): Logger.debug(f"Removing Player {player.name} From World {self.world.name}", module="world-player") # Delete User From Player List + Deallocate ID if player.playerId is not None: self.deallocateId(player.playerId) else: raise ServerError(f"Trying to Remove Player {player.name} With No Player Id") # Send Player Disconnect Packet To All Players (Except Joining User) await self.sendWorldPacket( Packets.Response.DespawnPlayer, player.playerId, ignoreList=[player] # Don't send packet to self! ) Logger.debug(f"Removed Player {player.networkHandler.ip} Username {player.name} Id {player.playerId} Joined World {self.world.name}", module="world-player") # Sending Leave Chat Message await self.sendWorldMessage(f"&e{player.name} Left The World &9(ID {player.playerId})&f")
def generateTable(self): try: table = PrettyTableLite() # Create Pretty List Class table.field_names = ["World Format", "Version", "Module"] # Loop Through All World Formats And Add Value for _, worldFormat in self._format_list.items(): # Add Row To Table table.add_row([ worldFormat.NAME, worldFormat.VERSION, worldFormat.MODULE.NAME ]) return table except FatalError as e: # Pass Down Fatal Error To Base Server raise e except Exception as e: Logger.error( f"Error While Printing Table - {type(e).__name__}: {e}", module="table")
def generateTable(self): try: table = PrettyTableLite() # Create Pretty List Class table.field_names = ["Command", "Activators", "Version", "Module"] # Loop Through All Commands And Add Value for _, command in self._command_list.items(): # Add Row To Table table.add_row([ command.NAME, command.ACTIVATORS, command.VERSION, command.MODULE.NAME ]) return table except FatalError as e: # Pass Down Fatal Error To Base Server raise e except Exception as e: Logger.error( f"Error While Printing Table - {type(e).__name__}: {e}", module="table")
def generateMap(self, sizeX, sizeY, sizeZ, generator: AbstractMapGenerator, *args, **kwargs): Logger.debug( f"Generating World With Size {sizeX}, {sizeY}, {sizeX} With Generator {generator.NAME}", module="init-world") # Call Generate Map Function From Generator generatedMap = generator.generateMap(sizeX, sizeY, sizeZ, *args, **kwargs) # Verify Map Data Size expectedSize = sizeX * sizeY * sizeZ if len(generatedMap) != expectedSize: raise MapGenerationError( f"Expected Map Size {expectedSize} While Generating World. Got {len(generatedMap)}" ) Logger.debug(f"Generated Map With Final Size Of {len(generatedMap)}", module="init-world") # Return Generated Map Bytesarray return generatedMap