changeset 281:9b56e954c674

Protagonist actions, now required for operating doors.
author Jeremy Thurgood <firxen@gmail.com>
date Thu, 05 Sep 2013 15:58:24 +0200
parents 7bb6296024c4
children 9d186b897d82
files data/levels/level1 nagslang/constants.py nagslang/environment.py nagslang/game_object.py nagslang/protagonist.py nagslang/screens/area.py
diffstat 6 files changed, 79 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/data/levels/level1	Thu Sep 05 14:12:55 2013 +0200
+++ b/data/levels/level1	Thu Sep 05 15:58:24 2013 +0200
@@ -30,20 +30,6 @@
   classname: puzzle.StateLogicalAndPuzzler
   name: both_switches
 - args:
-  - [400, 400]
-  - level2
-  - [900, 200]
-  - 0
-  classname: Door
-- args:
-  - [600, 200]
-  - level1
-  - [600, 700]
-  - 0
-  - door_switch
-  classname: Door
-  name: switch_door
-- args:
   - [620, 220]
   - door_switch
   classname: FloorLight
@@ -58,6 +44,20 @@
   - door_switch
   classname: Bulkhead
   name: switch_bulkhead
+- args:
+  - [410, 400]
+  - level2
+  - [900, 200]
+  - 0
+  classname: Door
+- args:
+  - [561, 250]
+  - level1
+  - [600, 700]
+  - 0
+  - door_switch
+  classname: Door
+  name: switch_door
 lines:
 - - [750, 680]
   - [800, 680]
--- a/nagslang/constants.py	Thu Sep 05 14:12:55 2013 +0200
+++ b/nagslang/constants.py	Thu Sep 05 15:58:24 2013 +0200
@@ -35,6 +35,13 @@
     COLLISION_TYPE_DOOR,
 ]
 
+NON_GAME_OBJECT_COLLIDERS = [
+    # These collision types are excluded from action checks, etc.
+    COLLISION_TYPE_WALL,
+    COLLISION_TYPE_PROJECTILE,
+    COLLISION_TYPE_WEREWOLF_ATTACK,
+]
+
 ZORDER_FLOOR = 0
 ZORDER_LOW = 1
 ZORDER_MID = 2
--- a/nagslang/environment.py	Thu Sep 05 14:12:55 2013 +0200
+++ b/nagslang/environment.py	Thu Sep 05 15:58:24 2013 +0200
@@ -74,11 +74,22 @@
         return self.func(protagonist)
 
 
+class PuzzleStateCondition(ProtagonistCondition):
+    """Condition that is met if the provided function returns `True`.
+    """
+    def __init__(self, puzzler):
+        self.puzzler = puzzler
+
+    def check(self, protagonist):
+        return self.puzzler.get_state()
+
+
 class Action(object):
     """Representation of an action that can be performed.
 
     If the (optional) condition is met, the provided function will be called
-    with the protagonist and the target as parameters.
+    with the protagonist as a parameter. It is assumed that the function
+    already knows about the target.
     """
     def __init__(self, func, condition=None):
         self.func = func
@@ -89,10 +100,10 @@
             return True
         return self.condition.check(protagonist)
 
-    def perform(self, protagonist, target):
+    def perform(self, protagonist):
         if not self.check(protagonist):
             raise ValueError("Attempt to perform invalid action.")
-        return self.func(protagonist, target)
+        return self.func(protagonist)
 
 
 class Interactible(object):
@@ -102,6 +113,9 @@
     def __init__(self, *actions):
         self.actions = actions
 
+    def set_game_object(self, game_object):
+        self.game_object = game_object
+
     def select_action(self, protagonist):
         """Select a possible action given the protagonist's state.
         """
--- a/nagslang/game_object.py	Thu Sep 05 14:12:55 2013 +0200
+++ b/nagslang/game_object.py	Thu Sep 05 15:58:24 2013 +0200
@@ -3,6 +3,7 @@
 
 import math
 
