home · contact · privacy
Fix buggy task code leading to endless collision flicker.
[plomrogue2] / plomrogue / game.py
index d7be311f3d54dc02535fa1471c510d2e8db03263..ff761556e0da3f4299411ad0ae27051db7957a3f 100755 (executable)
@@ -1,10 +1,9 @@
-from plomrogue.tasks import (Task_WAIT, Task_MOVE, Task_WRITE,
-                             Task_FLATTEN_SURROUNDINGS)
 from plomrogue.errors import GameError, PlayError
 from plomrogue.io import GameIO
 from plomrogue.misc import quote
 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex, Map
 import string
 from plomrogue.errors import GameError, PlayError
 from plomrogue.io import GameIO
 from plomrogue.misc import quote
 from plomrogue.mapping import YX, MapGeometrySquare, MapGeometryHex, Map
 import string
+import datetime
 
 
 
 
 
 
@@ -60,15 +59,15 @@ class SaveableMap(Map):
             self.set_line(3, 'X' * self.geometry.size.x)
             self.set_line(4, 'X' * self.geometry.size.x)
             for y in range(self.geometry.size.y):
             self.set_line(3, 'X' * self.geometry.size.x)
             self.set_line(4, 'X' * self.geometry.size.x)
             for y in range(self.geometry.size.y):
-                self[YX(y,0)] = 'X'
-                self[YX(y,1)] = 'X'
-                self[YX(y,2)] = 'X'
-                self[YX(y,3)] = 'X'
-                self[YX(y,4)] = 'X'
+                self[YX(y, 0)] = 'X'
+                self[YX(y, 1)] = 'X'
+                self[YX(y, 2)] = 'X'
+                self[YX(y, 3)] = 'X'
+                self[YX(y, 4)] = 'X'
         elif type(self.geometry) == MapGeometryHex:
             # TODO: for this to work we need a map side length divisible by 6.
 
         elif type(self.geometry) == MapGeometryHex:
             # TODO: for this to work we need a map side length divisible by 6.
 
-            def draw_grid(offset=YX(0,0)):
+            def draw_grid(offset=YX(0, 0)):
                 dirs = ('DOWNRIGHT', 'RIGHT', 'UPRIGHT', 'RIGHT')
 
                 def draw_snake(start):
                 dirs = ('DOWNRIGHT', 'RIGHT', 'UPRIGHT', 'RIGHT')
 
                 def draw_snake(start):
@@ -91,20 +90,23 @@ class SaveableMap(Map):
 
                 if alternate_hex:
                     draw_snake(offset + YX(0, 0))
 
                 if alternate_hex:
                     draw_snake(offset + YX(0, 0))
-                draw_snake(offset + YX((0 + alternate_hex) * distance, -int(1.5*distance)))
-                draw_snake(offset + YX((1 + alternate_hex) * distance, 0))
-                draw_snake(offset + YX((2 + alternate_hex) * distance, -int(1.5*distance)))
+                draw_snake(offset + YX((0 + alternate_hex) * distance,
+                           -int(1.5 * distance)))
+                draw_snake(offset + YX((1 + alternate_hex) * distance,
+                           0))
+                draw_snake(offset + YX((2 + alternate_hex) * distance,
+                           -int(1.5 * distance)))
 
             distance = self.geometry.size.y // 3
             draw_grid()
 
             distance = self.geometry.size.y // 3
             draw_grid()
-            draw_grid(YX(2,0))
-            draw_grid(YX(0,2))
-            draw_grid(YX(1,0))
-            draw_grid(YX(0,1))
-            draw_grid(YX(-1,0))
-            draw_grid(YX(0,-1))
-            draw_grid(YX(-2,0))
-            draw_grid(YX(0,-2))
+            draw_grid(YX(2, 0))
+            draw_grid(YX(0, 2))
+            draw_grid(YX(1, 0))
+            draw_grid(YX(0, 1))
+            draw_grid(YX(-1, 0))
+            draw_grid(YX(0, -1))
+            draw_grid(YX(-2, 0))
+            draw_grid(YX(0, -2))
         self.modified = old_modified
 
 
         self.modified = old_modified
 
 
@@ -123,10 +125,14 @@ 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.portals = {}
         self.player_chars = string.digits + string.ascii_letters
         self.player_char_i = -1
         self.admin_passwords = []
         self.portals = {}
         self.player_chars = string.digits + string.ascii_letters
         self.player_char_i = -1
         self.admin_passwords = []
