changeset 244:93a20b51963f

Merge
author David Sharpe
date Wed, 04 Sep 2013 22:26:15 +0200
parents 46707efbb3a5 (current diff) 28d906fc2ab1 (diff)
children 21da1b41bbb6
files nagslang/screens/area.py
diffstat 9 files changed, 232 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- 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")]
--- 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,
--- 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")]
--- 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")]
--- 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
--- 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,
         ]
 
--- 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)
--- /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
+            ])
--- 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)