Commits

Andrew Godwin  committed 18ef081

Clearout for starting 2.0

  • Participants
  • Parent commits 5f19874
  • Branches 2.0

Comments (0)

Files changed (57)

File archive_client.py

-
-import sys
-import datetime
-import os
-import struct
-from ConfigParser import SafeConfigParser as ConfigParser
-import urllib
-import urllib2
-import cookielib
-import re
-
-from twisted.internet import reactor, protocol
-
-from myne.protocol import MyneServerProtocol, TYPE_FORMATS
-from myne.constants import *
-
-class RipClient(MyneServerProtocol):
-    """Once connected, send a message, then print the result."""
-    
-    def connectionMade(self):
-        print "Identifying to server..."
-        self.buffer = ""
-        self.name = None
-        self.gzipped = ""
-        self.sendPacked(TYPE_INITIAL, 6, self.factory.username, self.factory.mppass, 0)
-    
-    def connectionLost(self, reason):
-        pass
-    
-    def dataReceived(self, data):
-        # First, add the data we got onto our internal buffer
-        self.buffer += data
-        # While there's still data there...
-        while self.buffer:
-            # Examine the first byte, to see what the command is
-            type = ord(self.buffer[0])
-            try:
-                format = TYPE_FORMATS[type]
-            except KeyError:
-                self.transport.loseConnection()
-            # See if we have all its data
-            if len(self.buffer) - 1 < len(format):
-                # Nope, wait a bit
-                break
-            # OK, decode the data
-            parts = list(format.decode(self.buffer[1:]))
-            self.buffer = self.buffer[len(format)+1:]
-            if type == TYPE_INITIAL:
-                protocol, name, desc, naff = parts
-                print "Server identifies as '%s'" % name
-                self.name = name.replace("/", "-")
-            if type == TYPE_PRECHUNK:
-                print "Receiving level data..."
-            elif type == TYPE_CHUNK:
-                # Grab the chunked data
-                length, chunk, progress = parts
-                print "...%i%%" % progress
-                self.gzipped += chunk[:length]
-            elif type == TYPE_LEVELSIZE:
-                # Store level size
-                self.sx, self.sy, self.sz = parts
-                print "Done. Got %i bytes of level." % len(self.gzipped)
-                print "Level size (%s, %s, %s)" % (self.sx, self.sy, self.sz)
-            elif type == TYPE_SPAWNPOINT and self.name:
-                naff, nick, x, y, z, h, nafftoo = parts
-                
-                basename = os.path.join(self.name, datetime.datetime.utcnow().strftime("%Y-%m-%d_%H:%M"))
-                try:
-                    os.makedirs(basename)
-                except OSError:
-                    pass
-                
-                # Write out the blocks data
-                fh = open(os.path.join(basename, "blocks.gz"), "w")
-                fh.write(self.gzipped)
-                fh.close()
-                
-                # Write out meta info
-                config = ConfigParser()
-                x >>= 5
-                y >>= 5
-                z >>= 5
-                h = int(h * (360/255.0))
-                config.add_section("size")
-                config.add_section("spawn")
-                config.set("size", "x", str(self.sx))
-                config.set("size", "y", str(self.sy))
-                config.set("size", "z", str(self.sz))
-                config.set("spawn", "x", str(x))
-                config.set("spawn", "y", str(y))
-                config.set("spawn", "z", str(z))
-                config.set("spawn", "h", str(h))
-                fp = open(os.path.join(basename, "world.meta"), "w")
-                config.write(fp)
-                fp.close()
-                
-                print "Spawn point is at (%s, %s, %s, %s)" % (x, y, z, h)
-                print "Saved as %s." % fh.name
-                self.name = None
-                self.transport.loseConnection()
-            elif type == TYPE_ERROR:
-                print "Error! %s" % parts[0]
-                self.transport.loseConnection()
-
-
-class RipFactory(protocol.ClientFactory):
-    protocol = RipClient
-    
-    def __init__(self, username, mppass):
-        self.username = username
-        self.mppass = mppass
-
-    def clientConnectionFailed(self, connector, reason):
-        print "Connection failed."
-        reactor.stop()
-    
-    def clientConnectionLost(self, connector, reason):
-        print "Connection terminated."
-        reactor.stop()
-
-
-# this connects the protocol to a server runing on port 8000
-def rip(key, username, password):
-    login_url = 'http://minecraft.net/login.jsp'
-    play_url = 'http://minecraft.net/play.jsp?server=%s'
-    
-    cj = cookielib.CookieJar()
-    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
-    login_data = urllib.urlencode({'username': username, 'password': password})
-    print "Logging in..."
-    opener.open(login_url, login_data)
-    print "Fetching server info..."
-    html = opener.open(play_url % key).read()
-    ip = re.search(r'param name\="server" value="([0-9.]+)"', html).groups()[0]
-    port = int(re.search(r'param name\="port" value="([0-9]+)"', html).groups()[0])
-    mppass = re.search(r'param name\="mppass" value="([0-9a-zA-Z]+)"', html).groups()[0]
-    print "Got details. Connecting..."
-    f = RipFactory(username, mppass)
-    reactor.connectTCP(ip, port, f)
-    reactor.run()
-
-def main():
-    config = ConfigParser()
-    config.read(os.path.join(os.path.dirname(__file__), "client.conf"))
-    rip(sys.argv[1], config.get("client", "username"), config.get("client", "password"))
-
-# this only runs if the module was *not* imported
-if __name__ == '__main__':
-    main()

File archives/README

Empty file removed.

File chat_client.py

-
-import sys
-import os
-import fcntl
-import datetime
-import struct
-from ConfigParser import SafeConfigParser as ConfigParser
-import urllib
-import urllib2
-import cookielib
-import re
-
-from twisted.internet import reactor, protocol
-
-from protocol import MyneServerProtocol, TYPE_FORMATS
-from constants import *
-
-# Set stdin to nonblocking
-fcntl.fcntl(0, fcntl.F_SETFL, os.O_NONBLOCK)
-
-COLOR_CHARS = ["&%s" % c for c in "0123456789abcdef"]
-
-class ChatClient(MyneServerProtocol):
-    """Once connected, allows chatting, but no movement."""
-    
-    def connectionMade(self):
-        print "Identifying to server..."
-        self.buffer = ""
-        self.chat_buffer = ""
-        self.name = None
-        self.gzipped = ""
-        self.sendPacked(TYPE_INITIAL, 6, self.factory.username, self.factory.mppass, 0)
-        reactor.callLater(0.1, self.checkStdin)
-    
-    def checkStdin(self):
-        "Reads stdin, possibly sending a message."
-        try:
-            data = sys.stdin.read()
-        except IOError:
-            data = None
-        if data:
-            self.chat_buffer += data
-            lines = self.chat_buffer.split("\n")
-            print
-            for line in lines[:-1]:
-                self.sendPacked(TYPE_MESSAGE, 255, line[:64])
-            self.chat_buffer = lines[-1]
-        reactor.callLater(0.1, self.checkStdin)
-    
-    def connectionLost(self, reason):
-        pass
-    
-    def dataReceived(self, data):
-        # First, add the data we got onto our internal buffer
-        self.buffer += data
-        # While there's still data there...
-        while self.buffer:
-            # Examine the first byte, to see what the command is
-            type = ord(self.buffer[0])
-            try:
-                format = TYPE_FORMATS[type]
-            except KeyError:
-                self.transport.loseConnection()
-            # See if we have all its data
-            if len(self.buffer) - 1 < len(format):
-                # Nope, wait a bit
-                break
-            # OK, decode the data
-            try:
-                parts = list(format.decode(self.buffer[1:]))
-            except Exception, e:
-                print "%s! string: %r" % (e, self.buffer)
-                self.buffer = ""
-                continue
-            self.buffer = self.buffer[len(format)+1:]
-            if type == TYPE_MESSAGE:
-                if parts[0] == 255:
-                    print "> %s" % parts[1]
-                else:
-                    msg = parts[1]
-                    for colour in COLOR_CHARS:
-                        msg = msg.replace(colour, "")
-                    print msg
-            elif type == TYPE_ERROR:
-                print "Error! %s" % parts[0]
-                self.transport.loseConnection()
-
-
-class ChatFactory(protocol.ClientFactory):
-    protocol = ChatClient
-    
-    def __init__(self, username, mppass):
-        self.username = username
-        self.mppass = mppass
-
-    def clientConnectionFailed(self, connector, reason):
-        print "Connection failed."
-        reactor.stop()
-    
-    def clientConnectionLost(self, connector, reason):
-        print "Connection terminated."
-        reactor.stop()
-
-
-# this connects the protocol to a server runing on port 8000
-def rip(key, username, password):
-    login_url = 'http://minecraft.net/login.jsp'
-    play_url = 'http://minecraft.net/play.jsp?server=%s'
-    
-    cj = cookielib.CookieJar()
-    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
-    login_data = urllib.urlencode({'username': username, 'password': password})
-    print "Logging in..."
-    opener.open(login_url, login_data)
-    print "Fetching server info..."
-    html = opener.open(play_url % key).read()
-    ip = re.search(r'param name\="server" value="([0-9.]+)"', html).groups()[0]
-    port = int(re.search(r'param name\="port" value="([0-9]+)"', html).groups()[0])
-    mppass = re.search(r'param name\="mppass" value="([0-9a-zA-Z]+)"', html).groups()[0]
-    print "Got details. Connecting..."
-    f = ChatFactory(username, mppass)
-    reactor.connectTCP(ip, port, f)
-    reactor.run()
-
-def main():
-    config = ConfigParser()
-    config.read(os.path.join(os.path.dirname(__file__), "client.conf"))
-    rip(sys.argv[1], config.get("client", "username"), config.get("client", "password"))
-
-# this only runs if the module was *not* imported
-if __name__ == '__main__':
-    main()

