changeset 765:2f1952748cdb i18n

merge i18n and Russian translation
author Stefano Rivera <stefano@rivera.za.net>
date Tue, 08 Mar 2011 14:37:43 +0200
parents 0ce08d5e2acb (diff) 7d8e4f2a5bbe (current diff)
children 1eb1f80ef3a3
files Resources/fonts/Vera-Copyright.txt gamelib/gamescreen.py gamelib/scenes/bridge.py gamelib/scenes/crew_quarters.py gamelib/scenes/cryo.py gamelib/scenes/engine.py gamelib/scenes/game_widgets.py gamelib/scenes/machine.py gamelib/scenes/map.py gamelib/scenes/mess.py gamelib/widgets.py po/POTFILES po/ru.po po/suspended-sentence.pot
diffstat 30 files changed, 1474 insertions(+), 954 deletions(-) [+]
line wrap: on
line diff
Binary file Resources/images/cryo/comp_info_detail.png has changed
Binary file Resources/images/cryo/comp_info_detail_fixed.png has changed
Binary file Resources/images/items/superconductor_broken.png has changed
Binary file Resources/images/items/superconductor_broken_cursor.png has changed
--- a/TODO	Tue Mar 08 13:26:00 2011 +0200
+++ b/TODO	Tue Mar 08 14:37:43 2011 +0200
@@ -1,14 +1,29 @@
 Specific:
-  * Make arrow keys work in rect_drawer
   * Make number keys work in game
   * Full screen mode
+  * Brighter text background in overlayed messages.
+  * Add support for descriptions for items.
 
 Cleanups:
   * Clean-up finding and loading of speech strings.
   * Fix thing.enter being called multiple times without leaving
 
 Hints:
-  * Interacts with broken pipes & cryo/pipe / tins
+  * More help with the starting puzzle (another interactable cryo-chamber?)
+  * Interacts with broken pipes & cryo/pipe / tins / < 3 fixed pipe
   * Interacts with laser cutter & tins
   * Interacts with cameras
   * Interacts with cryo-resevoirs & empty bottle
+  * Interacts with cryo-puddles & cans.
+  * Interacts with fish bowl & engine room
+  * Make JIM messages more clearly from JIM.
+  * Make it clearer who JIM is (That he is separate from the ship computers)
+  * JIM Speaks after fixing engines & repairing environment?
+
+Game:
+  * Keep track of total sentence.
+  * Add descriptions for items.
+  * Add sound to more rooms.
+
+Bugs:
+  * Individualized cans.
--- a/gamelib/cursor.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/cursor.py	Tue Mar 08 14:37:43 2011 +0200
@@ -90,7 +90,7 @@
 
     @classmethod
     def set_cursor(cls, item):
-        if item is None:
+        if item is None or item.CURSOR is None:
             cls.cursor = HAND
         else:
             cls.cursor = item.CURSOR
--- a/gamelib/endscreen.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/endscreen.py	Tue Mar 08 14:37:43 2011 +0200
@@ -3,9 +3,7 @@
 # Victory screen for the game
 
 from albow.screen import Screen
-from albow.controls import Button
 from albow.resource import get_image
-from albow.layout import Column
 
 from gamelib.widgets import BoomImageButton
 
--- a/gamelib/gamescreen.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/gamescreen.py	Tue Mar 08 14:37:43 2011 +0200
@@ -8,9 +8,8 @@
 from albow.screen import Screen
 from pygame import Rect, mouse
 from pygame.color import Color
-from pygame.locals import BLEND_ADD
 
-from constants import SCREEN, BUTTON_SIZE, SCENE_SIZE
+from constants import SCREEN, BUTTON_SIZE, SCENE_SIZE, LEAVE
 from cursor import CursorWidget
 from state import initial_state, handle_result
 from widgets import (MessageDialog, BoomButton, HandButton, PopupMenu,
@@ -40,8 +39,8 @@
         item = self.state.inventory[item_no]
         if self.item_is_selected(item_no):
             self.unselect()
-        elif self.state.tool or hasattr(item, 'interact_without'):
-            result = item.interact(self.state.tool, self.state)
+        elif item.is_interactive(self.state.tool):
+            result = item.interact(self.state.tool)
             handle_result(result, self.state_widget)
         else:
             self.state.set_tool(self.state.inventory[item_no])
@@ -68,7 +67,11 @@
         self.detail = DetailWindow(screen)
 
     def draw(self, surface):
-        self.state.draw(surface, self.screen)
+        if self.state.previous_scene and self.state.do_check == LEAVE:
+            # We still need to handle leave events, so still display the scene
+            self.state.previous_scene.draw(surface, self)
+        else:
+            self.state.current_scene.draw(surface, self)
 
     def mouse_down(self, event):
         self.mouse_move(event)
@@ -97,7 +100,8 @@
 
     def _mouse_move(self, pos):
         self.state.highlight_override = False
-        self.state.mouse_move(pos)
+        self.state.current_scene.mouse_move(pos)
+        self.state.old_pos = pos
 
     def show_message(self, message, style=None):
         # Display the message as a modal dialog
@@ -112,8 +116,8 @@
 
     def show_detail(self, detail):
         self.clear_detail()
-        w, h = self.state.set_current_detail(detail)
-        self.detail.set_image_rect(Rect(0, 0, w, h))
+        detail_obj = self.state.set_current_detail(detail)
+        self.detail.set_image_rect(Rect((0, 0), detail_obj.get_detail_size()))
         self.add_centered(self.detail)
         self.state.do_enter_detail()
 
@@ -160,7 +164,7 @@
         overlay = scene_surface.convert_alpha()
         overlay.fill(Color(0, 0, 0, 191))
         scene_surface.blit(overlay, (0, 0))
-        self.state.draw_detail(surface.subsurface(self.image_rect), self.screen)
+        self.state.current_detail.draw(surface.subsurface(self.image_rect), self)
 
     def mouse_down(self, event):
         self.mouse_move(event)
@@ -175,7 +179,7 @@
 
     def _mouse_move(self, pos):
         self.state.highlight_override = False
-        self.state.mouse_move_detail(self.global_to_local(pos))
+        self.state.current_detail.mouse_move(self.global_to_local(pos))
 
     def show_message(self, message, style=None):
         self.parent.show_message(message, style)
--- a/gamelib/menu.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/menu.py	Tue Mar 08 14:37:43 2011 +0200
@@ -3,10 +3,7 @@
 # Main menu for the game
 
 from albow.screen import Screen
-from albow.controls import Image, Button, Label
-from albow.layout import Column
 from albow.resource import get_image
-from pygame import Rect
 
 from gamelib.widgets import BoomImageButton
 
--- a/gamelib/scenes/__init__.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/scenes/__init__.py	Tue Mar 08 14:37:43 2011 +0200
@@ -1,1 +1,13 @@
 """Package for holding scenes."""
+
+SCENE_LIST = [
+        "cryo",
+        "bridge",
+        "mess",
+        "engine",
+        "machine",
+        "crew_quarters",
+        "map",
+        "manual",
+        ]
+INITIAL_SCENE = 'cryo'
--- a/gamelib/scenes/bridge.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/scenes/bridge.py	Tue Mar 08 14:37:43 2011 +0200
@@ -9,16 +9,16 @@
 from albow.resource import get_image
 
 from gamelib.cursor import CursorSprite
+from gamelib.i18n import _
 from gamelib.state import Scene, Item, Thing, Result
 from gamelib.sound import get_current_playlist
 from gamelib.constants import DEBUG
+from gamelib.scenewidgets import (InteractNoImage, InteractRectUnion,
+                                  InteractImage, InteractAnimated,
+                                  GenericDescThing)
 
 from gamelib.scenes.game_constants import PLAYER_ID
-from gamelib.scenes.scene_widgets import (Door, InteractText, InteractNoImage,
-                                          InteractRectUnion, InteractImage,
-                                          InteractAnimated, GenericDescThing,
-                                          BaseCamera, make_jim_dialog)
-from gamelib.i18n import _
+from gamelib.scenes.game_widgets import Door, BaseCamera, make_jim_dialog
 
 
 class Bridge(Scene):
@@ -171,7 +171,7 @@
         return self.state.current_scene.things['bridge.massagechair_base'] \
                    .get_description()
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
 
@@ -202,7 +202,7 @@
         # Fill in the doctor's rect
         self.scene.doctor.rect.append(self.rect)
         return Result(_("You pick up the stethoscope and verify that the doctor's "
-                      "heart has stopped. Probably a while ago."))
+                        "heart has stopped. Probably a while ago."))
 
 
 class TapedSuperconductor(Item):
@@ -218,12 +218,12 @@
     INVENTORY_IMAGE = 'superconductor_fixed.png'
     CURSOR = CursorSprite('superconductor_fixed.png')
 
-    def interact_with_duct_tape(self, item, state):
+    def interact_with_duct_tape(self, item):
         taped_superconductor = TapedSuperconductor('taped_superconductor')
-        state.add_item(taped_superconductor)
-        state.replace_inventory_item(self.name, taped_superconductor.name)
+        self.state.add_item(taped_superconductor)
+        self.state.replace_inventory_item(self.name, taped_superconductor.name)
         return Result(_("You rip off a piece of duct tape and stick it on the superconductor. "
-                      "It almost sticks to itself, but you successfully avoid disaster."))
+                        "It almost sticks to itself, but you successfully avoid disaster."))
 
 
 class SuperconductorThing(Thing):
@@ -260,7 +260,7 @@
 
     INITIAL = 'stars'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
 
@@ -270,7 +270,7 @@
         super(BlinkingLights, self).__init__()
         self.description = None
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
     def leave(self):
@@ -359,8 +359,6 @@
     BACKGROUND = 'chair_detail.png'
     NAME = 'chair_detail'
 
-    SIZE = (300, 300)
-
     def __init__(self, state):
         super(ChairDetail, self).__init__(state)
         self.add_thing(SuperconductorThing())
@@ -380,7 +378,7 @@
     INITIAL = 'log tab'
     COMPUTER = 'bridge_comp_detail'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return self.state.detail_views[self.COMPUTER].get_data('tab') != 'log'
 
     def interact_without(self):
@@ -400,7 +398,7 @@
     INITIAL = 'alert tab'
     COMPUTER = 'bridge_comp_detail'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return self.state.detail_views[self.COMPUTER].get_data('tab') != 'alert'
 
     def interact_without(self):
@@ -419,7 +417,7 @@
     INITIAL = 'nav tab'
     COMPUTER = 'bridge_comp_detail'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return self.state.detail_views[self.COMPUTER].get_data('tab') != 'nav'
 
     def interact_without(self):
@@ -445,7 +443,7 @@
         self.ai_blocked = ai_blocked
         self.set_interact('line')
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return self.state.detail_views[self.COMPUTER].get_data('tab') == 'nav'
 
     def interact_without(self):
@@ -469,7 +467,7 @@
     INITIAL = 'up'
     COMPUTER = 'bridge_comp_detail'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         tab = self.state.detail_views[self.COMPUTER].get_data('tab')
         page = self.state.detail_views[self.COMPUTER].get_data('log page')
         return tab == 'log' and page > 0
@@ -492,7 +490,7 @@
     INITIAL = 'down'
     COMPUTER = 'bridge_comp_detail'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         tab = self.state.detail_views[self.COMPUTER].get_data('tab')
         page = self.state.detail_views[self.COMPUTER].get_data('log page')
         max_page = self.state.detail_views[self.COMPUTER].get_data('max page')
@@ -524,8 +522,6 @@
     FOLDER = 'bridge'
     NAME = 'bridge_comp_detail'
 
-    SIZE = (640, 400)
-
     ALERT_BASE = 'comp_alert_base.png'
     ALERTS = {
             'ai looping' : 'comp_alert_ai_looping.png',
--- a/gamelib/scenes/crew_quarters.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/scenes/crew_quarters.py	Tue Mar 08 14:37:43 2011 +0200
@@ -2,12 +2,11 @@
 
 from gamelib.cursor import CursorSprite
 from gamelib.state import Scene, Item, Thing, Result
+from gamelib.scenewidgets import (InteractNoImage, InteractImage,
+                                  InteractAnimated, GenericDescThing)
 
 from gamelib.scenes.game_constants import PLAYER_ID
-from gamelib.scenes.scene_widgets import (Door, InteractText, InteractNoImage,
-                                          InteractRectUnion, InteractImage,
-                                          InteractAnimated, GenericDescThing,
-                                          BaseCamera, make_jim_dialog)
+from gamelib.scenes.game_widgets import Door, BaseCamera, make_jim_dialog
 
 from gamelib.i18n import _
 
@@ -123,7 +122,11 @@
         return Result(_("The fishbowl is useful, but its contents aren't."))
 
     def get_description(self):
-        return _("This fishbowl looks exactly like an old science fiction space helmet.")
+        if self.get_data('has_bowl'):
+            return _("This fishbowl looks exactly like an old science fiction space helmet.")
+        else:
+            return _("An evicted dead fish and some sand lie forlornly on the table")
+
 
 class Fishbowl(Item):
     "A bowl. Sans fish."
@@ -132,12 +135,12 @@
     CURSOR = CursorSprite('fishbowl.png')
     NAME = "fishbowl"
 
-    def interact_with_duct_tape(self, item, state):
+    def interact_with_duct_tape(self, item):
         helmet = FishbowlHelmet('helmet')
-        state.add_item(helmet)
-        state.replace_inventory_item(self.name, helmet.name)
+        self.state.add_item(helmet)
+        self.state.replace_inventory_item(self.name, helmet.name)
         return Result(_("You duct tape the edges of the helmet. The seal is"
-                " crude, but it will serve as a workable helmet if needed."))
+                        " crude, but it will serve as a workable helmet if needed."))
 
 
 class FishbowlHelmet(Item):
--- a/gamelib/scenes/cryo.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/scenes/cryo.py	Tue Mar 08 14:37:43 2011 +0200
@@ -5,16 +5,14 @@
 from albow.music import change_playlist, get_music, PlayList
 from albow.resource import get_image
 
-from gamelib import speech
-from gamelib.sound import get_sound
 from gamelib.cursor import CursorSprite
 from gamelib.state import Scene, Item, CloneableItem, Thing, Result
-from gamelib.constants import DEBUG
+from gamelib.scenewidgets import (InteractNoImage, InteractRectUnion,
+                                  InteractImage, InteractAnimated,
+                                  GenericDescThing)
+
 from gamelib.scenes.game_constants import PLAYER_ID
-from gamelib.scenes.scene_widgets import (Door, InteractText, InteractNoImage,
-                                          InteractRectUnion, InteractImage,
-                                          InteractAnimated, GenericDescThing,
-                                          make_jim_dialog)
+from gamelib.scenes.game_widgets import Door, make_jim_dialog
 
 from gamelib.i18n import _
 
@@ -175,7 +173,7 @@
                     ) % PLAYER_ID, self.state))
             return responses
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return self.get_data('fixed')
 
     def interact_without(self):
@@ -206,7 +204,7 @@
         return Result(_("These pipes carry fluid to the working cryo units."
                 " Chopping them down doesn't seem sensible."))
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return True
 
     def interact_without(self):
@@ -301,7 +299,7 @@
         super(GenericCryoUnit, self).__init__('cryo.unit', number, description, areas)
         self.detailed_description = detailed_description
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return True
 
     def interact_without(self):
@@ -461,7 +459,7 @@
         return _("Coolant leaks disturbingly from the bulkheads.")
 
     def interact_without(self):
-        return Result("It's gooey")
+        return Result(_("It's gooey"))
 
     def interact_with_detergent_bottle(self, item):
         full = FullBottle('full_detergent_bottle')
@@ -477,8 +475,6 @@
     BACKGROUND_FIXED = "comp_info_detail_fixed.png"
     NAME = "cryo_comp_detail"
 
-    SIZE = (640, 400)
-
     def __init__(self, state):
         super(CryoCompDetail, self).__init__(state)
         self._background_fixed = get_image(self.FOLDER, self.BACKGROUND_FIXED)
@@ -496,8 +492,6 @@
     BACKGROUND = "cryo_unit_detail.png"
     NAME = "cryo_detail"
 
-    SIZE = (300, 300)
-
     def __init__(self, state):
         super(CryoUnitWithCorpse, self).__init__(state)
         self.add_thing(TitaniumLegThing())
--- a/gamelib/scenes/engine.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/scenes/engine.py	Tue Mar 08 14:37:43 2011 +0200
@@ -3,11 +3,12 @@
 from albow.resource import get_image
 from gamelib.cursor import CursorSprite
 from gamelib.state import Scene, Item, Thing, Result
+from gamelib.scenewidgets import (InteractNoImage, InteractRectUnion,
+                                  InteractImage, InteractAnimated,
+                                  GenericDescThing)
+
 from gamelib.scenes.game_constants import PLAYER_ID
-from gamelib.scenes.scene_widgets import (Door, InteractText, InteractNoImage,
-                                          InteractRectUnion, InteractImage,
-                                          InteractAnimated, GenericDescThing,
-                                          make_jim_dialog)
+from gamelib.scenes.game_widgets import Door, make_jim_dialog
 
 from gamelib.i18n import _
 
@@ -25,7 +26,6 @@
         super(Engine, self).__init__(state)
         self.add_item(CanOpener('canopener'))
         self.add_thing(CanOpenerThing())
-        self.add_item(BrokenSuperconductor('superconductor_broken'))
         self.add_thing(SuperconductorSocket())
         self.add_thing(PowerLines())
         self.add_thing(CryoContainers())
@@ -46,12 +46,6 @@
                 (549, 479, 60, 55),
             )
         ))
-        self.add_thing(GenericDescThing('engine.controlpanel', 2,
-            _("A control panel. It seems dead."),
-            (
-                (513, 330, 58, 50),
-            )
-        ))
         self.add_thing(GenericDescThing('engine.superconductors', 4,
             _("Superconductors. The engines must be power hogs."),
             (
@@ -61,7 +55,8 @@
             )
         ))
         self.add_thing(GenericDescThing('engine.floor_hole', 5,
-            _("A gaping hole in the floor of the room. You're guessing that's why there's a vacuum in here."),
+            _("A gaping hole in the floor of the room. "
+              "It is clearly irreparable."),
             (
                 (257, 493, 141, 55),
                 (301, 450, 95, 45),
@@ -120,7 +115,8 @@
             )
         ))
         self.add_thing(GenericDescThing('engine.exit_sign', 13,
-            _("It's one of those glow-in-the-dark exit signs that you see everywhere."),
+            _("It's one of those glow-in-the-dark signs needed to satisfy the "
+              "health and safety inspectors."),
             (
                 (681, 322, 80, 33),
             )
@@ -152,7 +148,7 @@
 
     INITIAL = 'on'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
     def get_description(self):
@@ -183,11 +179,6 @@
                       "the vacuum has kept it in perfect condition."))
 
 
-class BrokenSuperconductor(Item):
-    INVENTORY_IMAGE = 'superconductor_broken.png'
-    CURSOR = CursorSprite('superconductor_broken_cursor.png')
-
-
 class SuperconductorSocket(Thing):
     NAME = 'engine.superconductor'
 
@@ -222,8 +213,8 @@
         if self.get_data('present') and not self.get_data('working'):
             self.set_interact('removed')
             self.set_data('present', False)
-            self.state.add_inventory_item('superconductor_broken')
-            return Result(_("With leverage, the burned-out superconductor snaps out."))
+            return Result(_("With leverage, the burned-out superconductor snaps out. "
+                            "You discard it."))
 
     def interact_with_superconductor(self, item):
         if self.get_data('present'):
@@ -267,7 +258,7 @@
             return _("Those are coolant reservoirs. They look empty.")
         return _("The coolant reservoirs are full.")
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
 
@@ -351,7 +342,7 @@
         return _("These pipes carry coolant to the superconductors. " \
                "They are very cold.")
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
 
@@ -377,7 +368,7 @@
             return _("Power lines. They are delivering power to the engines.")
         return _("Power lines. It looks like they aren't working correctly.")
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
 
@@ -394,7 +385,7 @@
 
     INITIAL = 'arrows'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
 
@@ -411,7 +402,7 @@
 
     INITIAL = 'arrows'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
 
@@ -428,7 +419,7 @@
 
     INITIAL = 'arrows'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
 
@@ -444,7 +435,7 @@
 
     INITIAL = 'sign'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
 
