# 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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]),