# HG changeset patch
# User David Sharpe
# Date 1378250623 -7200
# Node ID ec567098cf41fc9814883972bb9b69c142c4ebff
# Parent f89576cec59aab79cb116111efb30e42661a9090# Parent d3d602a527bd09b676257ae4aa9a2407282be37d
Merge
diff -r f89576cec59a -r ec567098cf41 data/images/creatures/werewolf_N_1.png
Binary file data/images/creatures/werewolf_N_1.png has changed
diff -r f89576cec59a -r ec567098cf41 data/images/creatures/werewolf_N_2.png
Binary file data/images/creatures/werewolf_N_2.png has changed
diff -r f89576cec59a -r ec567098cf41 data/images/creatures/werewolf_S_1.png
Binary file data/images/creatures/werewolf_S_1.png has changed
diff -r f89576cec59a -r ec567098cf41 data/images/creatures/werewolf_S_2.png
Binary file data/images/creatures/werewolf_S_2.png has changed
diff -r f89576cec59a -r ec567098cf41 data/levels/level1
--- a/data/levels/level1 Wed Sep 04 01:23:32 2013 +0200
+++ b/data/levels/level1 Wed Sep 04 01:23:43 2013 +0200
@@ -27,7 +27,7 @@
classname: FloorLight
name: light2
- args: [light_switch, door_switch]
- classname: StateLogicalAndPuzzler
+ classname: puzzle.StateLogicalAndPuzzler
name: both_switches
- args:
- [400, 400]
@@ -50,6 +50,15 @@
- [290, 160]
- Run around, press some buttons, have fun!
classname: Note
+lines:
+- - [750, 680]
+ - [950, 680]
+- - [750, 480]
+ - [950, 480]
+- - [750, 480]
+ - [750, 680]
+- - [950, 480]
+ - [950, 680]
polygons:
1:
- [60, 780]
diff -r f89576cec59a -r ec567098cf41 data/levels/level2
--- a/data/levels/level2 Wed Sep 04 01:23:32 2013 +0200
+++ b/data/levels/level2 Wed Sep 04 01:23:43 2013 +0200
@@ -6,6 +6,7 @@
- level1
- [600, 700]
classname: Door
+lines: []
polygons:
1:
- [70, 440]
diff -r f89576cec59a -r ec567098cf41 nagslang/enemies.py
--- a/nagslang/enemies.py Wed Sep 04 01:23:32 2013 +0200
+++ b/nagslang/enemies.py Wed Sep 04 01:23:43 2013 +0200
@@ -1,9 +1,9 @@
import pymunk
import pymunk.pygame_util
+from nagslang import render
from nagslang.constants import COLLISION_TYPE_ENEMY, ZORDER_MID
-from nagslang.game_object import (
- GameObject, SingleShapePhysicser, AnimatedFacingImageRenderer, make_body)
+from nagslang.game_object import GameObject, SingleShapePhysicser, make_body
from nagslang.mutators import FLIP_H
from nagslang.resources import resources
@@ -42,19 +42,19 @@
self._direction = 'away'
def _setup_physics(self, space, position):
- self._body = make_body(5, pymunk.inf, position, 0.8)
+ self._body = make_body(10, pymunk.inf, position, 0.8)
self._shape = pymunk.Circle(self._body, 30)
self._shape.elasticity = 1.0
- self._shape.friction = 10.0
+ self._shape.friction = 0.05
self._shape.collision_type = COLLISION_TYPE_ENEMY
self._physicser = SingleShapePhysicser(space, self._shape)
self.impulse_factor = 50
self.angle = 0
def _setup_renderer(self):
- self.renderer = AnimatedFacingImageRenderer(
+ self.renderer = render.AnimatedFacingImageRenderer(
(self._get_image('alien_A_1.png'),
self._get_image('alien_A_1.png'),
self._get_image('alien_A_1.png'),
diff -r f89576cec59a -r ec567098cf41 nagslang/game_object.py
--- a/nagslang/game_object.py Wed Sep 04 01:23:32 2013 +0200
+++ b/nagslang/game_object.py Wed Sep 04 01:23:43 2013 +0200
@@ -1,94 +1,16 @@
-import math
-
-import pygame
import pymunk
import pymunk.pygame_util
+from nagslang import puzzle
+from nagslang import render
from nagslang.constants import (
SWITCH_PUSHERS, COLLISION_TYPE_SWITCH, COLLISION_TYPE_BOX, ZORDER_LOW,
- ZORDER_FLOOR, COLLISION_TYPE_DOOR, COLLISION_TYPE_PLAYER)
-from nagslang.options import options
+ ZORDER_FLOOR, COLLISION_TYPE_DOOR)
from nagslang.resources import resources
from nagslang.events import DoorEvent
from nagslang.widgets.text import LabelWidget
-class PuzzleGlue(object):
- """Glue that holds bits of a puzzle together.
- """
- def __init__(self):
- self._components = {}
-
- def add_component(self, name, puzzler):
- if not isinstance(puzzler, Puzzler):
- puzzler = puzzler.puzzler
- self._components[name] = puzzler
- puzzler.set_glue(self)
-
- def get_state_of(self, name):
- return self._components[name].get_state()
-
-
-class Puzzler(object):
- """Behaviour specific to a puzzle component.
- """
- def set_glue(self, glue):
- self.glue = glue
-
- def set_game_object(self, game_object):
- self.game_object = game_object
-
- def get_state(self):
- raise NotImplementedError()
-
-
-class YesPuzzler(Puzzler):
- """Yes sir, I'm always on.
- """
- def get_state(self):
- return True
-
-
-class NoPuzzler(Puzzler):
- """No sir, I'm always off.
- """
- def get_state(self):
- return False
-
-
-class CollidePuzzler(Puzzler):
- def __init__(self, *collision_types):
- if not collision_types:
- collision_types = (COLLISION_TYPE_PLAYER,)
- self._collision_types = collision_types
-
- def get_state(self):
- space = self.game_object.get_space()
- for shape in space.shape_query(self.game_object.get_shape()):
- if shape.collision_type in self._collision_types:
- return True
- return False
-
-
-class StateProxyPuzzler(Puzzler):
- def __init__(self, state_source):
- self._state_source = state_source
-
- def get_state(self):
- return self.glue.get_state_of(self._state_source)
-
-
-class StateLogicalAndPuzzler(Puzzler):
- def __init__(self, *state_sources):
- self._state_sources = state_sources
-
- def get_state(self):
- for state_source in self._state_sources:
- if not self.glue.get_state_of(state_source):
- return False
- return True
-
-
class Physicser(object):
def __init__(self, space):
self._space = space
@@ -148,163 +70,6 @@
return self._shape.body.apply_impulse(j, r)
-class Renderer(object):
- def set_game_object(self, game_object):
- self.game_object = game_object
-
- def _render_shape(self, surface):
- shape = self.game_object.get_shape()
- # Less general that pymunk.pygame_util.draw, but also a lot less noisy.
- color = getattr(shape, 'color', pygame.color.THECOLORS['lightblue'])
- # We only explicitly draw Circle and Poly shapes. Everything else we
- # forward to pymunk.
- if isinstance(shape, pymunk.Circle):
- centre = pymunk.pygame_util.to_pygame(shape.body.position, surface)
- radius = int(shape.radius)
- pygame.draw.circle(surface, color, centre, radius, 2)
- elif isinstance(shape, pymunk.Poly):
- # polygon bounding box
- points = [pymunk.pygame_util.to_pygame(p, surface)
- for p in shape.get_vertices()]
- pygame.draw.lines(surface, color, True, points, 2)
- else:
- pymunk.pygame_util.draw(surface, shape)
-
- def render(self, surface):
- if options.debug:
- self._render_shape(surface)
-
- def animate(self):
- # Used by time animatations to advance the clock
- pass
-
-
-def image_pos(image, pos):
- return (pos[0] - image.get_width() / 2,
- pos[1] - image.get_height() / 2)
-
-
-class ImageRenderer(Renderer):
- def __init__(self, image):
- self._image = image
-
- def get_image(self):
- return self._image
-
- def rotate_image(self, image):
- angle = self.game_object.get_render_angle() * 180 / math.pi
- return pygame.transform.rotate(image, angle)
-
- def render_image(self, surface, image):
- image = self.rotate_image(image)
- pos = self.game_object.get_render_position(surface)
- surface.blit(image, image_pos(image, pos))
-
- def render(self, surface):
- self.render_image(surface, self.get_image())
- super(ImageRenderer, self).render(surface)
-
-
-class ImageStateRenderer(ImageRenderer):
- def __init__(self, state_images):
- self._state_images = state_images
-
- def get_image(self):
- return self._state_images[self.game_object.puzzler.get_state()]
-
-
-class FacingImageRenderer(ImageRenderer):
- def __init__(self, left_image, right_image):
- self._images = {
- 'left': left_image,
- 'right': right_image,
- }
- self._face = 'left'
-
- def _update_facing(self, angle):
- if abs(angle) < math.pi / 2:
- self._face = 'right'
- elif abs(angle) > math.pi / 2:
- self._face = 'left'
-
- def rotate_image(self, image):
- # Facing images don't get rotated.
- return image
-
- def get_facing_image(self):
- return self._images[self._face]
-
- def get_image(self):
- angle = self.game_object.get_render_angle()
- self._update_facing(angle)
- return self.get_facing_image()
-
-
-class AnimatedFacingImageRenderer(FacingImageRenderer):
- def __init__(self, left_images, right_images):
- self._images = {
- 'left': left_images,
- 'right': right_images,
- }
- self._frame = 0
- self._moving = False
- self._face = 'left'
-
- def get_facing_image(self):
- if self._frame >= len(self._images[self._face]):
- self._frame = 0
- return self._images[self._face][self._frame]
-
- def animate(self):
- if self._moving:
- self._frame += 1
- else:
- self._frame = 0
-
- def start(self):
- self._moving = True
-
- def stop(self):
- self._moving = False
-
-
-class TimedAnimatedRenderer(ImageRenderer):
-
- def __init__(self, images):
- self._images = images
- self._frame = 0
- self._image = None
-
- def get_image(self):
- if self._frame > len(self._imaages):
- self._frame = 0
- return self._images[self._frame]
-
- def animate(self):
- self._frame += 1
-
-
-class ShapeRenderer(Renderer):
- def render(self, surface):
- self._render_shape(surface)
- super(ShapeRenderer, self).render(surface)
-
-
-class ShapeStateRenderer(ShapeRenderer):
- """Renders the shape in a different colour depending on the state.
-
- Requires the game object it's attached to to have a puzzler.
- """
- def render(self, surface):
- if self.game_object.puzzler.get_state():
- color = pygame.color.THECOLORS['green']
- else:
- color = pygame.color.THECOLORS['red']
-
- self.game_object.get_shape().color = color
- super(ShapeStateRenderer, self).render(surface)
-
-
def damping_velocity_func(body, gravity, damping, dt):
"""Apply custom damping to this body's velocity.
"""
@@ -325,7 +90,7 @@
def set_game_object(self, game_object):
self.game_object = game_object
- def render(self, surface):
+ def render(self, surface, display_offset):
pass
def is_visible(self):
@@ -337,7 +102,13 @@
self.text = text
self.widget = LabelWidget((20, 20), self.text)
- def render(self, surface):
+ def render(self, surface, display_offset):
+ x, y = 20, 20
+ if display_offset[0] < 0:
+ x += abs(display_offset[0])
+ if display_offset[1] < 0:
+ y += abs(display_offset[1])
+ self.widget.rect.topleft = (x, y)
self.widget.draw(surface)
@@ -399,11 +170,11 @@
self.shape.sensor = True
super(FloorSwitch, self).__init__(
SingleShapePhysicser(space, self.shape),
- ImageStateRenderer({
+ render.ImageStateRenderer({
True: resources.get_image('objects', 'sensor_on.png'),
False: resources.get_image('objects', 'sensor_off.png'),
}),
- CollidePuzzler(*SWITCH_PUSHERS),
+ puzzle.CollidePuzzler(*SWITCH_PUSHERS),
)
@@ -416,8 +187,8 @@
self.shape.sensor = True
super(Note, self).__init__(
SingleShapePhysicser(space, self.shape),
- ImageRenderer(resources.get_image('objects', 'note.png')),
- CollidePuzzler(),
+ render.ImageRenderer(resources.get_image('objects', 'note.png')),
+ puzzle.CollidePuzzler(),
TextOverlay(message),
)
@@ -432,11 +203,11 @@
self.shape.sensor = True
super(FloorLight, self).__init__(
SingleShapePhysicser(space, self.shape),
- ImageStateRenderer({
+ render.ImageStateRenderer({
True: resources.get_image('objects', 'light_on.png'),
False: resources.get_image('objects', 'light_off.png'),
}),
- StateProxyPuzzler(state_source),
+ puzzle.StateProxyPuzzler(state_source),
)
@@ -445,10 +216,11 @@
body = make_body(10, 10000, position, damping=0.5)
self.shape = pymunk.Poly(
body, [(-20, -20), (20, -20), (20, 20), (-20, 20)])
+ self.shape.friction = 0.5
self.shape.collision_type = COLLISION_TYPE_BOX
super(Box, self).__init__(
SingleShapePhysicser(space, self.shape),
- ImageRenderer(resources.get_image('objects', 'crate.png')),
+ render.ImageRenderer(resources.get_image('objects', 'crate.png')),
)
@@ -464,12 +236,12 @@
self.destination = destination
self.dest_pos = tuple(dest_pos)
if key_state is None:
- puzzler = YesPuzzler()
+ puzzler = puzzle.YesPuzzler()
else:
- puzzler = StateProxyPuzzler(key_state)
+ puzzler = puzzle.StateProxyPuzzler(key_state)
super(Door, self).__init__(
SingleShapePhysicser(space, self.shape),
- ImageRenderer(resources.get_image('objects', 'door.png')),
+ render.ImageRenderer(resources.get_image('objects', 'door.png')),
puzzler,
)
diff -r f89576cec59a -r ec567098cf41 nagslang/level.py
--- a/nagslang/level.py Wed Sep 04 01:23:32 2013 +0200
+++ b/nagslang/level.py Wed Sep 04 01:23:43 2013 +0200
@@ -3,6 +3,7 @@
from nagslang import game_object as go
from nagslang import enemies
+from nagslang import puzzle
from nagslang.resources import resources
from nagslang.yamlish import load, dump
@@ -16,6 +17,9 @@
}
+LINE_COLOR = pygame.color.THECOLORS['orange']
+
+
class Level(object):
def __init__(self, name):
@@ -24,11 +28,12 @@
self.x = 800
self.y = 600
self.polygons = {}
+ self.lines = []
self.basetile = 'tiles/floor.png'
self._tile_image = None
self._surface = None
self._exterior = False
- self._glue = go.PuzzleGlue()
+ self._glue = puzzle.PuzzleGlue()
self.drawables = []
self.overlay_drawables = []
self._game_objects = []
@@ -45,6 +50,7 @@
'size': [self.x, self.y],
'base_tile': self.basetile,
'polygons': self.polygons,
+ 'lines': self.lines,
'game_objects': self._game_objects,
'enemies': self._enemies,
}, f)
@@ -57,6 +63,7 @@
self.polygons[i] = []
for point in points:
self.polygons[i].append(tuple(point))
+ self.lines = data.get('lines', [])
self._game_objects = data.get('game_objects', [])
for game_object_dict in self._game_objects:
self._create_game_object(space, **game_object_dict)
@@ -65,10 +72,17 @@
self._create_enemy(space, **enemy_dict)
def _create_game_object(self, space, classname, args, name=None):
- # We should probably build a registry of game objects or something.
- # At least this is better than just calling `eval`, right?
- cls = getattr(go, classname)
- if issubclass(cls, go.Puzzler):
+ modules = {
+ 'game_object': go,
+ 'puzzle': puzzle,
+ }
+ if '.' in classname:
+ module, classname = classname.split('.')
+ else:
+ module = 'game_object'
+ cls = getattr(modules[module], classname)
+
+ if issubclass(cls, puzzle.Puzzler):
gobj = cls(*args)
elif issubclass(cls, go.GameObject):
gobj = cls(space, *args)
@@ -131,7 +145,9 @@
return (pos[0], self.y - pos[1])
def get_walls(self):
- return self.polygons.values()
+ walls = self.polygons.values()
+ walls.extend(self.lines)
+ return walls
def _draw_walls(self):
for index, polygon in self.polygons.items():
@@ -139,6 +155,9 @@
if len(polygon) > 1:
pointlist = [self.point_to_pygame(p) for p in polygon]
pygame.draw.lines(self._surface, color, False, pointlist, 2)
+ for line in self.lines:
+ pointlist = [self.point_to_pygame(p) for p in line]
+ pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 2)
def get_background(self):
self._draw_background()
diff -r f89576cec59a -r ec567098cf41 nagslang/protagonist.py
--- a/nagslang/protagonist.py Wed Sep 04 01:23:32 2013 +0200
+++ b/nagslang/protagonist.py Wed Sep 04 01:23:43 2013 +0200
@@ -3,9 +3,9 @@
import math
+from nagslang import render
from nagslang.constants import COLLISION_TYPE_PLAYER, ZORDER_MID
-from nagslang.game_object import (
- GameObject, SingleShapePhysicser, AnimatedFacingImageRenderer, make_body)
+from nagslang.game_object import GameObject, SingleShapePhysicser, make_body
from nagslang.mutators import FLIP_H
from nagslang.resources import resources
@@ -42,10 +42,11 @@
self._body, [(-15, -30), (15, -30), (15, 30), (-15, 30)]),
self.WOLF_FORM: pymunk.Circle(self._body, 30),
}
+ self._shapes[self.HUMAN_FORM].friction = 1.0
+ self._shapes[self.WOLF_FORM].friction = 0.05
self._physicsers = {}
for form, shape in self._shapes.iteritems():
shape.elasticity = 1.0
- shape.friction = 10.0
shape.collision_type = COLLISION_TYPE_PLAYER
self._physicsers[form] = SingleShapePhysicser(space, shape)
self.angle = 0
@@ -55,7 +56,7 @@
def _setup_renderers(self):
self._renderers = {
- self.HUMAN_FORM: AnimatedFacingImageRenderer(
+ self.HUMAN_FORM: render.AnimatedFacingImageRenderer(
(self._get_image('human_1.png'),
self._get_image('human_1.png'),
self._get_image('human_1.png'),
@@ -68,7 +69,7 @@
self._get_image('human_2.png', FLIP_H),
self._get_image('human_2.png', FLIP_H),
self._get_image('human_2.png', FLIP_H))),
- self.HUMAN_FORM_BACK: AnimatedFacingImageRenderer(
+ self.HUMAN_FORM_BACK: render.AnimatedFacingImageRenderer(
(self._get_image('human_back_1.png'),
self._get_image('human_back_1.png'),
self._get_image('human_back_1.png'),
@@ -81,7 +82,7 @@
self._get_image('human_back_2.png', FLIP_H),
self._get_image('human_back_2.png', FLIP_H),
self._get_image('human_back_2.png', FLIP_H))),
- self.WOLF_FORM: AnimatedFacingImageRenderer(
+ self.WOLF_FORM: render.AnimatedFacingImageRenderer(
(self._get_image('werewolf_1.png'),
self._get_image('werewolf_1.png'),
self._get_image('werewolf_1.png'),
@@ -94,7 +95,7 @@
self._get_image('werewolf_2.png', FLIP_H),
self._get_image('werewolf_2.png', FLIP_H),
self._get_image('werewolf_2.png', FLIP_H))),
- self.WOLF_FORM_BACK: AnimatedFacingImageRenderer(
+ self.WOLF_FORM_BACK: render.AnimatedFacingImageRenderer(
(self._get_image('werewolf_back_1.png'),
self._get_image('werewolf_back_1.png'),
self._get_image('werewolf_back_1.png'),
@@ -126,6 +127,7 @@
self._physicsers[self.form].remove_from_space()
self.form = self.WOLF_FORM
self._physicsers[self.form].add_to_space()
+ self.physicser = self._physicsers[self.form]
self._body.mass = 100
self._body.velocity_limit = 1000
self.impulse_factor = 4000
@@ -142,6 +144,7 @@
self._physicsers[self.form].remove_from_space()
self.form = self.HUMAN_FORM
self._physicsers[self.form].add_to_space()
+ self.physicser = self._physicsers[self.form]
self._body.mass = 10
self._body.velocity_limit = 1000
self.impulse_factor = 500
@@ -199,6 +202,7 @@
self.inventory = old_protagonist.inventory
self.renderer = self._renderers[self.render_form]
self._physicsers[self.form].add_to_space()
+ self.physicser = self._physicsers[self.form]
def toggle_form(self):
if self.form == self.WOLF_FORM:
diff -r f89576cec59a -r ec567098cf41 nagslang/puzzle.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nagslang/puzzle.py Wed Sep 04 01:23:43 2013 +0200
@@ -0,0 +1,77 @@
+from nagslang.constants import COLLISION_TYPE_PLAYER
+
+
+class PuzzleGlue(object):
+ """Glue that holds bits of a puzzle together.
+ """
+ def __init__(self):
+ self._components = {}
+
+ def add_component(self, name, puzzler):
+ if not isinstance(puzzler, Puzzler):
+ puzzler = puzzler.puzzler
+ self._components[name] = puzzler
+ puzzler.set_glue(self)
+
+ def get_state_of(self, name):
+ return self._components[name].get_state()
+
+
+class Puzzler(object):
+ """Behaviour specific to a puzzle component.
+ """
+ def set_glue(self, glue):
+ self.glue = glue
+
+ def set_game_object(self, game_object):
+ self.game_object = game_object
+
+ def get_state(self):
+ raise NotImplementedError()
+
+
+class YesPuzzler(Puzzler):
+ """Yes sir, I'm always on.
+ """
+ def get_state(self):
+ return True
+
+
+class NoPuzzler(Puzzler):
+ """No sir, I'm always off.
+ """
+ def get_state(self):
+ return False
+
+
+class CollidePuzzler(Puzzler):
+ def __init__(self, *collision_types):
+ if not collision_types:
+ collision_types = (COLLISION_TYPE_PLAYER,)
+ self._collision_types = collision_types
+
+ def get_state(self):
+ space = self.game_object.get_space()
+ for shape in space.shape_query(self.game_object.get_shape()):
+ if shape.collision_type in self._collision_types:
+ return True
+ return False
+
+
+class StateProxyPuzzler(Puzzler):
+ def __init__(self, state_source):
+ self._state_source = state_source
+
+ def get_state(self):
+ return self.glue.get_state_of(self._state_source)
+
+
+class StateLogicalAndPuzzler(Puzzler):
+ def __init__(self, *state_sources):
+ self._state_sources = state_sources
+
+ def get_state(self):
+ for state_source in self._state_sources:
+ if not self.glue.get_state_of(state_source):
+ return False
+ return True
diff -r f89576cec59a -r ec567098cf41 nagslang/render.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nagslang/render.py Wed Sep 04 01:23:43 2013 +0200
@@ -0,0 +1,163 @@
+import math
+
+import pygame
+import pymunk
+
+from nagslang.options import options
+
+
+class Renderer(object):
+ def set_game_object(self, game_object):
+ self.game_object = game_object
+
+ def _render_shape(self, surface):
+ shape = self.game_object.get_shape()
+ # Less general that pymunk.pygame_util.draw, but also a lot less noisy.
+ color = getattr(shape, 'color', pygame.color.THECOLORS['lightblue'])
+ # We only explicitly draw Circle and Poly shapes. Everything else we
+ # forward to pymunk.
+ if isinstance(shape, pymunk.Circle):
+ centre = pymunk.pygame_util.to_pygame(shape.body.position, surface)
+ radius = int(shape.radius)
+ pygame.draw.circle(surface, color, centre, radius, 2)
+ elif isinstance(shape, pymunk.Poly):
+ # polygon bounding box
+ points = [pymunk.pygame_util.to_pygame(p, surface)
+ for p in shape.get_vertices()]
+ pygame.draw.lines(surface, color, True, points, 2)
+ else:
+ pymunk.pygame_util.draw(surface, shape)
+
+ def render(self, surface):
+ if options.debug:
+ self._render_shape(surface)
+
+ def animate(self):
+ # Used by time animatations to advance the clock
+ pass
+
+
+def image_pos(image, pos):
+ return (pos[0] - image.get_width() / 2,
+ pos[1] - image.get_height() / 2)
+
+
+class ImageRenderer(Renderer):
+ def __init__(self, image):
+ self._image = image
+
+ def get_image(self):
+ return self._image
+
+ def rotate_image(self, image):
+ angle = self.game_object.get_render_angle() * 180 / math.pi
+ return pygame.transform.rotate(image, angle)
+
+ def render_image(self, surface, image):
+ image = self.rotate_image(image)
+ pos = self.game_object.get_render_position(surface)
+ surface.blit(image, image_pos(image, pos))
+
+ def render(self, surface):
+ self.render_image(surface, self.get_image())
+ super(ImageRenderer, self).render(surface)
+
+
+class ImageStateRenderer(ImageRenderer):
+ def __init__(self, state_images):
+ self._state_images = state_images
+
+ def get_image(self):
+ return self._state_images[self.game_object.puzzler.get_state()]
+
+
+class FacingImageRenderer(ImageRenderer):
+ def __init__(self, left_image, right_image):
+ self._images = {
+ 'left': left_image,
+ 'right': right_image,
+ }
+ self._face = 'left'
+
+ def _update_facing(self, angle):
+ if abs(angle) < math.pi / 2:
+ self._face = 'right'
+ elif abs(angle) > math.pi / 2:
+ self._face = 'left'
+
+ def rotate_image(self, image):
+ # Facing images don't get rotated.
+ return image
+
+ def get_facing_image(self):
+ return self._images[self._face]
+
+ def get_image(self):
+ angle = self.game_object.get_render_angle()
+ self._update_facing(angle)
+ return self.get_facing_image()
+
+
+class AnimatedFacingImageRenderer(FacingImageRenderer):
+ def __init__(self, left_images, right_images):
+ self._images = {
+ 'left': left_images,
+ 'right': right_images,
+ }
+ self._frame = 0
+ self._moving = False
+ self._face = 'left'
+
+ def get_facing_image(self):
+ if self._frame >= len(self._images[self._face]):
+ self._frame = 0
+ return self._images[self._face][self._frame]
+
+ def animate(self):
+ if self._moving:
+ self._frame += 1
+ else:
+ self._frame = 0
+
+ def start(self):
+ self._moving = True
+
+ def stop(self):
+ self._moving = False
+
+
+class TimedAnimatedRenderer(ImageRenderer):
+
+ def __init__(self, images):
+ self._images = images
+ self._frame = 0
+ self._image = None
+
+ def get_image(self):
+ if self._frame > len(self._imaages):
+ self._frame = 0
+ return self._images[self._frame]
+
+ def animate(self):
+ self._frame += 1
+
+
+class ShapeRenderer(Renderer):
+ def render(self, surface):
+ self._render_shape(surface)
+ super(ShapeRenderer, self).render(surface)
+
+
+class ShapeStateRenderer(ShapeRenderer):
+ """Renders the shape in a different colour depending on the state.
+
+ Requires the game object it's attached to to have a puzzler.
+ """
+ def render(self, surface):
+ if self.game_object.puzzler.get_state():
+ color = pygame.color.THECOLORS['green']
+ else:
+ color = pygame.color.THECOLORS['red']
+
+ self.game_object.get_shape().color = color
+ super(ShapeStateRenderer, self).render(surface)
diff -r f89576cec59a -r ec567098cf41 nagslang/screens/area.py
--- a/nagslang/screens/area.py Wed Sep 04 01:23:32 2013 +0200
+++ b/nagslang/screens/area.py Wed Sep 04 01:23:43 2013 +0200
@@ -173,7 +173,7 @@
surface.blit(mysurface, (0, 0), render_rect)
for overlay in self._level.overlay_drawables:
if overlay.is_visible():
- overlay.render(surface)
+ overlay.render(surface, render_rect.topleft)
def tick_protagonist(self):
dx, dy = self.keys.get_direction()
diff -r f89576cec59a -r ec567098cf41 nagslang/tests/test_game_object.py
--- a/nagslang/tests/test_game_object.py Wed Sep 04 01:23:32 2013 +0200
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-from unittest import TestCase
-
-from nagslang.constants import COLLISION_TYPE_OTHER, SWITCH_PUSHERS
-from nagslang import game_object
-
-
-class FakeShape(object):
- def __init__(self, collision_type=COLLISION_TYPE_OTHER):
- self.collision_type = collision_type
-
-
-class FakeSpace(object):
- def __init__(self, *shapes):
- self._shapes = shapes
-
- def shape_query(self, shape):
- return self._shapes
-
-
-class FakeGameObject(object):
- def __init__(self, shape, space):
- self._shape = shape
- self._space = space
-
- def get_shape(self):
- return self._shape
-
- def get_space(self):
- return self._space
-
-
-class FakePuzzler(game_object.Puzzler):
- def __init__(self, fake_state):
- self.fake_state = fake_state
-
- def get_state(self):
- return self.fake_state
-
-
-class TestPuzzles(TestCase):
- def mkpuzzler(self, gobj, cls, *args, **kw):
- puzzler = cls(*args, **kw)
- puzzler.set_game_object(gobj)
- return puzzler
-
- def assert_collide_state(self, expected, shapes, collision_types):
- gobj = FakeGameObject(None, FakeSpace(*shapes))
- puzzler = self.mkpuzzler(
- gobj, game_object.CollidePuzzler, *collision_types)
- self.assertEqual(expected, puzzler.get_state())
-
- def test_collide_puzzler(self):
- self.assert_collide_state(False, [], [])
- self.assert_collide_state(False, [FakeShape()], SWITCH_PUSHERS)
-
- for collision_type in SWITCH_PUSHERS:
- self.assert_collide_state(
- True, [FakeShape(collision_type)], SWITCH_PUSHERS)
- self.assert_collide_state(
- True, [FakeShape(), FakeShape(collision_type)], SWITCH_PUSHERS)
-
- def test_state_proxy_puzzler(self):
- glue = game_object.PuzzleGlue()
- puzzler = game_object.StateProxyPuzzler('faker')
- glue.add_component('puzzler', puzzler)
- faker = FakePuzzler('foo')
- glue.add_component('faker', faker)
-
- self.assertEqual('foo', puzzler.get_state())
- faker.fake_state = 'bar'
- self.assertEqual('bar', puzzler.get_state())
-
- def test_glue_add_component(self):
- glue = game_object.PuzzleGlue()
- puzzler = FakePuzzler('foo')
- gobj = FakeGameObject(None, None)
- gobj.puzzler = FakePuzzler('bar')
-
- self.assertEqual({}, glue._components)
- glue.add_component('foo', puzzler)
- self.assertEqual({'foo': puzzler}, glue._components)
- glue.add_component('bar', gobj)
- self.assertEqual(
- {'foo': puzzler, 'bar': gobj.puzzler}, glue._components)
diff -r f89576cec59a -r ec567098cf41 nagslang/tests/test_level.py
--- a/nagslang/tests/test_level.py Wed Sep 04 01:23:32 2013 +0200
+++ b/nagslang/tests/test_level.py Wed Sep 04 01:23:43 2013 +0200
@@ -3,6 +3,7 @@
from StringIO import StringIO
from nagslang import game_object as go
+from nagslang import puzzle
from nagslang.level import Level
from nagslang.yamlish import load
@@ -31,7 +32,7 @@
level.load(FakeSpace())
self.assertEqual((5, 10), level.get_size())
self.assertEqual([], level.get_walls())
- self.assertEqual([], level.get_drawables())
+ self.assertEqual([], level.drawables)
level = self.make_level('foo', {
'size': [5, 10],
@@ -48,7 +49,7 @@
level.load(FakeSpace())
self.assertEqual((5, 10), level.get_size())
self.assertEqual([[(1, 1), (2, 1), (1, 2)]], level.get_walls())
- self.assertEqual([], level.get_drawables())
+ self.assertEqual([], level.drawables)
level = self.make_level('foo', {
'size': [5, 10],
@@ -66,7 +67,7 @@
level.load(FakeSpace())
self.assertEqual((5, 10), level.get_size())
self.assertEqual([], level.get_walls())
- [box, switch] = level.get_drawables()
+ [box, switch] = level.drawables
self.assertTrue(isinstance(box, go.Box))
self.assertEqual(box.shape.body.position, (3, 3))
self.assertTrue(isinstance(switch, go.FloorSwitch))
@@ -76,9 +77,10 @@
self.assertEqual(['foo', 'foo_proxy'],
sorted(puzzle_bits.keys()))
self.assertTrue(
- isinstance(puzzle_bits['foo_proxy'], go.StateProxyPuzzler))
+ isinstance(puzzle_bits['foo_proxy'], puzzle.StateProxyPuzzler))
self.assertEqual('foo', puzzle_bits['foo_proxy']._state_source)
- self.assertTrue(isinstance(puzzle_bits['foo'], go.CollidePuzzler))
+ self.assertTrue(isinstance(puzzle_bits['foo'],
+ puzzle.CollidePuzzler))
level = self.make_level('foo', {
'size': [5, 10],
@@ -96,7 +98,7 @@
},
{
'name': 'foo_proxy',
- 'classname': 'StateProxyPuzzler',
+ 'classname': 'puzzle.StateProxyPuzzler',
'args': ['foo'],
},
],
diff -r f89576cec59a -r ec567098cf41 nagslang/tests/test_puzzle.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nagslang/tests/test_puzzle.py Wed Sep 04 01:23:43 2013 +0200
@@ -0,0 +1,84 @@
+from unittest import TestCase
+
+from nagslang.constants import COLLISION_TYPE_OTHER, SWITCH_PUSHERS
+from nagslang import puzzle
+
+
+class FakeShape(object):
+ def __init__(self, collision_type=COLLISION_TYPE_OTHER):
+ self.collision_type = collision_type
+
+
+class FakeSpace(object):
+ def __init__(self, *shapes):
+ self._shapes = shapes
+
+ def shape_query(self, shape):
+ return self._shapes
+
+
+class FakeGameObject(object):
+ def __init__(self, shape, space):
+ self._shape = shape
+ self._space = space
+
+ def get_shape(self):
+ return self._shape
+
+ def get_space(self):
+ return self._space
+
+
+class FakePuzzler(puzzle.Puzzler):
+ def __init__(self, fake_state):
+ self.fake_state = fake_state
+
+ def get_state(self):
+ return self.fake_state
+
+
+class TestPuzzles(TestCase):
+ def mkpuzzler(self, gobj, cls, *args, **kw):
+ puzzler = cls(*args, **kw)
+ puzzler.set_game_object(gobj)
+ return puzzler
+
+ def assert_collide_state(self, expected, shapes, collision_types):
+ gobj = FakeGameObject(None, FakeSpace(*shapes))
+ puzzler = self.mkpuzzler(
+ gobj, puzzle.CollidePuzzler, *collision_types)
+ self.assertEqual(expected, puzzler.get_state())
+
+ def test_collide_puzzler(self):
+ self.assert_collide_state(False, [], [])
+ self.assert_collide_state(False, [FakeShape()], SWITCH_PUSHERS)
+
+ for collision_type in SWITCH_PUSHERS:
+ self.assert_collide_state(
+ True, [FakeShape(collision_type)], SWITCH_PUSHERS)
+ self.assert_collide_state(
+ True, [FakeShape(), FakeShape(collision_type)], SWITCH_PUSHERS)
+
+ def test_state_proxy_puzzler(self):
+ glue = puzzle.PuzzleGlue()
+ puzzler = puzzle.StateProxyPuzzler('faker')
+ glue.add_component('puzzler', puzzler)
+ faker = FakePuzzler('foo')
+ glue.add_component('faker', faker)
+
+ self.assertEqual('foo', puzzler.get_state())
+ faker.fake_state = 'bar'
+ self.assertEqual('bar', puzzler.get_state())
+
+ def test_glue_add_component(self):
+ glue = puzzle.PuzzleGlue()
+ puzzler = FakePuzzler('foo')
+ gobj = FakeGameObject(None, None)
+ gobj.puzzler = FakePuzzler('bar')
+
+ self.assertEqual({}, glue._components)
+ glue.add_component('foo', puzzler)
+ self.assertEqual({'foo': puzzler}, glue._components)
+ glue.add_component('bar', gobj)
+ self.assertEqual(
+ {'foo': puzzler, 'bar': gobj.puzzler}, glue._components)
diff -r f89576cec59a -r ec567098cf41 screenshots/alien-r213.png
Binary file screenshots/alien-r213.png has changed
diff -r f89576cec59a -r ec567098cf41 source/images/creatures/werewolf_N_1.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/source/images/creatures/werewolf_N_1.svg Wed Sep 04 01:23:43 2013 +0200
@@ -0,0 +1,619 @@
+
+
+
+
diff -r f89576cec59a -r ec567098cf41 source/images/creatures/werewolf_N_2.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/source/images/creatures/werewolf_N_2.svg Wed Sep 04 01:23:43 2013 +0200
@@ -0,0 +1,573 @@
+
+
+
+
diff -r f89576cec59a -r ec567098cf41 source/images/creatures/werewolf_S_1.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/source/images/creatures/werewolf_S_1.svg Wed Sep 04 01:23:43 2013 +0200
@@ -0,0 +1,569 @@
+
+
+
+
diff -r f89576cec59a -r ec567098cf41 source/images/creatures/werewolf_S_2.svg
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/source/images/creatures/werewolf_S_2.svg Wed Sep 04 01:23:43 2013 +0200
@@ -0,0 +1,638 @@
+
+
+
+
diff -r f89576cec59a -r ec567098cf41 tools/area_editor.py
--- a/tools/area_editor.py Wed Sep 04 01:23:32 2013 +0200
+++ b/tools/area_editor.py Wed Sep 04 01:23:43 2013 +0200
@@ -23,11 +23,13 @@
from albow.root import RootWidget
from albow.widget import Widget
-from albow.controls import Button
+from albow.controls import Button, Label, CheckBox
from albow.dialogs import alert
+from nagslang.options import parse_args
from nagslang.constants import SCREEN
-from nagslang.level import Level, POLY_COLORS
+from nagslang.level import Level, POLY_COLORS, LINE_COLOR
+from nagslang.enemies import Enemy
# layout constants
@@ -60,13 +62,10 @@
point = self.point_to_pymunk(self.round_point(pos))
self.polygons[poly_index].append(point)
else:
- add_pos = self.fix_angle(poly_index, pos)
+ add_pos = self.fix_poly_angle(poly_index, pos)
self.polygons[poly_index].append(add_pos)
- def fix_angle(self, index, pos):
- # Last point
- point1 = self.point_to_pygame(self.polygons[index][-1])
- pos = self.round_point(pos)
+ def _fix_angle(self, point1, pos):
# We want the line (point1 to pos) to be an angle of
# 0, 45, 90, 135, 180, 225, 270, 305
# However, we only need to consider half the circle
@@ -87,6 +86,17 @@
min_dist = dist
return self.point_to_pymunk(new_pos)
+ def fix_line_angle(self, start_pos, pos):
+ start_pos = self.round_point(start_pos)
+ pos = self.round_point(pos)
+ return self._fix_angle(start_pos, pos)
+
+ def fix_poly_angle(self, index, pos):
+ # Last point
+ point1 = self.point_to_pygame(self.polygons[index][-1])
+ pos = self.round_point(pos)
+ return self._fix_angle(point1, pos)
+
def delete_point(self, index):
if index in self.polygons and len(self.polygons[index]) > 0:
self.polygons[index].pop()
@@ -100,7 +110,7 @@
# Too small
return False
first = self.polygons[index][0]
- if self.fix_angle(index, self.point_to_pygame(first)) == first:
+ if self.fix_poly_angle(index, self.point_to_pygame(first)) == first:
self.add_point(index, self.point_to_pygame(first))
return True
candidates = [(first[0] + 10 * i, first[1]) for
@@ -114,7 +124,7 @@
min_dist = 99999
poss = None
for cand in candidates:
- if self.fix_angle(index, self.point_to_pygame(cand)) == cand:
+ if self.fix_poly_angle(index, self.point_to_pygame(cand)) == cand:
dist = (first[0] - cand[0]) ** 2 + (first[1] - cand[1]) ** 2
if dist < min_dist:
poss = cand
@@ -124,7 +134,12 @@
return True
return False
- def draw(self, surface, topleft, mouse_pos, mouse_poly, filled):
+ def add_line(self, start_pos, end_pos):
+ endpoint = self.fix_line_angle(start_pos, end_pos)
+ startpoint = self.point_to_pymunk(self.round_point(start_pos))
+ self.lines.append([startpoint, endpoint])
+
+ def draw(self, mouse_pos, mouse_poly, filled, draw_cand_line, start_pos):
self._draw_background(True)
# Draw polygons as needed for the editor
if filled:
@@ -135,12 +150,19 @@
pointlist = [self.point_to_pygame(p) for p in polygon]
pygame.draw.lines(self._surface, color, False, pointlist, 2)
if index == mouse_poly and mouse_pos:
- endpoint = self.fix_angle(index, mouse_pos)
+ endpoint = self.fix_poly_angle(index, mouse_pos)
pygame.draw.line(self._surface, color,
self.point_to_pygame(polygon[-1]),
self.point_to_pygame(endpoint))
- surface_area = pygame.rect.Rect(topleft, SCREEN)
- surface.blit(self._surface, (0, 0), surface_area)
+ for line in self.lines:
+ pointlist = [self.point_to_pygame(p) for p in line]
+ pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 2)
+ if draw_cand_line and start_pos and mouse_pos:
+ endpoint = self.fix_line_angle(start_pos, mouse_pos)
+ pointlist = [self.round_point(start_pos),
+ self.point_to_pygame(endpoint)]
+ pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 1)
+ return self._surface.copy()
class LevelWidget(Widget):
@@ -154,6 +176,10 @@
self.mouse_pos = None
self.cur_poly = None
self._mouse_drag = False
+ self._draw_objects = False
+ self._draw_enemies = False
+ self._draw_lines = False
+ self._start_pos = None
def _level_coordinates(self, pos):
# Move positions to level values
@@ -173,21 +199,51 @@
new_pos[1] = self.pos[1]
self.pos = tuple(new_pos)
+ def set_objects(self, value):
+ if self._draw_objects != value:
+ self._draw_objects = value
+ self.invalidate()
+
+ def set_enemies(self, value):
+ if self._draw_enemies != value:
+ self._draw_enemies = value
+ self.invalidate()
+
def draw(self, surface):
if (self.cur_poly is not None and self.cur_poly in self.level.polygons
and len(self.level.polygons[self.cur_poly])):
# We have an active polygon
mouse_pos = self._level_coordinates(self.mouse_pos)
+ elif self._draw_lines:
+ # Interior wall mode
+ mouse_pos = self._level_coordinates(self.mouse_pos)
else:
mouse_pos = None
- level.draw(surface, self.pos, mouse_pos, self.cur_poly,
- self.filled_mode)
+ level_surface = level.draw(mouse_pos, self.cur_poly, self.filled_mode,
+ self._draw_lines, self._start_pos)
+ if self._draw_objects:
+ for thing in self.level.drawables:
+ if not isinstance(thing, Enemy):
+ thing.render(level_surface)
+ if self._draw_enemies:
+ for thing in self.level.drawables:
+ if isinstance(thing, Enemy):
+ thing.render(level_surface)
+ surface_area = pygame.rect.Rect(self.pos, SCREEN)
+ surface.blit(level_surface, (0, 0), surface_area)
def change_poly(self, new_poly):
self.cur_poly = new_poly
+ self._draw_lines = False
if self.cur_poly is not None:
self.filled_mode = False
+ def line_mode(self):
+ self.cur_poly = None
+ self._draw_lines = True
+ self.filled_mode = False
+ self._start_pos = None
+
def key_down(self, ev):
if ev.key == pgl.K_LEFT:
self._move_view((-10, 0))
@@ -213,13 +269,14 @@
if closed:
self.cur_poly = None
self.filled_mode = True
+ self._draw_lines = False
else:
alert('Not all polygons closed, so not filling')
def mouse_move(self, ev):
old_pos = self.mouse_pos
self.mouse_pos = ev.pos
- if self.cur_poly and old_pos != self.mouse_pos:
+ if old_pos != self.mouse_pos and (self.cur_poly or self._draw_lines):
self.invalidate()
def mouse_drag(self, ev):
@@ -233,8 +290,16 @@
def mouse_down(self, ev):
if ev.button == 1:
- print "Click: %r" % (
- self.level.point_to_pymunk(self._level_coordinates(ev.pos)),)
+ if self._draw_lines:
+ if self._start_pos is None:
+ self._start_pos = ev.pos
+ else:
+ self.level.add_line(self._start_pos, ev.pos)
+ self._start_pos = None
+ else:
+ print "Click: %r" % (
+ self.level.point_to_pymunk(
+ self._level_coordinates(ev.pos)),)
if ev.button == 4: # Scroll up
self._move_view((0, -10))
elif ev.button == 5: # Scroll down
@@ -304,12 +369,21 @@
button_rect = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
+ check_rect = pygame.rect.Rect(0, 0, MENU_BUTTON_HEIGHT // 2,
+ MENU_BUTTON_HEIGHT // 2)
+
end_poly_but = PolyButton(None, self.level_widget)
end_poly_but.rect = button_rect.copy()
end_poly_but.rect.move_ip(MENU_LEFT, y)
self.add(end_poly_but)
y += MENU_BUTTON_HEIGHT + MENU_PAD
+ draw_line = Button("Draw interior wall", self.level_widget.line_mode)
+ draw_line.rect = button_rect.copy()
+ draw_line.rect.move_ip(MENU_LEFT, y)
+ self.add(draw_line)
+ y += MENU_BUTTON_HEIGHT + MENU_PAD
+
fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
fill_but.rect = button_rect.copy()
fill_but.rect.move_ip(MENU_LEFT, y)
@@ -329,6 +403,25 @@
self.add(close_poly_but)
y += MENU_BUTTON_HEIGHT + MENU_PAD
+ white = pygame.color.Color("white")
+ self.show_objs = CheckBox(fg_color=white)
+ self.show_objs.rect = check_rect.copy()
+ self.show_objs.rect.move_ip(MENU_LEFT, y)
+ label = Label("Show Objects", fg_color=white)
+ label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
+ self.add(self.show_objs)
+ self.add(label)
+ y += label.rect.height + MENU_PAD
+
+ self.show_enemies = CheckBox(fg_color=white)
+ self.show_enemies.rect = check_rect.copy()
+ self.show_enemies.rect.move_ip(MENU_LEFT, y)
+ label = Label("Show enemy start pos", fg_color=white)
+ label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
+ self.add(self.show_enemies)
+ self.add(label)
+ y += label.rect.height + MENU_PAD
+
quit_but = Button('Quit', action=self.quit)
quit_but.rect = button_rect.copy()
quit_but.rect.move_ip(MENU_LEFT, y)
@@ -355,11 +448,19 @@
def mouse_move(self, ev):
self.level_widget.mouse_move(ev)
+ def draw(self, surface):
+ # Update checkbox state
+ self.level_widget.set_objects(self.show_objs.value)
+ self.level_widget.set_enemies(self.show_enemies.value)
+ super(EditorApp, self).draw(surface)
+
if __name__ == "__main__":
if len(sys.argv) not in [2, 4]:
print 'Please supply a levelname or levelname and level size'
sys.exit()
+ # Need to ensure we have defaults for rendering
+ parse_args([])
pygame.display.init()
pygame.font.init()
pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),