File client.conf.example

-[client]
-username = foo
-password = bar

File myne/__init__.py

Empty file removed.

File myne/blockstore.py

-
-import os
-import gzip
-import struct
-import mmap
-from threading import Thread
-from Queue import Queue
-import logging
-
-from array import array
-
-from physics import Physics
-from constants import *
-
-
-class BlockStore(Thread):
-    """
-    A class which deals with storing the block maps, flushing them, etc.
-    """
-    
-    def __init__(self, blocks_path,  sx, sy, sz):
-        Thread.__init__(self)
-        self.x, self.y, self.z = sx, sy, sz
-        self.blocks_path = blocks_path
-        self.in_queue = Queue()
-        self.out_queue = Queue()
-    
-    def run(self):
-        
-        # Initialise variables
-        self.physics = False
-        self.physics_engine = Physics(self)
-        self.raw_blocks = None
-        self.running = True
-        self.unflooding = False
-        self.finite_water = False
-        self.queued_blocks = {} # Blocks which need to be flushed into the file.
-        
-        # Start physics engine
-        self.physics_engine.start()
-        
-        # Main eval loop
-        while self.running:
-            try:
-                # Pop something off the queue
-                task = self.in_queue.get()
-                # If we've been asked to flush, do so, and say we did.
-                if task[0] is TASK_FLUSH:
-                    self.flush()
-                    self.out_queue.put([TASK_FLUSH])
-                # New block?
-                elif task[0] is TASK_BLOCKSET:
-                    try:
-                        self[task[1]] = task[2]
-                    except AssertionError:
-                        logging.log("Tried to set a block at %s in %s!" % (task[1], self.blocks_path), logging.WARN)
-                # Asking for a block?
-                elif task[0] is TASK_BLOCKGET:
-                    self.out_queue.put([TASK_BLOCKGET, task[1], self[task[1]]])
-                # Perhaps physics was enabled?
-                elif task[0] is TASK_PHYSICSOFF:
-                    logging.log(logging.DEBUG, "Disabling physics on '%s'..." % self.blocks_path)
-                    self.disable_physics()
-                # Or disabled?
-                elif task[0] is TASK_PHYSICSON:
-                    logging.log(logging.DEBUG, "Enabling physics on '%s'..." % self.blocks_path)
-                    self.enable_physics()
-                # I can haz finite water tiem?
-                elif task[0] is TASK_FWATERON:
-                    logging.log(logging.DEBUG, "Enabling finite water on '%s'..." % self.blocks_path)
-                    self.finite_water = True
-                # Noes, no more finite water.
-                elif task[0] is TASK_FWATEROFF:
-                    logging.log(logging.DEBUG, "Disabling finite water on '%s'..." % self.blocks_path)
-                    self.finite_water = False
-                # Do they need to do a Moses?
-                elif task[0] is TASK_UNFLOOD:
-                    logging.log(logging.DEBUG, "Unflood started on '%s'..." % self.blocks_path)
-                    self.unflooding = True
-                # Perhaps that's it, and we need to stop?
-                elif task[0] is TASK_STOP:
-                    logging.log(logging.DEBUG, "Stopping block store '%s'..." % self.blocks_path)
-                    self.physics_engine.stop()
-                    self.flush()
-                    logging.log(logging.DEBUG, "Stopped block store '%s'." % self.blocks_path)
-                    return
-                # ???
-                else:
-                    raise ValueError("Unknown BlockStore task: %s" % task)
-            except (KeyboardInterrupt, IOError):
-                pass
-    
-    def enable_physics(self):
-        "Turns on physics"
-        self.flush()
-        self.create_raw_blocks()
-        self.physics = True
-    
-    def disable_physics(self):
-        "Disables physics, and clears the in-memory store."
-        self.physics = False
-        self.raw_blocks = None
-    
-    def create_raw_blocks(self):
-        "Reads in the gzipped data into a raw array"
-        # Open the blocks file
-        fh = gzip.GzipFile(self.blocks_path)
-        self.raw_blocks = array('c')
-        # Read off the size header
-        fh.read(4)
-        # Copy into the array in chunks
-        chunk = fh.read(2048)
-        while chunk:
-            self.raw_blocks.extend(chunk)
-            chunk = fh.read(2048)
-        fh.close()
-    
-    def get_offset(self, x, y, z):
-        "Turns block coordinates into a data offset"
-        assert 0 <= x < self.x
-        assert 0 <= y < self.y
-        assert 0 <= z < self.z
-        return y*(self.x*self.z) + z*(self.x) + x
-
-    def get_coords(self, offset):
-        "Turns a data offset into coordinates"
-        x = offset % self.x
-        z = (offset // self.x) % self.z
-        y = offset // (self.x * self.z)
-        return x, y, z
-
-    def world_message(self, message):
-        "Sends a message out to users about this World."
-        self.out_queue.put([TASK_WORLDMESSAGE, message])
-    
-    def admin_message(self, message):
-        "Sends a message out to admins about this World."
-        self.out_queue.put([TASK_ADMINMESSAGE, message])
-
-    def send_block(self, x, y, z):
-        "Tells the server to update the given block for clients."
-        self.out_queue.put([TASK_BLOCKSET, (x, y, z, self[x, y, z])])
-    
-    def __setitem__(self, (x, y, z), block):
-        "Set a block in this level to the given value."
-        assert isinstance(block, str) and len(block) == 1
-        # Save to queued blocks
-        offset = self.get_offset(x, y, z)
-        self.queued_blocks[offset] = block
-        # And directly to raw blocks, if we must
-        if self.raw_blocks:
-            self.raw_blocks[offset] = block
-        # Ask the physics engine if they'd like a look at that
-        self.physics_engine.handle_change(offset, block)
-    
-    def __getitem__(self, (x, y, z)):
-        "Return the value at position x, y, z - possibly not efficiently."
-        offset = self.get_offset(x, y, z)
-        try:
-            return self.raw_blocks[offset]
-        except (TypeError,):
-            try:
-                return self.queued_blocks[offset]
-            except (IndexError, KeyError):
-                # Expensive! Open the gzip and read the byte.
-                gz = gzip.GzipFile(self.blocks_path)
-                gz.seek(offset + 4)
-                block = gz.read(1)
-                gz.close()
-                return block
-    
-    def flush(self):
-        """
-        Flushes queued blocks into the .gz file.
-        Needed before sending gzipped block data to clients.
-        """
-        # Don't flush if there's nothing to do
-        if not self.queued_blocks:
-            return
-        logging.log(logging.DEBUG, "Flushing %s..." % self.blocks_path)
-        # Open the old and the new file
-        gz = gzip.GzipFile(self.blocks_path)
-        new_gz = gzip.GzipFile(self.blocks_path + ".new", 'wb', compresslevel=4)
-        # Copy over the size header
-        new_gz.write(gz.read(4))
-        # Order the blocks we're going to write
-        ordered_blocks = sorted(self.queued_blocks.items())
-        # Start writing out the blocks in chunks, replacing as we go.
-        chunk_size = 1024
-        chunk = list(gz.read(chunk_size))
-        pos = 0
-        blocks_pos = 0
-        chunk_end = len(chunk)
-        while chunk:
-            while blocks_pos < len(ordered_blocks) and ordered_blocks[blocks_pos][0] < chunk_end:
-                offset, value = ordered_blocks[blocks_pos]
-                chunk[offset - pos] = value
-                blocks_pos += 1
-            chunk_str = "".join(chunk)
-            new_gz.write(chunk_str)
-            pos += len(chunk)
-            chunk = list(gz.read(chunk_size))
-            chunk_end = pos + len(chunk)
-        # Safety first. If this isn't true, there's a bug.
-        assert blocks_pos == len(ordered_blocks)
-        # OK, close up shop.
-        gz.close()
-        new_gz.close()
-        self.queued_blocks = {}
-        # Copy the new level over the old.
-        os.rename(self.blocks_path + ".new", self.blocks_path)
-    
-    @classmethod
-    def create_new(cls, blocks_path, sx, sy, sz, levels):
-        """
-        Creates a new blocks file, where levels contains one character for each
-        level, signifying its block type.
-        """
-        assert len(levels) == sy
-        # Open the gzip
-        fh = gzip.GzipFile(blocks_path, mode="wb")
-        # Write a size header
-        fh.write(struct.pack("!i", sx*sy*sz))
-        # Write each level
-        for level in levels:
-            fh.write(chr(level)*(sx*sz))
-        fh.close()

File myne/constants.py

-
-
-FORMAT_LENGTHS = {
-    "b": 1,
-    "a": 1024,
-    "s": 64,
-    "h": 2,
-    "i": 4,
-}
-
-from format import Format
-
-TYPE_INITIAL = 0
-TYPE_KEEPALIVE = 1
-TYPE_PRECHUNK = 2
-TYPE_CHUNK = 3
-TYPE_LEVELSIZE = 4
-TYPE_BLOCKCHANGE = 5
-TYPE_BLOCKSET = 6
-TYPE_SPAWNPOINT = 7
-TYPE_PLAYERPOS = 8
-TYPE_NINE = 9
-TYPE_TEN = 10
-TYPE_PLAYERDIR = 11
-TYPE_PLAYERLEAVE = 12
-TYPE_MESSAGE = 13
-TYPE_ERROR = 14
-
-TYPE_FORMATS = {
-    TYPE_INITIAL: Format("bssb"),
-    TYPE_KEEPALIVE: Format(""),
-    TYPE_PRECHUNK: Format(""),
-    TYPE_CHUNK: Format("hab"),
-    TYPE_LEVELSIZE: Format("hhh"),
-    TYPE_BLOCKCHANGE: Format("hhhbb"),
-    TYPE_BLOCKSET: Format("hhhb"),
-    TYPE_SPAWNPOINT: Format("bshhhbb"),
-    TYPE_PLAYERPOS: Format("bhhhbb"),
-    TYPE_NINE: Format("bbbbbb"),
-    TYPE_TEN: Format("bbbb"),
-    TYPE_PLAYERDIR: Format("bbb"),
-    TYPE_PLAYERLEAVE: Format("b"),
-    TYPE_MESSAGE: Format("bs"),
-    TYPE_ERROR: Format("s"),
-}
-
-TASK_BLOCKSET = 1
-TASK_PLAYERPOS = 2
-TASK_MESSAGE = 3
-TASK_NEWPLAYER = 4
-TASK_PLAYERLEAVE = 5
-TASK_PLAYERDIR = 6
-TASK_WORLDCHANGE = 7
-TASK_ADMINMESSAGE = 8
-TASK_WORLDMESSAGE = 9
-TASK_ACTION = 10
-TASK_SERVERMESSAGE = 11
-TASK_PHYSICSON = 12
-TASK_PHYSICSOFF = 13
-TASK_FLUSH = 14
-TASK_BLOCKGET = 15
-TASK_STOP = 16
-TASK_PLAYERCONNECT = 17
-TASK_UNFLOOD = 18
-TASK_FWATERON = 19
-TASK_FWATEROFF = 20
-TASK_PLAYERRESPAWN = 21
-
-COLOUR_BLACK = "&0"
-COLOUR_DARKBLUE = "&1"
-COLOUR_DARKGREEN = "&2"
-COLOUR_DARKCYAN = "&3"
-COLOUR_DARKRED = "&4"
-COLOUR_DARKPURPLE = "&5"
-COLOUR_DARKYELLOW = "&6"
-COLOUR_GREY = "&7"
-COLOUR_DARKGREY = "&8"
-COLOUR_BLUE = "&9"
-COLOUR_GREEN = "&a"
-COLOUR_CYAN = "&b"
-COLOUR_RED = "&c"
-COLOUR_PURPLE = "&d"
-COLOUR_YELLOW = "&e"
-COLOUR_WHITE = "&f"
-
-
-BLOCK_AIR = 0
-BLOCK_ROCK = 1
-BLOCK_GRASS = 2
-BLOCK_DIRT = 3
-BLOCK_STONE = 4
-BLOCK_WOOD = 5
-BLOCK_PLANT = 6
-BLOCK_GROUND_ROCK = 7
-BLOCK_WATER = 8
-BLOCK_STILL_WATER = 9
-BLOCK_LAVA = 10
-BLOCK_STILL_LAVA = 11
-BLOCK_SAND = 12
-BLOCK_GRAVEL = 13
-BLOCK_GOLD_ORE = 14
-BLOCK_COPPER_ORE = 15
-BLOCK_COAL_ORE = 16
-BLOCK_LOG = 17
-BLOCK_LEAVES = 18
-BLOCK_SPONGE = 19
-BLOCK_GLASS = 20
-BLOCK_RED_CLOTH = 21
-BLOCK_ORANGE_CLOTH = 22
-BLOCK_YELLOW_CLOTH = 23
-BLOCK_LIME_CLOTH = 24
-BLOCK_GREEN_CLOTH = 25
-BLOCK_TURQUOISE_CLOTH = 26
-BLOCK_CYAN_CLOTH = 27
-BLOCK_BLUE_CLOTH = 28
-BLOCK_INDIGO_CLOTH = 29
-BLOCK_VIOLET_CLOTH = 30
-BLOCK_PURPLE_CLOTH = 31
-BLOCK_MAGENTA_CLOTH = 32
-BLOCK_PINK_CLOTH = 33
-BLOCK_DARKGREY_CLOTH = 34
-BLOCK_GREY_CLOTH = 35
-BLOCK_WHITE_CLOTH = 36
-BLOCK_YELLOW_FLOWER = 37
-BLOCK_RED_FLOWER = 38
-BLOCK_BROWN_MUSHROOM = 39
-BLOCK_RED_MUSHROOM = 40
-BLOCK_GOLD = 41
-
-class ServerFull(Exception):
-    pass

File myne/controller.py

-
-import logging
-import traceback
-import simplejson
-
-from twisted.protocols.basic import LineReceiver
-from twisted.internet.protocol import Factory
-
-class ControllerProtocol(LineReceiver):
-    
-    """
-    Protocol for dealing with controller requests.
-    """
-    
-    def connectionMade(self):
-        peer = self.transport.getPeer()
-        logging.log(logging.INFO, "Control connection made from %s:%s" % (peer.host, peer.port))
-        self.factory, self.controller_factory = self.factory.main_factory, self.factory
-    
-    def connectionLost(self, reason):
-        peer = self.transport.getPeer()
-        logging.log(logging.INFO, "Control connection lost from %s:%s" % (peer.host, peer.port))
-    
-    def sendJson(self, data):
-        self.sendLine(simplejson.dumps(data))
-    
-    def lineReceived(self, line):
-        data = simplejson.loads(line)
-        peer = self.transport.getPeer()
-        if data['password'] != self.factory.control_password:
-            self.sendJson({"error": "invalid password"})
-            logging.log(logging.INFO, "Control: Invalid password %s (%s:%s)" % (data, peer.host, peer.port))
-        else:
-            command = data['command'].lower()
-            try:
-                func = getattr(self, "command%s" % command.title())
-            except AttributeError:
-                self.sendLine("ERROR Unknown command '%s'" % command)
-            else:
-                logging.log(logging.INFO, "Control: %s %s (%s:%s)" % (command.upper(), data, peer.host, peer.port))
-                try:
-                    func(data)
-                except Exception, e:
-                    self.sendLine("ERROR %s" % e)
-                    traceback.print_exc()
-    
-    def commandUsers(self, data):
-        self.sendJson({"users": list(self.factory.usernames.keys())})
-        
-    def commandAdmins(self, data):
-        self.sendJson({"admins": list(self.factory.admins)})
-    
-    def commandWorlds(self, data):
-        self.sendJson({"worlds": list(self.factory.worlds.keys())})
-    
-    def commandUserworlds(self, data):
-        self.sendJson({"worlds": [
-            (world.id, [client.username for client in world.clients if client.username], {
-                "id": world.id,
-                "ops": list(world.ops),
-                "writers": list(world.writers),
-                "private": world.private,
-                "archive": world.is_archive,
-                "locked": not world.all_write,
-                "physics": world.physics,
-            })
-            for world in self.factory.worlds.values()
-        ]})
-    
-    def commandWorldinfo(self, data):
-        world = self.factory.worlds[data['world_id']]
-        self.sendJson({
-            "id": world.id,
-            "ops": list(world.ops),
-            "writers": list(world.writers),
-            "private": world.private,
-            "archive": world.is_archive,
-            "locked": not world.all_write,
-            "physics": world.physics,
-        })
-
-
-class ControllerFactory(Factory):
-    
-    protocol = ControllerProtocol
-    
-    def __init__(self, main_factory):
-        self.main_factory = main_factory

File myne/datformat.py

-
-
-class DatFormat(object):
-    
-    """
-    Handler for reading server_level.dat type files.
-    """
-    
-    pass

File myne/decorators.py

-"""
-Decorators for protocol (command) methods.
-"""
-
-def admin_only(func):
-    "Decorator for admin-only command methods."
-    func.admin_only = True
-    return func
-
-def op_only(func):
-    "Decorator for op-only command methods."
-    func.op_only = True
-    return func
-
-def writer_only(func):
-    "Decorator for writer-only command methods."
-    func.writer_only = True
-    return func
-
-def username_command(func):
-    "Decorator for commands that accept a single username parameter, and need a Client"
-    def inner(self, parts):
-        if len(parts) == 1:
-            self.client.sendServerMessage("Please specify a username.")
-        else:
-            username = parts[1].lower()
-            if username not in self.client.factory.usernames:
-                self.client.sendServerMessage("No such user '%s'" % username)
-            else:
-                if len(parts) > 2:
-                    func(self, self.client.factory.usernames[username], parts[2:])
-                else:
-                    func(self, self.client.factory.usernames[username])
-    inner.__doc__ = func.__doc__
-    return inner
-
-def only_string_command(string_name):
-    def only_inner(func):
-        "Decorator for commands that accept a single username/plugin/etc parameter, and don't need it checked"
-        def inner(self, parts):
-            if len(parts) == 1:
-                self.client.sendServerMessage("Please specify a %s." % string_name)
-            else:
-                username = parts[1].lower()
-                if len(parts) > 2:
-                    func(self, username, parts[2:])
-                else:
-                    func(self, username)
-        inner.__doc__ = func.__doc__
-        return inner
-    return only_inner
-
-only_username_command = only_string_command("username")
-
-def username_world_command(func):
-    "Decorator for commands that accept a single username parameter and possibly a world name."
-    def inner(self, parts):
-        if len(parts) == 1:
-            self.client.sendServerMessage("Please specify a username.")
-        else:
-            username = parts[1].lower()
-            if len(parts) == 3:
-                try:
-                    world = self.client.factory.worlds[parts[2].lower()]
-                except KeyError:
-                    self.client.sendServerMessage("Unknown world '%s'." % parts[2].lower())
-                    return
-            else:
-                world = self.client.world
-            func(self, username, world)
-    inner.__doc__ = func.__doc__
-    return inner
-
-def on_off_command(func):
-    "Decorator for commands that accept a single on/off parameter"
-    def inner(self, parts):
-        if len(parts) == 1:
-            self.client.sendServerMessage("Please specify 'on' or 'off'.")
-        else:
-            if parts[1].lower() not in ["on", "off"]:
-                self.client.sendServerMessage("Use 'on' or 'off', not '%s'" % parts[1])
-            else:
-                func(self, parts[1].lower())
-    inner.__doc__ = func.__doc__
-    return inner

File myne/deferred.py

-
-class Deferred(object):
-    
-    """
-    Simple implementation of the Deferred pattern - a way to do
-    asynchronous operations in a simple single-threaded manner.
-    """
-    
-    def __init__(self):
-        self.callbacks = []
-        self.errbacks = []
-        self.stepbacks = []
-        self.called_back = None
-        self.erred_back = None
-        self.stepped_back = None
-    
-    def addCallback(self, func, *args, **kwargs):
-        "Adds a callback for success."
-        if self.called_back is None:
-            self.callbacks.append((func, args, kwargs))
-        else:
-            self.merge_call(func, args, self.called_back[0], kwargs, self.called_back[1])
-    
-    def addErrback(self, func, *args, **kwargs):
-        "Adds a callback for error."
-        if self.erred_back is None:
-            self.errbacks.append((func, args, kwargs))
-        else:
-            self.merge_call(func, args, self.erred_back[0], kwargs, self.erred_back[1])
-    
-    def addStepback(self, func, *args, **kwargs):
-        "Adds a callback for algorithm steps."
-        self.stepbacks.append((func, args, kwargs))
-        if self.stepped_back is not None:
-            self.merge_call(func, args, self.stepped_back[0], kwargs, self.stepped_back[1])
-    
-    def merge_call(self, func, args1, args2, kwargs1, kwargs2):
-        "Merge two function call definitions together sensibly, and run them."
-        kwargs1.update(kwargs2)
-        func(*(args1+args2), **kwargs1)
-    
-    def callback(self, *args, **kwargs):
-        "Send a successful-callback signal."
-        for func, fargs, fkwargs in self.callbacks:
-            self.merge_call(func, fargs, args, fkwargs, kwargs)
-        self.called_back = (args, kwargs)
-    
-    def errback(self, *args, **kwargs):
-        "Send an error-callback signal."
-        for func, fargs, fkwargs in self.errbacks:
-            self.merge_call(func, fargs, args, fkwargs, kwargs)
-        self.erred_back = (args, kwargs)
-    
-    def stepback(self, *args, **kwargs):
-        "Send a step-callback signal."
-        for func, fargs, fkwargs in self.stepbacks:
-            self.merge_call(func, fargs, args, fkwargs, kwargs)
-        self.stepped_back = (args, kwargs)

File myne/format.py

-
-import struct
-from constants import *
-
-class Format(object):
-    
-    def __init__(self, format):
-        self.format = format
-    
-    def __len__(self):
-        length = 0
-        for char in self.format:
-            length += FORMAT_LENGTHS[char]
-        return length
-    
-    def decode(self, data):
-        for char in self.format:
-            if char == "b":
-                yield struct.unpack("!B", data[0])[0]
-            elif char == "a":
-                yield data[:1024]
-            elif char == "s":
-                yield data[:64].strip()
-            elif char == "h":
-                yield struct.unpack("!h", data[:2])[0]
-            elif char == "i":
-                yield struct.unpack("!i", data[:4])[0]
-            data = data[FORMAT_LENGTHS[char]:]
-    
-    def encode(self, *args):
-        assert len(self.format) == len(args)
-        data = ""
-        for char, arg in zip(self.format, args):
-            if char == "a": # Array, 1024 long
-                data += self.packString(arg[:1024], length=1024, packWith="\0")
-            elif char == "s": # String, 64 long
-                data += self.packString(arg[:64])
-            elif char == "h": # Short
-                data += struct.pack("!h", arg)
-            elif char == "i": # Integer
-                data += struct.pack("!i", arg)
-            elif char == "b": # Byte
-                if isinstance(arg, int):
-                    data += chr(arg)
-                elif isinstance(arg, str):
-                    data += arg
-                else:
-                    raise ValueError("Invalid value for byte: %r" % arg)
-        return data
-    
-    def packString(self, string, length=64, packWith=" "):
-        return string + (packWith*(length-len(string)))

File myne/irc_client.py

-
-from twisted.words.protocols import irc
-from twisted.internet import protocol
-
-import logging
-from constants import *
-
-class ChatBot(irc.IRCClient):
-    """An IRC-server chat integration bot."""
-    
-    nickname = "arbot"
-    
-    def connectionMade(self):
-        self.nickname = self.factory.main_factory.irc_nick
-        irc.IRCClient.connectionMade(self)
-        self.factory.instance = self
-        self.factory, self.controller_factory = self.factory.main_factory, self.factory
-        logging.log(logging.INFO, "IRC client connected.")
-        self.world = None
-
-    def connectionLost(self, reason):
-        irc.IRCClient.connectionLost(self, reason)
-        logging.log(logging.INFO, "IRC client disconnected. (%s)" % reason)
-
-    # callbacks for events
-
-    def signedOn(self):
-        """Called when bot has succesfully signed on to server."""
-        self.join(self.factory.irc_channel)
-
-    def joined(self, channel):
-        """This will get called when the bot joins the channel."""
-        logging.log(logging.INFO, "IRC client joined %s." % channel)
-
-    def privmsg(self, user, channel, msg):
-        """This will get called when the bot receives a message."""
-        user = user.split('!', 1)[0]
-        msg = "".join([char for char in msg if ord(char) < 128 and char != "&"])
-        
-        if channel == self.factory.irc_channel:
-            if msg.startswith(self.nickname):
-                self.factory.queue.put((self, TASK_MESSAGE, (127, COLOUR_PURPLE, user, msg[len(self.nickname):].strip(":").strip())))
-
-    def action(self, user, channel, msg):
-        """This will get called when the bot sees someone do an action."""
-        user = user.split('!', 1)[0]
-        msg = "".join([char for char in msg if ord(char) < 128 and char != "&"])
-        self.factory.queue.put((self, TASK_ACTION, (127, COLOUR_PURPLE, user, msg)))
-
-    def sendMessage(self, username, message):
-        self.msg(self.factory.irc_channel, "%s: %s" % (username, message))
-
-    def sendServerMessage(self, message):
-        self.msg(self.factory.irc_channel, ">> %s" % (message, ))
-
-    def sendAction(self, username, message):
-        self.msg(self.factory.irc_channel, "* %s %s" % (username, message))
-
-    # irc callbacks
-
-    def irc_NICK(self, prefix, params):
-        """Called when an IRC user changes their nickname."""
-        old_nick = prefix.split('!')[0]
-        new_nick = params[0]
-        pass
-
-
-class ChatBotFactory(protocol.ClientFactory):
-
-    # the class of the protocol to build when new connection is made
-    protocol = ChatBot
-    
-    def __init__(self, main_factory):
-        self.main_factory = main_factory
-        self.instance = None
-
-    def clientConnectionLost(self, connector, reason):
-        """If we get disconnected, reconnect to server."""
-        self.instance = None
-        connector.connect()
-
-    def clientConnectionFailed(self, connector, reason):
-        logging.log(logging.WARN, "IRC connection failed: %s" % reason)
-        self.instance = None
-
-    def sendMessage(self, username, message):
-        if self.instance:
-            self.instance.sendMessage(username, message)
-
-    def sendAction(self, username, message):
-        if self.instance:
-            self.instance.sendAction(username, message)
-
-    def sendServerMessage(self, message):
-        if self.instance:
-            self.instance.sendServerMessage(message)

File myne/physics.py

-
-import logging
-import time
-from collections import deque
-from threading import Thread, Lock
-from twisted.internet import reactor
-from constants import *
-
-CHR_WATER = chr(BLOCK_WATER)
-CHR_LAVA = chr(BLOCK_LAVA)
-CHR_AIR = chr(BLOCK_AIR)
-CHR_STILL_WATER = chr(BLOCK_STILL_WATER)
-CHR_DIRT = chr(BLOCK_DIRT)
-CHR_GLASS = chr(BLOCK_GLASS)
-CHR_GRASS = chr(BLOCK_GRASS)
-CHR_SPONGE = chr(BLOCK_SPONGE)
-CHR_LEAVES = chr(BLOCK_LEAVES)
-
-BLOCK_SPOUT = BLOCK_INDIGO_CLOTH
-BLOCK_LAVA_SPOUT = BLOCK_MAGENTA_CLOTH
-CHR_SPOUT = chr(BLOCK_SPOUT)
-CHR_LAVA_SPOUT = chr(BLOCK_LAVA_SPOUT)
-
-REQUEUE_FLUID = -1
-
-class Physics(Thread):
-    
-    """
-    Given a BlockStore, works out what needs doing (water, grass etc.)
-    and send the changes back to the BlockStore.
-    """
-    
-    LAG_INTERVAL = 60 # How long between "ack we're lagging" messages
-    
-    FLUID_LIMIT = 700
-    AIR_LIMIT = 800
-    SPONGE_LIMIT = 50
-    GRASS_GROW_LIMIT = 10
-    GRASS_DIE_LIMIT = 10
-    
-    def __init__(self, blockstore):
-        Thread.__init__(self)
-        self.blockstore = blockstore
-        self.last_lag = 0
-        self.running = True
-        self.was_physics = False
-        self.was_unflooding = False
-        self.init_queues()
-    
-    def stop(self):
-        self.running = False
-        self.join()
-    
-    def run(self):
-        while self.running:
-            
-            if self.blockstore.physics:
-                
-                logging.log(logging.DEBUG, "Starting physics run for '%s'. (a%i, f%i, s%i, gg%i, gd%i)" % (self.blockstore.blocks_path, len(self.air_queue), len(self.fluid_queue), len(self.sponge_queue), len(self.grass_grow_queue), len(self.grass_die_queue)))
-                
-                # If this is the first of a physics run, redo the queues from scratch
-                if self.was_physics == False:
-                    logging.log(logging.DEBUG, "Performing queue scan for '%s'." % self.blockstore.blocks_path)
-                    self.scan_blocks()
-                # SCIENCE!!!
-                changes, overflow = self.run_iteration()
-                
-                # Replay the changes
-                for x, y, z, block in changes:
-                    if block is REQUEUE_FLUID:
-                        self.fluid_queue.add(self.blockstore.get_offset(x, y, z))
-                    else:
-                        self.blockstore[x, y, z] = chr(block)
-                        self.blockstore.send_block(x, y, z)
-                
-                if overflow and (time.time() - self.last_lag > self.LAG_INTERVAL):
-                    self.blockstore.admin_message("Physics is currently lagging in %(id)s.")
-                    self.last_lag = time.time()
-                
-                logging.log(logging.DEBUG, "Ended physics run for '%s' (c%i, a%i, f%i, s%i, gg%i, gd%i)." % (self.blockstore.blocks_path, len(changes), len(self.air_queue), len(self.fluid_queue), len(self.sponge_queue), len(self.grass_grow_queue), len(self.grass_die_queue)))
-            else:
-                if self.was_physics:
-                    self.init_queues()
-            
-            self.was_physics = self.blockstore.physics
-            self.was_unflooding = self.blockstore.unflooding
-            # Wait till next iter
-            time.sleep(0.7)
-    
-    
-    def init_queues(self):
-        self.fluid_queue = set()
-        self.grass_grow_queue = set()
-        self.grass_die_queue = set()
-        self.air_queue = set()
-        self.sponge_queue = set()
-        self.sponge_locations = set()
-    
-    
-    def scan_blocks(self):
-        "Scans the blockstore, looking for things to add to the queues."
-        # Initialise the queues
-        self.init_queues()
-        # Scan
-        for offset, block in enumerate(self.blockstore.raw_blocks):
-            if block is CHR_LAVA or block is CHR_WATER or block is CHR_SPOUT or block is CHR_LAVA_SPOUT:
-                self.fluid_queue.add(offset)
-            elif block is CHR_GRASS:
-                self.grass_grow_queue.add(offset)
-            elif block is CHR_SPONGE:
-                self.sponge_queue.add(offset)
-    
-    
-    def handle_change(self, offset, block):
-        "Gets called when a block is changed, with its position and type."
-        assert isinstance(block, str)
-        if self.blockstore.physics:
-            if block is CHR_LAVA or block is CHR_WATER or block is CHR_SPOUT or block is CHR_LAVA_SPOUT:
-                self.fluid_queue.add(offset)
-            elif block is CHR_DIRT or block is CHR_GRASS:
-                self.grass_grow_queue.add(offset)
-            elif block is CHR_AIR:
-                self.sponge_queue.add(offset)
-                self.air_queue.add(offset)
-            elif block is CHR_SPONGE:
-                self.sponge_queue.add(offset)
-    
-    
-    def apply_ops(self, ops):
-        "Immediately applies changes to the in-memory state. Returns the changes."
-        for x, y, z, block in ops:
-            if block is not REQUEUE_FLUID:
-                self.blockstore.raw_blocks[self.blockstore.get_offset(x, y, z)] = chr(block)
-            yield x, y, z, block
-    
-    
-    def run_iteration(self):
-        
-        changes = []
-        
-        fluid_done = 0
-        # Are we unflooding? If so, do it.
-        if self.blockstore.unflooding:
-            if not self.was_unflooding:
-                # We've just started, do a scan to get all the fluids.
-                self.scan_blocks()
-            # Do n fluid removals
-            try:
-                while fluid_done < self.FLUID_LIMIT:
-                    offset = self.fluid_queue.pop()
-                    x, y, z = self.blockstore.get_coords(offset)
-                    changes.append((x, y, z, BLOCK_AIR))
-                    fluid_done += 1
-            except KeyError:
-                pass
-            # Did we do nothing? Unflooding complete.
-            if fluid_done == 0:
-                self.blockstore.unflooding = False
-                self.blockstore.world_message("Unflooding complete.")
-
-        # Do up to n air things
-        air_done = 0
-        try:
-            while air_done < self.AIR_LIMIT:
-                offset = self.air_queue.pop()
-                ops = list(self.apply_ops(self.handle_air(offset)))
-                air_done += len(ops)
-                changes.extend(ops)
-        except KeyError:
-            pass        
-        
-        
-        # Do up to n fluid things
-        try:
-            while fluid_done < self.FLUID_LIMIT:
-                offset = self.fluid_queue.pop()
-                ops = list(self.apply_ops(self.handle_fluid(offset)))
-                fluid_done += len(ops)
-                changes.extend(ops)
-        except KeyError:
-            pass
-        
-        
-        # Do up to n sponge things
-        sponge_done = 0
-        try:
-            while sponge_done < self.SPONGE_LIMIT:
-                offset = self.sponge_queue.pop()
-                ops = list(self.apply_ops(self.handle_sponge(offset)))
-                sponge_done += len(ops)
-                changes.extend(ops)
-        except KeyError:
-            pass
-        
-        
-        # Let's move on to grass growing. Do n of these.
-        grass_grown = 0
-        try:
-            while grass_grown < self.GRASS_GROW_LIMIT:
-                offset = self.grass_grow_queue.pop()
-                ops = list(self.apply_ops(self.handle_grass_grow(offset)))
-                grass_grown += len(ops)
-                changes.extend(ops)
-        except KeyError:
-            pass
-        
-        return changes, (fluid_done >= self.FLUID_LIMIT)
-    
-    
-    def get_blocks(self, x, y, z, deltas):
-        "Given a starting point and some deltas, returns all offsets which exist."
-        for dx, dy, dz in deltas:
-            try:
-                new_offset = self.blockstore.get_offset(x+dx, y+dy, z+dz)
-            except AssertionError:
-                pass
-            else:
-                yield x+dx, y+dy, z+dz, new_offset
-    
-    
-    def is_blocked(self, x, y, z):
-        "Given coords, determines if the block can see the sky."
-        blocked = False
-        for ny in range(y+1, self.blockstore.y):
-            blocker_offset = self.blockstore.get_offset(x, ny, z)
-            blocker_block = self.blockstore.raw_blocks[blocker_offset]
-            if not ((blocker_block is CHR_AIR) or (blocker_block is CHR_GLASS) or (blocker_block is CHR_LEAVES)):
-                blocked = True
-                break
-        return blocked
-    
-    
-    def block_radius(self, r):
-        "Returns blocks within the radius"
-        for x in range(-r, r+1):
-            for y in range(-r, r+1):
-                for z in range(-r, r+1):
-                    if x or y or z:
-                        yield (x, y, z)
-    
-    
-    def handle_air(self, offset):
-        "Handles the appearance of an air block, for fluids or growth"
-        
-        x, y, z = self.blockstore.get_coords(offset)
-        block = self.blockstore.raw_blocks[offset]
-        if block is not CHR_AIR:
-            return
-        
-        # We're a generator, really.
-        if False:
-            yield
-        
-        # Is there water/lava above/beside it?
-        for nx, ny, nz, new_offset in self.get_blocks(x, y, z, self.block_radius(1)):
-            new_block = self.blockstore.raw_blocks[new_offset]
-            if new_block is CHR_WATER or new_block is CHR_LAVA:
-                self.fluid_queue.add(new_offset)
-    
-    
-    def handle_sponge(self, offset):
-        "Handles simulation of sponge blocks."
-        x, y, z = self.blockstore.get_coords(offset)
-        block = self.blockstore.raw_blocks[offset]
-        
-        if block is CHR_SPONGE:
-            # OK, it's a sponge. Add it to sponge locations.
-            self.sponge_locations.add((offset))
-            
-            # Make sure all the water blocks around it go away
-            for nx, ny, nz, new_offset in self.get_blocks(x, y, z, self.block_radius(2)):
-                block = self.blockstore.raw_blocks[new_offset]
-                if block is CHR_WATER and not self.blockstore.finite_water:
-                    yield (nx, ny, nz, BLOCK_AIR)
-            
-            # If it's finite water, re-animate anything at the edges.
-            if self.blockstore.finite_water:
-                for nx, ny, nz, new_offset in self.get_blocks(x, y, z, self.block_radius(1)):
-                    block = self.blockstore.raw_blocks[new_offset]
-                    if block is CHR_WATER or block is CHR_LAVA:
-                        self.fluid_queue.add(new_offset)
-        
-        if block is CHR_AIR:
-            if offset in self.sponge_locations:
-                self.sponge_locations.remove(offset)
-                # See if there's some water or lava that needs reanimating
-                for nx, ny, nz, new_offset in self.get_blocks(x, y, z, self.block_radius(3)):
-                    block = self.blockstore.raw_blocks[new_offset]
-                    if block is CHR_WATER or block is CHR_LAVA:
-                        self.fluid_queue.add(new_offset)
-    
-    
-    def sponge_within_radius(self, x, y, z, r):
-        for nx, ny, nz, new_offset in self.get_blocks(x, y, z, self.block_radius(r)):
-            if new_offset in self.sponge_locations:
-                return True
-        return False
-    
-    
-    def handle_fluid(self, offset):
-        "Handles simulation of fluid blocks."
-        
-        x, y, z = self.blockstore.get_coords(offset)
-        block = self.blockstore.raw_blocks[offset]
-        
-        # Spouts produce water in finite mode
-        if block is CHR_SPOUT or block is CHR_LAVA_SPOUT:
-            # If there's a gap below, produce water
-            if self.blockstore.finite_water:
-                try:
-                    below = self.blockstore.get_offset(x, y-1, z)
-                except AssertionError:
-                    pass # At bottom of map
-                else:
-                    if self.blockstore.raw_blocks[below] is CHR_AIR:
-                        if block is CHR_SPOUT:
-                            yield (x, y-1, z, BLOCK_WATER)
-                        else:
-                            yield (x, y-1, z, BLOCK_LAVA)
-            yield (x, y, z, REQUEUE_FLUID)
-        
-        if block is not CHR_WATER and block is not CHR_LAVA:
-            return
-        
-        # OK, so, can it drop?
-        try:
-            below = self.blockstore.get_offset(x, y-1, z)
-        except AssertionError:
-            pass # At bottom of map
-        else:
-            if self.blockstore.finite_water:
-                if self.blockstore.raw_blocks[below] is CHR_AIR:
-                    yield (x, y-1, z, ord(block))
-                    yield (x, y, z, BLOCK_AIR)
-                    return
-                elif self.blockstore.raw_blocks[below] is CHR_SPONGE:
-                    yield (x, y, z, BLOCK_AIR)
-                    return
-            else:
-                if self.blockstore.raw_blocks[below] is CHR_AIR and not self.sponge_within_radius(x, y-1, z, 2):
-                    yield (x, y-1, z, ord(block))
-                    yield (x, y, z, BLOCK_AIR)
-                    return
-        
-        # Noice. Now, can it spread?
-        if self.blockstore.finite_water:
-            # Finite water first tries to move downwards and straight
-            for nx, ny, nz, new_offset in self.get_blocks(x, y, z, [(0, -1, 1), (0, -1, -1), (1, -1, 0), (-1, -1, 0)]):
-                above_offset = self.blockstore.get_offset(nx, ny+1, nz)
-                if self.blockstore.raw_blocks[above_offset] is CHR_AIR:
-                    # Air? Fall.
-                    if self.blockstore.raw_blocks[new_offset] is CHR_AIR:
-                        yield (nx, ny, nz, ord(block))
-                        yield (x, y, z, BLOCK_AIR)
-                        return
-                    # Sponge? Absorb.
-                    if self.blockstore.raw_blocks[new_offset] is CHR_SPONGE:
-                        yield (x, y, z, BLOCK_AIR)
-                        return
-            # Then it tries a diagonal
-            for nx, ny, nz, new_offset in self.get_blocks(x, y, z, [(1, -1, 1), (1, -1, -1), (-1, -1, 1), (-1, -1, -1)]):
-                above_offset = self.blockstore.get_offset(nx, ny+1, nz)
-                left_offset = self.blockstore.get_offset(x, ny+1, nz)
-                right_offset = self.blockstore.get_offset(nx, ny+1, z)
-                if self.blockstore.raw_blocks[above_offset] is CHR_AIR and \
-                   (self.blockstore.raw_blocks[left_offset] is CHR_AIR or \
-                   self.blockstore.raw_blocks[right_offset] is CHR_AIR):
-                    # Air? Fall.
-                    if self.blockstore.raw_blocks[new_offset] is CHR_AIR:
-                        yield (nx, ny, nz, ord(block))
-                        yield (x, y, z, BLOCK_AIR)
-                        return
-                    # Sponge? Absorb.
-                    if self.blockstore.raw_blocks[new_offset] is CHR_SPONGE:
-                        yield (x, y, z, BLOCK_AIR)
-                        return
-        else:
-            # Infinite water spreads in the 4 horiz directions.
-            for nx, ny, nz, new_offset in self.get_blocks(x, y, z, [(0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0)]):
-                if self.blockstore.raw_blocks[new_offset] is CHR_AIR and not self.sponge_within_radius(nx, ny, nz, 2):
-                    yield (nx, ny, nz, ord(block))
-    
-    
-    def handle_grass_grow(self, offset):
-        """
-        Handles grass growing. We get passed either a patch of grass that
-        might spread, or a dirt tile that might grow.
-        """
-        x, y, z = self.blockstore.get_coords(offset)
-        block = self.blockstore.raw_blocks[offset]
-        
-        if block is CHR_DIRT:
-            # See if there's any grass next to us
-            for nx, ny, nz, new_offset in self.get_blocks(x, y, z, [(0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0)]):
-                if self.blockstore.raw_blocks[new_offset] is CHR_GRASS:
-                    # Alright, can we see the sun?
-                    if not self.is_blocked(x, y, z):
-                        yield (x, y, z, BLOCK_GRASS)
-                        self.grass_grow_queue.add(offset)
-        
-        elif block is CHR_GRASS:
-            # See if there's any dirt next to us
-            for nx, ny, nz, new_offset in self.get_blocks(x, y, z, [(0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0)]):
-                if self.blockstore.raw_blocks[new_offset] is CHR_DIRT:
-                    # Alright, can we see the sun?
-                    if not self.is_blocked(nx, ny, nz):
-                        yield (nx, ny, nz, BLOCK_GRASS)
-                        self.grass_grow_queue.add(new_offset)
-        

File myne/plugins/__init__.py

-
-import logging
-
-protocol_plugins = []
-server_plugins = []
-
-class PluginMetaclass(type):
-    
-    """
-    A metaclass which registers any subclasses of Plugin.
-    """
-    
-    def __new__(cls, name, bases, dct):
-        # Supercall
-        new_cls = type.__new__(cls, name, bases, dct)
-        # Register!
-        if bases != (object,):
-            if ProtocolPlugin in bases:
-                logging.log(logging.DEBUG, "Loaded protocol plugin: %s" % name)
-                protocol_plugins.append(new_cls)
-            elif ServerPlugin in bases:
-                logging.log(logging.DEBUG, "Loaded server plugin: %s" % name)
-                server_plugins.append(new_cls)
-            else:
-                logging.log(logging.WARN, "Plugin '%s' is not a server or a protocol plugin." % name)
-        return new_cls
-
-
-class ServerPlugin(object):
-    """
-    Parent object all plugins inherit from.
-    """
-    __metaclass__ = PluginMetaclass
-
-
-class ProtocolPlugin(object):
-    """
-    Parent object all plugins inherit from.
-    """
-    __metaclass__ = PluginMetaclass
-    
-    def __init__(self, client):
-        # Store the client
-        self.client = client
-        # Register our commands
-        if hasattr(self, "commands"):
-            for name, fname in self.commands.items():
-                self.client.registerCommand(name, getattr(self, fname))
-        # Register our hooks
-        if hasattr(self, "hooks"):
-            for name, fname in self.hooks.items():
-                self.client.registerHook(name, getattr(self, fname))
-        # Call clean setup method
-        self.gotClient()
-    
-    def unregister(self):
-        # Unregister our commands
-        if hasattr(self, "commands"):
-            for name, fname in self.commands.items():
-                self.client.unregisterCommand(name, getattr(self, fname))
-        # Unregister our hooks
-        if hasattr(self, "hooks"):
-            for name, fname in self.hooks.items():
-                self.client.unregisterHook(name, getattr(self, fname))
-        del self.client
-    
-    def gotClient(self):
-        pass
-
-
-def load_plugins(plugins):
-    "Given a list of plugin names, imports them so they register."
-    for module_name in plugins:
-        try:
-            __import__("myne.plugins.%s" % module_name)
-        except ImportError:
-            logging.log(logging.ERROR, "Cannot load plugin %s." % module_name)
-
-
-def unload_plugin(plugin_name):
-    "Given a plugin name, reloads and re-imports its code."
-    # Unload all its classes from our lists
-    for plugin in plugins_by_module_name(plugin_name):
-        if plugin in protocol_plugins:
-            protocol_plugins.remove(plugin)
-        if plugin in server_plugins:
-            server_plugins.remove(plugin)
-
-
-def load_plugin(plugin_name):
-    # Reload the module, in case it was imported before
-    reload(__import__("myne.plugins.%s" % plugin_name, {}, {}, ["*"]))
-    load_plugins([plugin_name])
-
-
-def plugins_by_module_name(module_name):
-    "Given a module name, returns the plugin classes in it."
-    try:
-        module = __import__("myne.plugins.%s" % module_name, {}, {}, ["*"])
-    except ImportError:
-        raise ValueError("Cannot load plugin %s." % module_name)
-    else:
-        for name, val in module.__dict__.items():
-            if isinstance(val, type):
-                if issubclass(val, ProtocolPlugin) and val is not ProtocolPlugin:
-                    yield val
-                elif issubclass(val, ServerPlugin) and val is not ServerPlugin:
-                    yield val

File myne/plugins/adminblocks.py

-
-from myne.plugins import ProtocolPlugin
-from myne.decorators import *
-from myne.constants import *
-
-class AdminBlocksPlugin(ProtocolPlugin):
-    
-    commands = {
-        "solid": "commandSolid",
-        "adminblocks": "commandAdminblocks",
-    }
-    
-    hooks = {
-        "blockchange": "blockChanged",
-        "rankchange": "sendAdminBlockUpdate",
-        "canbreakadmin": "canBreakAdminBlocks",
-    }
-    
-    def gotClient(self):
-        self.building_solid = False
-    
-    def blockChanged(self, x, y, z, block, selected_block):
-        "Hook trigger for block changes."
-        # Admincrete hack check
-        if not self.canBreakAdminBlocks():
-            def check_block(block):
-                if ord(block) == BLOCK_GROUND_ROCK:
-                    self.client.sendError("Don't build admincrete!")
-                    self.client.world[x, y, z] = chr(BLOCK_AIR)
-            self.client.world[x,y,z].addCallback(check_block)
-        # See if they are in solid-building mode
-        if self.building_solid and block == BLOCK_ROCK:
-            return BLOCK_GROUND_ROCK
-    
-    def canBreakAdminBlocks(self):
-        "Shortcut for checking permissions."
-        if hasattr(self.client, "world"):
-            return (not self.client.world.admin_blocks) or self.client.isOp()
-        else:
-            return False
-    
-    def sendAdminBlockUpdate(self):
-        "Sends a packet that updates the client's admin-building ability"
-        self.client.sendPacked(TYPE_INITIAL, 6, "Admincrete Update", "If you see this, it's a bug", self.canBreakAdminBlocks() and 100 or 0)
-    
-    @op_only
-    @on_off_command
-    def commandAdminblocks(self, onoff):
-        "/adminblocks on|off - Turns on/off unbreakable admin/op blocks."
-        if onoff == "on":
-            self.client.world.admin_blocks = True
-            self.client.sendWorldMessage("Admin blocks are now enabled here.")
-            self.client.sendServerMessage("Admin Blocks on in %s" % self.client.world.id)
-        else:
-            self.client.world.admin_blocks = False
-            self.client.sendWorldMessage("Admin blocks are now disabled here.")
-            self.client.sendServerMessage("Admin Blocks off in %s" % self.client.world.id)
-        for client in self.client.world.clients:
-            client.sendAdminBlockUpdate()
-    
-    @op_only
-    def commandSolid(self, parts):
-        "/solid - Toggles admincrete creation."
-        if self.building_solid:
-            self.client.sendServerMessage("You are now placing normal rock.")
-        else:
-            self.client.sendServerMessage("You are now placing admin rock.")
-        self.building_solid = not self.building_solid
-    

File myne/plugins/admins.py

-
-from myne.plugins import ProtocolPlugin
-from myne.decorators import *
-from myne.constants import *
-
-class AdminsPlugin(ProtocolPlugin):
-    
-    commands = {
-        "admin": "commandAdmin",
-        "deadmin": "commandDeadmin",
-    }
-
-    @admin_only
-    @only_username_command
-    def commandAdmin(self, username):
-        "/admin username - Adds the user as an admin."
-        self.client.factory.admins.add(username)
-        self.client.sendServerMessage("%s is now an admin." % username)
-        if username in self.client.factory.usernames:
-            self.client.factory.usernames[username].sendAdminUpdate()
-
-    @admin_only
-    @only_username_command
-    def commandDeadmin(self, username):
-        "/deadmin username - Removes the user as an admin."
-        self.client.factory.admins.remove(username)
-        self.client.sendServerMessage("%s is no longer an admin." % username.lower())
-        if username in self.client.factory.usernames:
-            self.client.factory.usernames[username].sendAdminUpdate()
-    

File myne/plugins/archives.py

-
-import datetime
-
-from myne.plugins import ProtocolPlugin
-from myne.decorators import *
-from myne.constants import *
-
-class ArchivesPlugin(ProtocolPlugin):
-    
-    commands = {
-        "aname": "commandAname",
-        "atime": "commandAtime",
-        "aboot": "commandAboot",
-    }
-    
-    def gotClient(self):
-        self.selected_archive_name = None
-        self.selected_archive = None
-    
-    def commandAname(self, parts):
-        "/aname searchterm - Selects an archive name, by part or all of the name."
-        if len(parts) == 1:
-            self.client.sendServerMessage("Please enter a search term")
-        else:
-            # See how many archives match
-            searchterm = parts[1].lower()
-            matches = [x for x in self.client.factory.archives if searchterm in x.lower()]
-            if len(matches) == 0:
-                self.client.sendServerMessage("No matches for '%s'" % searchterm)
-            elif len(matches) == 1:
-                self.client.sendServerMessage("Selected '%s'." % matches[0])
-                self.selected_archive_name = matches[0]
-            else:
-                self.client.sendServerMessage("%s matches! Be more specific." % len(matches))
-                for match in matches[:3]:
-                    self.client.sendServerMessage(match)
-                if len(matches) > 3:
-                    self.client.sendServerMessage("..and %s more." % (len(matches) - 3))
-    
-    def commandAtime(self, parts):
-        "/atime yyyy/mm/dd hh:mm - Selects the archive time to get"
-        if len(parts) == 2:
-            # Hackish. So sue me.
-            if parts[1].lower() == "newest":
-                parts[1] = "2020/1/1"
-                parts.append("00:00")
-            elif parts[1].lower() == "oldest":
-                parts[1] = "1970/1/1"
-                parts.append("00:00")
-        if len(parts) < 3:
-            self.client.sendServerMessage("Please enter a date and time.")
-        elif not self.selected_archive_name or self.selected_archive_name not in self.client.factory.archives:
-            self.client.sendServerMessage("Please select an archive name first. (/aname)")
-        else:
-            try:
-                when = datetime.datetime.strptime(parts[1] + " " + parts[2], "%Y/%m/%d %H:%M")
-            except ValueError:
-                self.client.sendServerMessage("Please use the format yyyy/mm/dd hh:mm")
-            else:
-                # Pick the closest time
-                times = []
-                for awhen, filename in self.client.factory.archives[self.selected_archive_name].items():
-                    dt = when - awhen
-                    secs = abs(dt.seconds + (dt.days * 86400))
-                    times.append((secs, awhen, filename))
-                times.sort()
-                self.selected_archive = times[0][2]
-                self.client.sendServerMessage("Selected archive from %s" % times[0][1].strftime("%Y/%m/%d %H:%M"))
-    
-    def commandAboot(self, parts):
-        "/aboot - Boots an archive after you've done /aname and /atime"
-        if not self.selected_archive:
-            if not self.selected_archive_name:
-                self.client.sendServerMessage("Please select an archive name first. (/aname)")
-            else:
-                self.client.sendServerMessage("Please select an archive time first. (/atime)")
-        else:
-            world_id = self.client.factory.loadArchive(self.selected_archive)
-            self.client.factory.worlds[world_id].admin_blocks = False
-            self.client.sendServerMessage("Archive loaded, as %s" % world_id)
-            self.client.changeToWorld(world_id)
-    

File myne/plugins/banish.py

-
-from myne.plugins import ProtocolPlugin
-from myne.decorators import *
-
-class BanishPlugin(ProtocolPlugin):
-    
-    commands = {
-        "banish": "commandBanish",
-    }
-    
-    @op_only
-    @username_command
-    def commandBanish(self, user):
-        "/banish username - Banishes the user to the default world."
-        if user.world == self.client.world:
-            user.sendServerMessage("You were banished from '%s'." % self.client.world.id)
-            user.changeToWorld("default")
-            self.client.sendServerMessage("User %s banished." % user.username)
-        else:
-            self.client.sendServerMessage("That user is in another world!")
-    

File myne/plugins/blb.py

-
-from twisted.internet import reactor
-
-from myne.plugins import ProtocolPlugin
-from myne.decorators import *
-from myne.constants import *
-
-class BlbPlugin(ProtocolPlugin):
-    
-    commands = {
-        "blb": "commandBlb",
-    }
-    
-    @op_only
-    def commandBlb(self, parts):
-        "/blb type [x y z x2 y2 z2] - Sets all blocks in this cuboid to type."
-        if len(parts) < 8 and len(parts) != 2:
-            self.client.sendServerMessage("Please enter a type (and possibly two coord triples)")
-        else:
-            # Try getting the block as a direct integer type.
-            try:
-                block = chr(int(parts[1]))
-            except ValueError:
-                # OK, try a symbolic type.
-                try:
-                    block = chr(globals()['BLOCK_%s' % parts[1].upper()])
-                except KeyError:
-                    self.client.sendServerMessage("'%s' is not a valid block type." % parts[1])
-                    return
-            
-            # Check the block is valid
-            if ord(block) > 41:
-                self.client.sendServerMessage("'%s' is not a valid block type." % parts[1])
-                return
-            
-            # If they only provided the type argument, use the last two block places
-            if len(parts) == 2:
-                try:
-                    x, y, z = self.client.last_block_changes[0]
-                    x2, y2, z2 = self.client.last_block_changes[1]
-                except IndexError:
-                    self.client.sendServerMessage("You have not clicked two corners yet.")
-                    return
-            else:
-                try:
-                    x = int(parts[2])
-                    y = int(parts[3])
-                    z = int(parts[4])
-                    x2 = int(parts[5])
-                    y2 = int(parts[6])
-                    z2 = int(parts[7])
-                except ValueError:
-                    self.client.sendServerMessage("All parameters must be integers")
-                    return
-            
-            if x > x2:
-                x, x2 = x2, x
-            if y > y2:
-                y, y2 = y2, y
-            if z > z2:
-                z, z2 = z2, z
-            
-            if self.client.isAdmin():
-                limit = 300000
-            else:
-                limit = 50000
-            # Stop them doing silly things
-            if (x2 - x) * (y2 - y) * (z2 - z) > limit:
-                self.client.sendServerMessage("Sorry, that area is too big for you to blb.")
-                return
-            
-            # Draw all the blocks on, I guess
-            # We use a generator so we can slowly release the blocks
-            # We also keep world as a local so they can't change worlds and affect the new one
-            world = self.client.world
-            def generate_changes():
-                for i in range(x, x2+1):
-                    for j in range(y, y2+1):
-                        for k in range(z, z2+1):
-                            try:
-                                world[i, j, k] = block
-                            except AssertionError:
-                                self.client.sendServerMessage("Out of bounds blb error.")
-                                return
-                            self.client.queueTask(TASK_BLOCKSET, (i, j, k, block), world=world)
-                            self.client.sendBlock(i, j, k, block)
-                            yield
-            
-            # Now, set up a loop delayed by the reactor
-            block_iter = iter(generate_changes())
-            def do_step():
-                # Do 10 blocks
-                try:
-                    for x in range(10):
-                        block_iter.next()
-                    reactor.callLater(0.01, do_step)
-                except StopIteration:
-                    pass
-            do_step()

File myne/plugins/build.py

-
-from myne.plugins import ProtocolPlugin
-from myne.decorators import *
-from myne.constants import *
-
-class BuildPlugin(ProtocolPlugin):
-    
-    commands = {
-        "build": "commandBuild",
-    }
-    
-    hooks = {
-        "blockchange": "blockChanged",
-    }
-    
-    def gotClient(self):
-        self.block_overrides = {}
-    
-    def blockChanged(self, x, y, z, block, selected_block):
-        "Hook trigger for block changes."
-        if block in self.block_overrides:
-            return self.block_overrides[block]
-    
-    @writer_only
-    def commandBuild(self, parts):
-        "/build water|watervator|lava|stilllava|grass|gold|copper|coal"
-        possibles = {
-            "water": (BLOCK_WATER, BLOCK_BLUE_CLOTH, "Blue cloth"),
-            "watervator": (BLOCK_STILL_WATER, BLOCK_CYAN_CLOTH, "Cyan cloth"),
-            "stillwater": (BLOCK_STILL_WATER, BLOCK_CYAN_CLOTH, "Cyan cloth"),
-            "lava": (BLOCK_LAVA, BLOCK_RED_CLOTH, "Red cloth"),
-            "stilllava": (BLOCK_STILL_LAVA, BLOCK_ORANGE_CLOTH, "Orange cloth"),
-            "grass": (BLOCK_GRASS, BLOCK_GREEN_CLOTH, "Green cloth"),
-            "coal": (BLOCK_COAL_ORE, BLOCK_DARKGREY_CLOTH, "Dark grey cloth"),
-            "gold": (BLOCK_GOLD_ORE, BLOCK_YELLOW_CLOTH, "Yellow cloth"),
-            "copper": (BLOCK_COPPER_ORE, BLOCK_TURQUOISE_CLOTH, "Turquoise cloth"),
-        }
-        if len(parts) == 1:
-            self.client.sendServerMessage("Specify a type to toggle.")
-        else:
-            name = parts[1].lower()
-            try:
-                new, old, old_name = possibles[name]
-            except KeyError:
-                self.client.sendServerMessage("'%s' is not a special block type." % name)
-            else:
-                if old in self.block_overrides:
-                    del self.block_overrides[old]
-                    self.client.sendServerMessage("%s is back to normal." % old_name)
-                else:
-                    self.block_overrides[old] = new
-                    self.client.sendServerMessage("%s will turn into %s." % (old_name, name))
-    
-    

File myne/plugins/core.py

-
-from myne.plugins import ProtocolPlugin
-from myne.decorators import *
-
-class CorePlugin(ProtocolPlugin):
-    
-    commands = {
-        "pluginload": "commandPluginload",
-        "pll": "commandPluginload",
-        "pluginunload": "commandPluginunload",
-        "plu": "commandPluginunload",
-        "pluginreload": "commandPluginreload",
-        "plr": "commandPluginreload",
-    }
-    
-    @admin_only
-    @only_string_command("plugin name")
-    def commandPluginreload(self, plugin_name):
-        try:
-            self.client.factory.unloadPlugin(plugin_name)
-            self.client.factory.loadPlugin(plugin_name)
-        except IOError:
-            self.client.sendServerMessage("No such plugin '%s'." % plugin_name)
-        else:
-            self.client.sendServerMessage("Plugin '%s' reloaded." % plugin_name)
-    
-    @admin_only
-    @only_string_command("plugin name")
-    def commandPluginload(self, plugin_name):
-        try:
-            self.client.factory.loadPlugin(plugin_name)
-        except IOError:
-            self.client.sendServerMessage("No such plugin '%s'." % plugin_name)
-        else:
-            self.client.sendServerMessage("Plugin '%s' loaded." % plugin_name)
-    
-    @admin_only
-    @only_string_command("plugin name")
-    def commandPluginunload(self, plugin_name):
-        try:
-            self.client.factory.unloadPlugin(plugin_name)
-        except IOError:
-            self.client.sendServerMessage("No such plugin '%s'." % plugin_name)
-        else:
-            self.client.sendServerMessage("Plugin '%s' unloaded." % plugin_name)
-    

File myne/plugins/debug.py

-
-from myne.plugins import ProtocolPlugin
-from myne.decorators import *
-
-
-class DebugPlugin(ProtocolPlugin):
-    
-    commands = {
-        "sanal": "commandStartanalysis",
-        "eanal": "commandEndanalysis",
-        "test": "commandTest",
-    }
-    
-    def gotClient(self):
-        from guppy import hpy
-        self.h = hpy()
-    
-    def commandStartanalysis(self, user):
-        self.h.setrelheap()
-    
-    def commandEndanalysis(self, user):
-        hp = self.h.heap()
-        import pdb
-        pdb.set_trace()
-    
-    def commandTest(self, parts):
-        import logging
-        logging.log(logging.ERROR, "Test error!")
-    
-    def __del__(self):
-        print "delDebug"
-
-print "IMPORTED DEBUG PLUGIN"
-    
-    

File myne/plugins/dynamite.py

-
-from myne.plugins import ProtocolPlugin
-from myne.decorators import *
-from myne.constants import *
-from twisted.internet import reactor
-
-class DynamitePlugin(ProtocolPlugin):
-    
-    commands = {
-        "dynamite": "commandDynamite",
-    }
-    
-    hooks = {
-        "blockchange": "blockChanged",
-        "newworld": "newWorld",
-    }
-    
-    def gotClient(self):
-        self.build_dynamite = False
-        self.explosion_radius = 2
-        self.delay = 2
-    
-    def newWorld(self, world):
-        "Hook to reset dynamiting abilities in new worlds if not op."
-        if not self.client.isOp():
-            self.build_dynamite = False
-    
-    def blockChanged(self, x, y, z, block, selected_block):
-        "Hook trigger for block changes."
-        tobuild = []
-        # Randomise the variables
-        fanout = self.explosion_radius
-        if self.build_dynamite and block == BLOCK_WOOD:
-            def explode():
-                # Clear the explosion radius
-                for i in range(-fanout, fanout+1):
-                    for j in range(-fanout, fanout+1):
-                        for k in range(-fanout, fanout+1):
-                                tobuild.append((i, j, k, BLOCK_AIR))
-                # OK, send the build changes
-                for dx, dy, dz, block in tobuild:
-                    try:
-                        self.client.world[x+dx, y+dy, z+dz] = chr(block)
-                        self.client.sendBlock(x+dx, y+dy, z+dz, block)
-                        self.client.factory.queue.put((self.client, TASK_BLOCKSET, (x+dx, y+dy, z+dz, block)))
-                    except AssertionError: # OOB
-                        pass
-            # Explode in 2 seconds