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
Example #5
0
    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")
Example #6
0
    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
Example #7
0
    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")
Example #9
0
    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")
Example #13
0
    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")
Example #15
0
 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
Example #19
0
 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)
Example #20
0
 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
Example #21
0
    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])
Example #26
0
 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")
Example #28
0
    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")
Example #30
0
    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