+from nagslang import environment
 from nagslang import puzzle
 from nagslang import render
 from nagslang.constants import (
@@ -105,7 +106,8 @@
     zorder = ZORDER_LOW
     is_moving = False  # `True` if a movement animation should play.
 
-    def __init__(self, physicser, renderer, puzzler=None, overlay=None):
+    def __init__(self, physicser, renderer, puzzler=None, overlay=None,
+                 interactible=None):
         self.physicser = physicser
         physicser.set_game_object(self)
         self.physicser.add_to_space()
@@ -117,6 +119,9 @@
         self.overlay = overlay
         if overlay is not None:
             self.overlay.set_game_object(self)
+        self.interactible = interactible
+        if interactible is not None:
+            self.interactible.set_game_object(self)
 
     def get_space(self):
         return self.physicser.get_space()
@@ -244,26 +249,26 @@
     def __init__(self, space, position, destination, dest_pos, angle,
                  key_state=None):
         body = make_body(pymunk.inf, pymunk.inf, position, damping=0.5)
-        self.shape = pymunk.Poly(
-            body, [(-4, -30), (4, -30), (4, 30), (-4, 30)])
+        self.shape = pymunk.Circle(body, 30)
         self.shape.collision_type = COLLISION_TYPE_DOOR
         self.shape.body.angle = float(angle) / 180 * math.pi
         self.shape.sensor = True
         self.destination = destination
         self.dest_pos = tuple(dest_pos)
-        if key_state is None:
-            puzzler = puzzle.YesPuzzler()
-        else:
+        puzzler = None
+        action = environment.Action(self._post_door_event)
+        if key_state is not None:
             puzzler = puzzle.StateProxyPuzzler(key_state)
+            action.condition = environment.PuzzleStateCondition(puzzler)
         super(Door, self).__init__(
             SingleShapePhysicser(space, self.shape),
             render.ImageRenderer(resources.get_image('objects', 'door.png')),
             puzzler,
+            interactible=environment.Interactible(action),
         )
 
-    def collide_with_protagonist(self, protagonist):
-        if self.puzzler.get_state():
-            DoorEvent.post(self.destination, self.dest_pos)
+    def _post_door_event(self, protagonist):
+        DoorEvent.post(self.destination, self.dest_pos)
 
     @classmethod
     def requires(cls):
--- a/nagslang/protagonist.py	Thu Sep 05 14:12:55 2013 +0200
+++ b/nagslang/protagonist.py	Thu Sep 05 15:58:24 2013 +0200
@@ -3,9 +3,10 @@
 from pymunk.vec2d import Vec2d
 
 from nagslang import render
-from nagslang.constants import COLLISION_TYPE_PLAYER, ZORDER_MID, \
-    WEREWOLF_SOAK_FACTOR, PROTAGONIST_HEALTH_MIN_LEVEL, \
-    PROTAGONIST_HEALTH_MAX_LEVEL
+from nagslang.constants import (
+    COLLISION_TYPE_PLAYER, ZORDER_MID, WEREWOLF_SOAK_FACTOR,
+    PROTAGONIST_HEALTH_MIN_LEVEL, PROTAGONIST_HEALTH_MAX_LEVEL,
+    NON_GAME_OBJECT_COLLIDERS)
 from nagslang.events import FireEvent
 from nagslang.game_object import GameObject, Physicser, make_body
 from nagslang.mutators import FLIP_H
@@ -216,11 +217,28 @@
         else:
             self.go_werewolf()
 
-    def act_on(self, target):
+    def get_current_interactible(self):
+        for shape in self.get_space().shape_query(self.get_shape()):
+            if shape.collision_type in NON_GAME_OBJECT_COLLIDERS:
+                # No game object here.
+                continue
+            interactible = shape.physicser.game_object.interactible
+            if interactible is not None:
+                return interactible
+        return None
+
+    def perform_action(self):
         """Perform an action on the target.
         """
-        # TODO: Decide how best to do this.
-        pass
+        interactible = self.get_current_interactible()
+        if interactible is None:
+            # Nothing to interact with.
+            return
+        action = interactible.select_action(self)
+        if action is None:
+            # Nothing to do with it.
+            return
+        return action.perform(self)
 
     def attack(self):
         """Attempt to hurt something.
--- a/nagslang/screens/area.py	Thu Sep 05 14:12:55 2013 +0200
+++ b/nagslang/screens/area.py	Thu Sep 05 15:58:24 2013 +0200
@@ -129,9 +129,11 @@
             if ev.key == pygame.locals.K_c:
                 self.protagonist.toggle_form()
                 self.world.transformations += 1
-            if ev.key == pygame.locals.K_SPACE:
+            if ev.key == pygame.locals.K_z:
                 self.world.attacks += 1
                 self.protagonist.attack()
+            if ev.key == pygame.locals.K_SPACE:
+                self.protagonist.perform_action()
         elif DoorEvent.matches(ev):
             self.protagonist.set_position(ev.dest_pos)
             if ev.destination != self.name: