From 5cd44408532e23648ddcd1d59004a9dae59694af Mon Sep 17 00:00:00 2001
From: Christian Heller <c.heller@plomlompom.de>
Date: Wed, 16 Dec 2020 20:57:23 +0100
Subject: [PATCH] Add door keys and door locking.

---
 plomrogue/commands.py | 20 ++++++++++++++++++--
 plomrogue/game.py     |  4 +++-
 plomrogue/tasks.py    | 19 +++++++++++++++++--
 plomrogue/things.py   | 23 +++++++++++++++++++++--
 rogue_chat.py         |  6 ++++--
 5 files changed, 63 insertions(+), 9 deletions(-)

diff --git a/plomrogue/commands.py b/plomrogue/commands.py
index 653fc74..074cc34 100644
--- a/plomrogue/commands.py
+++ b/plomrogue/commands.py
@@ -277,14 +277,16 @@ def cmd_GOD_THING_PROTECTION(game, thing_id, protection_char):
     t.protection = protection_char
 cmd_GOD_THING_PROTECTION.argtypes = 'int:pos char'
 
-def cmd_THING_DOOR_CLOSED(game, thing_id):
+def cmd_THING_DOOR_CLOSED(game, thing_id, locked):
     t = game.get_thing(thing_id)
     if not t:
         raise GameError('thing of ID %s not found' % thing_id)
     if not t.type_ == 'Door':
         raise GameError('thing of ID %s not door' % thing_id)
     t.close()
-cmd_THING_DOOR_CLOSED.argtypes = 'int:pos'
+    if locked:
+        t.lock()
+cmd_THING_DOOR_CLOSED.argtypes = 'int:pos bool'
 
 def cmd_THING_MUSICPLAYER_SETTINGS(game, thing_id, playing, index, repeat):
     t = game.get_thing(thing_id)
@@ -380,3 +382,17 @@ def cmd_THING_HAT_DESIGN(game, thing_id, design):
         raise GameError('thing of ID %s not a hat' % thing_id)
     t.design = design
 cmd_THING_HAT_DESIGN.argtypes = 'int:pos string'
+
+def cmd_THING_DOOR_KEY(game, key_id, door_id):
+    key = game.get_thing(key_id)
+    if not key:
+        raise GameError('thing of ID %s not found' % key_id)
+    if key.type_ != 'DoorKey':
+        raise GameError('thing of ID %s not a door key' % key_id)
+    door = game.get_thing(door_id)
+    if not door:
+        raise GameError('thing of ID %s not found' % door_id)
+    if door.type_ != 'Door':
+        raise GameError('thing of ID %s not a door' % key_id)
+    key.door = door
+cmd_THING_DOOR_KEY.argtypes = 'int:pos int:pos'
diff --git a/plomrogue/game.py b/plomrogue/game.py
index ec663b7..d3ab1e7 100755
--- a/plomrogue/game.py
+++ b/plomrogue/game.py
@@ -545,7 +545,7 @@ class Game(GameBase):
                 if hasattr(t, 'installable') and (not t.portable):
                     write(f, 'THING_INSTALLED %s' % t.id_)
                 if t.type_ == 'Door' and t.blocks_movement:
-                    write(f, 'THING_DOOR_CLOSED %s' % t.id_)
+                    write(f, 'THING_DOOR_CLOSED %s %s' % (t.id_, int(t.locked)))
                 elif t.type_ == 'Hat':
                     write(f, 'THING_HAT_DESIGN %s %s' % (t.id_,
                                                          quote(t.design)))
@@ -557,6 +557,8 @@ 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_)
+                elif t.type_ == 'DoorKey':
+                    write(f, 'THING_DOOR_KEY %s %s' % (t.id_, t.door.id_))
             write(f, 'SPAWN_POINT %s %s' % (self.spawn_point[0],
                                             self.spawn_point[1]))
 
diff --git a/plomrogue/tasks.py b/plomrogue/tasks.py
index 4cf8700..6ee2731 100644
--- a/plomrogue/tasks.py
+++ b/plomrogue/tasks.py
@@ -190,9 +190,19 @@ class Task_DOOR(Task):
     def check(self):
         action_radius = list(self.thing.game.map_geometry.
                              get_neighbors_yxyx(self.thing.position).values())
-        if len([t for t in self.thing.game.things if
-                  t.type_ == 'Door' and t.position in action_radius]) == 0:
+        reachable_doors = [t for t in self.thing.game.things if
+                           t.type_ == 'Door' and t.position in action_radius]
+        if len(reachable_doors) == 0:
             raise PlayError('not standing next to a door to open/close')
