home · contact · privacy
Refactor parser code.
[plomrogue2] / plomrogue / game.py
index 78598143628df6057efc41e4e216af0f211de4d6..cb749cffa939f38c3672945fb240ae3d6e3aef79 100755 (executable)
@@ -2,7 +2,6 @@ from plomrogue.errors import GameError, PlayError
 from plomrogue.io import GameIO
 from plomrogue.misc import quote
 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex, Map
 from plomrogue.io import GameIO
 from plomrogue.misc import quote
 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex, Map
-import string
 import datetime
 
 
 import datetime
 
 
@@ -50,7 +49,14 @@ class SaveableMap(Map):
             return False
         return True
 
             return False
         return True
 
-    def draw_presets(self, alternate_hex=0):
+    def draw_presets(self, big_yx, type_):
+        if type_ == 1:
+            if big_yx.y < 0:
+                self.terrain = 'X' * self.size_i
+        elif type_ == 2:
+            self.draw_presets_grid(big_yx)
+
+    def draw_presets_grid(self, big_yx):
         old_modified = self.modified
         if type(self.geometry) == MapGeometrySquare:
             self.set_line(0, 'X' * self.geometry.size.x)
         old_modified = self.modified
         if type(self.geometry) == MapGeometrySquare:
             self.set_line(0, 'X' * self.geometry.size.x)
@@ -88,6 +94,7 @@ class SaveableMap(Map):
                                 if self.inside(yx):
                                     self[yx] = 'X'
 
                                 if self.inside(yx):
                                     self[yx] = 'X'
 