@@ -460,7 +451,7 @@
 
     INITIAL = 'stars'
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
     def get_description(self):
@@ -504,7 +495,10 @@
     NAME = "engine.computer_console"
 
     INTERACTS = {
-        'console': InteractNoImage(293, 287, 39, 36),
+        'console': InteractRectUnion((
+            (293, 287, 39, 36),
+            (513, 330, 58, 50),
+        )),
     }
 
     INITIAL = 'console'
@@ -522,8 +516,6 @@
     BACKGROUND = "engine_comp_detail.png"
     NAME = "engine_comp_detail"
 
-    SIZE = (640, 400)
-
     ALERTS = {
             'cryo leaking' : 'ec_cryo_leaking.png',
             'cryo empty' : 'ec_cryo_reservoir_empty.png',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamelib/scenes/game_widgets.py	Tue Mar 08 14:37:43 2011 +0200
@@ -0,0 +1,73 @@
+"""Generic, game specific widgets"""
+
+
+from gamelib.i18n import _
+from gamelib.state import Thing, Result
+
+
+class Door(Thing):
+    """A door somewhere"""
+
+    DEST = "map"
+    SCENE = None
+
+    def __init__(self):
+        self.NAME = self.SCENE + '.door'
+        Thing.__init__(self)
+
+    def is_interactive(self, tool=None):
+        return True
+
+    def interact_without(self):
+        """Go to map."""
+        self.state.set_current_scene("map")
+
+    def get_description(self):
+        return _('An open doorway leads to the rest of the ship.')
+
+    def interact_default(self, item):
+        return self.interact_without()
+
+
+def make_jim_dialog(mesg, state):
+    "Utility helper function"
+    if state.scenes['bridge'].get_data('ai status') == 'online':
+        return Result(mesg, style='JIM')
+    else:
+        return None
+
+
+class BaseCamera(Thing):
+    "Base class for the camera puzzles"
+
+    INITIAL = 'online'
+    INITIAL_DATA = {
+         'state': 'online',
+    }
+
+    def get_description(self):
+        status = self.state.scenes['bridge'].get_data('ai status')
+        if status == 'online':
+            return _("A security camera watches over the room")
+        elif status == 'looping':
+            return _("The security camera is currently offline but should be working soon")
+        else:
+            return _("The security camera is powered down")
+
+    def is_interactive(self, tool=None):
+        return self.state.scenes['bridge'].get_data('ai status') == 'online'
+
+    def interact_with_escher_poster(self, item):
+        # Order matters here, because of helper function
+        if self.state.scenes['bridge'].get_data('ai status') == 'online':
+            ai_response = make_jim_dialog(_("3D scene reconstruction failed. Critical error. Entering emergency shutdown."), self.state)
+            self.state.scenes['bridge'].set_data('ai status', 'looping')
+            return ai_response
+
+    def animate(self):
+        ai_status = self.state.scenes['bridge'].get_data('ai status')
+        if ai_status != self.get_data('status'):
+            self.set_data('status', ai_status)
+            self.set_interact(ai_status)
+        super(BaseCamera, self).animate()
+
--- a/gamelib/scenes/machine.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/scenes/machine.py	Tue Mar 08 14:37:43 2011 +0200
@@ -2,9 +2,10 @@
 
 from gamelib.state import Scene, Item, Thing, Result
 from gamelib.cursor import CursorSprite
-from gamelib.scenes.scene_widgets import (Door, InteractText, InteractNoImage,
-                                          InteractRectUnion, InteractImage,
-                                          InteractAnimated, GenericDescThing)
+from gamelib.scenewidgets import (InteractNoImage, InteractImage,
+                                  InteractAnimated, GenericDescThing)
+
+from gamelib.scenes.game_widgets import Door
 
 from gamelib.i18n import _
 
@@ -287,7 +288,10 @@
     INVENTORY_IMAGE = "manual.png"
     CURSOR = None
 
-    def interact_without(self, state):
+    def is_interactive(self, tool=None):
+        return True
+
+    def interact_without(self):
         return Result(detail_view='manual_detail')
 
 
--- a/gamelib/scenes/manual.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/scenes/manual.py	Tue Mar 08 14:37:43 2011 +0200
@@ -1,19 +1,10 @@
 """The inside of the maintenance manual."""
 
-import random
-
-from albow.music import change_playlist, get_music, PlayList
-from albow.resource import get_image
+from albow.music import change_playlist
 
-from gamelib.cursor import CursorSprite
-from gamelib.state import Scene, Item, Thing, Result
+from gamelib.state import Scene, Thing
 from gamelib.sound import get_current_playlist
-
-from gamelib.scenes.game_constants import PLAYER_ID
-from gamelib.scenes.scene_widgets import (Door, InteractText, InteractNoImage,
-                                          InteractRectUnion, InteractImage,
-                                          InteractAnimated, GenericDescThing,
-                                          BaseCamera, make_jim_dialog)
+from gamelib.scenewidgets import InteractNoImage, InteractImage
 
 
 # classes related the computer detail
@@ -35,7 +26,7 @@
         self.set_data('display', display)
         self.set_interact(display)
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return self.get_data('display') == 'on'
 
 
@@ -94,7 +85,7 @@
         'page': 0,
         }
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
     def set_page(self, page):
--- a/gamelib/scenes/map.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/scenes/map.py	Tue Mar 08 14:37:43 2011 +0200
@@ -7,12 +7,11 @@
    Many parts of the ship are derelict and inaccessible.
    """
 
-from gamelib.state import Scene, Item, Thing, Result
+from gamelib.state import Scene, Thing, Result
+from gamelib.scenewidgets import InteractRectUnion
+
 from gamelib.scenes.game_constants import PLAYER_ID
-from gamelib.scenes.scene_widgets import (Door, InteractText, InteractNoImage,
-                                          InteractRectUnion, InteractImage,
-                                          InteractAnimated, GenericDescThing,
-                                          make_jim_dialog)
+from gamelib.scenes.game_widgets import make_jim_dialog
 
 from gamelib.i18n import _
 
--- a/gamelib/scenes/mess.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/scenes/mess.py	Tue Mar 08 14:37:43 2011 +0200
@@ -1,17 +1,17 @@
 """Mess where crew eat. Fun stuff."""
 
-from random import choice, randint
+from random import randint
 
 from gamelib.state import Scene, Item, CloneableItem, Thing, Result
 from gamelib.cursor import CursorSprite
-from gamelib.scenes.scene_widgets import (Door, InteractText, InteractNoImage,
-                                          InteractRectUnion, InteractImage,
-                                          InteractImageRect, InteractAnimated,
-                                          GenericDescThing)
-
 from gamelib.sound import get_sound
 from gamelib import constants
+from gamelib.scenewidgets import (InteractNoImage, InteractImage,
+                                  InteractImageRect, InteractAnimated,
+                                  GenericDescThing)
+
 from gamelib.scenes.game_constants import PLAYER_ID
+from gamelib.scenes.game_widgets import Door
 
 from gamelib.i18n import _
 
@@ -51,22 +51,22 @@
 class BaseCan(CloneableItem):
     """Base class for the cans"""
 
-    def interact_with_full_can(self, item, state):
+    def interact_with_full_can(self, item):
         return Result(_("You bang the cans together. It sounds like two cans being banged together."), soundfile="can_hit.ogg")
 
-    def interact_with_dented_can(self, item, state):
-        return self.interact_with_full_can(item, state)
+    def interact_with_dented_can(self, item):
+        return self.interact_with_full_can(item)
 
-    def interact_with_empty_can(self, item, state):
-        return self.interact_with_full_can(item, state)
+    def interact_with_empty_can(self, item):
+        return self.interact_with_full_can(item)
 
-    def interact_with_machete(self, item, state):
+    def interact_with_machete(self, item):
         return Result(_("You'd mangle it beyond usefulness."))
 
-    def interact_with_canopener(self, item, state):
+    def interact_with_canopener(self, item):
         empty = EmptyCan('empty_can')
-        state.add_item(empty)
-        state.replace_inventory_item(self.name, empty.name)
+        self.state.add_item(empty)
+        self.state.replace_inventory_item(self.name, empty.name)
         return Result(_("You open both ends of the can, discarding the hideous contents."))
 
 
@@ -76,10 +76,10 @@
     INVENTORY_IMAGE = "empty_can.png"
     CURSOR = CursorSprite('empty_can_cursor.png')
 
-    def interact_with_titanium_leg(self, item, state):
+    def interact_with_titanium_leg(self, item):
         return Result(_("Flattening the can doesn't look like a useful thing to do."))
 
-    def interact_with_canopener(self, item, state):
+    def interact_with_canopener(self, item):
         return Result(_("There's nothing left to open on this can"))
 
 
@@ -89,10 +89,10 @@
     INVENTORY_IMAGE = "full_can.png"
     CURSOR = CursorSprite('full_can_cursor.png')
 
-    def interact_with_titanium_leg(self, item, state):
+    def interact_with_titanium_leg(self, item):
         dented = DentedCan("dented_can")
-        state.add_item(dented)
-        state.replace_inventory_item(self.name, dented.name)
+        self.state.add_item(dented)
+        self.state.replace_inventory_item(self.name, dented.name)
         return Result(_("You club the can with the femur. The can gets dented, but doesn't open."), soundfile="can_hit.ogg")
 
 
@@ -102,7 +102,7 @@
     INVENTORY_IMAGE = "dented_can.png"
     CURSOR = CursorSprite('dented_can_cursor.png')
 
-    def interact_with_titanium_leg(self, item, state):
+    def interact_with_titanium_leg(self, item):
         return Result(_("You club the can with the femur. The dents shift around, but it still doesn't open."), soundfile="can_hit.ogg")
 
 
@@ -249,7 +249,7 @@
 
     HISS = get_sound('boomslang.ogg')
 
-    def is_interactive(self):
+    def is_interactive(self, tool=None):
         return False
 
     def animate(self):
--- a/gamelib/scenes/scene_widgets.py	Tue Mar 08 13:26:00 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,209 +0,0 @@
-"""Generic, game specific widgets"""
-
-import random
-
-from pygame import Rect
-from pygame.color import Color
-from pygame.colordict import THECOLORS
-from pygame.surface import Surface
-from albow.resource import get_image
-
-from gamelib.state import Thing, Result
-from gamelib.constants import DEBUG
-from gamelib.widgets import BoomLabel
-
-from gamelib.i18n import _
-
-class Interact(object):
-
-    def __init__(self, image, rect, interact_rect):
-        self.image = image
-        self.rect = rect
-        self.interact_rect = interact_rect
-
-    def set_thing(self, thing):
-        pass
-
-    def draw(self, surface):
-        if self.image is not None:
-            surface.blit(self.image, self.rect, None)
-
-    def animate(self):
-        return False
-
-
-class InteractNoImage(Interact):
-
-    def __init__(self, x, y, w, h):
-        super(InteractNoImage, self).__init__(None, None, Rect(x, y, w, h))
-
-
-class InteractText(Interact):
-    """Display box with text to interact with -- mostly for debugging."""
-
-    def __init__(self, x, y, text, bg_color=None):
-        if bg_color is None:
-            bg_color = (127, 127, 127)
-        label = BoomLabel(text)
-        label.set_margin(5)
-        label.border_width = 1
-        label.border_color = (0, 0, 0)
-        label.bg_color = bg_color
-        label.fg_color = (0, 0, 0)
-        image = Surface(label.size)
-        rect = Rect((x, y), label.size)
-        label.draw_all(image)
-        super(InteractText, self).__init__(image, rect, rect)
-
-
-class InteractRectUnion(Interact):
-
-    def __init__(self, rect_list):
-        # pygame.rect.Rect.unionall should do this, but is broken
-        # in some pygame versions (including 1.8, it appears)
-        rect_list = [Rect(x) for x in rect_list]
-        union_rect = rect_list[0]
-        for rect in rect_list[1:]:
-            union_rect = union_rect.union(rect)
-        super(InteractRectUnion, self).__init__(None, None, union_rect)
-        self.interact_rect = rect_list
-
-
-class InteractImage(Interact):
-
-    def __init__(self, x, y, image_name):
-        super(InteractImage, self).__init__(None, None, None)
-        self._pos = (x, y)
-        self._image_name = image_name
-
-    def set_thing(self, thing):
-        self.image = get_image(thing.folder, self._image_name)
-        self.rect = Rect(self._pos, self.image.get_size())
-        self.interact_rect = self.rect
-
-
-class InteractImageRect(InteractImage):
-    def __init__(self, x, y, image_name, r_x, r_y, r_w, r_h):
-        super(InteractImageRect, self).__init__(x, y, image_name)
-        self._r_pos = (r_x, r_y)
-        self._r_size = (r_w, r_h)
-
-    def set_thing(self, thing):
-        super(InteractImageRect, self).set_thing(thing)
-        self.interact_rect = Rect(self._r_pos, self._r_size)
-
-
-class InteractAnimated(Interact):
-    """Interactive with an animation rather than an image"""
-
-    # FIXME: Assumes all images are the same size
-    # anim_seq - sequence of image names
-    # delay - number of frames to wait between changing images
-
-    def __init__(self, x, y, anim_seq, delay):
-        self._pos = (x, y)
-        self._anim_pos = 0
-        self._names = anim_seq
-        self._frame_count = 0
-        self._anim_seq = None
-        self._delay = delay
-
-    def set_thing(self, thing):
-        self._anim_seq = [get_image(thing.folder, x) for x in self._names]
-        self.image = self._anim_seq[0]
-        self.rect = Rect(self._pos, self.image.get_size())
-        self.interact_rect = self.rect
-
-    def animate(self):
-        if self._anim_seq:
-            self._frame_count += 1
-            if self._frame_count > self._delay:
-                self._frame_count = 0
-                self._anim_pos += 1
-                if self._anim_pos >= len(self._anim_seq):
-                    self._anim_pos = 0
-                self.image = self._anim_seq[self._anim_pos]
-                # queue redraw
-                return True
-        return False
-
-
-class GenericDescThing(Thing):
-    "Thing with an InteractiveUnionRect and a description"
-
-    INITIAL = "description"
-
-    def __init__(self, prefix, number, description, areas):
-        super(GenericDescThing, self).__init__()
-        self.description = description
-        self.name = '%s.%s' % (prefix, number)
-        self.interacts = {
-                'description' : InteractRectUnion(areas)
-                }
-        if DEBUG:
-            # Individual colors to make debugging easier
-            self._interact_hilight_color = Color(THECOLORS.keys()[number])
-
-    def get_description(self):
-        return self.description
-
-    def is_interactive(self):
-        return False
-
-
-class Door(Thing):
-    """A door somewhere"""
-
-    DEST = "map"
-    SCENE = None
-
-    def __init__(self):
-        self.NAME = self.SCENE + '.door'
-        Thing.__init__(self)
-
-    def is_interactive(self):
-        return True
-
-    def interact_without(self):
-        """Go to map."""
-        self.state.set_current_scene("map")
-
-    def get_description(self):
-        return _('An open doorway leads to the rest of the ship.')
-
-    def interact_default(self, item):
-        return self.interact_without()
-
-
-def make_jim_dialog(mesg, state):
-    "Utility helper function"
-    if state.scenes['bridge'].get_data('ai status') == 'online':
-        return Result(mesg, style='JIM')
-    else:
-        return None
-
-
-class BaseCamera(Thing):
-    "Base class for the camera puzzles"
- 
-    INITIAL = 'online'
-    INITIAL_DATA = {
-         'state': 'online',
-    }
- 
-    def get_description(self):
-        return _("A security camera watches over the room")
- 
-    def interact_with_escher_poster(self, item):
-        # Order matters here, because of helper function
-        ai_response = make_jim_dialog(_("3D scene reconstruction failed. Critical error. Entering emergency shutdown."), self.state)
-        self.state.scenes['bridge'].set_data('ai status', 'looping')
-        return ai_response
- 
-    def animate(self):
-        ai_status = self.state.scenes['bridge'].get_data('ai status')
-        if ai_status != self.get_data('status'):
-            self.set_data('status', ai_status)
-            self.set_interact(ai_status)
-        super(BaseCamera, self).animate()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamelib/scenewidgets.py	Tue Mar 08 14:37:43 2011 +0200
@@ -0,0 +1,150 @@
+"""Interactive elements within a Scene."""
+
+
+from pygame import Rect
+from pygame.color import Color
+from pygame.colordict import THECOLORS
+from pygame.surface import Surface
+from albow.resource import get_image
+
+from gamelib.state import Thing
+from gamelib.constants import DEBUG
+from gamelib.widgets import BoomLabel
+
+
+class Interact(object):
+
+    def __init__(self, image, rect, interact_rect):
+        self.image = image
+        self.rect = rect
+        self.interact_rect = interact_rect
+
+    def set_thing(self, thing):
+        pass
+
+    def draw(self, surface):
+        if self.image is not None:
+            surface.blit(self.image, self.rect, None)
+
+    def animate(self):
+        return False
+
+
+class InteractNoImage(Interact):
+
+    def __init__(self, x, y, w, h):
+        super(InteractNoImage, self).__init__(None, None, Rect(x, y, w, h))
+
+
+class InteractText(Interact):
+    """Display box with text to interact with -- mostly for debugging."""
+
+    def __init__(self, x, y, text, bg_color=None):
+        if bg_color is None:
+            bg_color = (127, 127, 127)
+        label = BoomLabel(text)
+        label.set_margin(5)
+        label.border_width = 1
+        label.border_color = (0, 0, 0)
+        label.bg_color = bg_color
+        label.fg_color = (0, 0, 0)
+        image = Surface(label.size)
+        rect = Rect((x, y), label.size)
+        label.draw_all(image)
+        super(InteractText, self).__init__(image, rect, rect)
+
+
+class InteractRectUnion(Interact):
+
+    def __init__(self, rect_list):
+        # pygame.rect.Rect.unionall should do this, but is broken
+        # in some pygame versions (including 1.8, it appears)
+        rect_list = [Rect(x) for x in rect_list]
+        union_rect = rect_list[0]
+        for rect in rect_list[1:]:
+            union_rect = union_rect.union(rect)
+        super(InteractRectUnion, self).__init__(None, None, union_rect)
+        self.interact_rect = rect_list
+
+
+class InteractImage(Interact):
+
+    def __init__(self, x, y, image_name):
+        super(InteractImage, self).__init__(None, None, None)
+        self._pos = (x, y)
+        self._image_name = image_name
+
+    def set_thing(self, thing):
+        self.image = get_image(thing.folder, self._image_name)
+        self.rect = Rect(self._pos, self.image.get_size())
+        self.interact_rect = self.rect
+
+
+class InteractImageRect(InteractImage):
+    def __init__(self, x, y, image_name, r_x, r_y, r_w, r_h):
+        super(InteractImageRect, self).__init__(x, y, image_name)
+        self._r_pos = (r_x, r_y)
+        self._r_size = (r_w, r_h)
+
+    def set_thing(self, thing):
+        super(InteractImageRect, self).set_thing(thing)
+        self.interact_rect = Rect(self._r_pos, self._r_size)
+
+
+class InteractAnimated(Interact):
+    """Interactive with an animation rather than an image"""
+
+    # FIXME: Assumes all images are the same size
+    # anim_seq - sequence of image names
+    # delay - number of frames to wait between changing images
+
+    def __init__(self, x, y, anim_seq, delay):
+        self._pos = (x, y)
+        self._anim_pos = 0
+        self._names = anim_seq
+        self._frame_count = 0
+        self._anim_seq = None
+        self._delay = delay
+
+    def set_thing(self, thing):
+        self._anim_seq = [get_image(thing.folder, x) for x in self._names]
+        self.image = self._anim_seq[0]
+        self.rect = Rect(self._pos, self.image.get_size())
+        self.interact_rect = self.rect
+
+    def animate(self):
+        if self._anim_seq:
+            self._frame_count += 1
+            if self._frame_count > self._delay:
+                self._frame_count = 0
+                self._anim_pos += 1
+                if self._anim_pos >= len(self._anim_seq):
+                    self._anim_pos = 0
+                self.image = self._anim_seq[self._anim_pos]
+                # queue redraw
+                return True
+        return False
+
+
+class GenericDescThing(Thing):
+    "Thing with an InteractiveUnionRect and a description"
+
+    INITIAL = "description"
+
+    def __init__(self, prefix, number, description, areas):
+        super(GenericDescThing, self).__init__()
+        self.description = description
+        self.name = '%s.%s' % (prefix, number)
+        self.interacts = {
+                'description' : InteractRectUnion(areas)
+                }
+        if DEBUG:
+            # Individual colors to make debugging easier
+            self._interact_hilight_color = Color(THECOLORS.keys()[number])
+
+    def get_description(self):
+        return self.description
+
+    def is_interactive(self, tool=None):
+        return False
+
--- a/gamelib/state.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/state.py	Tue Mar 08 14:37:43 2011 +0200
@@ -1,12 +1,15 @@
 """Utilities and base classes for dealing with scenes."""
 
-from albow.resource import get_image, get_font
+import copy
+
+from albow.resource import get_image
 from albow.utils import frame_rect
 from widgets import BoomLabel
 from pygame.rect import Rect
 from pygame.color import Color
 
 import constants
+from scenes import SCENE_LIST, INITIAL_SCENE
 from sound import get_sound
 
 # override the initial scene to for debugging
@@ -56,22 +59,16 @@
 
 def initial_state():
     """Load the initial state."""
-    state = State()
-    state.load_scenes("cryo")
-    state.load_scenes("bridge")
-    state.load_scenes("mess")
-    state.load_scenes("engine")
-    state.load_scenes("machine")
-    state.load_scenes("crew_quarters")
-    state.load_scenes("map")
-    state.load_scenes("manual")
-    initial_scene = "cryo" if DEBUG_SCENE is None else DEBUG_SCENE
+    state = GameState()
+    for scene in SCENE_LIST:
+        state.load_scenes(scene)
+    initial_scene = INITIAL_SCENE if DEBUG_SCENE is None else DEBUG_SCENE
     state.set_current_scene(initial_scene)
     state.set_do_enter_leave()
     return state
 
 
-class State(object):
+class GameState(object):
     """Complete game state.
 
     Game state consists of:
@@ -112,6 +109,7 @@
 
     def add_item(self, item):
         self.items[item.name] = item
+        item.set_state(self)
 
     def load_scenes(self, modname):
         mod = __import__("gamelib.scenes.%s" % (modname,), fromlist=[modname])
@@ -135,7 +133,7 @@
             self.current_detail = None
         else:
             self.current_detail = self.detail_views[name]
-            return self.current_detail.get_detail_size()
+            return self.current_detail
 
     def add_inventory_item(self, name):
         self.inventory.append(self.items[name])
@@ -165,16 +163,6 @@
     def set_tool(self, item):
         self.tool = item
 
-    def draw(self, surface, screen):
-        if self.do_check and self.previous_scene and self.do_check == constants.LEAVE:
-            # We still need to handle leave events, so still display the scene
-            self.previous_scene.draw(surface, screen)
-        else:
-            self.current_scene.draw(surface, screen)
-
-    def draw_detail(self, surface, screen):
-        self.current_detail.draw(surface, screen)
-
     def interact(self, pos):
         return self.current_scene.interact(self.tool, pos)
 
@@ -215,18 +203,10 @@
             return self.current_scene.enter()
         raise RuntimeError('invalid do_check value %s' % self.do_check)
 
-    def mouse_move(self, pos):
-        self.current_scene.mouse_move(pos)
-        # So we can do sensible things on enter and leave
-        self.old_pos = pos
-
     def set_do_enter_leave(self):
         """Flag that we need to run the enter loop"""
         self.do_check = constants.LEAVE
 
-    def mouse_move_detail(self, pos):
-        self.current_detail.mouse_move(pos)
-
 
 class StatefulGizmo(object):
 
@@ -236,7 +216,9 @@
     def __init__(self):
         self.data = {}
         if self.INITIAL_DATA:
-            self.data.update(self.INITIAL_DATA)
+            # deep copy of INITIAL_DATA allows lists, sets and
+            # other mutable types to safely be used in INITIAL_DATA
+            self.data.update(copy.deepcopy(self.INITIAL_DATA))
 
     def set_data(self, key, value):
         self.data[key] = value
@@ -260,9 +242,6 @@
     # Offset of the background image
     OFFSET = (0, 0)
 
-    # size (for detail views)
-    SIZE = constants.SCENE_SIZE
-
     def __init__(self, state):
         StatefulGizmo.__init__(self)
         # scene name
@@ -271,10 +250,7 @@
         self.state = state
         # map of thing names -> Thing objects
         self.things = {}
-        if self.BACKGROUND is not None:
-            self._background = get_image(self.FOLDER, self.BACKGROUND)
-        else:
-            self._background = None
+        self._background = None
 
     def add_item(self, item):
         self.state.add_item(item)
@@ -285,7 +261,9 @@
 
     def remove_thing(self, thing):
         del self.things[thing.name]
-        self.leave()
+        if thing is self.state.current_thing:
+            self.state.current_thing.leave()
+            self.state.current_thing = None
 
     def _get_description(self):
         text = (self.state.current_thing and
@@ -293,7 +271,6 @@
         if text is None:
             return None
         label = BoomLabel(text)
-        #font = get_font(15, 'DejaVuSans-Bold.ttf')
         label.set_margin(5)
         label.border_width = 1
         label.border_color = (0, 0, 0)
@@ -309,7 +286,12 @@
                 Rect(400-w/2, 5, w, h))
             description.draw_all(sub)
 
+    def _cache_background(self):
+        if self.BACKGROUND and not self._background:
+            self._background = get_image(self.FOLDER, self.BACKGROUND)
+
     def draw_background(self, surface):
+        self._cache_background()
         if self._background is not None:
             surface.blit(self._background, self.OFFSET, None)
         else:
@@ -369,10 +351,40 @@
         self.update_current_thing(pos)
 
     def get_detail_size(self):
+        self._cache_background()
         return self._background.get_size()
 
 
-class Thing(StatefulGizmo):
+class InteractiveMixin(object):
+    def is_interactive(self, tool=None):
+        return True
+
+    def interact(self, tool):
+        if not self.is_interactive(tool):
+            return None
+        if tool is None:
+            return self.interact_without()
+        handler = getattr(self, 'interact_with_' + tool.tool_name, None)
+        inverse_handler = self.get_inverse_interact(tool)
+        if handler is not None:
+            return handler(tool)
+        elif inverse_handler is not None:
+            return inverse_handler(self)
+        else:
+            return self.interact_default(tool)
+
+    def get_inverse_interact(self, tool):
+        return None
+
+    def interact_without(self):
+        return self.interact_default(None)
+
+    def interact_default(self, item=None):
+        return None
+
+
+
+class Thing(StatefulGizmo, InteractiveMixin):
     """Base class for things in a scene that you can interact with."""
 
     # name of thing
@@ -446,9 +458,6 @@
     def get_description(self):
         return None
 
-    def is_interactive(self):
-        return True
-
     def enter(self, item):
         """Called when the cursor enters the Thing."""
         pass
@@ -457,27 +466,9 @@
         """Called when the cursr leaves the Thing."""
         pass
 
-    def interact(self, item):
-        if not self.is_interactive():
-            return
-        if item is None:
-            return self.interact_without()
-        else:
-            handler = getattr(self, 'interact_with_' + item.tool_name, None)
-            if handler is not None:
-                return handler(item)
-            else:
-                return self.interact_default(item)
-
     def animate(self):
         return self.current_interact.animate()
 
-    def interact_without(self):
-        return self.interact_default(None)
-
-    def interact_default(self, item):
-        return None
-
     def draw(self, surface):
         old_rect = self.current_interact.rect
         if old_rect:
@@ -494,42 +485,50 @@
                             rect.inflate(1, 1), 1)
 
 
-class Item(object):
+class Item(InteractiveMixin):
     """Base class for inventory items."""
 
     # image for inventory
     INVENTORY_IMAGE = None
 
+    # name of item
+    NAME = None
+
     # name for interactions (i.e. def interact_with_<TOOL_NAME>)
     TOOL_NAME = None
 
     # set to instance of CursorSprite
     CURSOR = None
 
-    def __init__(self, name):
-        self.name = name
+    def __init__(self, name=None):
+        self.state = None
+        self.name = self.NAME
+        if name is not None:
+            self.name = name
         self.tool_name = name
         if self.TOOL_NAME is not None:
             self.tool_name = self.TOOL_NAME
-        self.inventory_image = get_image('items', self.INVENTORY_IMAGE)
+        self.inventory_image = None
+
+    def _cache_inventory_image(self):
+        if not self.inventory_image:
+            self.inventory_image = get_image('items', self.INVENTORY_IMAGE)
+
+    def set_state(self, state):
+        assert self.state is None
+        self.state = state
 
     def get_inventory_image(self):
+        self._cache_inventory_image()
         return self.inventory_image
 
-    def interact(self, tool, state):
-        if tool is None:
-            return self.interact_without(state)
-        handler = getattr(self, 'interact_with_' + tool.name, None)
-        inverse_handler = getattr(tool, 'interact_with_' + self.tool_name, None)
-        if handler is not None:
-            return handler(tool, state)
-        elif inverse_handler is not None:
-            return inverse_handler(self, state)
-        else:
-            return self.interact_default(tool, state)
+    def get_inverse_interact(self, tool):
+        return getattr(tool, 'interact_with_' + self.tool_name, None)
 
-    def interact_default(self, tool, state):
-        return Result("That doesn't do anything useful")
+    def is_interactive(self, tool=None):
+        if tool:
+            return True
+        return False
 
 
 class CloneableItem(Item):
@@ -540,9 +539,7 @@
         cls._counter += 1
         return cls._counter - 1
 
-    def __init__(self, name):
+    def __init__(self, name=None):
+        super(CloneableItem, self).__init__(name)
         my_count = self._get_new_id()
-        super(CloneableItem, self).__init__("%s.%s" % (name, my_count))
-        self.tool_name = name
-        if self.TOOL_NAME is not None:
-            self.tool_name = self.TOOL_NAME
+        self.name = "%s.%s" % (self.name, my_count)
--- a/gamelib/tests/game_logic_utils.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/tests/game_logic_utils.py	Tue Mar 08 14:37:43 2011 +0200
@@ -1,16 +1,18 @@
 import unittest
 
-import pygame
-from pygame.locals import SWSURFACE
-
 from gamelib import state
-from gamelib.constants import SCREEN
 
 
-# We need this stuff set up so we can load images and whatnot.
-pygame.display.init()
-pygame.font.init()
-pygame.display.set_mode(SCREEN, SWSURFACE)
+# Monkey-patch albow.resource.get_image to not do alpha-conversion,
+# which would require pygame display intialisation, which we don't
+# really want in the tests.
+import albow.resource
+
+def get_image_unoptimized(*names, **kw):
+    kw.setdefault('optimize', False)
+    return albow.resource._get_image(names, **kw)
+
+albow.resource.get_image = get_image_unoptimized
 
 
 class GameLogicTestCase(unittest.TestCase):
@@ -81,7 +83,7 @@
         self.assert_inventory_item(target_item)
         item_obj = self.state.items[item]
         target_obj = self.state.items[target_item]
-        result = target_obj.interact(item_obj, self.state)
+        result = target_obj.interact(item_obj)
         return self.handle_result(result)
 
     def close_detail(self):
--- a/gamelib/version.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/version.py	Tue Mar 08 14:37:43 2011 +0200
@@ -1,6 +1,6 @@
 """Suspended Sentence Version Information"""
 