+        for door in reachable_doors:
+            if not door.blocks_movement:
+                return
+            if not door.locked:
+                return
+            if self.thing.carrying and self.thing.carrying.type_ == 'DoorKey'\
+               and self.thing.carrying.door == door:
+                return
+        raise PlayError('cannot open locked door without its key')
 
     def do(self):
         action_radius = list(self.thing.game.map_geometry.
@@ -203,6 +213,11 @@ class Task_DOOR(Task):
                 t.open()
             else:
                 t.close()
+                if self.thing.carrying and\
+                   self.thing.carrying.type_ == 'DoorKey' and\
+                   self.thing.carrying.door == t:
+                    self.thing.send_msg('CHAT "You lock the door."')
+                    t.lock()
             self.thing.game.record_change(t.position, 'other')
             self.thing.game.record_change(t.position, 'fov')
 
diff --git a/plomrogue/things.py b/plomrogue/things.py
index 98d056a..3686ab0 100644
--- a/plomrogue/things.py
+++ b/plomrogue/things.py
@@ -127,8 +127,8 @@ class ThingSpawner(Thing):
     def proceed(self):
         for t in [t for t in self.game.things
                   if t != self and t.position == self.position]:
-            return
-        self.game.add_thing(self.child_type, self.position)
+            return None
+        return self.game.add_thing(self.child_type, self.position)
 
 
 
@@ -164,16 +164,31 @@ class ThingInstallable(Thing):
 class Thing_DoorSpawner(ThingSpawner):
     child_type = 'Door'
 
+    def proceed(self):
+        door = super().proceed()
+        if door:
+            key = self.game.add_thing('DoorKey', self.position)
+            key.door = door
+
+
+
+class Thing_DoorKey(Thing):
+    portable = True
+    symbol_hint = 'k'
+
+
 
 
 class Thing_Door(ThingInstallable):
     symbol_hint = 'D'
     blocks_movement = False
+    locked = False
 
     def open(self):
         self.blocks_movement = False
         self.blocks_light = False
         self.blocks_sound = False
+        self.locked = False
         del self.thing_char
 
     def close(self):
@@ -182,6 +197,10 @@ class Thing_Door(ThingInstallable):
         self.blocks_sound = True
         self.thing_char = '#'
 
+    def lock(self):
+        self.locked = True
+        self.thing_char = 'L'
+
 
 
 class Thing_Psychedelic(Thing):
diff --git a/rogue_chat.py b/rogue_chat.py
index 31e53e4..a3654b0 100755
--- a/rogue_chat.py
+++ b/rogue_chat.py
@@ -15,7 +15,7 @@ from plomrogue.commands import (cmd_ALL, cmd_LOGIN, cmd_NICK, cmd_PING, cmd_THIN
                                 cmd_THING_BOTTLE_EMPTY, cmd_PLAYER_FACE,
                                 cmd_GOD_PLAYER_FACE, cmd_GOD_PLAYER_HAT,
                                 cmd_GOD_PLAYERS_HAT_CHARS, cmd_PLAYER_HAT,
-                                cmd_TERRAIN_TAG)
+                                cmd_TERRAIN_TAG, cmd_THING_DOOR_KEY)
 from plomrogue.tasks import (Task_WAIT, Task_MOVE, Task_WRITE, Task_PICK_UP,
                              Task_DROP, Task_FLATTEN_SURROUNDINGS, Task_DOOR,
                              Task_INTOXICATE, Task_COMMAND, Task_INSTALL,
@@ -26,7 +26,7 @@ from plomrogue.things import (Thing_Player, Thing_Item, Thing_ItemSpawner,
                               Thing_BottleSpawner, Thing_BottleDeposit,
                               Thing_MusicPlayer, Thing_Hat, Thing_HatRemixer,
                               Thing_Cookie, Thing_CookieSpawner, Thing_Psychedelic,
-                              Thing_PsychedelicSpawner)
+                              Thing_PsychedelicSpawner, Thing_DoorKey)
 
 from plomrogue.config import config
 game = Game(config['savefile'])
@@ -70,6 +70,7 @@ game.register_command(cmd_GOD_PLAYER_HAT)
 game.register_command(cmd_GOD_PLAYERS_HAT_CHARS)
 game.register_command(cmd_PLAYER_HAT)
 game.register_command(cmd_THING_HAT_DESIGN)
+game.register_command(cmd_THING_DOOR_KEY)
 game.register_task(Task_WAIT)
 game.register_task(Task_MOVE)
 game.register_task(Task_WRITE)
@@ -88,6 +89,7 @@ game.register_thing_type(Thing_ItemSpawner)
 game.register_thing_type(Thing_SpawnPoint)
 game.register_thing_type(Thing_SpawnPointSpawner)
 game.register_thing_type(Thing_Door)
+game.register_thing_type(Thing_DoorKey)
 game.register_thing_type(Thing_DoorSpawner)
 game.register_thing_type(Thing_Bottle)
 game.register_thing_type(Thing_BottleSpawner)
-- 
2.30.2