+                alternate_hex = big_yx.y % 2
                 if alternate_hex:
                     draw_snake(offset + YX(0, 0))
                 draw_snake(offset + YX((0 + alternate_hex) * distance,
                 if alternate_hex:
                     draw_snake(offset + YX(0, 0))
                 draw_snake(offset + YX((0 + alternate_hex) * distance,
@@ -115,11 +122,13 @@ import os
 class Game(GameBase):
 
     def __init__(self, save_file, *args, **kwargs):
 class Game(GameBase):
 
     def __init__(self, save_file, *args, **kwargs):
+        import string
         from plomrogue.misc import Terrain
         super().__init__(*args, **kwargs)
         self.changed = True
         from plomrogue.misc import Terrain
         super().__init__(*args, **kwargs)
         self.changed = True
-        self.changed_tiles = []
+        self.changed_tiles = {'fov': [], 'other': []}
         self.io = GameIO(self, save_file)
         self.io = GameIO(self, save_file)
+        self.login_requests = []
         self.tasks = {}
         self.thing_types = {}
         self.sessions = {}
         self.tasks = {}
         self.thing_types = {}
         self.sessions = {}
@@ -129,14 +138,16 @@ class Game(GameBase):
         self.map_controls = {}
         self.map_control_passwords = {}
         self.annotations = {}
         self.map_controls = {}
         self.map_control_passwords = {}
         self.annotations = {}
-        self.spawn_point = YX(0, 0), YX(0, 0)
+        self.spawn_points = []
         self.portals = {}
         self.portals = {}
+        self.intro_messages = []
         self.player_chars = string.digits + string.ascii_letters
         self.player_chars = string.digits + string.ascii_letters
+        self.players_hat_chars = {}
         self.player_char_i = -1
         self.admin_passwords = []
         self.player_char_i = -1
         self.admin_passwords = []
-        self.send_gamestate_interval = datetime.timedelta(seconds=0.04)
+        self.send_gamestate_min_interval = datetime.timedelta(seconds=0.04)
         self.last_send_gamestate = datetime.datetime.now() -\
         self.last_send_gamestate = datetime.datetime.now() -\
-            self.send_gamestate_interval
+            self.send_gamestate_min_interval
         self.terrains = {
             '.': Terrain('.', 'floor'),
             'X': Terrain('X', 'wall', blocks_light=True, blocks_sound=True,
         self.terrains = {
             '.': Terrain('.', 'floor'),
             'X': Terrain('X', 'wall', blocks_light=True, blocks_sound=True,
@@ -144,12 +155,15 @@ class Game(GameBase):
             '=': Terrain('=', 'glass', blocks_sound=True, blocks_movement=True),
             'T': Terrain('T', 'table', blocks_movement=True),
         }
             '=': Terrain('=', 'glass', blocks_sound=True, blocks_movement=True),
             'T': Terrain('T', 'table', blocks_movement=True),
         }
+        self.draw_control_presets = 1
         if os.path.exists(self.io.save_file):
             if not os.path.isfile(self.io.save_file):
                 raise GameError('save file path refers to non-file')
         if os.path.exists(self.io.save_file):
             if not os.path.isfile(self.io.save_file):
                 raise GameError('save file path refers to non-file')
+        self.io.train_parser()
 
     def register_thing_type(self, thing_type):
         self._register_object(thing_type, 'thing_type', 'Thing_')
 
     def register_thing_type(self, thing_type):
         self._register_object(thing_type, 'thing_type', 'Thing_')
+        self.io.train_parser()
 
     def register_task(self, task):
         self._register_object(task, 'task', 'Task_')
 
     def register_task(self, task):
         self._register_object(task, 'task', 'Task_')
@@ -178,19 +192,11 @@ class Game(GameBase):
                 return False
         return True
 
                 return False
         return True
 
-    def get_string_options(self, string_option_type):
-        if string_option_type == 'direction':
-            return self.map_geometry.directions
-        elif string_option_type == 'direction+here':
-            return ['HERE'] + self.map_geometry.directions
-        elif string_option_type == 'char':
-            return [c for c in
-                    string.digits + string.ascii_letters + string.punctuation + ' ']
-        elif string_option_type == 'map_geometry':
-            return ['Hex', 'Square']
-        elif string_option_type == 'thing_type':
-            return self.thing_types.keys()
-        return None
+    def get_default_spawn_point(self):
+        import random
+        if len(self.spawn_points) == 0:
+            return (YX(0, 0), YX(0, 0))
+        return random.choice(self.spawn_points)
 
     def get_map_geometry_shape(self):
         return self.map_geometry.__class__.__name__[len('MapGeometry'):]
 
     def get_map_geometry_shape(self):
         return self.map_geometry.__class__.__name__[len('MapGeometry'):]
@@ -213,7 +219,9 @@ class Game(GameBase):
         if t.carrying:
             t.uncarry()
         self.things.remove(t)
         if t.carrying:
             t.uncarry()
         self.things.remove(t)
-        self.record_fov_change(t.position)
+        self.record_change(t.position, 'other')
+        if t.blocks_light:
+            self.record_change(t.position, 'fov')
 
     def add_thing(self, type_, position, id_=0):
         t_old = None
 
     def add_thing(self, type_, position, id_=0):
         t_old = None
@@ -224,7 +232,9 @@ class Game(GameBase):
             self.things[self.things.index(t_old)] = t
         else:
             self.things += [t]
             self.things[self.things.index(t_old)] = t
         else:
             self.things += [t]
-        self.record_fov_change(t.position)
+        self.record_change(t.position, 'other')
+        if t.blocks_light:
+            self.record_change(t.position, 'fov')
         return t
 
     def send_gamestate(self, connection_id=None):
         return t
 
     def send_gamestate(self, connection_id=None):
@@ -239,14 +249,18 @@ class Game(GameBase):
             c_ids = [c_id for c_id in self.sessions]
         # Only recalc FOVs for players with ._fov = None
         player_fovs = []
             c_ids = [c_id for c_id in self.sessions]
         # Only recalc FOVs for players with ._fov = None
         player_fovs = []
-        player_fov_ids = []
+        player_ids_send_fov = []
+        player_ids_send_other = []
         for c_id in c_ids:
             player = self.get_player(c_id)
         for c_id in c_ids:
             player = self.get_player(c_id)
-            if player._fov:
-                continue
-            player.prepare_multiprocessible_fov_stencil()
-            player_fovs += [player._fov]
-            player_fov_ids += [player.id_]
+            if not player._fov:
+                player.prepare_multiprocessible_fov_stencil()
+                player_fovs += [player._fov]
+                player_ids_send_fov += [player.id_]
+            if None in (player._seen_things,
+                        player._seen_annotation_positions,
+                        player._seen_portal_positions):
+                player_ids_send_other += [player.id_]
         new_fovs = []
         single_core_until = 16  # since multiprocess has its own overhead
         if len(player_fovs) > single_core_until:
         new_fovs = []
         single_core_until = 16  # since multiprocess has its own overhead
         if len(player_fovs) > single_core_until:
@@ -257,26 +271,25 @@ class Game(GameBase):
         elif len(player_fovs) <= single_core_until:
             for fov in player_fovs:
                 new_fovs += [fov.init_terrain()]
         elif len(player_fovs) <= single_core_until:
             for fov in player_fovs:
                 new_fovs += [fov.init_terrain()]
-        for i in range(len(player_fov_ids)):
-            id_ = player_fov_ids[i]
+        for i in range(len(player_ids_send_fov)):
+            id_ = player_ids_send_fov[i]
             player = self.get_thing(id_)
             player._fov = new_fovs[i]
         for c_id in c_ids:
             self.io.send('TURN ' + str(self.turn), c_id)
             player = self.get_player(c_id)
             player = self.get_thing(id_)
             player._fov = new_fovs[i]
         for c_id in c_ids:
             self.io.send('TURN ' + str(self.turn), c_id)
             player = self.get_player(c_id)
-            if player.id_ in player_fov_ids:
+            self.io.send('PLAYERS_HAT_CHARS ' + quote(player.get_cookie_chars()),
+                         c_id)
+            self.io.send('STATS %s %s' % (player.need_for_toilet,
+                                          player.energy), c_id)
+            if player.id_ in player_ids_send_fov:
                 self.io.send('FOV %s' % quote(player.fov_stencil.terrain), c_id)
                 self.io.send('MAP %s %s %s' % (self.get_map_geometry_shape(),
                                                player.fov_stencil.geometry.size,
                                                quote(player.visible_terrain)), c_id)
                 self.io.send('MAP_CONTROL %s' % quote(player.visible_control), c_id)
                 self.io.send('FOV %s' % quote(player.fov_stencil.terrain), c_id)
                 self.io.send('MAP %s %s %s' % (self.get_map_geometry_shape(),
                                                player.fov_stencil.geometry.size,
                                                quote(player.visible_terrain)), c_id)
                 self.io.send('MAP_CONTROL %s' % quote(player.visible_control), c_id)
-            if player.id_ in player_fov_ids:
-                # FIXME: Many of the following updates are triggered by technically
-                # inappropriate calls to game.record_fov_change, since they depict
-                # states that might change independent of FOV changes.  They are
-                # collected here as a shortcut, but a cleaner way would be to
-                # differentiate the changes somehow.
-                self.io.send('PSEUDO_FOV_WIPE', c_id)
+            if player.id_ in player_ids_send_other:
+                self.io.send('OTHER_WIPE', c_id)
                 for t in player.seen_things:
                     target_yx = player.fov_stencil.target_yx(*t.position)
                     self.io.send('THING %s %s %s %s %s %s'
                 for t in player.seen_things:
                     target_yx = player.fov_stencil.target_yx(*t.position)
                     self.io.send('THING %s %s %s %s %s %s'
@@ -297,34 +310,73 @@ class Game(GameBase):
                     if hasattr(t, 'installable') and not t.portable:
                         self.io.send('THING_INSTALLED %s' % (t.id_), c_id)
                     if hasattr(t, 'design'):
                     if hasattr(t, 'installable') and not t.portable:
                         self.io.send('THING_INSTALLED %s' % (t.id_), c_id)
                     if hasattr(t, 'design'):
-                        self.io.send('THING_HAT %s %s' % (t.id_,
-                                                          quote(t.design)), c_id)
+                        self.io.send('THING_DESIGN %s %s %s'
+                                     % (t.id_, t.design_size, quote(t.design)),
+                                     c_id)
                 for t in [t for t in player.seen_things if t.carrying]:
                     # send this last so all carryable things are already created
                     self.io.send('THING_CARRYING %s %s' % (t.id_, t.carrying.id_),
                                  c_id)
                 for t in [t for t in player.seen_things if t.carrying]:
                     # send this last so all carryable things are already created
                     self.io.send('THING_CARRYING %s %s' % (t.id_, t.carrying.id_),
                                  c_id)
-                for big_yx in self.portals:
-                    for little_yx in [little_yx for little_yx in self.portals[big_yx]
-                                      if player.fov_test(big_yx, little_yx)]:
-                        target_yx = player.fov_stencil.target_yx(big_yx, little_yx)
-                        portal = self.portals[big_yx][little_yx]
-                        self.io.send('PORTAL %s %s' % (target_yx, quote(portal)), c_id)
-                for big_yx in self.annotations:
-                    for little_yx in [little_yx for little_yx in self.annotations[big_yx]
-                                      if player.fov_test(big_yx, little_yx)]:
-                        target_yx = player.fov_stencil.target_yx(big_yx, little_yx)
-                        annotation = self.annotations[big_yx][little_yx]
-                        self.io.send('ANNOTATION %s %s' % (target_yx,
-                                                           quote(annotation)), c_id)
+                for position in player.seen_portal_positions:
+                    target_yx = player.fov_stencil.target_yx(position[0],
+                                                             position[1])
+                    portal = self.portals[position[0]][position[1]]
+                    self.io.send('PORTAL %s %s' % (target_yx, quote(portal)), c_id)
+                for position in player.seen_annotation_positions:
+                    target_yx = player.fov_stencil.target_yx(position[0],
+                                                             position[1])
+                    annotation = self.annotations[position[0]][position[1]]
+                    self.io.send('ANNOTATION %s %s' % (target_yx,
+                                                       quote(annotation)), c_id)
             self.io.send('GAME_STATE_COMPLETE', c_id)
 
             self.io.send('GAME_STATE_COMPLETE', c_id)
 
-    def record_fov_change(self, position):
+    def record_change(self, position, type_):
         big_yx, little_yx = position
         big_yx, little_yx = position
-        self.changed_tiles += [self.map_geometry.undouble_yxyx(big_yx,
-                                                               little_yx)]
+        self.changed_tiles[type_] += [self.map_geometry.undouble_yxyx(big_yx,
+                                                                      little_yx)]
         self.changed = True
 
         self.changed = True
 
+    def login(self, nick, connection_id):
+        login_limit_filename = 'login_limit'
+        if os.path.exists(login_limit_filename):
+            with open(login_limit_filename, 'r') as f:
+                lines = f.readlines()
+                login_limit = int(lines[0])
+                if len(self.sessions) > login_limit - 1:
+                    print('DEBUG LOGIN TOO MANY FOR', nick, connection_id)
+                    self.io.send('CHAT "sorry, too many users currently '
+                                 'logged in, try again later '
+                                 'by re-entering your name"', connection_id)
+                    return
+        for t in [t for t in self.things
+                  if t.type_ == 'Player' and t.name == nick]:
+            self.io.send('GAME_ERROR ' + quote('name already in use'),
+                         connection_id)
+            return
+        t = self.add_thing('Player', self.get_default_spawn_point())
+        t.name = nick
+        t.thing_char = self.get_next_player_char()
+        self.sessions[connection_id] = {
+            'thing_id': t.id_,
+            'status': 'player'
+        }
+        print('DEBUG LOGIN', t.name, len(self.sessions))
+        self.io.send('PLAYER_ID %s' % t.id_, connection_id)
+        self.io.send('LOGIN_OK', connection_id)
+        for msg in self.intro_messages:
+            self.io.send('CHAT ' + quote(msg), connection_id)
+        self.io.send('CHAT ' + quote(t.name + ' entered the map.'))
+        for s in [s for s in self.things
+                  if s.type_ == 'SpawnPoint' and s.name == t.name]:
+            t.position = s.position
+            if s.temporary:
+                self.remove_thing(s)
+                break
+        t.try_to_sit()
+
     def run_tick(self):
     def run_tick(self):
+
+        # update player sessions
         to_delete = []
         for connection_id in self.sessions:
             connection_id_found = False
         to_delete = []
         for connection_id in self.sessions:
             connection_id_found = False
@@ -336,11 +388,19 @@ class Game(GameBase):
                 t = self.get_player(connection_id)
                 if hasattr(t, 'name'):
                     self.io.send('CHAT ' + quote(t.name + ' left the map.'))
                 t = self.get_player(connection_id)
                 if hasattr(t, 'name'):
                     self.io.send('CHAT ' + quote(t.name + ' left the map.'))
+                spawn_point = self.add_thing('SpawnPoint', t.position)
+                spawn_point.temporary = True
+                spawn_point.name = t.name
+                print('DEBUG LEFT MAP', t.name)
                 self.remove_thing(t)
                 to_delete += [connection_id]
         for connection_id in to_delete:
             del self.sessions[connection_id]
                 self.remove_thing(t)
                 to_delete += [connection_id]
         for connection_id in to_delete:
             del self.sessions[connection_id]
-            # self.changed = True  already handled by remove_thing
+        while len(self.login_requests) > 0:
+            login_request = self.login_requests.pop()
+            self.login(login_request[0], login_request[1])
+
+        # update game state
         for t in [t for t in self.things]:
             if t in self.things:
                 try:
         for t in [t for t in self.things]:
             if t in self.things:
                 try:
@@ -353,13 +413,18 @@ class Game(GameBase):
                     for connection_id in [c_id for c_id in self.sessions
                                           if self.sessions[c_id]['thing_id'] == t.id_]:
                         self.io.send('PLAY_ERROR ' + quote(str(e)), connection_id)
                     for connection_id in [c_id for c_id in self.sessions
                                           if self.sessions[c_id]['thing_id'] == t.id_]:
                         self.io.send('PLAY_ERROR ' + quote(str(e)), connection_id)
+
+        # send gamestate if it makes sense at this point
         if self.changed:
             self.turn += 1
             # send_gamestate() can be rather expensive, due to among other reasons
             # re-calculating players' FOVs, so don't send it out too often
             if self.last_send_gamestate < \
         if self.changed:
             self.turn += 1
             # send_gamestate() can be rather expensive, due to among other reasons
             # re-calculating players' FOVs, so don't send it out too often
             if self.last_send_gamestate < \
-               datetime.datetime.now() -self.send_gamestate_interval:
-                if len(self.changed_tiles) > 0:
+               datetime.datetime.now() - self.send_gamestate_min_interval:
+                n_changes = 0
+                for type_ in self.changed_tiles:
+                    n_changes += len(self.changed_tiles[type_])
+                if n_changes > 0:
                     for t in [t for t in self.things if t.type_ == 'Player']:
                         fov_radius = 12  # TODO: un-hardcode
                         absolute_position =\
                     for t in [t for t in self.things if t.type_ == 'Player']:
                         fov_radius = 12  # TODO: un-hardcode
                         absolute_position =\
@@ -370,18 +435,19 @@ class Game(GameBase):
                         x_range_start = absolute_position.x - fov_radius
                         x_range_end = absolute_position.x + fov_radius
                         # TODO: refactor with SourcedMap.inside?
                         x_range_start = absolute_position.x - fov_radius
                         x_range_end = absolute_position.x + fov_radius
                         # TODO: refactor with SourcedMap.inside?
-                        for position in self.changed_tiles:
-                            if position.y < y_range_start\
-                               or position.y > y_range_end:
-                                continue
-                            if position.x < x_range_start\
-                               or position.x > x_range_end:
-                                continue
-                            t.invalidate_map_view()
-                            break
+                        for type_ in self.changed_tiles:
+                            for position in self.changed_tiles[type_]:
+                                if position.y < y_range_start\
+                                   or position.y > y_range_end:
+                                    continue
+                                if position.x < x_range_start\
+                                   or position.x > x_range_end:
+                                    continue
+                                t.invalidate(type_)
+                                break
                 self.send_gamestate()
                 self.changed = False
                 self.send_gamestate()
                 self.changed = False
-                self.changed_tiles = []
+                self.changed_tiles = {'fov': [], 'other': []}
                 self.save()
                 self.last_send_gamestate = datetime.datetime.now()
 
                 self.save()
                 self.last_send_gamestate = datetime.datetime.now()
 
@@ -446,7 +512,7 @@ class Game(GameBase):
         return self.get_foo_blockers('movement')
 
     def get_flatland(self):
         return self.get_foo_blockers('movement')
 
     def get_flatland(self):
-        for t in self.terrains.values:
+        for t in self.terrains.values():
             if not t.blocks_movement:
                 return t.character
 
             if not t.blocks_movement:
                 return t.character
 
@@ -458,6 +524,9 @@ class Game(GameBase):
         with open(self.io.save_file, 'w') as f:
             write(f, 'TURN %s' % self.turn)
             map_geometry_shape = self.get_map_geometry_shape()
         with open(self.io.save_file, 'w') as f:
             write(f, 'TURN %s' % self.turn)
             map_geometry_shape = self.get_map_geometry_shape()
+            # must come before MAP, otherwise first get_map uses the default
+            # TODO: refactor into MAP
+            write(f, 'MAP_CONTROL_PRESETS %s' % self.draw_control_presets)
             write(f, 'MAP %s %s' % (map_geometry_shape, self.map_geometry.size,))
             for terrain in self.terrains.values():
                 write(f, 'TERRAIN %s %s %s %s %s' % (quote(terrain.character),
             write(f, 'MAP %s %s' % (map_geometry_shape, self.map_geometry.size,))
             for terrain in self.terrains.values():
                 write(f, 'TERRAIN %s %s %s %s %s' % (quote(terrain.character),
@@ -465,6 +534,10 @@ class Game(GameBase):
                                                      int(terrain.blocks_light),
                                                      int(terrain.blocks_sound),
                                                      int(terrain.blocks_movement)))
                                                      int(terrain.blocks_light),
                                                      int(terrain.blocks_sound),
                                                      int(terrain.blocks_movement)))
+                if len(terrain.tags) > 0:
+                    for tag in terrain.tags:
+                        write(f, 'TERRAIN_TAG %s %s' % (quote(terrain.character),
+                                                        quote(tag)))
             for big_yx in [yx for yx in self.maps if self.maps[yx].modified]:
                 for y, line in self.maps[big_yx].lines():
                     write(f, 'MAP_LINE %s %5s %s' % (big_yx, y, quote(line)))
             for big_yx in [yx for yx in self.maps if self.maps[yx].modified]:
                 for y, line in self.maps[big_yx].lines():
                     write(f, 'MAP_LINE %s %5s %s' % (big_yx, y, quote(line)))
@@ -491,6 +564,9 @@ class Game(GameBase):
             for name in self.hats:
                 write(f, 'GOD_PLAYER_HAT %s %s' % (quote(name),
                                                    quote(self.hats[name])))
             for name in self.hats:
                 write(f, 'GOD_PLAYER_HAT %s %s' % (quote(name),
                                                    quote(self.hats[name])))
+            for name in self.players_hat_chars:
+                write(f, 'GOD_PLAYERS_HAT_CHARS %s %s' %
+                      (quote(name), quote(self.players_hat_chars[name])))
             for t in [t for t in self.things if not t.type_ == 'Player']:
                 write(f, 'THING %s %s %s %s' % (t.position[0],
                                                 t.position[1], t.type_, t.id_))
             for t in [t for t in self.things if not t.type_ == 'Player']:
                 write(f, 'THING %s %s %s %s' % (t.position[0],
                                                 t.position[1], t.type_, t.id_))
@@ -499,11 +575,13 @@ class Game(GameBase):
                     write(f, 'GOD_THING_NAME %s %s' % (t.id_, quote(t.name)))
                 if hasattr(t, 'installable') and (not t.portable):
                     write(f, 'THING_INSTALLED %s' % t.id_)
                     write(f, 'GOD_THING_NAME %s %s' % (t.id_, quote(t.name)))
                 if hasattr(t, 'installable') and (not t.portable):
                     write(f, 'THING_INSTALLED %s' % t.id_)
-                if t.type_ == 'Door' and t.blocking:
-                    write(f, 'THING_DOOR_CLOSED %s' % t.id_)
-                elif t.type_ == 'Hat':
-                    write(f, 'THING_HAT_DESIGN %s %s' % (t.id_,
-                                                         quote(t.design)))
+                if hasattr(t, 'design'):
+                    if t.type_ != 'Hat':
+                        write(f, 'GOD_THING_DESIGN_SIZE %s %s' % (t.id_,
+                                                                  t.design_size))
+                    write(f, 'GOD_THING_DESIGN %s %s' % (t.id_, quote(t.design)))
+                if t.type_ == 'Door' and t.blocks_movement:
+                    write(f, 'THING_DOOR_CLOSED %s %s' % (t.id_, int(t.locked)))
                 elif t.type_ == 'MusicPlayer':
                     write(f, 'THING_MUSICPLAYER_SETTINGS %s %s %s %s' %
                           (t.id_, int(t.playing), t.playlist_index, int(t.repeat)))
                 elif t.type_ == 'MusicPlayer':
                     write(f, 'THING_MUSICPLAYER_SETTINGS %s %s %s %s' %
                           (t.id_, int(t.playing), t.playlist_index, int(t.repeat)))
@@ -512,8 +590,31 @@ class Game(GameBase):
                               (t.id_, quote(item[0]), item[1]))
                 elif t.type_ == 'Bottle' and not t.full:
                     write(f, 'THING_BOTTLE_EMPTY %s' % t.id_)
                               (t.id_, quote(item[0]), item[1]))
                 elif t.type_ == 'Bottle' and not t.full:
                     write(f, 'THING_BOTTLE_EMPTY %s' % t.id_)
-            write(f, 'SPAWN_POINT %s %s' % (self.spawn_point[0],
-                                            self.spawn_point[1]))
+                elif t.type_ == 'DoorKey':
+                    write(f, 'THING_DOOR_KEY %s %s' % (t.id_, t.door.id_))
+                elif t.type_ == 'Crate':
+                    for item in t.content:
+                        write(f, 'THING_CRATE_ITEM %s %s' % (t.id_, item.id_))
+                elif t.type_ == 'SpawnPoint':
+                    timestamp = 0
+                    if t.temporary:
+                        timestamp = int(t.created_at.timestamp())
+                    write(f, 'THING_SPAWNPOINT_CREATED %s %s' % (t.id_,
+                                                                 timestamp))
+            next_thing_id = self.new_thing_id()
+            for t in [t for t in self.things if t.type_ == 'Player']:
+                write(f, 'THING %s %s SpawnPoint %s'
+                      % (t.position[0], t.position[1], next_thing_id))
+                write(f, 'GOD_THING_NAME %s %s' % (next_thing_id, t.name))
+                write(f, 'THING_SPAWNPOINT_CREATED %s %s'
+                      % (next_thing_id, int(datetime.datetime.now().timestamp())))
+                next_thing_id += 1
+            for s in self.spawn_points:
+                write(f, 'SPAWN_POINT %s %s' % (s[0], s[1]))
+            for msg in self.intro_messages:
+                write(f, 'INTRO_MSG %s' % quote(msg))
+
+
 
     def get_map(self, big_yx, type_='normal'):
         if type_ == 'normal':
 
     def get_map(self, big_yx, type_='normal'):
         if type_ == 'normal':
@@ -523,7 +624,7 @@ class Game(GameBase):
         if big_yx not in maps:
             maps[big_yx] = SaveableMap(self.map_geometry)
             if type_ == 'control':
         if big_yx not in maps:
             maps[big_yx] = SaveableMap(self.map_geometry)
             if type_ == 'control':
-                maps[big_yx].draw_presets(big_yx.y % 2)
+                maps[big_yx].draw_presets(big_yx, self.draw_control_presets)
         return maps[big_yx]
 
     def new_world(self, map_geometry):
         return maps[big_yx]
 
     def new_world(self, map_geometry):
@@ -532,8 +633,8 @@ class Game(GameBase):
         self.annotations = {}
         self.portals = {}
         self.admin_passwords = []
         self.annotations = {}
         self.portals = {}
         self.admin_passwords = []
-        self.spawn_point = YX(0, 0), YX(0, 0)
         self.map_geometry = map_geometry
         self.map_geometry = map_geometry
+        self.io.train_parser()
         self.map_control_passwords = {'X': 'secret'}
         self.get_map(YX(0, 0))
         self.get_map(YX(0, 0), 'control')
         self.map_control_passwords = {'X': 'secret'}
         self.get_map(YX(0, 0))
         self.get_map(YX(0, 0), 'control')