-VERSION = (1, 0, 1, 'final', 0)
+VERSION = (1, 1, 0, 'alpha', 0)
 BASE_VERSION_STR = '.'.join([str(x) for x in VERSION[:3]])
 VERSION_STR = {
     'final': BASE_VERSION_STR,
--- a/gamelib/widgets.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/gamelib/widgets.py	Tue Mar 08 14:37:43 2011 +0200
@@ -10,7 +10,7 @@
 from albow.resource import get_font, get_image
 from pygame.color import Color
 from pygame.rect import Rect
-from pygame.draw import lines
+from pygame.draw import lines as draw_lines
 from pygame import mouse
 
 from constants import BUTTON_SIZE
@@ -94,7 +94,7 @@
         r = surface.get_rect()
         w = 2
         top, bottom, left, right = r.top, r.bottom, r.left, r.right
-        lines(surface, self._frame_color, False, [
+        draw_lines(surface, self._frame_color, False, [
             (left, bottom), (left, top), (right-w, top), (right-w, bottom)
             ], w)
 
--- a/po/POTFILES	Tue Mar 08 13:26:00 2011 +0200
+++ b/po/POTFILES	Tue Mar 08 14:37:43 2011 +0200
@@ -8,4 +8,4 @@
 gamelib/scenes/manual.py
 gamelib/scenes/map.py
 gamelib/scenes/mess.py
-gamelib/scenes/scene_widgets.py
+gamelib/scenes/game_widgets.py
--- a/po/ru.po	Tue Mar 08 13:26:00 2011 +0200
+++ b/po/ru.po	Tue Mar 08 14:37:43 2011 +0200
@@ -8,28 +8,30 @@
 msgstr ""
 "Project-Id-Version: suspended-sentence 1.0.1\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-02-16 18:29+0600\n"
+"POT-Creation-Date: 2011-03-08 14:36+0200\n"
 "PO-Revision-Date: 2011-02-16 18:36+0600\n"
 "Last-Translator: Serguey G Basalaev <sbasalaev@gmail.com>\n"
 "Language-Team: Russian\n"
+"Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
 
-#: gamelib/widgets.py:178
+#: gamelib/widgets.py:175
 msgid "Quit Game"
 msgstr "Выйти из игры"
 
-#: gamelib/widgets.py:179
+#: gamelib/widgets.py:176
 msgid "Exit to Main Menu"
 msgstr "Главное меню"
 
-#: gamelib/gamescreen.py:142
+#: gamelib/gamescreen.py:146
 msgid "Close"
 msgstr "Закрыть"
 
-#: gamelib/gamescreen.py:214
+#: gamelib/gamescreen.py:218
 msgid "Menu"
 msgstr "Меню"
 
@@ -58,8 +60,12 @@
 msgstr "Экран главного компьютера капитанского моста."
 
 #: gamelib/scenes/bridge.py:146
-msgid "A top of the line Massage-o-Matic Captain's Executive Command Chair. It's massaging a skeleton."
-msgstr "Лучшее из линейки Массаж-о-матик Кресло Капитана Команды. Оно массажирует скелет."
+msgid ""
+"A top of the line Massage-o-Matic Captain's Executive Command Chair. It's "
+"massaging a skeleton."
+msgstr ""
+"Лучшее из линейки Массаж-о-матик Кресло Капитана Команды. Оно массажирует "
+"скелет."
 
 #: gamelib/scenes/bridge.py:148
 msgid "The chair won't work any more, it has no power."
@@ -70,12 +76,20 @@
 msgstr "Стетоскоп свисает с шеи скелета."
 
 #: gamelib/scenes/bridge.py:204
-msgid "You pick up the stethoscope and verify that the doctor's heart has stopped. Probably a while ago."
-msgstr "Вы берёте стетоскоп и убеждаетесь, что сердце доктора остановилось. Вероятно уже давно."
+msgid ""
+"You pick up the stethoscope and verify that the doctor's heart has stopped. "
+"Probably a while ago."
+msgstr ""
+"Вы берёте стетоскоп и убеждаетесь, что сердце доктора остановилось. Вероятно "
+"уже давно."
 
 #: gamelib/scenes/bridge.py:225
-msgid "You rip off a piece of duct tape and stick it on the superconductor. It almost sticks to itself, but you successfully avoid disaster."
-msgstr "Вы отрываете кусок трубопроводной ленты и приклеиваете её к сверхпроводнику. Она почти прилипает сама к себе, но вы успешно избегаете катастрофы."
+msgid ""
+"You rip off a piece of duct tape and stick it on the superconductor. It "
+"almost sticks to itself, but you successfully avoid disaster."
+msgstr ""
+"Вы отрываете кусок трубопроводной ленты и приклеиваете её к сверхпроводнику. "
+"Она почти прилипает сама к себе, но вы успешно избегаете катастрофы."
 
 #: gamelib/scenes/bridge.py:245
 msgid "The superconductor module unclips easily."
@@ -83,8 +97,12 @@
 
 #: gamelib/scenes/bridge.py:246
 #, python-format
-msgid "Prisoner %s. That chair you've destroyed was property of the ship's captain. You will surely be punished."
-msgstr "Заключённый %s. Кресло, испорченное вами, было имуществом капитана корабля. Вы обязательно будете наказаны."
+msgid ""
+"Prisoner %s. That chair you've destroyed was property of the ship's captain. "
+"You will surely be punished."
+msgstr ""
+"Заключённый %s. Кресло, испорченное вами, было имуществом капитана корабля. "
+"Вы обязательно будете наказаны."
 
 #: gamelib/scenes/bridge.py:278
 msgid "The lights flash in interesting patterns."
@@ -124,258 +142,343 @@
 
 #: gamelib/scenes/bridge.py:353
 #, python-format
-msgid "Prisoner %s. Please step away from the panel. You are not an authorized technician."
-msgstr "Заключённый %s. Пожалуйста, отойдите от панели. Вы не являетесь авторизованным техником."
+msgid ""
+"Prisoner %s. Please step away from the panel. You are not an authorized "
+"technician."
+msgstr ""
+"Заключённый %s. Пожалуйста, отойдите от панели. Вы не являетесь "
+"авторизованным техником."
 
-#: gamelib/scenes/bridge.py:453
+#: gamelib/scenes/bridge.py:451
 msgid "You are not authorized to change the destination."
 msgstr "Вы не имеете права изменять пункт назначения."
 
-#: gamelib/scenes/bridge.py:455
+#: gamelib/scenes/bridge.py:453
 msgid "There's no good reason to choose to go to the penal colony."
 msgstr "Нет причины выбирать полёт в колонию."
 
+#: gamelib/scenes/bridge.py:455
+msgid ""
+"You could change the destination, but when JIM recovers, it'll just get "
+"reset."
+msgstr ""
+"Вы можете изменить пункт назначения, но когда JIM восстановится, он сбросит "
+"его обратно."
+
 #: gamelib/scenes/bridge.py:457
-msgid "You could change the destination, but when JIM recovers, it'll just get reset."
-msgstr "Вы можете изменить пункт назначения, но когда JIM восстановится, он сбросит его обратно."
-
-#: gamelib/scenes/bridge.py:459
 msgid "You change the destination."
 msgstr "Вы изменяете пункт назначения."
 
-#: gamelib/scenes/crew_quarters.py:32
+#: gamelib/scenes/crew_quarters.py:31
 msgid "The plant is doing surprisingly well for centuries of neglect"
 msgstr "Растение перенесло века забвения неожиданно хорошо."
 
-#: gamelib/scenes/crew_quarters.py:35
+#: gamelib/scenes/crew_quarters.py:34
 msgid "A picture of a cat labelled 'Clementine'"
 msgstr "Изображение кота, подписанное 'Клементин'"
 
-#: gamelib/scenes/crew_quarters.py:74
+#: gamelib/scenes/crew_quarters.py:73
 msgid "Duct tape. It'll stick to everything except ducts, apparently."
 msgstr "Трубопроводная лента. Она прилипает ко всему, кроме трубопровода."
 
-#: gamelib/scenes/crew_quarters.py:76
-msgid "The perfectly balanced door swings frictionlessly to and fro. What craftsmanship!"
-msgstr "Идеально сбалансированная дверь легко качается туда-сюда. Ну что за работа!"
+#: gamelib/scenes/crew_quarters.py:75
+msgid ""
+"The perfectly balanced door swings frictionlessly to and fro. What "
+"craftsmanship!"
+msgstr ""
+"Идеально сбалансированная дверь легко качается туда-сюда. Ну что за работа!"
 
-#: gamelib/scenes/crew_quarters.py:78
-msgid "The safe is locked. This might be an interesting challenge, if suitable equipment can be found."
-msgstr "Сейф закрыт. Может быть интересным вызовом для вас, если найти подходящее оборудование."
+#: gamelib/scenes/crew_quarters.py:77
+msgid ""
+"The safe is locked. This might be an interesting challenge, if suitable "
+"equipment can be found."
+msgstr ""
+"Сейф закрыт. Может быть интересным вызовом для вас, если найти подходящее "
+"оборудование."
 
-#: gamelib/scenes/crew_quarters.py:83
+#: gamelib/scenes/crew_quarters.py:82
 msgid "It's already unlocked. There's no more challenge."
 msgstr "Он уже открыт. Это больше не интересно."
 
-#: gamelib/scenes/crew_quarters.py:88
-msgid "Even after centuries of neglect, the tumblers slide almost silently into place. Turns out the combination was '1 2 3 4 5'. An idiot must keep his luggage in here."
-msgstr "Даже после веков забвения, замки двигаются почти бесшумно. Выясняется, что комбинация была '1 2 3 4 5'. Наверное, здесь хранил свой багаж идиот."
-
-#: gamelib/scenes/crew_quarters.py:92
-#, python-format
-msgid "Prisoner %s, you have been observed committing a felony violation. This will go onto your permanent record, and your sentence may be extended by up to twenty years."
-msgstr "Заключённый %s, вы были замечены за совершением уголовного преступления. Это войдёт в ваше личное дело, и ваш срок может быть увеличен до двадцати лет."
+#: gamelib/scenes/crew_quarters.py:87
+msgid ""
+"Even after centuries of neglect, the tumblers slide almost silently into "
+"place. Turns out the combination was '1 2 3 4 5'. An idiot must keep his "
+"luggage in here."
+msgstr ""
+"Даже после веков забвения, замки двигаются почти бесшумно. Выясняется, что "
+"комбинация была '1 2 3 4 5'. Наверное, здесь хранил свой багаж идиот."
 
-#: gamelib/scenes/crew_quarters.py:97
-msgid "Ah, a vintage Knoxx & Co. model QR3. Quaint, but reasonably secure."
-msgstr "Ах, винтажная модель QR3 от Knoxx & Co. Древняя, но довольно безопасная."
+#: gamelib/scenes/crew_quarters.py:91
+#, python-format
+msgid ""
+"Prisoner %s, you have been observed committing a felony violation. This will "
+"go onto your permanent record, and your sentence may be extended by up to "
+"twenty years."
+msgstr ""
+"Заключённый %s, вы были замечены за совершением уголовного преступления. Это "
+"войдёт в ваше личное дело, и ваш срок может быть увеличен до двадцати лет."
 
-#: gamelib/scenes/crew_quarters.py:118
-msgid "What's the point of lugging around a very dead fish and a kilogram or so of sand?"
-msgstr "Какой смысл таскать с собой мёртвую рыбину и килограмм или около того песка?"
+#: gamelib/scenes/crew_quarters.py:96
+msgid "Ah, a vintage Knoxx & Co. model QR3. Quaint, but reasonably secure."
+msgstr ""
+"Ах, винтажная модель QR3 от Knoxx & Co. Древняя, но довольно безопасная."
 
-#: gamelib/scenes/crew_quarters.py:123
+#: gamelib/scenes/crew_quarters.py:117
+msgid ""
+"What's the point of lugging around a very dead fish and a kilogram or so of "
+"sand?"
+msgstr ""
+"Какой смысл таскать с собой мёртвую рыбину и килограмм или около того песка?"
+
+#: gamelib/scenes/crew_quarters.py:122
 msgid "The fishbowl is useful, but its contents aren't."
 msgstr "Аквариум может пригодиться, а вот его содержимое вряд ли."
 
 #: gamelib/scenes/crew_quarters.py:126
 msgid "This fishbowl looks exactly like an old science fiction space helmet."
-msgstr "Этот аквариум выглядит в точности как космический шлем из старой научной фантастики."
+msgstr ""
+"Этот аквариум выглядит в точности как космический шлем из старой научной "
+"фантастики."
+
+#: gamelib/scenes/crew_quarters.py:128
+msgid "An evicted dead fish and some sand lie forlornly on the table"
+msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:139
-msgid "You duct tape the edges of the helmet. The seal is crude, but it will serve as a workable helmet if needed."
-msgstr "Вы прилепяете ленту к краям шлема. Крепление грубое, но оно может послужить, как рабочий шлем, если нужно."
+#: gamelib/scenes/crew_quarters.py:142
+msgid ""
+"You duct tape the edges of the helmet. The seal is crude, but it will serve "
+"as a workable helmet if needed."
+msgstr ""
+"Вы прилепяете ленту к краям шлема. Крепление грубое, но оно может послужить, "
+"как рабочий шлем, если нужно."
 
-#: gamelib/scenes/crew_quarters.py:186
+#: gamelib/scenes/crew_quarters.py:189
 msgid "This poster will go nicely on your bedroom wall."
 msgstr "Этот плакат будет неплохо смотреться на стене вашей спальни."
 
-#: gamelib/scenes/crew_quarters.py:189
+#: gamelib/scenes/crew_quarters.py:192
 msgid "A paradoxical poster hangs below the security camera."
 msgstr "Парадоксальный плакат висит под камерой безопасности."
 
-#: gamelib/scenes/cryo.py:55
+#: gamelib/scenes/cryo.py:53
 msgid "These pipes carry cooling fluid to the cryo units."
 msgstr "По этим трубам охлаждающая жидкость подаётся к криокамерам."
 
-#: gamelib/scenes/cryo.py:66
+#: gamelib/scenes/cryo.py:64
 msgid "An empty cryo chamber."
 msgstr "Пустая криокамера."
 
-#: gamelib/scenes/cryo.py:67
+#: gamelib/scenes/cryo.py:65
 msgid "Prisoner 81E4-C8900480E635. Embezzlement. 20 years."
 msgstr "Заключённый 81E4-C8900480E635. Хищение имущества. 20 лет."
 
-#: gamelib/scenes/cryo.py:76
-#: gamelib/scenes/cryo.py:95
-msgid "A working cryo chamber. The frosted glass obscures the details of the occupant."
+#: gamelib/scenes/cryo.py:74 gamelib/scenes/cryo.py:93
+msgid ""
+"A working cryo chamber. The frosted glass obscures the details of the "
+"occupant."
 msgstr "Работающая криокамера. Обитатель не виден из-за замёрзшего стекла."
 
-#: gamelib/scenes/cryo.py:77
+#: gamelib/scenes/cryo.py:75
 msgid "Prisoner 9334-CE1EB0243BAB. Murder. 40 years."
 msgstr "Заключённый 9334-CE1EB0243BAB. Убийство. 40 лет."
 
-#: gamelib/scenes/cryo.py:86
+#: gamelib/scenes/cryo.py:84
 msgid "A broken cryo chamber. The skeleton inside has been picked clean."
 msgstr "Сломанная криокамера. Скелет внутри был обчищен."
 
-#: gamelib/scenes/cryo.py:87
-msgid "Prisoner BFBC-8BF4C6B7492B. Importing illegal alien biomatter. 15 years."
-msgstr "Заключённый BFBC-8BF4C6B7492B. Импорт нелегальной биоматерии пришельцев. 15 лет."
+#: gamelib/scenes/cryo.py:85
+msgid ""
+"Prisoner BFBC-8BF4C6B7492B. Importing illegal alien biomatter. 15 years."
+msgstr ""
+"Заключённый BFBC-8BF4C6B7492B. Импорт нелегальной биоматерии пришельцев. 15 "
+"лет."
 
-#: gamelib/scenes/cryo.py:96
+#: gamelib/scenes/cryo.py:94
 msgid "Prisoner B520-99495B8C41CE. Copyright infringement. 60 years."
 msgstr "Заключённый B520-99495B8C41CE. Нарушение авторских прав. 60 лет."
 
-#: gamelib/scenes/cryo.py:103
+#: gamelib/scenes/cryo.py:101
 msgid "An empty cryo unit. Recently filled by you."
 msgstr "Пустая криокамера. Недавно в ней были вы."
 
-#: gamelib/scenes/cryo.py:104
+#: gamelib/scenes/cryo.py:102
 #, python-format
 msgid "Prisoner %s. Safecracking, grand larceny. 30 years."
 msgstr "Заключённый %s. Взлом сейфа, кражи в крупных размерах. 30 лет."
 
-#: gamelib/scenes/cryo.py:111
-#: gamelib/scenes/cryo.py:120
+#: gamelib/scenes/cryo.py:109 gamelib/scenes/cryo.py:118
 msgid "An empty cryo unit."
 msgstr "Пустая криокамера."
 
-#: gamelib/scenes/cryo.py:112
+#: gamelib/scenes/cryo.py:110
 msgid "Prisoner 83F1-CE32D3234749. Spamming. 5 years."
 msgstr "Заключённый 83F1-CE32D3234749. Спам. 5 лет."
 
-#: gamelib/scenes/cryo.py:121
+#: gamelib/scenes/cryo.py:119
 msgid "Prisoner A455-9DF9F43C43E5. Medical malpractice. 10 years."
 msgstr "Заключённый A455-9DF9F43C43E5. Медицинская халатность. 10 лет."
 
-#: gamelib/scenes/cryo.py:137
+#: gamelib/scenes/cryo.py:135
 #, python-format
-msgid "Greetings, Prisoner %s. I am the Judicial Incarceration Monitor. You have been woken early under the terms of the emergency conscription act to assist with repairs to the ship. Your behaviour during this time will be noted on your record and will be relayed to prison officials when we reach the destination. Please report to the bridge."
-msgstr "Приветствую вас, Заключённый %s. Я — JIM, Монитор Судебного Заключения. Вы были пробуждены раньше срока в соответствии с предписанием о помощи в починке корабля при чрезвычайной ситуации. Ваше поведение в течение ремонтных работ будет занесено в личное дело и рассмотрено официальными лицами по прибытии в пункт назначения. Пожалуйста, отрапортуйте на капитанский мост."
+msgid ""
+"Greetings, Prisoner %s. I am the Judicial Incarceration Monitor. You have "
+"been woken early under the terms of the emergency conscription act to assist "
+"with repairs to the ship. Your behaviour during this time will be noted on "
+"your record and will be relayed to prison officials when we reach the "
+"destination. Please report to the bridge."
+msgstr ""
+"Приветствую вас, Заключённый %s. Я — JIM, Монитор Судебного Заключения. Вы "
+"были пробуждены раньше срока в соответствии с предписанием о помощи в "
+"починке корабля при чрезвычайной ситуации. Ваше поведение в течение "
+"ремонтных работ будет занесено в личное дело и рассмотрено официальными "
+"лицами по прибытии в пункт назначения. Пожалуйста, отрапортуйте на "
+"капитанский мост."
 
-#: gamelib/scenes/cryo.py:167
-msgid "It takes more effort than one would expect, but eventually the pipe is separated from the wall."
-msgstr "Это требует больше усилий, чем можно было ожидать, но в конце концов труба отделяется от стены."
+#: gamelib/scenes/cryo.py:165
+msgid ""
+"It takes more effort than one would expect, but eventually the pipe is "
+"separated from the wall."
+msgstr ""
+"Это требует больше усилий, чем можно было ожидать, но в конце концов труба "
+"отделяется от стены."
 
-#: gamelib/scenes/cryo.py:173
+#: gamelib/scenes/cryo.py:171
 #, python-format
-msgid "Prisoner %s. Vandalism is an offence punishable by a minimum of an additional 6 months to your sentence."
-msgstr "Заключённый %s. Вандализм является правонарушением и карается как минимум дополнительными 6 меcяцами к вашему сроку."
+msgid ""
+"Prisoner %s. Vandalism is an offence punishable by a minimum of an "
+"additional 6 months to your sentence."
+msgstr ""
+"Заключённый %s. Вандализм является правонарушением и карается как минимум "
+"дополнительными 6 меcяцами к вашему сроку."
 
-#: gamelib/scenes/cryo.py:183
-#: gamelib/scenes/cryo.py:213
+#: gamelib/scenes/cryo.py:181 gamelib/scenes/cryo.py:211
 msgid "These pipes aren't attached to the wall very solidly."
 msgstr "Эти трубы не очень прочно прикреплены к стене."
 
-#: gamelib/scenes/cryo.py:188
+#: gamelib/scenes/cryo.py:186
 msgid "These pipes carry cooling fluid to empty cryo units."
 msgstr "По этим трубам охлаждающая жидкость подаётся к пустым криокамерам."
 
-#: gamelib/scenes/cryo.py:189
+#: gamelib/scenes/cryo.py:187
 msgid "There used to be a pipe carrying cooling fluid here."
 msgstr "Здесь была труба, подающая охлаждающую жидкость."
 
-#: gamelib/scenes/cryo.py:206
-msgid "These pipes carry fluid to the working cryo units. Chopping them down doesn't seem sensible."
+#: gamelib/scenes/cryo.py:204
+msgid ""
+"These pipes carry fluid to the working cryo units. Chopping them down "
+"doesn't seem sensible."
 msgstr "Эти трубы подают охлаждающую жидкость к работающим криокамерам. "
 
-#: gamelib/scenes/cryo.py:216
+#: gamelib/scenes/cryo.py:214
 msgid "These pipes carry cooling fluid to the working cryo units."
 msgstr "По этим трубам охлаждающая жидкость подаётся к рабочим криокамерам."
 
-#: gamelib/scenes/cryo.py:288
-msgid "You hit the chamber that used to hold this very leg. Nothing happens as a result."
-msgstr "Вы ударяете по камере, в которой находилась эта нога. В итоге ничего не происходит."
+#: gamelib/scenes/cryo.py:286
+msgid ""
+"You hit the chamber that used to hold this very leg. Nothing happens as a "
+"result."
+msgstr ""
+"Вы ударяете по камере, в которой находилась эта нога. В итоге ничего не "
+"происходит."
 
-#: gamelib/scenes/cryo.py:293
+#: gamelib/scenes/cryo.py:291
 msgid "A broken cryo chamber, with a poor unfortunate corpse inside."
 msgstr "Сломанная криокамера, с бедным несчастным телом внутри."
 
-#: gamelib/scenes/cryo.py:294
+#: gamelib/scenes/cryo.py:292
 msgid "A broken cryo chamber. The corpse inside is missing a leg."
 msgstr "Сломанная криокамера. У тела внутри отсутствует нога."
 
-#: gamelib/scenes/cryo.py:315
+#: gamelib/scenes/cryo.py:313
 msgid "You bang on the chamber with the titanium femur. Nothing much happens."
-msgstr "Вы стучите по камере титановым бедром. Не происходит ничего особенного."
+msgstr ""
+"Вы стучите по камере титановым бедром. Не происходит ничего особенного."
 
-#: gamelib/scenes/cryo.py:316
+#: gamelib/scenes/cryo.py:314
 msgid "Hitting the cryo unit with the femur doesn't achieve anything."
 msgstr "Стучание ребром по криокамере ничего не даёт."
 
-#: gamelib/scenes/cryo.py:317
+#: gamelib/scenes/cryo.py:315
 msgid "You hit the chamber with the femur. Nothing happens."
 msgstr "Вы бьёте бедром по камере. Ничего не происходит."
 
+#: gamelib/scenes/cryo.py:339
+msgid ""
+"You wedge the titanium femur into the chain and twist. With a satisfying "
+"*snap*, the chain breaks and the door opens."
+msgstr ""
+"Вы просовываете титановое бедро в цепь и поворачиваете. С удовлетворённым "
+"*хрусь*, цепь ломается, и дверь открывается."
+
 #: gamelib/scenes/cryo.py:341
-msgid "You wedge the titanium femur into the chain and twist. With a satisfying *snap*, the chain breaks and the door opens."
-msgstr "Вы просовываете титановое бедро в цепь и поворачиваете. С удовлетворённым *хрусь*, цепь ломается, и дверь открывается."
-
-#: gamelib/scenes/cryo.py:343
-msgid "You bang on the door with the titanium femur. It makes a clanging sound."
+msgid ""
+"You bang on the door with the titanium femur. It makes a clanging sound."
 msgstr "Вы стучите по двери титановым бедром. Она издаёт гулкий звук."
 
-#: gamelib/scenes/cryo.py:346
+#: gamelib/scenes/cryo.py:344
 msgid "You wave the femur in the doorway. Nothing happens."
 msgstr "Вы машете бедром в дверном проходе. Ничего не происходит."
 
-#: gamelib/scenes/cryo.py:352
-msgid "It moves slightly and then stops. A chain on the other side is preventing it from opening completely."
-msgstr "Дверь немного двигается и затем останавливается. Цепь на другой стороне мешает ей полностью открыться."
+#: gamelib/scenes/cryo.py:350
+msgid ""
+"It moves slightly and then stops. A chain on the other side is preventing it "
+"from opening completely."
+msgstr ""
+"Дверь немного двигается и затем останавливается. Цепь на другой стороне "
+"мешает ей полностью открыться."
 
-#: gamelib/scenes/cryo.py:370
-#: gamelib/scenes/scene_widgets.py:172
+#: gamelib/scenes/cryo.py:368 gamelib/scenes/game_widgets.py:26
 msgid "An open doorway leads to the rest of the ship."
 msgstr "Открытый дверной проём ведёт к оставшей части корабля."
 
+#: gamelib/scenes/cryo.py:370
+msgid ""
+"A rusty door. It can't open all the way because of a chain on the other side."
+msgstr ""
+"Ржавая дверь. Она не может полностью открыться из-за цепи на другой стороне."
+
 #: gamelib/scenes/cryo.py:372
-msgid "A rusty door. It can't open all the way because of a chain on the other side."
-msgstr "Ржавая дверь. Она не может полностью открыться из-за цепи на другой стороне."
-
-#: gamelib/scenes/cryo.py:374
 msgid "A rusty door. It is currently closed."
 msgstr "Ржавая дверь. Сейчас она закрыта."
 
-#: gamelib/scenes/cryo.py:395
+#: gamelib/scenes/cryo.py:393
 msgid "Hitting it with the leg accomplishes nothing."
 msgstr "Стучание ногой по этому не принесёт результатов."
 
-#: gamelib/scenes/cryo.py:398
+#: gamelib/scenes/cryo.py:396
 msgid "A computer terminal, with some text on it."
 msgstr "Терминал компьютера, на нём какой-то текст."
 
-#: gamelib/scenes/cryo.py:416
-msgid "The skeletal occupant of this cryo unit has an artificial femur made of titanium. You take it."
-msgstr "У скелета, обитающего в этой криокамере искусственное бедро, сделанное из титана. Вы берёте его."
+#: gamelib/scenes/cryo.py:414
+msgid ""
+"The skeletal occupant of this cryo unit has an artificial femur made of "
+"titanium. You take it."
+msgstr ""
+"У скелета, обитающего в этой криокамере искусственное бедро, сделанное из "
+"титана. Вы берёте его."
 
-#: gamelib/scenes/cryo.py:419
+#: gamelib/scenes/cryo.py:417
 msgid "This femur looks synthetic."
 msgstr "Это бедро кажется искусственным."
 
-#: gamelib/scenes/cryo.py:434
+#: gamelib/scenes/cryo.py:432
 msgid "The plaque is welded to the unit. You can't shift it."
 msgstr "Пластина впаяна в камеру. Вы не можете её сдвинуть."
 
-#: gamelib/scenes/cryo.py:437
+#: gamelib/scenes/cryo.py:435
 msgid "'Prisoner 98CC-764E646391EE. War crimes. 45 years."
 msgstr "Заключённый 98CC-764E646391EE. Военные преступления. 45 лет."
 
-#: gamelib/scenes/cryo.py:461
+#: gamelib/scenes/cryo.py:459
 msgid "Coolant leaks disturbingly from the bulkheads."
 msgstr "Охладитель тревожно вытекает из-под переборки."
 
-#: gamelib/scenes/cryo.py:470
+#: gamelib/scenes/cryo.py:462
+msgid "It's gooey"
+msgstr ""
+
+#: gamelib/scenes/cryo.py:468
 msgid "You scoop up some coolant and fill the bottle."
 msgstr "Вы зачерпываете немного охладителя и наполняете бутылку."
 
@@ -384,303 +487,407 @@
 msgstr "Чёрт. Похоже все эти банки уже просрочены."
 
 #: gamelib/scenes/engine.py:50
-msgid "A control panel. It seems dead."
-msgstr "Панель управления. Похоже, она не работает."
-
-#: gamelib/scenes/engine.py:56
 msgid "Superconductors. The engines must be power hogs."
 msgstr "Сверхпроводники. Двигатели наверняка прожорливы."
 
-#: gamelib/scenes/engine.py:64
-#: gamelib/scenes/engine.py:467
-msgid "A gaping hole in the floor of the room. You're guessing that's why there's a vacuum in here."
+#: gamelib/scenes/engine.py:58
+#, fuzzy
+msgid "A gaping hole in the floor of the room. It is clearly irreparable."
 msgstr "Сквозная дыра в полу. Вы полагаете что это причина вакуума здесь."
 
-#: gamelib/scenes/engine.py:73
-msgid "Empty chocolate-covered bacon cans? Poor guy, he must have found them irresistible."
-msgstr "Пустые банки из под бекона в шоколаде? Бедный парень, он наверное нашёл их беззащитными."
+#: gamelib/scenes/engine.py:68
+msgid ""
+"Empty chocolate-covered bacon cans? Poor guy, he must have found them "
+"irresistible."
+msgstr ""
+"Пустые банки из под бекона в шоколаде? Бедный парень, он наверное нашёл их "
+"беззащитными."
 
-#: gamelib/scenes/engine.py:79
+#: gamelib/scenes/engine.py:74
 msgid "The engines. They don't look like they are working."
 msgstr "Двигатели. Непохоже, что они работают."
 
-#: gamelib/scenes/engine.py:85
-msgid "A burned-out laser cutter. It may be responsible for the hole in the floor."
-msgstr "Перегоревший лазерный резак. Он может быть ответственен за дыру в полу."
+#: gamelib/scenes/engine.py:80
+msgid ""
+"A burned-out laser cutter. It may be responsible for the hole in the floor."
+msgstr ""
+"Перегоревший лазерный резак. Он может быть ответственен за дыру в полу."
 
-#: gamelib/scenes/engine.py:91
+#: gamelib/scenes/engine.py:86
 msgid "The main fuel line for the engines."
 msgstr "Главная топливная шина для двигателей."
 
-#: gamelib/scenes/engine.py:111
-msgid "The spare fuel line. If something went wrong with the main one, you would hook that one up."
-msgstr "Запасная топливная шина. Она задействуется, если что-то случится с главной."
+#: gamelib/scenes/engine.py:106
+msgid ""
+"The spare fuel line. If something went wrong with the main one, you would "
+"hook that one up."
+msgstr ""
+"Запасная топливная шина. Она задействуется, если что-то случится с главной."
 
-#: gamelib/scenes/engine.py:117
+#: gamelib/scenes/engine.py:112
 msgid "The sign says DANGER. You would be wise to listen to it."
 msgstr "Знак гласит ОПАСНОСТЬ. Будет мудро прислушаться к нему."
 
-#: gamelib/scenes/engine.py:123
-msgid "It's one of those glow-in-the-dark exit signs that you see everywhere."
-msgstr "Это один из тех светящихся в темноте знаков выхода, которые вы видите повсюду."
+#: gamelib/scenes/engine.py:118
+#, fuzzy
+msgid ""
+"It's one of those glow-in-the-dark signs needed to satisfy the health and "
+"safety inspectors."
+msgstr ""
+"Это один из тех светящихся в темноте знаков выхода, которые вы видите "
+"повсюду."
 
-#: gamelib/scenes/engine.py:135
+#: gamelib/scenes/engine.py:131
 #, python-format
-msgid "The engines are now operational. You havedone a satisfactory job, Prisoner %s."
-msgstr "Двигатели теперь работоспособны. Вы проделали удовлетворительную работу, Заключённый %s."
+msgid ""
+"The engines are now operational. You havedone a satisfactory job, Prisoner "
+"%s."
+msgstr ""
+"Двигатели теперь работоспособны. Вы проделали удовлетворительную работу, "
+"Заключённый %s."
 
-#: gamelib/scenes/engine.py:143
-msgid "With your improvised helmet, the automatic airlock allows you into the engine room. Even if there wasn't a vacuum it would be eerily quiet."
-msgstr "С вашим импровизированным шлемом, автоматический шлюз допускает вас в двигательный отсек. Даже если бы здесь не было вакуума, тишина бы была жуткой."
+#: gamelib/scenes/engine.py:139
+msgid ""
+"With your improvised helmet, the automatic airlock allows you into the "
+"engine room. Even if there wasn't a vacuum it would be eerily quiet."
+msgstr ""
+"С вашим импровизированным шлемом, автоматический шлюз допускает вас в "
+"двигательный отсек. Даже если бы здесь не было вакуума, тишина бы была "
+"жуткой."
 
-#: gamelib/scenes/engine.py:159
+#: gamelib/scenes/engine.py:155
 msgid "All systems are go! Or at least the engines are."
 msgstr "Все системы приведены в действие! Ну, по крайней мере, двигатели."
 
-#: gamelib/scenes/engine.py:177
+#: gamelib/scenes/engine.py:173
 msgid "A can opener. Looks like you won't be starving"
 msgstr "Открывашка для консервов. Похоже, вы не умрёте с голоду"
 
-#: gamelib/scenes/engine.py:182
-msgid "You pick up the can opener. It looks brand new; the vacuum has kept it in perfect condition."
-msgstr "Вы подбираете открывашку для консервов. Она выглядит совершенно новой; вакуум сохранил её в отличном состоянии."
+#: gamelib/scenes/engine.py:178
+msgid ""
+"You pick up the can opener. It looks brand new; the vacuum has kept it in "
+"perfect condition."
+msgstr ""
+"Вы подбираете открывашку для консервов. Она выглядит совершенно новой; "
+"вакуум сохранил её в отличном состоянии."
 
-#: gamelib/scenes/engine.py:209
-msgid "That superconductor looks burned out. It's wedged in there pretty firmly."
+#: gamelib/scenes/engine.py:200
+msgid ""
+"That superconductor looks burned out. It's wedged in there pretty firmly."
 msgstr "Сверхпроводник выглядит сгоревшим. Он впаян достаточно крепко."
 
-#: gamelib/scenes/engine.py:211
+#: gamelib/scenes/engine.py:202
 msgid "An empty superconductor socket"
 msgstr "Пустой разъём для сверхпроводника"
 
-#: gamelib/scenes/engine.py:213
+#: gamelib/scenes/engine.py:204
 msgid "A working superconductor."
 msgstr "Рабочий сверхпроводник."
 
-#: gamelib/scenes/engine.py:217
+#: gamelib/scenes/engine.py:208
 msgid "It's wedged in there pretty firmly, it won't come out."
 msgstr "Он впаян достаточно крепко и не вынимается."
 
-#: gamelib/scenes/engine.py:219
-msgid "You decide that working engines are more important than having a shiny superconductor."
-msgstr "Вы решаете, что иметь работающие двигатели важнее, чем сверкающий сверхпроводник."
+#: gamelib/scenes/engine.py:210
+msgid ""
+"You decide that working engines are more important than having a shiny "
+"superconductor."
+msgstr ""
+"Вы решаете, что иметь работающие двигатели важнее, чем сверкающий "
+"сверхпроводник."
 
-#: gamelib/scenes/engine.py:226
-msgid "With leverage, the burned-out superconductor snaps out."
+#: gamelib/scenes/engine.py:216
+#, fuzzy
+msgid "With leverage, the burned-out superconductor snaps out. You discard it."
 msgstr "С помощью рычага, сгоревший сверхпроводник выскакивает."
 
-#: gamelib/scenes/engine.py:230
+#: gamelib/scenes/engine.py:221
 msgid "It might help to remove the broken superconductor first"
 msgstr "Возможно, стоит сначала вынуть сгоревший сверхпроводник"
 
-#: gamelib/scenes/engine.py:232
-msgid "You plug in the superconductor, and feel a hum as things kick into life. Unfortunately, it's the wrong size for the socket and just falls out again when you let go."
-msgstr "Вы вставляете сверхпроводник и чувствуете вибрацию, когда вещи начинают оживать. К сожалению, он не подходит к сокету по размеру и выпадает, когда вы убираете руку."
+#: gamelib/scenes/engine.py:223
+msgid ""
+"You plug in the superconductor, and feel a hum as things kick into life. "
+"Unfortunately, it's the wrong size for the socket and just falls out again "
+"when you let go."
+msgstr ""
+"Вы вставляете сверхпроводник и чувствуете вибрацию, когда вещи начинают "
+"оживать. К сожалению, он не подходит к сокету по размеру и выпадает, когда "
+"вы убираете руку."
 
-#: gamelib/scenes/engine.py:243
-msgid "The chair's superconductor looks over-specced for this job, but it should work."
-msgstr "Сверхпроводник от кресла кажется слишком специфичным для этой работы, но он работает."
+#: gamelib/scenes/engine.py:234
+msgid ""
+"The chair's superconductor looks over-specced for this job, but it should "
+"work."
+msgstr ""
+"Сверхпроводник от кресла кажется слишком специфичным для этой работы, но он "
+"работает."
 
-#: gamelib/scenes/engine.py:248
+#: gamelib/scenes/engine.py:239
 msgid "It might help to remove the broken superconductor first."
 msgstr "Может стоит сначала удалить сгоревший сверхпроводник."
 
-#: gamelib/scenes/engine.py:267
+#: gamelib/scenes/engine.py:258
 msgid "Those are coolant reservoirs. They look empty."
 msgstr "Это резервуары для охладителя. Они пусты."
 
-#: gamelib/scenes/engine.py:268
+#: gamelib/scenes/engine.py:259
 msgid "The coolant reservoirs are full."
 msgstr "Резервуары наполнены охладителем."
 
-#: gamelib/scenes/engine.py:288
+#: gamelib/scenes/engine.py:279
 msgid "The receptacles for the coolant reservoirs."
 msgstr "Приёмники для резервуаров с охладителем."
 
-#: gamelib/scenes/engine.py:291
+#: gamelib/scenes/engine.py:282
 msgid "You stick your finger in the receptacle. It almost gets stuck."
 msgstr "Вы засовываете палец в приёмник. Он почти застревает."
 
-#: gamelib/scenes/engine.py:296
-msgid "Pouring the precious cryo fluid into a container connected to a cracked pipe would be a waste."
-msgstr "Вылить драгоценный охладитель в контейнер, соединённый с треснувшей трубой, было бы расточительством."
+#: gamelib/scenes/engine.py:287
+msgid ""
+"Pouring the precious cryo fluid into a container connected to a cracked pipe "
+"would be a waste."
+msgstr ""
+"Вылить драгоценный охладитель в контейнер, соединённый с треснувшей трубой, "
+"было бы расточительством."
 
-#: gamelib/scenes/engine.py:301
-msgid "You fill the reservoirs. The detergent bottle was just big enough, which is handy, because it's sprung a leak."
-msgstr "Вы наполняете резервуары. Бутылка очистителя оказалось достаточно большой, что удобно, поскольку в ней обнаружилась течь."
+#: gamelib/scenes/engine.py:292
+msgid ""
+"You fill the reservoirs. The detergent bottle was just big enough, which is "
+"handy, because it's sprung a leak."
+msgstr ""
+"Вы наполняете резервуары. Бутылка очистителя оказалось достаточно большой, "
+"что удобно, поскольку в ней обнаружилась течь."
 
-#: gamelib/scenes/engine.py:349
+#: gamelib/scenes/engine.py:340
 msgid "These pipes carry coolant to the superconductors. They feel warm."
 msgstr "Эти трубы подают охладитель к сверхпроводникам. Они тёплые."
 
-#: gamelib/scenes/engine.py:351
+#: gamelib/scenes/engine.py:342
 msgid "These pipes carry coolant to the superconductors. They are very cold."
 msgstr "Эти трубы подают охладитель к сверхпроводникам. Они очень холодные."
 
-#: gamelib/scenes/engine.py:377
+#: gamelib/scenes/engine.py:368
 msgid "Power lines. They are delivering power to the engines."
 msgstr "Шины питания. По ним подаётся питание на двигатели."
 
-#: gamelib/scenes/engine.py:378
+#: gamelib/scenes/engine.py:369
 msgid "Power lines. It looks like they aren't working correctly."
 msgstr "Шины питания. Похоже, они не работают."
 
-#: gamelib/scenes/engine.py:487
+#: gamelib/scenes/engine.py:458
+msgid ""
+"A gaping hole in the floor of the room. You're guessing that's why there's a "
+"vacuum in here."
+msgstr "Сквозная дыра в полу. Вы полагаете что это причина вакуума здесь."
+
+#: gamelib/scenes/engine.py:478
 msgid "The duct tape appears to be holding."
 msgstr "Похоже, клейкая лента держится."
 
-#: gamelib/scenes/engine.py:489
+#: gamelib/scenes/engine.py:480
 msgid "The pipe looks cracked and won't hold fluid until it's fixed."
-msgstr "В трубе трещина и она не сможет удерживать жидкость, пока её не починят."
+msgstr ""
+"В трубе трещина и она не сможет удерживать жидкость, пока её не починят."
 
-#: gamelib/scenes/engine.py:494
+#: gamelib/scenes/engine.py:485
 msgid "The duct tape already there appears to be sufficient."
 msgstr "На этом уже достаточно клейкой ленты."
 
-#: gamelib/scenes/engine.py:499
+#: gamelib/scenes/engine.py:490
 msgid "You apply your trusty duct tape to the creak, sealing it."
 msgstr "Вы заклеиваете разрыв вашей надёжной лентой, запечатывая его."
 
-#: gamelib/scenes/engine.py:516
+#: gamelib/scenes/engine.py:510
 msgid "A computer console. It's alarmingly close to the engine."
 msgstr "Компьютерная консоль. Она в опасной близости от двигателя."
 
-#: gamelib/scenes/engine.py:575
+#: gamelib/scenes/engine.py:567
 msgid "The airlock leads back to the rest of the ship."
 msgstr "Шлюз ведёт к оставшей части корабля."
 
-#: gamelib/scenes/machine.py:30
+#: gamelib/scenes/machine.py:31
 msgid "Wires run to all the machines in the room"
 msgstr "Провода идут ко всем приборам в этой комнате"
 
-#: gamelib/scenes/machine.py:50
+#: gamelib/scenes/machine.py:51
 msgid "A wiring diagram of some sort"
 msgstr "Диаграмма какого-то соединения проводов."
 
-#: gamelib/scenes/machine.py:53
+#: gamelib/scenes/machine.py:54
 msgid "The cables to this power point have been cut"
 msgstr "Кабели к этой точке питания были обрезаны"
 
-#: gamelib/scenes/machine.py:56
+#: gamelib/scenes/machine.py:57
 msgid "All the machines run off this powerpoint"
 msgstr "Все приборы птаются из этой точки"
 
-#: gamelib/scenes/machine.py:59
+#: gamelib/scenes/machine.py:60
 msgid "An impressive looking laser drill press"
 msgstr "Внушительно выглядящая лазерная дрель"
 
-#: gamelib/scenes/machine.py:72
+#: gamelib/scenes/machine.py:73
 msgid "The block for the laser drill press"
 msgstr "Блок для лазерной дрели"
 
-#: gamelib/scenes/machine.py:118
+#: gamelib/scenes/machine.py:119
 msgid "You really don't want to put your hand in there."
 msgstr "Вы точно не хотите класть туда свою руку."
 
-#: gamelib/scenes/machine.py:123
+#: gamelib/scenes/machine.py:124
 msgid "There is already a can in the welder."
 msgstr "В сварочной машине уже есть банка."
 
-#: gamelib/scenes/machine.py:127
+#: gamelib/scenes/machine.py:128
 msgid "You carefully place the can in the laser welder."
 msgstr "Вы осторожно кладёте банку в лазерную сварку."
 
-#: gamelib/scenes/machine.py:132
+#: gamelib/scenes/machine.py:133
 msgid "There is already a tube fragment in the welder."
 msgstr "В сварочной машине уже есть фрагмент трубы."
 
-#: gamelib/scenes/machine.py:136
+#: gamelib/scenes/machine.py:137
 msgid "You carefully place the tube fragments in the laser welder."
 msgstr "Вы осторожно кладёте фрагмент трубы в лазерную сварку."
 
-#: gamelib/scenes/machine.py:141
+#: gamelib/scenes/machine.py:142
 msgid "This is a Smith and Wesson 'zOMG' class high-precision laser welder."
-msgstr "Это высокоточная лазерная сварочная машина Смита и Вессона класса 'zOMG'."
+msgstr ""
+"Это высокоточная лазерная сварочная машина Смита и Вессона класса 'zOMG'."
 
-#: gamelib/scenes/machine.py:143
+#: gamelib/scenes/machine.py:144
 msgid "The laser welder looks hungry, somehow."
 msgstr "Лазерная сварка выглядит какой-то голодной."
 
-#: gamelib/scenes/machine.py:145
+#: gamelib/scenes/machine.py:146
 msgid " It currently contains an empty can."
 msgstr " Сейчас она содержит пустую банку."
 
-#: gamelib/scenes/machine.py:147
+#: gamelib/scenes/machine.py:148
 msgid " It currently contains a tube fragment."
 msgstr " Сейчас она содержит фрагмент трубы."
 
-#: gamelib/scenes/machine.py:149
+#: gamelib/scenes/machine.py:150
 msgid "The laser welder looks expectant. "
 msgstr "Лазерная сварка выглядит выжидающей. "
 
-#: gamelib/scenes/machine.py:151
+#: gamelib/scenes/machine.py:152
 msgid " It currently contains an empty can and a tube fragment."
 msgstr " Сейчас она содержит пустую банку и фрагмент трубы."
 
-#: gamelib/scenes/machine.py:168
+#: gamelib/scenes/machine.py:169
 msgid "The laser welder doesn't currently contain anything weldable."
 msgstr "Лазерная сварка сейчас не содержит ничего, что можно сварить."
 
-#: gamelib/scenes/machine.py:171
+#: gamelib/scenes/machine.py:172
 msgid "The laser welder needs something to weld the can to."
 msgstr "Лазерной сварке нужно что-нибудь, к чему можно приварить банку."
 
-#: gamelib/scenes/machine.py:173
+#: gamelib/scenes/machine.py:174
 msgid "The laser welder needs something to weld the tube fragments to."
-msgstr "Лазерной сварке нужно что-нибудь, к чему можно приварить фрагменты трубы."
+msgstr ""
+"Лазерной сварке нужно что-нибудь, к чему можно приварить фрагменты трубы."
 
-#: gamelib/scenes/machine.py:179
-msgid "With high-precision spitzensparken, you weld together a second pipe. You bundle the two pipes together."
-msgstr "С высокоточным искрением, вы свариваете вторую трубу. Вы кладёте обе трубы вместе."
+#: gamelib/scenes/machine.py:180
+msgid ""
+"With high-precision spitzensparken, you weld together a second pipe. You "
+"bundle the two pipes together."
+msgstr ""
+"С высокоточным искрением, вы свариваете вторую трубу. Вы кладёте обе трубы "
+"вместе."
 
-#: gamelib/scenes/machine.py:184
-msgid "With high-precision spitzensparken, you create yet another pipe. You store it with the other two."
-msgstr "С высокоточным искрением, вы создаёте ещё одну трубу. Вы добавляете её к первым двум."
+#: gamelib/scenes/machine.py:185
+msgid ""
+"With high-precision spitzensparken, you create yet another pipe. You store "
+"it with the other two."
+msgstr ""
+"С высокоточным искрением, вы создаёте ещё одну трубу. Вы добавляете её к "
+"первым двум."
 
-#: gamelib/scenes/machine.py:192
-msgid "With high-precision spitzensparken, the can and tube are welded into a whole greater than the sum of the parts."
-msgstr "С высокоточным искрением, банка и труба свариваются в целое, большее суммы своих частей."
+#: gamelib/scenes/machine.py:193
+msgid ""
+"With high-precision spitzensparken, the can and tube are welded into a whole "
+"greater than the sum of the parts."
+msgstr ""
+"С высокоточным искрением, банка и труба свариваются в целое, большее суммы "
+"своих частей."
 
-#: gamelib/scenes/machine.py:208
+#: gamelib/scenes/machine.py:209
 msgid "The power lights pulse expectantly."
 msgstr "Огни питания выжидающе пульсируют."
 
-#: gamelib/scenes/machine.py:246
-msgid "It looks like it eats fingers. Perhaps a different approach is in order?"
+#: gamelib/scenes/machine.py:247
+msgid ""
+"It looks like it eats fingers. Perhaps a different approach is in order?"
 msgstr "Похоже, эта машина ест пальцы. Может нужен другой подход?"
 
-#: gamelib/scenes/machine.py:250
-msgid "After much delicate grinding and a few close calls with various body parts, the titanium femur now resembles a machete more than a bone. Nice and sharp, too."
-msgstr "После достаточно затруднительной заточки, пару раз чуть не стоившей вам некоторых частей тела, титановое бедро теперь больше напоминает мачете, чем кость. Прелестное и острое."
+#: gamelib/scenes/machine.py:251
+msgid ""
+"After much delicate grinding and a few close calls with various body parts, "
+"the titanium femur now resembles a machete more than a bone. Nice and sharp, "
+"too."
+msgstr ""
+"После достаточно затруднительной заточки, пару раз чуть не стоившей вам "
+"некоторых частей тела, титановое бедро теперь больше напоминает мачете, чем "
+"кость. Прелестное и острое."
 
-#: gamelib/scenes/machine.py:256
+#: gamelib/scenes/machine.py:257
 msgid "A pretty ordinary, albeit rather industrial, grinding machine."
 msgstr "Вполне обычная, хотя и вполне промышленная, шлифовальная машина."
 
-#: gamelib/scenes/machine.py:279
-msgid "Ah! The ship's instruction manual. You'd feel better if the previous owner wasn't lying next to it with a gaping hole in his rib cage."
-msgstr "Ах! Инструкция по обращению с кораблём. Вы бы почувствовали себя лучше, если бы предыдущий владелец не лежал рядом с ней со сквозной дырой в грудной клетке."
+#: gamelib/scenes/machine.py:280
+msgid ""
+"Ah! The ship's instruction manual. You'd feel better if the previous owner "
+"wasn't lying next to it with a gaping hole in his rib cage."
+msgstr ""
+"Ах! Инструкция по обращению с кораблём. Вы бы почувствовали себя лучше, если "
+"бы предыдущий владелец не лежал рядом с ней со сквозной дырой в грудной "
+"клетке."
 
-#: gamelib/scenes/map.py:43
-msgid "Under the terms of the emergency conscription act, I have downloaded the ship's schematics to your neural implant to help you navigate around the ship."
-msgstr "В соответствии с предписанием при чрезвычайных ситуациях, я загрузил схему корабля в ваш невральный имплантант, чтобы помочь вам ориентироваться на корабле."
+#: gamelib/scenes/map.py:42
+msgid ""
+"Under the terms of the emergency conscription act, I have downloaded the "
+"ship's schematics to your neural implant to help you navigate around the "
+"ship."
+msgstr ""
+"В соответствии с предписанием при чрезвычайных ситуациях, я загрузил схему "
+"корабля в ваш невральный имплантант, чтобы помочь вам ориентироваться на "
+"корабле."
 
-#: gamelib/scenes/map.py:48
+#: gamelib/scenes/map.py:47
 #, python-format
-msgid "Prisoner %s, you are a class 1 felon. Obtaining access to the ship's schematics constitutes a level 2 offence and carries a minimal penalty of an additional 3 years on your sentence."
-msgstr "Заключённый %s, вы нарушитель класса 1. Получение доступа к схемам корабля является преступлением уровня 2 и карается как минимум 3 дополнительными годами к вашему сроку."
-
-#: gamelib/scenes/map.py:130
-msgid "The airlock refuses to open. The automated voice says: \"Hull breach beyond this door. Personnel must be equipped for vacuum before entry.\""
-msgstr "Шлюз не открывается. Голос автомата произносит: «За этой дверью повреждён корпус. Перед входом персонал должен быть экипирован для вакуума.»"
+msgid ""
+"Prisoner %s, you are a class 1 felon. Obtaining access to the ship's "
+"schematics constitutes a level 2 offence and carries a minimal penalty of an "
+"additional 3 years on your sentence."
+msgstr ""
+"Заключённый %s, вы нарушитель класса 1. Получение доступа к схемам корабля "
+"является преступлением уровня 2 и карается как минимум 3 дополнительными "
+"годами к вашему сроку."
 
-#: gamelib/scenes/map.py:185
-msgid "You look in the door, but just see empty space: that room appears to have been obliterated by meteors."
-msgstr "Вы смотрете в дверь, но видите только пустое пространство: эта комната была уничтожена метеоритами."
+#: gamelib/scenes/map.py:129
+msgid ""
+"The airlock refuses to open. The automated voice says: \"Hull breach beyond "
+"this door. Personnel must be equipped for vacuum before entry.\""
+msgstr ""
+"Шлюз не открывается. Голос автомата произносит: «За этой дверью повреждён "
+"корпус. Перед входом персонал должен быть экипирован для вакуума.»"
 
-#: gamelib/scenes/map.py:202
-msgid "Peering in through the window, you see that the entire chamber is overgrown with giant broccoli. It would take you years to cut a path through that."
-msgstr "Взглянув в окно, вы видите, что вся комната заросла гигантской брокколи. Вам потребуются годы, чтобы прорубить через неё дорогу."
+#: gamelib/scenes/map.py:184
+msgid ""
+"You look in the door, but just see empty space: that room appears to have "
+"been obliterated by meteors."
+msgstr ""
+"Вы смотрете в дверь, но видите только пустое пространство: эта комната была "
+"уничтожена метеоритами."
+
+#: gamelib/scenes/map.py:201
+msgid ""
+"Peering in through the window, you see that the entire chamber is overgrown "
+"with giant broccoli. It would take you years to cut a path through that."
+msgstr ""
+"Взглянув в окно, вы видите, что вся комната заросла гигантской брокколи. Вам "
+"потребуются годы, чтобы прорубить через неё дорогу."
 
 #: gamelib/scenes/mess.py:38
 msgid "A large collection of rusted, useless cans."
@@ -691,8 +898,11 @@
 msgstr "Впечатляюще разросшаяся брокколи."
 
 #: gamelib/scenes/mess.py:55
-msgid "You bang the cans together. It sounds like two cans being banged together."
-msgstr "Вы стучите банками друг о друга. Звучит так, словно стучат двумя банками друг о друга."
+msgid ""
+"You bang the cans together. It sounds like two cans being banged together."
+msgstr ""
+"Вы стучите банками друг о друга. Звучит так, словно стучат двумя банками "
+"друг о друга."
 
 #: gamelib/scenes/mess.py:64
 msgid "You'd mangle it beyond usefulness."
@@ -715,8 +925,12 @@
 msgstr "Вы долбите банку бедром. Банка становится помятой, но не открывается."
 
 #: gamelib/scenes/mess.py:106
-msgid "You club the can with the femur. The dents shift around, but it still doesn't open."
-msgstr "Вы долбите банку бедром. Она вся покрывается вмятинами, но всё же не открывается."
+msgid ""
+"You club the can with the femur. The dents shift around, but it still "
+"doesn't open."
+msgstr ""
+"Вы долбите банку бедром. Она вся покрывается вмятинами, но всё же не "
+"открывается."
 
 #: gamelib/scenes/mess.py:137
 msgid "Best before a long time in the past. Better not eat these."
@@ -755,8 +969,14 @@
 msgstr "Похоже, ваши заплатки хорошо держатся."
 
 #: gamelib/scenes/mess.py:179
-msgid "With a flurry of disgusting mutant vegetable chunks, you clear the overgrown broccoli away from the access panel and reveal some broken tubes. They look important."
-msgstr "В туче отвратительных кусков мутировавшего овоща, вы расчищаете панель доступа от брокколи-переростка, и освобождаете некоторые сломанные трубы. Они выглядят важными."
+msgid ""
+"With a flurry of disgusting mutant vegetable chunks, you clear the overgrown "
+"broccoli away from the access panel and reveal some broken tubes. They look "
+"important."
+msgstr ""
+"В туче отвратительных кусков мутировавшего овоща, вы расчищаете панель "
+"доступа от брокколи-переростка, и освобождаете некоторые сломанные трубы. "
+"Они выглядят важными."
 
 #: gamelib/scenes/mess.py:185
 msgid "It looks broken enough already."
@@ -767,25 +987,37 @@
 msgstr "Прорубание дыр не починит течи."
 
 #: gamelib/scenes/mess.py:189
-msgid "After all that effort fixing it, chopping it to bits doesn't seem very smart."
-msgstr "После всего труда, затраченного на их восстановление, порубить их на кусочки — не очень умный поступок."
+msgid ""
+"After all that effort fixing it, chopping it to bits doesn't seem very smart."
+msgstr ""
+"После всего труда, затраченного на их восстановление, порубить их на кусочки "
+"— не очень умный поступок."
 
-#: gamelib/scenes/mess.py:194
-#: gamelib/scenes/mess.py:207
+#: gamelib/scenes/mess.py:194 gamelib/scenes/mess.py:207
 msgid "It would get lost in the fronds."
 msgstr "Это потеряется в зарослях."
 
 #: gamelib/scenes/mess.py:201
-msgid "The pipes slot neatly into place, but don't make an airtight seal. One of the pipes has cracked slightly as well."
-msgstr "Трубы с трудом вставляются на место и крепление не герметично. К тому же одна из труб треснула."
+msgid ""
+"The pipes slot neatly into place, but don't make an airtight seal. One of "
+"the pipes has cracked slightly as well."
+msgstr ""
+"Трубы с трудом вставляются на место и крепление не герметично. К тому же "
+"одна из труб треснула."
 
 #: gamelib/scenes/mess.py:209
 msgid "There's quite enough tape on the ducting already."
 msgstr "На трубопроводе уже вполне достаточно ленты."
 
 #: gamelib/scenes/mess.py:216
-msgid "It takes quite a lot of tape, but eventually everything is airtight and ready to hold pressure. Who'd've thought duct tape could actually be used to tape ducts?"
-msgstr "На это уходит довольно много ленты, но в конце концов всё герметично и способно выдержать давление. Кто бы мог подумать, что трубопроводная лента может быть использована для обмотки труб?"
+msgid ""
+"It takes quite a lot of tape, but eventually everything is airtight and "
+"ready to hold pressure. Who'd've thought duct tape could actually be used to "
+"tape ducts?"
+msgstr ""
+"На это уходит довольно много ленты, но в конце концов всё герметично и "
+"способно выдержать давление. Кто бы мог подумать, что трубопроводная лента "
+"может быть использована для обмотки труб?"
 
 #: gamelib/scenes/mess.py:222
 msgid "The mutant broccoli resists your best efforts."
@@ -809,18 +1041,36 @@
 msgstr "Оставшиеся бутылки протекают."
 
 #: gamelib/scenes/mess.py:292
-msgid "You pick up an empty dishwashing liquid bottle. You can't find any sponges."
-msgstr "Вы подбираете пусую бутылку из-под чистящего средства. Вы не можете найти губку."
+msgid ""
+"You pick up an empty dishwashing liquid bottle. You can't find any sponges."
+msgstr ""
+"Вы подбираете пусую бутылку из-под чистящего средства. Вы не можете найти "
+"губку."
 
 #: gamelib/scenes/mess.py:295
 msgid "Empty plastic containers. They used to hold dishwasher soap."
-msgstr "Пустые пластиковые контейнеры. В них хранилось средство для мытья посуды."
+msgstr ""
+"Пустые пластиковые контейнеры. В них хранилось средство для мытья посуды."
 
-#: gamelib/scenes/scene_widgets.py:195
+#: gamelib/scenes/game_widgets.py:51
 msgid "A security camera watches over the room"
 msgstr "Камера безопасности следит за комнатой"
 
-#: gamelib/scenes/scene_widgets.py:199
-msgid "3D scene reconstruction failed. Critical error. Entering emergency shutdown."
-msgstr "Реконструция 3D-сцены не удалась. Критическая ошибка. Начинаю аварийное отключение."
+#: gamelib/scenes/game_widgets.py:53
+msgid "The security camera is currently offline but should be working soon"
+msgstr ""
+
+#: gamelib/scenes/game_widgets.py:55
+#, fuzzy
+msgid "The security camera is powered down"
+msgstr "Камера безопасности следит за комнатой"
 
+#: gamelib/scenes/game_widgets.py:63
+msgid ""
+"3D scene reconstruction failed. Critical error. Entering emergency shutdown."
+msgstr ""
+"Реконструция 3D-сцены не удалась. Критическая ошибка. Начинаю аварийное "
+"отключение."
+
+#~ msgid "A control panel. It seems dead."
+#~ msgstr "Панель управления. Похоже, она не работает."
--- a/po/suspended-sentence.pot	Tue Mar 08 13:26:00 2011 +0200
+++ b/po/suspended-sentence.pot	Tue Mar 08 14:37:43 2011 +0200
@@ -8,27 +8,28 @@
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-02-16 18:41+0600\n"
+"POT-Creation-Date: 2011-03-08 14:36+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=CHARSET\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: gamelib/widgets.py:178
+#: gamelib/widgets.py:175
 msgid "Quit Game"
 msgstr ""
 
-#: gamelib/widgets.py:179
+#: gamelib/widgets.py:176
 msgid "Exit to Main Menu"
 msgstr ""
 
-#: gamelib/gamescreen.py:142
+#: gamelib/gamescreen.py:146
 msgid "Close"
 msgstr ""
 
-#: gamelib/gamescreen.py:214
+#: gamelib/gamescreen.py:218
 msgid "Menu"
 msgstr ""
 
@@ -136,60 +137,60 @@
 "technician."
 msgstr ""
 
+#: gamelib/scenes/bridge.py:451
+msgid "You are not authorized to change the destination."
+msgstr ""
+
 #: gamelib/scenes/bridge.py:453
-msgid "You are not authorized to change the destination."
+msgid "There's no good reason to choose to go to the penal colony."
 msgstr ""
 
 #: gamelib/scenes/bridge.py:455
-msgid "There's no good reason to choose to go to the penal colony."
-msgstr ""
-
-#: gamelib/scenes/bridge.py:457
 msgid ""
 "You could change the destination, but when JIM recovers, it'll just get "
 "reset."
 msgstr ""
 
-#: gamelib/scenes/bridge.py:459
+#: gamelib/scenes/bridge.py:457
 msgid "You change the destination."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:32
+#: gamelib/scenes/crew_quarters.py:31
 msgid "The plant is doing surprisingly well for centuries of neglect"
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:35
+#: gamelib/scenes/crew_quarters.py:34
 msgid "A picture of a cat labelled 'Clementine'"
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:74
+#: gamelib/scenes/crew_quarters.py:73
 msgid "Duct tape. It'll stick to everything except ducts, apparently."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:76
+#: gamelib/scenes/crew_quarters.py:75
 msgid ""
 "The perfectly balanced door swings frictionlessly to and fro. What "
 "craftsmanship!"
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:78
+#: gamelib/scenes/crew_quarters.py:77
 msgid ""
 "The safe is locked. This might be an interesting challenge, if suitable "
 "equipment can be found."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:83
+#: gamelib/scenes/crew_quarters.py:82
 msgid "It's already unlocked. There's no more challenge."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:88
+#: gamelib/scenes/crew_quarters.py:87
 msgid ""
 "Even after centuries of neglect, the tumblers slide almost silently into "
 "place. Turns out the combination was '1 2 3 4 5'. An idiot must keep his "
 "luggage in here."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:92
+#: gamelib/scenes/crew_quarters.py:91
 #, python-format
 msgid ""
 "Prisoner %s, you have been observed committing a felony violation. This will "
@@ -197,17 +198,17 @@
 "twenty years."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:97
+#: gamelib/scenes/crew_quarters.py:96
 msgid "Ah, a vintage Knoxx & Co. model QR3. Quaint, but reasonably secure."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:118
+#: gamelib/scenes/crew_quarters.py:117
 msgid ""
 "What's the point of lugging around a very dead fish and a kilogram or so of "
 "sand?"
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:123
+#: gamelib/scenes/crew_quarters.py:122
 msgid "The fishbowl is useful, but its contents aren't."
 msgstr ""
 
@@ -215,77 +216,81 @@
 msgid "This fishbowl looks exactly like an old science fiction space helmet."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:139
+#: gamelib/scenes/crew_quarters.py:128
+msgid "An evicted dead fish and some sand lie forlornly on the table"
+msgstr ""
+
+#: gamelib/scenes/crew_quarters.py:142
 msgid ""
 "You duct tape the edges of the helmet. The seal is crude, but it will serve "
 "as a workable helmet if needed."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:186
+#: gamelib/scenes/crew_quarters.py:189
 msgid "This poster will go nicely on your bedroom wall."
 msgstr ""
 
-#: gamelib/scenes/crew_quarters.py:189
+#: gamelib/scenes/crew_quarters.py:192
 msgid "A paradoxical poster hangs below the security camera."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:55
+#: gamelib/scenes/cryo.py:53
 msgid "These pipes carry cooling fluid to the cryo units."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:66
+#: gamelib/scenes/cryo.py:64
 msgid "An empty cryo chamber."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:67
+#: gamelib/scenes/cryo.py:65
 msgid "Prisoner 81E4-C8900480E635. Embezzlement. 20 years."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:76 gamelib/scenes/cryo.py:95
+#: gamelib/scenes/cryo.py:74 gamelib/scenes/cryo.py:93
 msgid ""
 "A working cryo chamber. The frosted glass obscures the details of the "
 "occupant."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:77
+#: gamelib/scenes/cryo.py:75
 msgid "Prisoner 9334-CE1EB0243BAB. Murder. 40 years."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:86
+#: gamelib/scenes/cryo.py:84
 msgid "A broken cryo chamber. The skeleton inside has been picked clean."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:87
+#: gamelib/scenes/cryo.py:85
 msgid ""
 "Prisoner BFBC-8BF4C6B7492B. Importing illegal alien biomatter. 15 years."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:96
+#: gamelib/scenes/cryo.py:94
 msgid "Prisoner B520-99495B8C41CE. Copyright infringement. 60 years."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:103
+#: gamelib/scenes/cryo.py:101
 msgid "An empty cryo unit. Recently filled by you."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:104
+#: gamelib/scenes/cryo.py:102
 #, python-format
 msgid "Prisoner %s. Safecracking, grand larceny. 30 years."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:111 gamelib/scenes/cryo.py:120
+#: gamelib/scenes/cryo.py:109 gamelib/scenes/cryo.py:118
 msgid "An empty cryo unit."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:112
+#: gamelib/scenes/cryo.py:110
 msgid "Prisoner 83F1-CE32D3234749. Spamming. 5 years."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:121
+#: gamelib/scenes/cryo.py:119
 msgid "Prisoner A455-9DF9F43C43E5. Medical malpractice. 10 years."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:137
+#: gamelib/scenes/cryo.py:135
 #, python-format
 msgid ""
 "Greetings, Prisoner %s. I am the Judicial Incarceration Monitor. You have "
@@ -295,132 +300,136 @@
 "destination. Please report to the bridge."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:167
+#: gamelib/scenes/cryo.py:165
 msgid ""
 "It takes more effort than one would expect, but eventually the pipe is "
 "separated from the wall."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:173
+#: gamelib/scenes/cryo.py:171
 #, python-format
 msgid ""
 "Prisoner %s. Vandalism is an offence punishable by a minimum of an "
 "additional 6 months to your sentence."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:183 gamelib/scenes/cryo.py:213
+#: gamelib/scenes/cryo.py:181 gamelib/scenes/cryo.py:211
 msgid "These pipes aren't attached to the wall very solidly."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:188
+#: gamelib/scenes/cryo.py:186
 msgid "These pipes carry cooling fluid to empty cryo units."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:189
+#: gamelib/scenes/cryo.py:187
 msgid "There used to be a pipe carrying cooling fluid here."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:206
+#: gamelib/scenes/cryo.py:204
 msgid ""
 "These pipes carry fluid to the working cryo units. Chopping them down "
 "doesn't seem sensible."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:216
+#: gamelib/scenes/cryo.py:214
 msgid "These pipes carry cooling fluid to the working cryo units."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:288
+#: gamelib/scenes/cryo.py:286
 msgid ""
 "You hit the chamber that used to hold this very leg. Nothing happens as a "
 "result."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:293
+#: gamelib/scenes/cryo.py:291
 msgid "A broken cryo chamber, with a poor unfortunate corpse inside."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:294
+#: gamelib/scenes/cryo.py:292
 msgid "A broken cryo chamber. The corpse inside is missing a leg."
 msgstr ""
 
+#: gamelib/scenes/cryo.py:313
+msgid "You bang on the chamber with the titanium femur. Nothing much happens."
+msgstr ""
+
+#: gamelib/scenes/cryo.py:314
+msgid "Hitting the cryo unit with the femur doesn't achieve anything."
+msgstr ""
+
 #: gamelib/scenes/cryo.py:315
-msgid "You bang on the chamber with the titanium femur. Nothing much happens."
-msgstr ""
-
-#: gamelib/scenes/cryo.py:316
-msgid "Hitting the cryo unit with the femur doesn't achieve anything."
-msgstr ""
-
-#: gamelib/scenes/cryo.py:317
 msgid "You hit the chamber with the femur. Nothing happens."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:341
+#: gamelib/scenes/cryo.py:339
 msgid ""
 "You wedge the titanium femur into the chain and twist. With a satisfying "
 "*snap*, the chain breaks and the door opens."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:343
+#: gamelib/scenes/cryo.py:341
 msgid ""
 "You bang on the door with the titanium femur. It makes a clanging sound."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:346
+#: gamelib/scenes/cryo.py:344
 msgid "You wave the femur in the doorway. Nothing happens."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:352
+#: gamelib/scenes/cryo.py:350
 msgid ""
 "It moves slightly and then stops. A chain on the other side is preventing it "
 "from opening completely."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:370 gamelib/scenes/scene_widgets.py:172
+#: gamelib/scenes/cryo.py:368 gamelib/scenes/game_widgets.py:26
 msgid "An open doorway leads to the rest of the ship."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:372
+#: gamelib/scenes/cryo.py:370
 msgid ""
 "A rusty door. It can't open all the way because of a chain on the other side."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:374
+#: gamelib/scenes/cryo.py:372
 msgid "A rusty door. It is currently closed."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:395
+#: gamelib/scenes/cryo.py:393
 msgid "Hitting it with the leg accomplishes nothing."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:398
+#: gamelib/scenes/cryo.py:396
 msgid "A computer terminal, with some text on it."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:416
+#: gamelib/scenes/cryo.py:414
 msgid ""
 "The skeletal occupant of this cryo unit has an artificial femur made of "
 "titanium. You take it."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:419
+#: gamelib/scenes/cryo.py:417
 msgid "This femur looks synthetic."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:434
+#: gamelib/scenes/cryo.py:432
 msgid "The plaque is welded to the unit. You can't shift it."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:437
+#: gamelib/scenes/cryo.py:435
 msgid "'Prisoner 98CC-764E646391EE. War crimes. 45 years."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:461
+#: gamelib/scenes/cryo.py:459
 msgid "Coolant leaks disturbingly from the bulkheads."
 msgstr ""
 
-#: gamelib/scenes/cryo.py:470
+#: gamelib/scenes/cryo.py:462
+msgid "It's gooey"
+msgstr ""
+
+#: gamelib/scenes/cryo.py:468
 msgid "You scoop up some coolant and fill the bottle."
 msgstr ""
 
@@ -429,327 +438,329 @@
 msgstr ""
 
 #: gamelib/scenes/engine.py:50
-msgid "A control panel. It seems dead."
-msgstr ""
-
-#: gamelib/scenes/engine.py:56
 msgid "Superconductors. The engines must be power hogs."
 msgstr ""
 
-#: gamelib/scenes/engine.py:64 gamelib/scenes/engine.py:467
-msgid ""
-"A gaping hole in the floor of the room. You're guessing that's why there's a "
-"vacuum in here."
+#: gamelib/scenes/engine.py:58
+msgid "A gaping hole in the floor of the room. It is clearly irreparable."
 msgstr ""
 
-#: gamelib/scenes/engine.py:73
+#: gamelib/scenes/engine.py:68
 msgid ""
 "Empty chocolate-covered bacon cans? Poor guy, he must have found them "
 "irresistible."
 msgstr ""
 
-#: gamelib/scenes/engine.py:79
+#: gamelib/scenes/engine.py:74
 msgid "The engines. They don't look like they are working."
 msgstr ""
 
-#: gamelib/scenes/engine.py:85
+#: gamelib/scenes/engine.py:80
 msgid ""
 "A burned-out laser cutter. It may be responsible for the hole in the floor."
 msgstr ""
 
-#: gamelib/scenes/engine.py:91
+#: gamelib/scenes/engine.py:86
 msgid "The main fuel line for the engines."
 msgstr ""
 
-#: gamelib/scenes/engine.py:111
+#: gamelib/scenes/engine.py:106
 msgid ""
 "The spare fuel line. If something went wrong with the main one, you would "
 "hook that one up."
 msgstr ""
 
-#: gamelib/scenes/engine.py:117
+#: gamelib/scenes/engine.py:112
 msgid "The sign says DANGER. You would be wise to listen to it."
 msgstr ""
 
-#: gamelib/scenes/engine.py:123
-msgid "It's one of those glow-in-the-dark exit signs that you see everywhere."
+#: gamelib/scenes/engine.py:118
+msgid ""
+"It's one of those glow-in-the-dark signs needed to satisfy the health and "
+"safety inspectors."
 msgstr ""
 
-#: gamelib/scenes/engine.py:135
+#: gamelib/scenes/engine.py:131
 #, python-format
 msgid ""
-"The engines are now operational. You havedone a satisfactory job, Prisoner %"
-"s."
+"The engines are now operational. You havedone a satisfactory job, Prisoner "
+"%s."
 msgstr ""
 
-#: gamelib/scenes/engine.py:143
+#: gamelib/scenes/engine.py:139
 msgid ""
 "With your improvised helmet, the automatic airlock allows you into the "
 "engine room. Even if there wasn't a vacuum it would be eerily quiet."
 msgstr ""
 
-#: gamelib/scenes/engine.py:159
+#: gamelib/scenes/engine.py:155
 msgid "All systems are go! Or at least the engines are."
 msgstr ""
 
-#: gamelib/scenes/engine.py:177
+#: gamelib/scenes/engine.py:173
 msgid "A can opener. Looks like you won't be starving"
 msgstr ""
 
-#: gamelib/scenes/engine.py:182
+#: gamelib/scenes/engine.py:178
 msgid ""
 "You pick up the can opener. It looks brand new; the vacuum has kept it in "
 "perfect condition."
 msgstr ""
 
-#: gamelib/scenes/engine.py:209
+#: gamelib/scenes/engine.py:200
 msgid ""
 "That superconductor looks burned out. It's wedged in there pretty firmly."
 msgstr ""
 
-#: gamelib/scenes/engine.py:211
+#: gamelib/scenes/engine.py:202
 msgid "An empty superconductor socket"
 msgstr ""
 
-#: gamelib/scenes/engine.py:213
+#: gamelib/scenes/engine.py:204
 msgid "A working superconductor."
 msgstr ""
 
-#: gamelib/scenes/engine.py:217
+#: gamelib/scenes/engine.py:208
 msgid "It's wedged in there pretty firmly, it won't come out."
 msgstr ""
 
-#: gamelib/scenes/engine.py:219
+#: gamelib/scenes/engine.py:210
 msgid ""
 "You decide that working engines are more important than having a shiny "
 "superconductor."
 msgstr ""
 
-#: gamelib/scenes/engine.py:226
-msgid "With leverage, the burned-out superconductor snaps out."
+#: gamelib/scenes/engine.py:216
+msgid "With leverage, the burned-out superconductor snaps out. You discard it."
 msgstr ""
 
-#: gamelib/scenes/engine.py:230
+#: gamelib/scenes/engine.py:221
 msgid "It might help to remove the broken superconductor first"
 msgstr ""
 
-#: gamelib/scenes/engine.py:232
+#: gamelib/scenes/engine.py:223
 msgid ""
 "You plug in the superconductor, and feel a hum as things kick into life. "
 "Unfortunately, it's the wrong size for the socket and just falls out again "
 "when you let go."
 msgstr ""
 
-#: gamelib/scenes/engine.py:243
+#: gamelib/scenes/engine.py:234
 msgid ""
 "The chair's superconductor looks over-specced for this job, but it should "
 "work."
 msgstr ""
 
-#: gamelib/scenes/engine.py:248
+#: gamelib/scenes/engine.py:239
 msgid "It might help to remove the broken superconductor first."
 msgstr ""
 
-#: gamelib/scenes/engine.py:267
+#: gamelib/scenes/engine.py:258
 msgid "Those are coolant reservoirs. They look empty."
 msgstr ""
 
-#: gamelib/scenes/engine.py:268
+#: gamelib/scenes/engine.py:259
 msgid "The coolant reservoirs are full."
 msgstr ""
 
-#: gamelib/scenes/engine.py:288
+#: gamelib/scenes/engine.py:279
 msgid "The receptacles for the coolant reservoirs."
 msgstr ""
 
-#: gamelib/scenes/engine.py:291
+#: gamelib/scenes/engine.py:282
 msgid "You stick your finger in the receptacle. It almost gets stuck."
 msgstr ""
 
-#: gamelib/scenes/engine.py:296
+#: gamelib/scenes/engine.py:287
 msgid ""
 "Pouring the precious cryo fluid into a container connected to a cracked pipe "
 "would be a waste."
 msgstr ""
 
-#: gamelib/scenes/engine.py:301
+#: gamelib/scenes/engine.py:292
 msgid ""
 "You fill the reservoirs. The detergent bottle was just big enough, which is "
 "handy, because it's sprung a leak."
 msgstr ""
 
-#: gamelib/scenes/engine.py:349
+#: gamelib/scenes/engine.py:340
 msgid "These pipes carry coolant to the superconductors. They feel warm."
 msgstr ""
 
-#: gamelib/scenes/engine.py:351
+#: gamelib/scenes/engine.py:342
 msgid "These pipes carry coolant to the superconductors. They are very cold."
 msgstr ""
 
-#: gamelib/scenes/engine.py:377
+#: gamelib/scenes/engine.py:368
 msgid "Power lines. They are delivering power to the engines."
 msgstr ""
 
-#: gamelib/scenes/engine.py:378
+#: gamelib/scenes/engine.py:369
 msgid "Power lines. It looks like they aren't working correctly."
 msgstr ""
 
-#: gamelib/scenes/engine.py:487
+#: gamelib/scenes/engine.py:458
+msgid ""
+"A gaping hole in the floor of the room. You're guessing that's why there's a "
+"vacuum in here."
+msgstr ""
+
+#: gamelib/scenes/engine.py:478
 msgid "The duct tape appears to be holding."
 msgstr ""
 
-#: gamelib/scenes/engine.py:489
+#: gamelib/scenes/engine.py:480
 msgid "The pipe looks cracked and won't hold fluid until it's fixed."
 msgstr ""
 
-#: gamelib/scenes/engine.py:494
+#: gamelib/scenes/engine.py:485
 msgid "The duct tape already there appears to be sufficient."
 msgstr ""
 
-#: gamelib/scenes/engine.py:499
+#: gamelib/scenes/engine.py:490
 msgid "You apply your trusty duct tape to the creak, sealing it."
 msgstr ""
 
-#: gamelib/scenes/engine.py:516
+#: gamelib/scenes/engine.py:510
 msgid "A computer console. It's alarmingly close to the engine."
 msgstr ""
 
-#: gamelib/scenes/engine.py:575
+#: gamelib/scenes/engine.py:567
 msgid "The airlock leads back to the rest of the ship."
 msgstr ""
 
-#: gamelib/scenes/machine.py:30
+#: gamelib/scenes/machine.py:31
 msgid "Wires run to all the machines in the room"
 msgstr ""
 
-#: gamelib/scenes/machine.py:50
+#: gamelib/scenes/machine.py:51
 msgid "A wiring diagram of some sort"
 msgstr ""
 
-#: gamelib/scenes/machine.py:53
+#: gamelib/scenes/machine.py:54
 msgid "The cables to this power point have been cut"
 msgstr ""
 
-#: gamelib/scenes/machine.py:56
+#: gamelib/scenes/machine.py:57
 msgid "All the machines run off this powerpoint"
 msgstr ""
 
-#: gamelib/scenes/machine.py:59
+#: gamelib/scenes/machine.py:60
 msgid "An impressive looking laser drill press"
 msgstr ""
 
-#: gamelib/scenes/machine.py:72
+#: gamelib/scenes/machine.py:73
 msgid "The block for the laser drill press"
 msgstr ""
 
-#: gamelib/scenes/machine.py:118
+#: gamelib/scenes/machine.py:119
 msgid "You really don't want to put your hand in there."
 msgstr ""
 
-#: gamelib/scenes/machine.py:123
+#: gamelib/scenes/machine.py:124
 msgid "There is already a can in the welder."
 msgstr ""
 
-#: gamelib/scenes/machine.py:127
+#: gamelib/scenes/machine.py:128
 msgid "You carefully place the can in the laser welder."
 msgstr ""
 
-#: gamelib/scenes/machine.py:132
+#: gamelib/scenes/machine.py:133
 msgid "There is already a tube fragment in the welder."
 msgstr ""
 
-#: gamelib/scenes/machine.py:136
+#: gamelib/scenes/machine.py:137
 msgid "You carefully place the tube fragments in the laser welder."
 msgstr ""
 
-#: gamelib/scenes/machine.py:141
+#: gamelib/scenes/machine.py:142
 msgid "This is a Smith and Wesson 'zOMG' class high-precision laser welder."
 msgstr ""
 
-#: gamelib/scenes/machine.py:143
+#: gamelib/scenes/machine.py:144
 msgid "The laser welder looks hungry, somehow."
 msgstr ""
 
-#: gamelib/scenes/machine.py:145
+#: gamelib/scenes/machine.py:146
 msgid " It currently contains an empty can."
 msgstr ""
 
-#: gamelib/scenes/machine.py:147
+#: gamelib/scenes/machine.py:148
 msgid " It currently contains a tube fragment."
 msgstr ""
 
-#: gamelib/scenes/machine.py:149
+#: gamelib/scenes/machine.py:150
 msgid "The laser welder looks expectant. "
 msgstr ""
 
-#: gamelib/scenes/machine.py:151
+#: gamelib/scenes/machine.py:152
 msgid " It currently contains an empty can and a tube fragment."
 msgstr ""
 
-#: gamelib/scenes/machine.py:168
+#: gamelib/scenes/machine.py:169
 msgid "The laser welder doesn't currently contain anything weldable."
 msgstr ""
 
-#: gamelib/scenes/machine.py:171
+#: gamelib/scenes/machine.py:172
 msgid "The laser welder needs something to weld the can to."
 msgstr ""
 
-#: gamelib/scenes/machine.py:173
+#: gamelib/scenes/machine.py:174
 msgid "The laser welder needs something to weld the tube fragments to."
 msgstr ""
 
-#: gamelib/scenes/machine.py:179
+#: gamelib/scenes/machine.py:180
 msgid ""
 "With high-precision spitzensparken, you weld together a second pipe. You "
 "bundle the two pipes together."
 msgstr ""
 
-#: gamelib/scenes/machine.py:184
+#: gamelib/scenes/machine.py:185
 msgid ""
 "With high-precision spitzensparken, you create yet another pipe. You store "
 "it with the other two."
 msgstr ""
 
-#: gamelib/scenes/machine.py:192
+#: gamelib/scenes/machine.py:193
 msgid ""
 "With high-precision spitzensparken, the can and tube are welded into a whole "
 "greater than the sum of the parts."
 msgstr ""
 
-#: gamelib/scenes/machine.py:208
+#: gamelib/scenes/machine.py:209
 msgid "The power lights pulse expectantly."
 msgstr ""
 
-#: gamelib/scenes/machine.py:246
+#: gamelib/scenes/machine.py:247
 msgid ""
 "It looks like it eats fingers. Perhaps a different approach is in order?"
 msgstr ""
 
-#: gamelib/scenes/machine.py:250
+#: gamelib/scenes/machine.py:251
 msgid ""
 "After much delicate grinding and a few close calls with various body parts, "
 "the titanium femur now resembles a machete more than a bone. Nice and sharp, "
 "too."
 msgstr ""
 
-#: gamelib/scenes/machine.py:256
+#: gamelib/scenes/machine.py:257
 msgid "A pretty ordinary, albeit rather industrial, grinding machine."
 msgstr ""
 
-#: gamelib/scenes/machine.py:279
+#: gamelib/scenes/machine.py:280
 msgid ""
 "Ah! The ship's instruction manual. You'd feel better if the previous owner "
 "wasn't lying next to it with a gaping hole in his rib cage."
 msgstr ""
 
-#: gamelib/scenes/map.py:43
+#: gamelib/scenes/map.py:42
 msgid ""
 "Under the terms of the emergency conscription act, I have downloaded the "
 "ship's schematics to your neural implant to help you navigate around the "
 "ship."
 msgstr ""
 
-#: gamelib/scenes/map.py:48
+#: gamelib/scenes/map.py:47
 #, python-format
 msgid ""
 "Prisoner %s, you are a class 1 felon. Obtaining access to the ship's "
@@ -757,19 +768,19 @@
 "additional 3 years on your sentence."
 msgstr ""
 
-#: gamelib/scenes/map.py:130
+#: gamelib/scenes/map.py:129
 msgid ""
 "The airlock refuses to open. The automated voice says: \"Hull breach beyond "
 "this door. Personnel must be equipped for vacuum before entry.\""
 msgstr ""
 
-#: gamelib/scenes/map.py:185
+#: gamelib/scenes/map.py:184
 msgid ""
 "You look in the door, but just see empty space: that room appears to have "
 "been obliterated by meteors."
 msgstr ""
 
-#: gamelib/scenes/map.py:202
+#: gamelib/scenes/map.py:201
 msgid ""
 "Peering in through the window, you see that the entire chamber is overgrown "
 "with giant broccoli. It would take you years to cut a path through that."
@@ -921,11 +932,19 @@
 msgid "Empty plastic containers. They used to hold dishwasher soap."
 msgstr ""
 
-#: gamelib/scenes/scene_widgets.py:195
+#: gamelib/scenes/game_widgets.py:51
 msgid "A security camera watches over the room"
 msgstr ""
 
-#: gamelib/scenes/scene_widgets.py:199
+#: gamelib/scenes/game_widgets.py:53
+msgid "The security camera is currently offline but should be working soon"
+msgstr ""
+
+#: gamelib/scenes/game_widgets.py:55
+msgid "The security camera is powered down"
+msgstr ""
+
+#: gamelib/scenes/game_widgets.py:63
 msgid ""
 "3D scene reconstruction failed. Critical error. Entering emergency shutdown."
 msgstr ""
Binary file sources/art/cryo_comp_info_detail.xcf has changed
--- a/tools/rect_drawer.py	Tue Mar 08 13:26:00 2011 +0200
+++ b/tools/rect_drawer.py	Tue Mar 08 14:37:43 2011 +0200
@@ -10,16 +10,22 @@
 from albow.root import RootWidget
 from albow.utils import frame_rect
 from albow.widget import Widget
-from albow.controls import Button, Image
+from albow.controls import Button, Image, Label
 from albow.palette_view import PaletteView
 from albow.file_dialogs import request_old_filename
 from albow.resource import get_font
-from pygame.locals import SWSURFACE
+from pygame.locals import SWSURFACE, K_LEFT, K_RIGHT, K_UP, K_DOWN, \
+        K_a, K_t, K_d, K_i, K_r, K_o, K_b, K_z, \
+        BLEND_RGBA_MIN, SRCALPHA
 import pygame
 from pygame.colordict import THECOLORS
 
 from gamelib import constants
 constants.DEBUG = True
+MENU_WIDTH = 200
+MENU_BUTTON_HEIGHT = 25
+ZOOM = 4
+ZOOM_STEP = 100
 
 from gamelib import state
 state.DEBUG_RECTS = True
@@ -39,7 +45,7 @@
 
     def __init__(self, app_image):
         self.image = app_image
-        super(AppPalette, self).__init__((35, 35), 5, 5, margin=2)
+        super(AppPalette, self).__init__((35, 35), 4, 5, margin=2)
         self.selection = 0
         self.image.rect_color = pygame.color.Color(self.colors[self.selection])
 
@@ -66,7 +72,7 @@
 
     def __init__(self, state):
         self.state = state
-        super(AppImage, self).__init__(pygame.rect.Rect(0, 0, 800, 600))
+        super(AppImage, self).__init__(pygame.rect.Rect(0, 0, constants.SCREEN[0], constants.SCREEN[1]))
         self.mode = 'draw'
         self.rects = []
         self.images = []
@@ -78,27 +84,34 @@
         self.close_button = BoomLabel('Close', font=get_font(20, 'Vera.ttf'))
         self.close_button.fg_color = (0, 0, 0)
         self.close_button.bg_color = (0, 0, 0)
+        self.draw_rects = True
+        self.draw_things = True
+        self.draw_thing_rects = True
+        self.draw_images = True
+        self.trans_images = False
+        self.draw_toolbar = True
+        self.old_mouse_pos = None
+        self.zoom_display = False
+        self.draw_anim = False
+        self.zoom_offset = (600, 600)
         if self.state.current_detail:
             w, h = self.state.current_detail.get_detail_size()
             rect = pygame.rect.Rect(0, 0, w, h)
             self.close_button.rect.midbottom = rect.midbottom
-            self.add(self.close_button)
             self.offset = (0, 0)
         else:
             self.offset = (-self.state.current_scene.OFFSET[0], - self.state.current_scene.OFFSET[1])
-        self.draw_rects = True
-        self.draw_things = True
-        self.draw_thing_rects = True
-        self.draw_images = True
-        self.draw_toolbar = True
         self.find_existing_intersects()
 
+    def _get_scene(self):
+        if self.state.current_detail:
+            return self.state.current_detail
+        else:
+            return self.state.current_scene
+
     def find_existing_intersects(self):
         """Parse the things in the scene for overlaps"""
-        if self.state.current_detail:
-            scene = self.state.current_detail
-        else:
-            scene = self.state.current_scene
+        scene = self._get_scene()
         # Pylint hates this function
         for thing in scene.things.itervalues():
             for interact_name in thing.interacts:
@@ -128,10 +141,7 @@
     def find_intersecting_rects(self, d):
         """Find if any rect collections intersect"""
         # I loath N^X brute search algorithm's, but whatever, hey
-        if self.state.current_detail:
-            scene = self.state.current_detail
-        else:
-            scene = self.state.current_scene
+        scene = self._get_scene()
         for (num, col) in enumerate(d):
             rect_list = d[col]
             for thing in scene.things.itervalues():
@@ -169,10 +179,7 @@
 
     def toggle_thing_rects(self):
         self.draw_thing_rects = not self.draw_thing_rects
-        if self.state.current_detail:
-            scene = self.state.current_detail
-        else:
-            scene = self.state.current_scene
+        scene = self._get_scene()
         for thing in scene.things.itervalues():
             if not self.draw_thing_rects:
                 if not hasattr(thing, 'old_colour'):
@@ -184,12 +191,22 @@
     def toggle_images(self):
         self.draw_images = not self.draw_images
 
+    def toggle_trans_images(self):
+        self.trans_images = not self.trans_images
+        self.invalidate()
+
     def toggle_rects(self):
         self.draw_rects = not self.draw_rects
 
     def toggle_toolbar(self):
         self.draw_toolbar = not self.draw_toolbar
 
+    def toggle_zoom(self):
+        self.zoom_display = not self.zoom_display
+
+    def toggle_anim(self):
+        self.draw_anim = not self.draw_anim
+
     def draw_mode(self):
         self.mode = 'draw'
 
@@ -198,15 +215,50 @@
         self.start_pos = None
         self.end_pos = None
 
+    def draw_sub_image(self, image, surface, cropped_rect):
+        """Tweaked image drawing to avoid albow's centring the image in the
+           subsurface"""
+        surf = pygame.surface.Surface((cropped_rect.w, cropped_rect.h), SRCALPHA).convert_alpha()
+        frame = surf.get_rect()
+        imsurf = image.get_image().convert_alpha()
+        r = imsurf.get_rect()
+        r.topleft = frame.topleft
+        if self.trans_images:
+            surf.fill(pygame.color.Color(255, 255, 255, 96))
+            surf.blit(imsurf, r, None, BLEND_RGBA_MIN)
+        else:
+            surf.blit(imsurf, r, None)
+        surface.blit(surf, cropped_rect)
+
     def draw(self, surface):
+        if self.zoom_display:
+            base_surface = surface.copy()
+            self.do_unzoomed_draw(base_surface)
+            zoomed = pygame.transform.scale(base_surface, (ZOOM * constants.SCREEN[0], ZOOM * constants.SCREEN[1]))
+            area = pygame.rect.Rect(self.zoom_offset[0], self.zoom_offset[1], self.zoom_offset[0] + constants.SCREEN[0], self.zoom_offset[1] + constants.SCREEN[1])
+            surface.blit(zoomed, (0, 0), area)
+        else:
+            self.do_unzoomed_draw(surface)
+
+    def do_unzoomed_draw(self, surface):
         if self.state.current_detail:
             if self.draw_things:
-                self.state.draw_detail(surface, None)
+                self.state.current_detail.draw(surface, None)
             else:
                 self.state.current_detail.draw_background(surface)
+            # We duplicate Albow's draw logic here, so we zoom the close
+            # button correctly
+            r = self.close_button.get_rect()
+            surf_rect = surface.get_rect()
+            sub_rect = surf_rect.clip(r)
+            try:
+                sub = surface.subsurface(sub_rect)
+                self.close_button.draw_all(sub)
+            except ValueError, e:
+                print 'Error, failed to draw close button', e
         else:
             if self.draw_things:
-                self.state.draw(surface, None)
+                self.state.current_scene.draw(surface, None)
             else:
                 self.state.current_scene.draw_background(surface)
         if self.mode == 'draw' and self.start_pos and self.draw_rects:
@@ -222,20 +274,18 @@
             for image in self.images:
                 if image.rect.colliderect(surface.get_rect()):
                     cropped_rect = image.rect.clip(surface.get_rect())
-                    sub = surface.subsurface(cropped_rect)
-                    image.draw(sub)
+                    self.draw_sub_image(image, surface, cropped_rect)
                 else:
                     print 'image outside surface', image
             if self.current_image and self.mode == 'image':
                 if self.current_image.rect.colliderect(surface.get_rect()):
                     cropped_rect = self.current_image.rect.clip(surface.get_rect())
-                    sub = surface.subsurface(cropped_rect)
-                    self.current_image.draw(sub)
+                    self.draw_sub_image(self.current_image, surface, cropped_rect)
         if self.draw_toolbar:
-            toolbar_rect = pygame.rect.Rect(0, 550, 800, 50)
-            tb_surf = surface.subsurface(0, 550, 800, 50).convert_alpha()
+            toolbar_rect = pygame.rect.Rect(0, constants.SCREEN[1] - constants.BUTTON_SIZE, constants.SCREEN[0], constants.BUTTON_SIZE)
+            tb_surf = surface.subsurface(0, constants.SCREEN[1] - constants.BUTTON_SIZE, constants.SCREEN[0], constants.BUTTON_SIZE).convert_alpha()
             tb_surf.fill(pygame.color.Color(127, 0, 0, 191))
-            surface.blit(tb_surf, (0, 550))
+            surface.blit(tb_surf, (0, constants.SCREEN[1] - constants.BUTTON_SIZE))
             # frame_rect(surface, (127, 0, 0), toolbar_rect, 2)
 
     def _make_dict(self):
@@ -271,7 +321,7 @@
             self.current_image = Image(image_data)
             self.place_image_menu.enabled = True
             # ensure we're off screen to start
-            self.current_image.rect = image_data.get_rect().move(1000, 600)
+            self.current_image.rect = image_data.get_rect().move(constants.SCREEN[0] + MENU_WIDTH, constants.SCREEN[1])
         except pygame.error, e:
             print 'Unable to load image %s' % e
 
@@ -279,15 +329,99 @@
         self.mode = 'image'
         self.start_pos = None
         self.end_pos = None
+        # So we do the right thing for off screen images
+        self.old_mouse_pos = None
 
-    def mouse_move(self, e):
+    def cycle_mode(self):
+        self.mode = 'cycle'
+
+    def _conv_pos(self, mouse_pos):
+        if self.zoom_display:
+            pos = ((mouse_pos[0] + self.zoom_offset[0]) / ZOOM, (mouse_pos[1] + self.zoom_offset[1]) / ZOOM)
+        else:
+            pos = mouse_pos
+        return pos
+
+    def _check_limits(self, offset):
+        if offset[0] < 0:
+            offset[0] = 0
+        if offset[1] < 0:
+            offset[1] = 0
+        if offset[0] > ZOOM * constants.SCREEN[0] - constants.SCREEN[0]:
+            offset[0] = ZOOM * constants.SCREEN[0] - constants.SCREEN[0]
+        if offset[1] > ZOOM * constants.SCREEN[1] - constants.SCREEN[1]:
+            offset[1] = ZOOM * constants.SCREEN[1] - constants.SCREEN[1]
+
+    def _make_zoom_offset(self, pos):
+        zoom_pos = (pos[0] * ZOOM, pos[1] * ZOOM)
+        offset = [zoom_pos[0] - constants.SCREEN[0] / 2,
+                zoom_pos[1] - constants.SCREEN[1] / 2]
+        self._check_limits(offset)
+        self.zoom_offset = tuple(offset)
+
+    def _move_zoom(self, x, y):
+        offset = list(self.zoom_offset)
+        offset[0] += ZOOM_STEP * x
+        offset[1] += ZOOM_STEP * y
+        self._check_limits(offset)
+        self.zoom_offset = tuple(offset)
+
+    def do_mouse_move(self, e):
+        pos = self._conv_pos(e.pos)
+        if not self.zoom_display:
+            # Construct zoom offset from mouse pos
+            self._make_zoom_offset(e.pos)
         if self.mode == 'image' and self.current_image:
-            self.current_image.rect.topleft = e.pos
+            if self.old_mouse_pos:
+                delta = (pos[0] - self.old_mouse_pos[0], pos[1] - self.old_mouse_pos[1])
+                self.current_image.rect.center = (self.current_image.rect.center[0] + delta[0], self.current_image.rect.center[1] + delta[1])
+            else:
+                self.current_image.rect.center = pos
             self.invalidate()
+            self.old_mouse_pos = pos
+
+    def key_down(self, e):
+        if self.mode == 'image' and self.current_image:
+            # Move the image by 1 pixel
+            cur_pos = self.current_image.rect.center
+            if e.key == K_LEFT:
+                self.current_image.rect.center = (cur_pos[0] - 1, cur_pos[1])
+            elif e.key == K_RIGHT:
+                self.current_image.rect.center = (cur_pos[0] + 1, cur_pos[1])
+            elif e.key == K_UP:
+                self.current_image.rect.center = (cur_pos[0], cur_pos[1] - 1)
+            elif e.key == K_DOWN:
+                self.current_image.rect.center = (cur_pos[0], cur_pos[1] + 1)
+        elif self.zoom_display:
+            if e.key == K_LEFT:
+                self._move_zoom(-1, 0)
+            elif e.key == K_RIGHT:
+                self._move_zoom(1, 0)
+            elif e.key == K_UP:
+                self._move_zoom(0, -1)
+            elif e.key == K_DOWN:
+                self._move_zoom(0, 1)
+
+        if e.key == K_o:
+            self.toggle_trans_images()
+        elif e.key == K_t:
+            self.toggle_things()
+        elif e.key == K_r:
+            self.toggle_thing_rects()
+        elif e.key == K_i:
+            self.toggle_images()
+        elif e.key == K_d:
+            self.toggle_rects()
+        elif e.key == K_b:
+            self.toggle_toolbar()
+        elif e.key == K_z:
+            self.toggle_zoom()
+        elif e.key == K_a:
+            self.toggle_anim()
 
     def mouse_down(self, e):
+        pos = self._conv_pos(e.pos)
         if self.mode == 'del':
-            pos = e.pos
             cand = None
             # Images are drawn above rectangles, so search those first
             for image in self.images:
@@ -305,13 +439,31 @@
             if cand:
                 self.rects.remove(cand)
                 self.invalidate()
+        elif self.mode == 'cycle':
+            scene = self._get_scene()
+            cand = None
+            for thing in scene.things.itervalues():
+                if thing.contains(pos):
+                    cand = thing
+                    break
+            if cand:
+                # Find current interacts in this thing
+                cur_interact = cand.current_interact
+                j = cand.interacts.values().index(cur_interact)
+                if j + 1< len(cand.interacts):
+                    next_name = cand.interacts.keys()[j+1]
+                else:
+                    next_name = cand.interacts.keys()[0]
+                if cand.interacts[next_name] != cur_interact:
+                    cand.set_interact(next_name)
         elif self.mode == 'draw':
-            self.start_pos = e.pos
-            self.end_pos = e.pos
+            self.start_pos = pos
+            self.end_pos = pos
         elif self.mode == 'image':
             if self.current_image:
                 self.images.append(self.current_image)
                 self.current_image = None
+                self.old_mouse_pos = None
                 self.invalidate()
             else:
                 cand = None
@@ -322,6 +474,8 @@
                 if cand:
                     self.images.remove(cand)
                     self.current_image = cand
+                    # We want to move relative to the current mouse pos, so
+                    self.old_mouse_pos = pos
                     self.invalidate()
 
     def mouse_up(self, e):
@@ -335,32 +489,139 @@
 
     def mouse_drag(self, e):
         if self.mode == 'draw':
-            self.end_pos = e.pos
+            self.end_pos = self._conv_pos(e.pos)
             self.invalidate()
 
+    def animate(self):
+        if self.draw_anim:
+            if self.state.animate():
+                self.invalidate()
+
+
+class ModeLabel(BoomLabel):
+
+    def __init__(self, app_image):
+        self.app_image = app_image
+        super(ModeLabel, self).__init__('Mode : ', 200,
+                font=get_font(15, 'VeraBd.ttf'),
+                fg_color = pygame.color.Color(128, 0, 255))
+        self.rect.move_ip(805, 0)
+
+    def draw_all(self, surface):
+        self.set_text('Mode : %s' % self.app_image.mode)
+        super(ModeLabel, self).draw_all(surface)
+
+
 def make_button(text, action, ypos):
-    button = Button(text, action=action)
+    button = Button(text, action=action, font=get_font(15, 'VeraBd.ttf'))
     button.align = 'l'
-    button.rect = pygame.rect.Rect(0, 0, 200, 35)
+    button.rect = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
     button.rect.move_ip(805, ypos)
     return button
 
+
+class RectApp(RootWidget):
+    """Handle the app stuff for the rect drawer"""
+
+    def __init__(self, display):
+        super(RectApp, self).__init__(display)
+        self.image = AppImage(state)
+        self.add(self.image)
+        mode_label = ModeLabel(self.image)
+        self.add(mode_label)
+        y = mode_label.get_rect().h
+        draw = make_button('Draw Rect', self.image.draw_mode, y)
+        self.add(draw)
+        y += draw.get_rect().h
+        load_image = make_button("Load image", self.image.image_load, y)
+        self.add(load_image)
+        y += load_image.get_rect().h
+        add_image = make_button("Place/Move images", self.image.image_mode, y)
+        add_image.enabled = False
+        self.add(add_image)
+        self.image.place_image_menu = add_image
+        y += add_image.get_rect().h
+        cycle = make_button("Cycle interacts", self.image.cycle_mode, y)
+        self.add(cycle)
+        y += cycle.get_rect().h
+        delete = make_button('Delete Objects', self.image.del_mode, y)
+        self.add(delete)
+        y += delete.get_rect().h
+        palette = AppPalette(self.image)
+        palette.rect.move_ip(810, y)
+        self.add(palette)
+        y += palette.get_rect().h
+        print_rects = make_button("Print objects", self.image.print_objs, y)
+        self.add(print_rects)
+        y += print_rects.get_rect().h
+        toggle_things = make_button("Show Things (t)", self.image.toggle_things, y)
+        self.add(toggle_things)
+        y += toggle_things.get_rect().h
+        toggle_thing_rects = make_button("Show Thing Rects (r)", self.image.toggle_thing_rects, y)
+        self.add(toggle_thing_rects)
+        y += toggle_thing_rects.get_rect().h
+        toggle_images = make_button("Show Images (i)", self.image.toggle_images, y)
+        self.add(toggle_images)
+        y += toggle_images.get_rect().h
+        trans_images = make_button("Opaque Images (o)", self.image.toggle_trans_images, y)
+        self.add(trans_images)
+        y += trans_images.get_rect().h
+        toggle_rects = make_button("Show Drawn Rects (d)", self.image.toggle_rects, y)
+        self.add(toggle_rects)
+        y += toggle_rects.get_rect().h
+        toggle_toolbar = make_button("Show Toolbar (b)", self.image.toggle_toolbar, y)
+        self.add(toggle_toolbar)
+        y += toggle_toolbar.get_rect().h
+        toggle_anim = make_button("Show Animations (a)", self.image.toggle_anim, y)
+        self.add(toggle_anim)
+        y += toggle_anim.get_rect().h
+        toggle_zoom = make_button("Zoom (z)", self.image.toggle_zoom, y)
+        self.add(toggle_zoom)
+        y += toggle_zoom.get_rect().h
+        quit_but = make_button("Quit", self.quit, 570)
+        self.add(quit_but)
+        self.set_timer(constants.FRAME_RATE)
+
+    def key_down(self, event):
+        # Dispatch to image widget
+        self.image.key_down(event)
+
+    def mouse_delta(self, event):
+        # We propogate mouse move from here to draw region, so images move
+        # off-screen
+        self.image.do_mouse_move(event)
+
+    def begin_frame(self):
+        self.image.animate()
+
+
+def list_scenes(state):
+    """List the scenes in the state"""
+    print 'Available scenes are : '
+    for scene in state.scenes:
+        print '    ',scene
+    print 'Available details are : '
+    for detail in state.detail_views:
+        print '    ',detail
+
 if __name__ == "__main__":
-    # FIXME: should load an actual scene with current things, not just a
-    # background image
-    if len(sys.argv) < 2:
-        print 'Please provide a scene name'
-        sys.exit(0)
     pygame.display.init()
     pygame.font.init()
-    display = pygame.display.set_mode((1000, 600))
+    display = pygame.display.set_mode((constants.SCREEN[0] + MENU_WIDTH, constants.SCREEN[1]))
     state = state.initial_state()
+    if len(sys.argv) < 2:
+        print 'Please provide a scene name or scene and detail names'
+        list_scenes(state)
+        sys.exit(0)
+    # enable key repeating
+    pygame.key.set_repeat(200, 100)
     if len(sys.argv) < 3:
         try:
             state.set_current_scene(sys.argv[1])
             state.do_check = None
         except KeyError:
             print 'Invalid scene name'
+            list_scenes(state)
             sys.exit(1)
     else:
         try:
@@ -368,36 +629,8 @@
             state.set_current_detail(sys.argv[2])
             state.do_check = None
         except KeyError:
-            print 'Invalid scene name'
+            print 'Invalid scene or detail name'
+            list_scenes(state)
             sys.exit(1)
-    app = RootWidget(display)
-    image = AppImage(state)
-    app.add(image)
-    draw = make_button('Draw Rect', image.draw_mode, 0)
-    app.add(draw)
-    load_image = make_button("Load image", image.image_load, 35)
-    app.add(load_image)
-    add_image = make_button("Place/Move images", image.image_mode, 70)
-    add_image.enabled = False
-    app.add(add_image)
-    image.place_image_menu = add_image
-    delete = make_button('Delete Objects', image.del_mode, 105)
-    app.add(delete)
-    palette = AppPalette(image)
-    palette.rect.move_ip(810, 140)
-    app.add(palette)
-    print_rects = make_button("Print objects", image.print_objs, 300)
-    app.add(print_rects)
-    toggle_things = make_button("Toggle Things", image.toggle_things, 335)
-    app.add(toggle_things)
-    toggle_thing_rects = make_button("Toggle Thing Rects", image.toggle_thing_rects, 370)
-    app.add(toggle_thing_rects)
-    toggle_images = make_button("Toggle Images", image.toggle_images, 405)
-    app.add(toggle_images)
-    toggle_rects = make_button("Toggle Rects", image.toggle_rects, 440)
-    app.add(toggle_rects)
-    toggle_toolbar = make_button("Toggle Toolbar", image.toggle_toolbar, 475)
-    app.add(toggle_toolbar)
-    quit_but = make_button("Quit", app.quit, 565)
-    app.add(quit_but)
+    app = RectApp(display)
     app.run()