# HG changeset patch # User David Sharpe # Date 1378326375 -7200 # Node ID 93a20b51963f900f0b413db351feddf21495da74 # Parent 46707efbb3a51f54b9e0b5431d812db64e21faee# Parent 28d906fc2ab1eba6403afb47b8ac2aba2637c23f Merge diff -r 46707efbb3a5 -r 93a20b51963f nagslang/enemies.py --- a/nagslang/enemies.py Wed Sep 04 22:25:54 2013 +0200 +++ b/nagslang/enemies.py Wed Sep 04 22:26:15 2013 +0200 @@ -10,6 +10,15 @@ from nagslang.resources import resources +def get_editable_enemies(): + classes = [] + for cls_name, cls in globals().iteritems(): + if isinstance(cls, type) and issubclass(cls, Enemy): + if hasattr(cls, 'requires'): + classes.append((cls_name, cls)) + return classes + + class Enemy(GameObject): """A base class for mobile enemies""" @@ -33,6 +42,10 @@ def attack(self): raise NotImplementedError + @classmethod + def requires(cls): + return [("name", "string"), ("position", "coordinates")] + class PatrollingAlien(Enemy): is_moving = True # Always walking. @@ -112,3 +125,8 @@ self._switch_direction() self.set_direction(x_step, y_step) super(PatrollingAlien, self).animate() + + @classmethod + def requires(cls): + return [("name", "string"), ("position", "coordinates"), + ("end_position", "coordinates")] diff -r 46707efbb3a5 -r 93a20b51963f nagslang/engine.py --- a/nagslang/engine.py Wed Sep 04 22:25:54 2013 +0200 +++ b/nagslang/engine.py Wed Sep 04 22:26:15 2013 +0200 @@ -7,6 +7,7 @@ from nagslang.screens.menu import MenuScreen from nagslang.screens.area import AreaScreen from nagslang.events import ScreenChange +from nagslang.world import World class Engine(object): @@ -15,7 +16,7 @@ self._clock = pygame.time.Clock() self._fps = constants.FPS self._dt = 1. / self._fps - self._world = None # TODO: create the world + self._world = World() self._current_screen = None self._screens = { 'menu': MenuScreen, diff -r 46707efbb3a5 -r 93a20b51963f nagslang/game_object.py --- a/nagslang/game_object.py Wed Sep 04 22:25:54 2013 +0200 +++ b/nagslang/game_object.py Wed Sep 04 22:26:15 2013 +0200 @@ -10,6 +10,14 @@ from nagslang.events import DoorEvent +def get_editable_game_objects(): + classes = [] + for cls_name, cls in globals().iteritems(): + if isinstance(cls, type) and hasattr(cls, 'requires'): + classes.append((cls_name, cls)) + return classes + + class Physicser(object): def __init__(self, space): self._space = space @@ -136,6 +144,11 @@ """ return True + @classmethod + def requires(cls): + """Hints for the level editor""" + return [("name", "string")] + class FloorSwitch(GameObject): zorder = ZORDER_FLOOR @@ -154,6 +167,10 @@ puzzle.CollidePuzzler(*SWITCH_PUSHERS), ) + @classmethod + def requires(cls): + return [("name", "string"), ("position", "coordinates")] + class Note(GameObject): zorder = ZORDER_FLOOR @@ -169,6 +186,11 @@ render.TextOverlay(message), ) + @classmethod + def requires(cls): + return [("name", "string"), ("position", "coordinates"), + ("message", "text")] + class FloorLight(GameObject): zorder = ZORDER_FLOOR @@ -187,6 +209,11 @@ puzzle.StateProxyPuzzler(state_source), ) + @classmethod + def requires(cls): + return [("name", "string"), ("position", "coordinates"), + ("state_source", "puzzler")] + class Box(GameObject): def __init__(self, space, position): @@ -200,6 +227,11 @@ render.ImageRenderer(resources.get_image('objects', 'crate.png')), ) + @classmethod + def requires(cls): + return [("name", "string"), ("position", "coordinates"), + ("state_source", "puzzler")] + class Door(GameObject): zorder = ZORDER_FLOOR @@ -226,6 +258,12 @@ if self.puzzler.get_state(): DoorEvent.post(self.destination, self.dest_pos) + @classmethod + def requires(cls): + return [("name", "string"), ("position", "coordinates"), + ("destination", "level name"), ("dest_pos", "coordinate"), + ("key_state", "puzzler")] + class Bulkhead(GameObject): zorder = ZORDER_FLOOR @@ -249,3 +287,8 @@ # Reject the collision, we can walk through. return False return True + + @classmethod + def requires(cls): + return [("name", "string"), ("end1", "coordinates"), + ("end2", "coordinates"), ("key_state", "puzzler")] diff -r 46707efbb3a5 -r 93a20b51963f nagslang/puzzle.py --- a/nagslang/puzzle.py Wed Sep 04 22:25:54 2013 +0200 +++ b/nagslang/puzzle.py Wed Sep 04 22:26:15 2013 +0200 @@ -1,6 +1,14 @@ from nagslang.constants import COLLISION_TYPE_PLAYER +def get_editable_puzzlers(): + classes = [] + for cls_name, cls in globals().iteritems(): + if isinstance(cls, type) and hasattr(cls, 'requires'): + classes.append((cls_name, cls)) + return classes + + class PuzzleGlue(object): """Glue that holds bits of a puzzle together. """ @@ -29,6 +37,13 @@ def get_state(self): raise NotImplementedError() + @classmethod + def requires(cls): + """Tell the level editor the arguments we require + + Format is a list of name: type hint tuples""" + return [("name", "string")] + class YesPuzzler(Puzzler): """Yes sir, I'm always on. @@ -57,6 +72,10 @@ return True return False + @classmethod + def requires(cls): + return [("name", "string"), ("collision_types", "list of ints")] + class StateProxyPuzzler(Puzzler): def __init__(self, state_source): @@ -65,6 +84,10 @@ def get_state(self): return self.glue.get_state_of(self._state_source) + @classmethod + def requires(cls): + return [("name", "string"), ("sources", "list of names")] + class StateLogicalAndPuzzler(Puzzler): def __init__(self, *state_sources): @@ -75,3 +98,7 @@ if not self.glue.get_state_of(state_source): return False return True + + @classmethod + def requires(cls): + return [("name", "string"), ("sources", "list of names")] diff -r 46707efbb3a5 -r 93a20b51963f nagslang/screens/area.py --- a/nagslang/screens/area.py Wed Sep 04 22:25:54 2013 +0200 +++ b/nagslang/screens/area.py Wed Sep 04 22:26:15 2013 +0200 @@ -126,11 +126,13 @@ ScreenChange.post('menu') if ev.key == pygame.locals.K_c: self.protagonist.toggle_form() + self.world.transformations += 1 elif DoorEvent.matches(ev): self.protagonist.set_position(ev.dest_pos) if ev.destination != self.name: # Go to anther screen self._disable_render = True + self.world.rooms += 1 ScreenChange.post(ev.destination, self.protagonist) return # else we're teleporting within the screen, and just the diff -r 46707efbb3a5 -r 93a20b51963f nagslang/screens/menu.py --- a/nagslang/screens/menu.py Wed Sep 04 22:25:54 2013 +0200 +++ b/nagslang/screens/menu.py Wed Sep 04 22:26:15 2013 +0200 @@ -4,7 +4,7 @@ from nagslang.screens.base import Screen from nagslang.events import QuitEvent, ScreenChange -from nagslang.widgets.text import TextWidget +from nagslang.widgets.text import TextWidget, MultiLineWidget class MenuScreen(Screen): @@ -23,6 +23,7 @@ TextWidget((40, 50), 'Start new game'), TextWidget((40, 70), 'Restore saved game'), TextWidget((40, 90), 'Quit'), + MultiLineWidget((60, 120), self.world.get_formatted_stats()), self.cursor, ] diff -r 46707efbb3a5 -r 93a20b51963f nagslang/widgets/text.py --- a/nagslang/widgets/text.py Wed Sep 04 22:25:54 2013 +0200 +++ b/nagslang/widgets/text.py Wed Sep 04 22:26:15 2013 +0200 @@ -1,7 +1,7 @@ +from nagslang.constants import FONT, FONT_SIZE +from nagslang.widgets.base import Widget import pygame -from nagslang.constants import FONT, FONT_SIZE -from nagslang.widgets.base import Widget from nagslang.utils import convert_colour from nagslang.resources import resources @@ -31,6 +31,26 @@ surface.blit(self.surface, self.rect) +class MultiLineWidget(TextWidget): + + def prepare(self): + self.font = resources.get_font(self.fontname, self.fontsize) + surfaces = [] + height = 0 + width = 0 + for line in self.text.split('\n'): + surface = self.font.render(line, True, self.colour) + width = max(width, surface.get_rect().width) + height += surface.get_rect().height + surfaces.append(surface) + self.surface = pygame.surface.Surface((width, height)) + self.surface.fill(pygame.Color('white')) + y = 0 + for surface in surfaces: + self.surface.blit(surface, (0, y)) + y += surface.get_rect().height + + class LabelWidget(TextWidget): def __init__(self, *args, **kwargs): self.padding = kwargs.pop('padding', 5) diff -r 46707efbb3a5 -r 93a20b51963f nagslang/world.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/nagslang/world.py Wed Sep 04 22:26:15 2013 +0200 @@ -0,0 +1,26 @@ +# The world object +# +# This is a global object for tracking state across scenes and all that + + +class World(object): + + def __init__(self): + self.transformations = 0 + self.kills = 0 + self.rooms = 0 + + def save(self): + # TODO: Do this + pass + + def load(self): + # TODO: Do this + pass + + def get_formatted_stats(self): + return "\n".join([ + "Times transformed: %d" % self.transformations, + "Enemies killed: %d" % self.kills, + "Rooms entered: %d" % self.rooms + ]) diff -r 46707efbb3a5 -r 93a20b51963f tools/area_editor.py --- a/tools/area_editor.py Wed Sep 04 22:25:54 2013 +0200 +++ b/tools/area_editor.py Wed Sep 04 22:26:15 2013 +0200 @@ -31,8 +31,9 @@ from nagslang.options import parse_args from nagslang.constants import SCREEN from nagslang.level import Level, POLY_COLORS, LINE_COLOR -from nagslang.enemies import Enemy - +from nagslang.enemies import Enemy, get_editable_enemies +from nagslang.game_object import get_editable_game_objects +from nagslang.puzzle import get_editable_puzzlers # layout constants MENU_BUTTON_HEIGHT = 35 @@ -377,11 +378,15 @@ row = Row(buttons) row.rect = pygame.rect.Rect(0, 450, 700, 50) edit_box.add(row) + edit_box.get_selection = lambda: table.get_selection() return edit_box def edit_objects(self): edit_box = self._make_edit_dialog(self.level._game_objects) res = edit_box.present() + choice = edit_box.get_selection() + if choice is None: + return if res == 'OK': # Edit object stuff goes here pass @@ -391,12 +396,58 @@ def edit_enemies(self): edit_box = self._make_edit_dialog(self.level._enemies) res = edit_box.present() + choice = edit_box.get_selection() + if choice is None: + return if res == 'OK': # Edit object stuff goes here pass elif res == 'Delete': pass + def _make_choice_dialog(self, classes): + # Dialog to hold the editor + data = [] + for cls_name, cls in classes: + data.append({"classname": cls_name, "class": cls}) + edit_box = Dialog() + edit_box.rect = pygame.rect.Rect(0, 0, 700, 500) + table = ObjectTable(data) + edit_box.add(table) + buttons = [] + for text in ['OK', 'Cancel']: + but = Button(text, action=lambda x=text: edit_box.dismiss(x)) + buttons.append(but) + row = Row(buttons) + row.rect = pygame.rect.Rect(0, 450, 700, 50) + edit_box.add(row) + edit_box.get_selection = lambda: table.get_selection() + return edit_box + + def add_game_object(self): + classes = get_editable_game_objects() + choose = self._make_choice_dialog(classes) + res = choose.present() + choice = choose.get_selection() + if res == 'OK' and choice is not None: + pass + + def add_enemy(self): + classes = get_editable_enemies() + choose = self._make_choice_dialog(classes) + res = choose.present() + choice = choose.get_selection() + if res == 'OK' and choice is not None: + pass + + def add_puzzler(self): + classes = get_editable_puzzlers() + choose = self._make_choice_dialog(classes) + res = choose.present() + choice = choose.get_selection() + if res == 'OK' and choice is not None: + pass + class PolyButton(Button): """Button for coosing the correct polygon""" @@ -465,12 +516,6 @@ widgets.append(fill_but) y += MENU_BUTTON_HEIGHT + MENU_PAD - save_but = Button('Save Level', action=self.save) - save_but.rect = BUTTON_RECT.copy() - save_but.rect.move_ip(MENU_LEFT, y) - widgets.append(save_but) - y += MENU_BUTTON_HEIGHT + MENU_PAD - close_poly_but = Button('Close Polygon', action=self.level_widget.close_poly) close_poly_but.rect = BUTTON_RECT.copy() @@ -497,12 +542,20 @@ widgets.append(label) y += label.rect.height + MENU_PAD + y += MENU_PAD switch_but = Button('Switch to Objects', action=self.switch_to_objects) switch_but.rect = BUTTON_RECT.copy() switch_but.rect.move_ip(MENU_LEFT, y) widgets.append(switch_but) y += switch_but.rect.height + MENU_PAD + save_but = Button('Save Level', action=self.save) + save_but.rect = BUTTON_RECT.copy() + save_but.rect.move_ip(MENU_LEFT, y) + widgets.append(save_but) + y += MENU_BUTTON_HEIGHT + MENU_PAD + + y += MENU_PAD quit_but = Button('Quit', action=self.quit) quit_but.rect = BUTTON_RECT.copy() quit_but.rect.move_ip(MENU_LEFT, y) @@ -530,18 +583,41 @@ widgets.append(edir_enemies_but) y += MENU_BUTTON_HEIGHT + MENU_PAD + add_obj_but = Button('Add Game Object', + action=self.level_widget.add_game_object) + add_obj_but.rect = BUTTON_RECT.copy() + add_obj_but.rect.move_ip(MENU_LEFT, y) + widgets.append(add_obj_but) + y += MENU_BUTTON_HEIGHT + MENU_PAD + + add_puzzle_but = Button('Add Puzzler', + action=self.level_widget.add_puzzler) + add_puzzle_but.rect = BUTTON_RECT.copy() + add_puzzle_but.rect.move_ip(MENU_LEFT, y) + widgets.append(add_puzzle_but) + y += MENU_BUTTON_HEIGHT + MENU_PAD + + add_enemy_but = Button('Add Enemy', + action=self.level_widget.add_enemy) + add_enemy_but.rect = BUTTON_RECT.copy() + add_enemy_but.rect.move_ip(MENU_LEFT, y) + widgets.append(add_enemy_but) + y += MENU_BUTTON_HEIGHT + MENU_PAD + + y += MENU_PAD + switch_but = Button('Switch to Drawing', action=self.switch_to_draw) + switch_but.rect = BUTTON_RECT.copy() + switch_but.rect.move_ip(MENU_LEFT, y) + widgets.append(switch_but) + y += switch_but.rect.height + MENU_PAD + save_but = Button('Save Level', action=self.save) save_but.rect = BUTTON_RECT.copy() save_but.rect.move_ip(MENU_LEFT, y) widgets.append(save_but) y += MENU_BUTTON_HEIGHT + MENU_PAD - switch_but = Button('Switch to Drawing', action=self.switch_to_draw) - switch_but.rect = BUTTON_RECT.copy() - switch_but.rect.move_ip(MENU_LEFT, y) - widgets.append(switch_but) - y += switch_but.rect.height + MENU_PAD - + y += MENU_PAD quit_but = Button('Quit', action=self.quit) quit_but.rect = BUTTON_RECT.copy() quit_but.rect.move_ip(MENU_LEFT, y)