+        self.send_gamestate_interval = datetime.timedelta(seconds=0.04)
+        self.last_send_gamestate = datetime.datetime.now() -\
+            self.send_gamestate_interval
         self.terrains = {
             '.': 'floor',
             'X': 'wall',
         self.terrains = {
             '.': 'floor',
             'X': 'wall',
@@ -157,6 +163,12 @@ class Game(GameBase):
                 print("FILE INPUT LINE %5s: %s" % (i, line), end='')
                 self.io.handle_input(line, god_mode=True)
 
                 print("FILE INPUT LINE %5s: %s" % (i, line), end='')
                 self.io.handle_input(line, god_mode=True)
 
+    def can_do_thing_with_pw(self, thing, pw):
+        if thing.protection in self.map_control_passwords.keys():
+            if pw != self.map_control_passwords[thing.protection]:
+                return False
+        return True
+
     def can_do_tile_with_pw(self, big_yx, little_yx, pw):
         map_control = self.get_map(big_yx, 'control')
         tile_class = map_control[little_yx]
     def can_do_tile_with_pw(self, big_yx, little_yx, pw):
         map_control = self.get_map(big_yx, 'control')
         tile_class = map_control[little_yx]
@@ -182,7 +194,7 @@ class Game(GameBase):
         return self.map_geometry.__class__.__name__[len('MapGeometry'):]
 
     def get_player(self, connection_id):
         return self.map_geometry.__class__.__name__[len('MapGeometry'):]
 
     def get_player(self, connection_id):
-        if not connection_id in self.sessions:
+        if connection_id not in self.sessions:
             return None
         player = self.get_thing(self.sessions[connection_id]['thing_id'])
         return player
             return None
         player = self.get_thing(self.sessions[connection_id]['thing_id'])
         return player
@@ -191,7 +203,23 @@ class Game(GameBase):
         """Send out game state data relevant to clients."""
 
         self.io.send('TURN ' + str(self.turn))
         """Send out game state data relevant to clients."""
 
         self.io.send('TURN ' + str(self.turn))
-        for c_id in self.sessions:
+        from plomrogue.mapping import FovMap
+        import multiprocessing
+        pool = multiprocessing.Pool()
+        players = []
+        c_ids = [c_id for c_id in self.sessions]
+        for c_id in c_ids:
+            players += [self.get_player(c_id)]
+        player_fovs = []
+        for player in players:
+            player.prepare_multiprocessible_fov_stencil()
+            player_fovs += [player._fov]
+        new_fovs = pool.map(FovMap.init_terrain, [fov for fov in player_fovs])
+        for i in range(len(players)):
+            players[i]._fov = new_fovs[i]
+        pool.close()
+        pool.join()
+        for c_id in c_ids:
             player = self.get_player(c_id)
             visible_terrain = player.fov_stencil_map()
             self.io.send('FOV %s' % quote(player.fov_stencil.terrain), c_id)
             player = self.get_player(c_id)
             visible_terrain = player.fov_stencil_map()
             self.io.send('FOV %s' % quote(player.fov_stencil.terrain), c_id)
@@ -202,12 +230,13 @@ class Game(GameBase):
             self.io.send('MAP_CONTROL %s' % quote(visible_control), c_id)
             for t in [t for t in self.things if player.fov_test(*t.position)]:
                 target_yx = player.fov_stencil.target_yx(*t.position)
             self.io.send('MAP_CONTROL %s' % quote(visible_control), c_id)
             for t in [t for t in self.things if player.fov_test(*t.position)]:
                 target_yx = player.fov_stencil.target_yx(*t.position)
-                self.io.send('THING %s %s %s' % (target_yx, t.type_, t.id_), c_id)
+                self.io.send('THING %s %s %s %s' % (target_yx, t.type_,
+                                                    quote(t.protection), t.id_), c_id)
                 if hasattr(t, 'name'):
                     self.io.send('THING_NAME %s %s' % (t.id_, quote(t.name)), c_id)
                 if hasattr(t, 'name'):
                     self.io.send('THING_NAME %s %s' % (t.id_, quote(t.name)), c_id)
-                if hasattr(t, 'player_char'):
+                if hasattr(t, 'thing_char'):
                     self.io.send('THING_CHAR %s %s' % (t.id_,
                     self.io.send('THING_CHAR %s %s' % (t.id_,
-                                                       quote(t.player_char)), c_id)
+                                                       quote(t.thing_char)), 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)]:
             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)]:
@@ -218,7 +247,6 @@ class Game(GameBase):
                 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)
                 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_HINT %s' % (target_yx,), c_id)
         self.io.send('GAME_STATE_COMPLETE')
 
                     self.io.send('ANNOTATION_HINT %s' % (target_yx,), c_id)
         self.io.send('GAME_STATE_COMPLETE')
 
@@ -253,9 +281,14 @@ class Game(GameBase):
                         self.io.send('PLAY_ERROR ' + quote(str(e)), connection_id)
         if self.changed:
             self.turn += 1
                         self.io.send('PLAY_ERROR ' + quote(str(e)), connection_id)
         if self.changed:
             self.turn += 1
-            self.send_gamestate()
-            self.changed = False
-            self.save()
+            # send_gamestate() can be rather expensive, due to among other reasons
+            # re-calculating each player's FOV, so don't send it out too often
+            if self.last_send_gamestate < \
+               datetime.datetime.now() -self.send_gamestate_interval:
+                self.send_gamestate()
+                self.changed = False
+                self.save()
+                self.last_send_gamestate = datetime.datetime.now()
 
     def get_command(self, command_name):
 
 
     def get_command(self, command_name):
 
@@ -302,45 +335,58 @@ class Game(GameBase):
 
     def save(self):
 
 
     def save(self):
 
-      def write(f, msg):
-          f.write(msg + '\n')
-
-      with open(self.io.save_file, 'w') as f:
-          write(f, 'TURN %s' % self.turn)
-          map_geometry_shape = self.get_map_geometry_shape()
-          write(f, 'MAP %s %s' % (map_geometry_shape, self.map_geometry.size,))
-          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 self.annotations:
-              for little_yx in self.annotations[big_yx]:
-                  write(f, 'GOD_ANNOTATE %s %s %s' %
-                        (big_yx, little_yx, quote(self.annotations[big_yx][little_yx])))
-          for big_yx in self.portals:
-              for little_yx in self.portals[big_yx]:
-                  write(f, 'GOD_PORTAL %s %s %s' % (big_yx, little_yx,
-                                                    quote(self.portals[big_yx][little_yx])))
-          for big_yx in [yx for yx in self.map_controls
-                         if self.map_controls[yx].modified]:
-              for y, line in self.map_controls[big_yx].lines():
-                  write(f, 'MAP_CONTROL_LINE %s %5s %s' % (big_yx, y, quote(line)))
-          for tile_class in self.map_control_passwords:
-              write(f, 'MAP_CONTROL_PW %s %s' % (tile_class,
-                                                 self.map_control_passwords[tile_class]))
-          for pw in self.admin_passwords:
-                  write(f, 'ADMIN_PASSWORD %s' % pw)
-          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_))
-              if hasattr(t, 'name'):
-                  write(f, 'THING_NAME %s %s' % (t.id_, quote(t.name)))
+        def write(f, msg):
+            f.write(msg + '\n')
+
+        with open(self.io.save_file, 'w') as f:
+            write(f, 'TURN %s' % self.turn)
+            map_geometry_shape = self.get_map_geometry_shape()
+            write(f, 'MAP %s %s' % (map_geometry_shape, self.map_geometry.size,))
+            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 self.annotations:
+                for little_yx in self.annotations[big_yx]:
+                    write(f, 'GOD_ANNOTATE %s %s %s' %
+                          (big_yx, little_yx, quote(self.annotations[big_yx][little_yx])))
+            for big_yx in self.portals:
+                for little_yx in self.portals[big_yx]:
+                    write(f, 'GOD_PORTAL %s %s %s' % (big_yx, little_yx,
+                                                      quote(self.portals[big_yx][little_yx])))
+            for big_yx in [yx for yx in self.map_controls
+                           if self.map_controls[yx].modified]:
+                for y, line in self.map_controls[big_yx].lines():
+                    write(f, 'MAP_CONTROL_LINE %s %5s %s' % (big_yx, y, quote(line)))
+            for tile_class in self.map_control_passwords:
+                write(f, 'MAP_CONTROL_PW %s %s' % (tile_class,
+                                                   self.map_control_passwords[tile_class]))
+            for pw in self.admin_passwords:
+                write(f, 'ADMIN_PASSWORD %s' % pw)
+            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_))
+                write(f, 'GOD_THING_PROTECTION %s %s' % (t.id_, quote(t.protection)))
+                if hasattr(t, 'name'):
+                    write(f, 'GOD_THING_NAME %s %s' % (t.id_, quote(t.name)))
+                if t.type_ == 'Door' and t.blocking:
+                    write(f, 'THING_DOOR_CLOSED %s' % t.id_)
+                elif t.type_ == 'MusicPlayer':
+                    write(f, 'THING_MUSICPLAYER_SETTINGS %s %s %s %s' %
+                          (t.id_, int(t.playing), t.playlist_index, int(t.repeat)))
+                    for item in t.playlist:
+                        write(f, 'THING_MUSICPLAYER_PLAYLIST_ITEM %s %s %s' %
+                              (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]))
 
     def get_map(self, big_yx, type_='normal'):
         if type_ == 'normal':
             maps = self.maps
         elif type_ == 'control':
             maps = self.map_controls
 
     def get_map(self, big_yx, type_='normal'):
         if type_ == 'normal':
             maps = self.maps
         elif type_ == 'control':
             maps = self.map_controls
-        if not big_yx in maps:
+        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] = SaveableMap(self.map_geometry)
             if type_ == 'control':
                 maps[big_yx].draw_presets(big_yx.y % 2)
@@ -352,8 +398,9 @@ 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_control_passwords = {'X': 'secret'}
         self.map_geometry = map_geometry
         self.map_control_passwords = {'X': 'secret'}
-        self.get_map(YX(0,0))
-        self.get_map(YX(0,0), 'control')
+        self.get_map(YX(0, 0))
+        self.get_map(YX(0, 0), 'control')
         self.annotations = {}
         self.annotations = {}