/[enigma]/enigma/src/objects.cc
ViewVC logotype

Diff of /enigma/src/objects.cc

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.82 by reallysoft, Sun May 18 13:29:56 2003 UTC revision 1.83 by reallysoft, Sun May 18 15:17:13 2003 UTC
# Line 24  Line 24 
24  #include "player.hh"  #include "player.hh"
25  #include "lua.hh"  #include "lua.hh"
26  #include "sound.hh"  #include "sound.hh"
 #include "laser.hh"  
27  #include "actors.hh"  #include "actors.hh"
28    
29  #include "px/tools.hh"  #include "px/tools.hh"
# Line 42  using namespace std; Line 41  using namespace std;
41  using namespace world;  using namespace world;
42  using namespace enigma;  using namespace enigma;
43    
 namespace  
 {  
     player::Inventory *  
     get_inventory(const Actor *a)  
     {  
         if (const Value *v = a->get_attrib("player"))  
             return player::GetInventory(to_int(*v));  
         return 0;  
     }  
   
     bool wielded_item_is(Actor *a, const string &kind)  
     {  
         if (player::Inventory *inv = get_inventory(a))  
             if (Item *it = inv->get_item(0))  
                 return it->get_kind() == kind;  
         return false;  
     }  
   
 }  
   
44  /* This function is used by all triggers, switches etc. that perform  /* This function is used by all triggers, switches etc. that perform
45     some particular action when activated (like opening doors or     some particular action when activated (like opening doors or
46     switching lasers on and off). */     switching lasers on and off). */
# Line 693  Stone::maybe_push_stone (const StoneCont Line 672  Stone::maybe_push_stone (const StoneCont
672  }  }
673    
674    
   
   
   
 //----------------------------------------  
 // SimpleStone  
 //  
 // This kind of stone can be defined from Lua programs with  
 // `DefineSimpleStone'.  
 //----------------------------------------  
 namespace  
 {  
     class SimpleStone : public Stone {  
     public:  
         SimpleStone(const string &knd, const string & snd)  
             : Stone(knd.c_str()), sound(snd)  
         {}  
     private:  
         const char *collision_sound() {  
             return sound.c_str();  
         }  
   
         Object *clone() {  
             return new SimpleStone(get_kind(), sound);  
         }  
         void dispose() { delete this; }  
   
         string sound;  
     };  
 }  
   
 //----------------------------------------  
 // SimpleStoneHollow  
 //  
 // This kind of stone can be defined from Lua programs with  
 // `DefineSimpleStoneHollow'.  
 //----------------------------------------  
 namespace  
 {  
     class SimpleStoneHollow : public Stone {  
     public:  
         SimpleStoneHollow(const string &knd)  
             : Stone(knd.c_str())  
         {}  
     private:  
         Object *clone() {  
             return new SimpleStoneHollow(get_kind());  
         }  
         StoneResponse collision_response(const StoneContact &sc) {  
             return STONE_PASS;  
         }  
         bool on_laserhit(Direction) { return true; }  
         void dispose() { delete this; }  
     };  
 }  
   
 //----------------------------------------  
 // SimpleStoneGlass  
 //  
 // This kind of stone can be defined from Lua programs with  
 // `DefineSimpleStoneGlass'.  
 //----------------------------------------  
 namespace  
 {  
     class SimpleStoneGlass : public Stone {  
     public:  
         SimpleStoneGlass(const string &knd, const string & snd)  
             : Stone(knd.c_str()), sound(snd)  
         {}  
     private:  
         const char *collision_sound() {  
             return sound.c_str();  
         }  
   
         bool on_laserhit(Direction) {return true;};  
   
         Object *clone() {  
             return new SimpleStoneGlass(get_kind(), sound);  
         }  
         void dispose() { delete this; }  
   
         string sound;  
     };  
 }  
   
 //----------------------------------------  
 // SimpleStoneMovable  
 //  
 // This kind of stone can be defined from Lua programs with  
 // `DefineSimpleStoneMovable'.  
 //----------------------------------------  
 namespace  
 {  
     class SimpleStoneMovable : public MovableStone {  
     public:  
         SimpleStoneMovable(const string &knd, const string & snd)  
             : MovableStone(knd.c_str()), sound(snd)  
         {}  
     private:  
         const char *collision_sound() {  
             return sound.c_str();  
         }  
   
         bool on_laserhit(Direction) {return true;};  
   
         Object *clone() {  
             return new SimpleStoneMovable(get_kind(), sound);  
         }  
         void dispose() { delete this; }  
   
         string sound;  
     };  
 }  
   
 //----------------------------------------  
 // One Way Stone; black, white  
 //  
 // This stone can only be passed in one direction.  
 //----------------------------------------  
   
 /** \page st-oneway Oneway Stone  
   
 This stone can be passed by the marble in only one direction  
   
 \subsection onewaya Attributes  
   
 - \c orientation  \n NORTH, EAST, SOUTH, WEST  
   
 \subsection onewaye Example  
 \verbatim  
 set_stone("st-oneway", 10,10, {orientation=EAST})  
 \endverbatim  
 or in short form  
 \verbatim  
 oneway( 10, 10, EAST)  
 \endverbatim  
   
 \image html st-oneway-e.png  
 */  
 namespace  
 {  
     class OneWayBase : public Stone {  
     protected:  
         OneWayBase(const char *kind, Direction dir) : Stone(kind)  
         {  
             set_orientation(dir);  
         }  
         Direction get_orientation() const {  
             return Direction(int_attrib("orientation"));  
         }  
         void set_orientation(Direction dir) {  
             set_attrib("orientation", Value(dir));  
         }  
         void actor_hit (const StoneContact&);  
         StoneResponse collision_response(const StoneContact &sc);  
   
         void init_model()  
         {  
             string mname = get_kind();  
             mname += to_suffix(get_orientation());  
             set_model (mname);  
         }  
   
         virtual bool actor_may_pass (Actor *a) = 0;  
     };  
 }  
   
 void  
 OneWayBase::actor_hit(const StoneContact &sc)  
 {  
     Direction o=get_orientation();  
   
     if (has_dir(contact_faces(sc), o)) {  
         if (wielded_item_is(sc.actor, "it-magicwand")) {  
             set_orientation(reverse(o));  
             init_model();  
         }  
     }  
 }  
   
 StoneResponse  
 OneWayBase::collision_response(const StoneContact &sc)  
 {  
     DirectionBits dirs=contact_faces(sc);  
     Direction o=get_orientation();  
   
     if (actor_may_pass(sc.actor))  
         return has_dir(dirs,o) ? STONE_REBOUND : STONE_PASS;  
     else  
         return STONE_REBOUND;  
 }  
   
   
 namespace  
 {  
     class OneWayStone : public OneWayBase {  
     public:  
         OneWayStone(Direction dir=SOUTH) : OneWayBase("st-oneway", dir) {}  
     private:  
         CLONEOBJ(OneWayStone);  
         virtual bool actor_may_pass (Actor *a) { return true; }  
     };  
   
   
     class OneWayStone_black : public OneWayBase {  
     public:  
         OneWayStone_black(Direction dir=SOUTH)  
         : OneWayBase("st-oneway_black",dir) {}  
     private:  
         CLONEOBJ(OneWayStone_black);  
         virtual bool actor_may_pass (Actor *a) {  
             return a->get_attrib("blackball") != 0;  
         }  
     };  
   
     class OneWayStone_white : public OneWayBase {  
     public:  
         OneWayStone_white(Direction dir=SOUTH)  
         : OneWayBase("st-oneway_white", dir) {}  
     private:  
         CLONEOBJ(OneWayStone_white);  
         virtual bool actor_may_pass (Actor *a) {  
             return a->get_attrib("whiteball") != 0;  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // Chameleon Stone  
 //  
 // This stone takes on the look of the floor beneath it.  Actors can  
 // move through it, so these stones are perfect for hiding stuff under  
 // them...  
 //----------------------------------------  
   
 /** \page st-chameleon Chameleon Stone  
   
 This stone takes on the look of the floor beneath it.  Actors can  
 move through it, so these stones are perfect for hiding stuff under  
 them...  
   
 */  
 namespace  
 {  
     class ChameleonStone : public Stone {  
         CLONEOBJ(ChameleonStone);  
     public:  
         ChameleonStone() : Stone("st-chameleon"){}  
     private:  
         void init_model() {  
             string modelname = "fl-gray";  
             if (Floor *fl = GetFloor(get_pos()))  
                 modelname = fl->get_kind();  
             set_model(modelname);  
         }  
         StoneResponse collision_response(const StoneContact &sc) {  
             return STONE_PASS;  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // Key Switch, Key_a Switch, Key_b Switch  
 //  
 // Attributes:  
 //  
 // :keycode         a numerical code; only keys with the same code  
 //                  can activate this switch  
 // :on              1 or 0  
 // :target,action   as usual  
 //----------------------------------------  
   
 /** \page st-key Key Switch Stone  
   
 This stone acts as a lock and can only be activated by  
 using a key item. You can use keycodes to let keys only  
 open specific key stones.  
   
 \subsection keye Example  
 \verbatim  
 set_stone( "st-key_b", 14,77, {action="openclose", target="door2"})  
 set_item("it-key_b", 12,7)  
 \endverbatim  
   
 \image html st-key0.png  
 */  
 namespace  
 {  
     class KeyStone : public OnOffStone  
     {  
     protected:  
         KeyStone(const char *kind, int keycode=0) : OnOffStone(kind) {  
             set_attrib("keycode", keycode);  
         }  
     private:  
         void init_model() {set_model(is_on() ? "st-key1" : "st-key0");}  
         void actor_hit(const StoneContact &sc);  
         const char *collision_sound() { return "st-metal"; }  
     };  
   
     class KeyStone_a : public KeyStone {  
         CLONEOBJ(KeyStone_a);  
     public:  
         KeyStone_a() : KeyStone("st-key_a", 1) {}  
     };  
     class KeyStone_b : public KeyStone {  
         CLONEOBJ(KeyStone_b);  
     public:  
         KeyStone_b() : KeyStone("st-key_b", 2) {}  
     };  
     class KeyStone_c : public KeyStone {  
         CLONEOBJ(KeyStone_c);  
     public:  
         KeyStone_c() : KeyStone("st-key_c", 3) {}  
     };  
 }  
   
 void  
 KeyStone::actor_hit(const StoneContact &sc)  
 {  
     if (player::Inventory *inv = get_inventory(sc.actor))  
     {  
         if (is_on()) {  
             Item *key = MakeItem("it-key");  
             key->set_attrib("keycode", int_attrib("keycode"));  
             inv->add_item(key);  
             set_on(false);  
             PerformAction(this, is_on());  
         }  
         else  
         {  
             Item *it = inv->get_item(0);  
             if (it && it->int_attrib("keycode") == int_attrib("keycode"))  
             {  
                 set_on(true);  
                 PerformAction(this, is_on());  
                 delete inv->yield_first();  
             }  
         }  
     }  
 }  
   
 //----------------------------------------  
 // SwapStone  
 //----------------------------------------  
   
 /** \page st-swap Swap Stone  
   
 This stone can exchange its position with other neighboring stones  
 if it is hit hard enough.  In a way, this makes swap stones a kind  
 of "movable stone", except that they can be only exchanged with  
 other stones and may not be moved on empty fields.  
   
 \subsection swape Example  
 \verbatim  
 set_stone("st-swap", 10,10)  
 \endverbatim  
   
 \image html st-swap.png  
 */  
 namespace  
 {  
     class SwapStone : public Stone {  
         CLONEOBJ(SwapStone);  
     public:  
         SwapStone() : Stone("st-swap") {}  
     private:  
         void actor_hit(const StoneContact &sc)  
         {  
             Direction dir = get_push_direction (sc);  
             if (dir != NODIR) {  
                 GridPos p = get_pos();  
                 GridPos newp = move(p, dir);  
                 if (GetStone(newp))  
                     SwapStones(p, newp);  
             }  
         }  
     };  
 }  
   
 //----------------------------------------  
 // BlockStone  
 //----------------------------------------  
   
 /** \page st-block Block Stone  
   
 This stone can is movable.  
 If moved into water it will disappear and  
 build new water floor at this place.  
   
 \subsection block Example  
 \verbatim  
 set_stone("st-block", 10,10)  
 \endverbatim  
   
 \image html st-block.png  
 */  
 namespace  
 {  
     class BlockStone : public MovableStone {  
         CLONEOBJ(BlockStone);  
     public:  
         BlockStone() : MovableStone("st-block") {}  
     private:  
         V2 get_center() const {  
             GridPos p = get_pos();  
             return V2(p.x+0.5, p.y+0.5);  
         }  
   
         void on_move(){  
             if (Floor *fl=GetFloor(get_pos())) {  
                 const string &k = fl->get_kind();  
                 if (k=="fl-water") {  
                     display::AddEffect (get_center(), "ring-anim");  
                     KillStone(get_pos());  
                 }  
             }  
         }  
     };  
   
 };  
   
 //----------------------------------------  
 // Window  
 //----------------------------------------  
   
 /** \page st-window Breakable Stone  
   
 Hit this window heavily with your marble to blast it into smithereens.  
   
 \image html st-window.png  
 */  
   
 namespace  
 {  
     class Window : public Stone {  
         CLONEOBJ(Window);  
         const char *collision_sound() {return "ballcollision";}  
     public:  
         Window() : Stone("st-window"), state(IDLE) {}  
     private:  
         bool on_laserhit(Direction dir) { return true; }  
         enum State { IDLE, BREAK };  
         State state;  
         void actor_hit(const StoneContact &sc)  
         {  
             Actor *a = sc.actor;  
             if( state == IDLE)  
             {  
                 if (a->get_vel() * sc.normal < -25 ) {  
                     play_sound("shatter");  
                     state = BREAK;  
                     set_anim("st-window-anim");  
                 }  
             }  
             {  
                 if (a->get_vel() * sc.normal < -28 ) {  
                     play_sound("shatter");  
                     state = BREAK;  
                     set_anim("st-window-anim");  
                     SendMessage(a, "shatter");  
                 }  
             }  
         }  
         void animcb() {  
             if (state == BREAK) {  
                 KillStone(get_pos());  
             }  
         }  
     };  
 }  
   
 //----------------------------------------  
 // Stone_break  
 //----------------------------------------  
   
 /** \page st-stone_break Breakable Stone  
   
 This stone can be destroyed by an actor having a  
 hammer.  
   
 \subsection stone_breake Example  
 \verbatim  
 set_stone("st-stone_break", 10,10)  
 \endverbatim  
   
 \image html st-stone_break.png  
 */  
 namespace  
 {  
     class Stone_break : public Stone {  
         CLONEOBJ(Stone_break);  
         const char *collision_sound() {return "st-stone";}  
     public:  
         Stone_break() : Stone("st-stone_break"), state(IDLE) {}  
     private:  
         enum State { IDLE, BREAK };  
         State state;  
         void actor_hit(const StoneContact &sc)  
         {  
             if( state == IDLE)  
             {  
                 if (wielded_item_is(sc.actor, "it-hammer")) {  
                     play_sound("explosion1");  
                     state = BREAK;  
                     set_anim("st-stone_break-anim");  
                 }  
             }  
         }  
         void animcb() {  
             if (state == BREAK) {  
                 KillStone(get_pos());  
             }  
         }  
     };  
 }  
   
 //----------------------------------------  
 // Break_acwhite  
 //----------------------------------------  
   
 /** \page st-break_acwhite Breakable Stone  
   
 This stone can be destroyed by actor (whiteball) having a  
 hammer.  
   
 \subsection break_acwhite Example  
 \verbatim  
 set_stone("st-break_acwhite", 10,10)  
 \endverbatim  
   
 \image html st-break_acwhite.png  
 */  
 namespace  
 {  
     class Break_acwhite : public Stone {  
         CLONEOBJ(Break_acwhite);  
         const char *collision_sound() {return "st-stone";}  
     public:  
         Break_acwhite() : Stone("st-break_acwhite"), state(IDLE) {}  
     private:  
         enum State { IDLE, FRAGILE, BREAK };  
         State state;  
         void change_state (State newstate) {  
             if (newstate == BREAK) {  
                 state = BREAK;  
                 play_sound("explosion1");  
                 set_anim("st-break_acwhite-anim");  
             }  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
             if (state == IDLE) {  
                 if (sc.actor->get_attrib("whiteball"))  
                     state = FRAGILE;  
             }  
             if (state == FRAGILE) {  
                 if (wielded_item_is(sc.actor, "it-hammer"))  
                     change_state(BREAK);  
             }  
         }  
         void animcb() {  
             if (state == BREAK)  
                 KillStone(get_pos());  
         }  
         bool on_laserhit(Direction) {  
             change_state(BREAK);  
             return false;  
         }  
         void Break_acwhite::message(const string &msg, const Value &) {  
             if (msg =="ignite")  
                 change_state(BREAK);  
         }  
     };  
 }  
   
 //----------------------------------------  
 // Break_acblack  
 //----------------------------------------  
   
 /** \page st-break_acblack Breakable Stone  
   
 This stone can be destroyed by actor (blackball) having a  
 hammer.  
   
 \subsection break_acblack Example  
 \verbatim  
 set_stone("st-break_acblack", 10,10)  
 \endverbatim  
   
 \image html st-break_acblack.png  
 */  
 namespace  
 {  
     class Break_acblack : public Stone {  
         CLONEOBJ(Break_acblack);  
         const char *collision_sound() {return "st-stone";}  
     public:  
         Break_acblack() : Stone("st-break_acblack"), state(IDLE) {}  
     private:  
         enum State { IDLE, FRAGILE, BREAK };  
         State state;  
         void change_state(State newstate) {  
             if (newstate == BREAK) {  
                 state = BREAK;  
                 play_sound("explosion1");  
                 set_anim("st-break_acblack-anim");  
             }  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
             if (state == IDLE) {  
                 if (sc.actor->get_attrib("blackball"))  
                     state = FRAGILE;  
             }  
             if( state == FRAGILE) {  
                 if (wielded_item_is(sc.actor, "it-hammer"))  
                     change_state(BREAK);  
             }  
         }  
         void animcb() {  
             if (state == BREAK)  
                 KillStone(get_pos());  
         }  
         bool on_laserhit(Direction) {  
             change_state(BREAK);  
             return false;  
         }  
         void message(const string &msg, const Value &) {  
             if (msg =="ignite")  
                 change_state(BREAK);  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // Brick Magic  
 //----------------------------------------  
   
 /** \page st-brick_magic Magic Brick Stone  
   
 This stone does initially looks like a "st-brick".  If touched by the  
 actor, having a magic wand, it turns into a "st-glass" stone and  
 allows lasers to go through it.  
   
 \subsection brick_magicke Example  
 \verbatim  
 set_stone("st-brick_magic", 10,10)  
 \endverbatim  
   
 \image html st-brick.png  
 */  
 namespace  
 {  
     class BrickMagic : public Stone {  
         CLONEOBJ(BrickMagic);  
         const char *collision_sound() {return "st-stone";}  
     public:  
         BrickMagic() : Stone("st-brick_magic"), state(BRICK) {}  
     private:  
         enum State { BRICK, GLASS } state;  
         void actor_hit(const StoneContact &sc)  
         {  
             if( state == BRICK)  
             {  
                 if (wielded_item_is(sc.actor, "it-magicwand")) {  
                     play_sound("st-magic");  
                     state = GLASS;  
                     set_model("st-glass");  
                     laser::MaybeRecalcLight(get_pos());  
                 }  
             }  
         }  
         bool on_laserhit(Direction dir) {  
             return state != BRICK;  
         }  
     };  
 }  
   
 //----------------------------------------  
 // Stonebrush  
 //----------------------------------------  
   
 /** \page st-stonebrush Brush Stone  
   
 This stone is initially invisible. If touched by an actor  
 having a brush it will be painted an become a "st-rock4".  
   
 \subsection stonebrushe Example  
 \verbatim  
 set_stone("st-stonebrush", 10,10)  
 \endverbatim  
   
 \image html st-rock4.png  
 */  
 namespace  
 {  
     class Stonebrush : public Stone {  
         CLONEOBJ(Stonebrush);  
         const char *collision_sound() {return "st-stone";}  
     public:  
         Stonebrush() : Stone("st-stonebrush"), state(INVISIBLE) {}  
     private:  
         enum State { INVISIBLE, BRUSH } state;  
         void actor_hit(const StoneContact &sc)  
         {  
             if( state == INVISIBLE)  
             {  
                 if (wielded_item_is(sc.actor, "it-brush")) {  
                     play_sound("st-magic");  
                     state = BRUSH;  
                     set_model("st-rock4");  
                 }  
             }  
         }  
     };  
 }  
   
 //----------------------------------------  
 // Break_invisible  
 //----------------------------------------  
   
 /** \page st-break_invisible Brush Stone  
   
 This stone is initially invisible. If touched by an actor  
 having a brush it will be painted an become a "st_stone_break".  
 This stone can be destroyed by an actor having a hammer.  
   
 \subsection break_invisible Example  
 \verbatim  
 set_stone("st-break_invisible", 10,10)  
 \endverbatim  
   
 \image html st-stone_break.png  
 */  
 namespace  
 {  
     class Break_invisible : public Stone {  
         CLONEOBJ(Break_invisible);  
         const char *collision_sound() {return "st-stone";}  
     public:  
         Break_invisible() : Stone("st-break_invisible"), state(INVISIBLE) {}  
     private:  
         enum State { INVISIBLE, BRUSH };  
         State state;  
         void actor_hit(const StoneContact &sc)  
         {  
             if( state == INVISIBLE)  
             {  
                 if (wielded_item_is(sc.actor, "it-brush")) {  
                     play_sound("st-magic");  
                     state = BRUSH;  
                     set_model("st-stone_break");  
                 }  
             }  
             {  
                 if( state == BRUSH)  
                 {  
                     if (wielded_item_is(sc.actor, "it-hammer")) {  
                         play_sound("explosion1");  
                         set_anim("st-stone_break-anim");  
                     }  
                 }  
             }  
         }  
         void animcb() {  
             if (state == BRUSH) {  
                 KillStone(get_pos());  
             }  
   
         }  
     };  
 }  
   
 //----------------------------------------  
 // Invisible Magic  
 //----------------------------------------  
   
 /** \page st-invisible_magic Magic Invisible Stone  
   
 This stone is initially invisible, and laserlight can pass through.  
 If touched by an actor having a magic wand, it will mutate into a  
 "st-greenbrown" and laserlight is blocked.  
   
 \subsection invisible_magice Example  
 \verbatim  
 set_stone("st-invisible_magic", 10,10)  
 \endverbatim  
   
 \image html st-greenbrown.png  
 */  
 namespace  
 {  
     class InvisibleMagic : public Stone {  
         CLONEOBJ(InvisibleMagic);  
         const char *collision_sound() {return "st-thud";}  
     public:  
         InvisibleMagic() : Stone("st-invisible_magic"), state(INVISIBLE) {}  
     private:  
         enum State { INVISIBLE, STONE } state;  
         void actor_hit(const StoneContact &sc)  
         {  
             if( state == INVISIBLE)  
             {  
                 if (wielded_item_is(sc.actor, "it-magicwand")) {  
                     play_sound("st-magic");  
                     state = STONE;  
                     set_model("st-greenbrown");  
                     laser::MaybeRecalcLight(get_pos());  
                 }  
             }  
         }  
         bool on_laserhit(Direction dir) {return state==INVISIBLE;}  
     };  
 }  
   
 //----------------------------------------  
 // WoodenStone  
 //----------------------------------------  
   
 /** \page st-wood Wooden Stone  
   
 This stone can is movable.  If moved into abyss or water it will  
 disappear and change into a wooden plank.  
   
 \subsection woode Example  
 \verbatim  
 set_stone("st-wood", 10,10)  
 \endverbatim  
   
 \image html st-wood.png  
 */  
 namespace  
 {  
     class WoodenStone : public MovableStone {  
         CLONEOBJ(WoodenStone);  
     public:  
         WoodenStone() : MovableStone("st-wood") {}  
     private:  
         void on_move() {  
             if (Floor *fl=GetFloor(get_pos())) {  
                 const string &k = fl->get_kind();  
                 if (k == "fl-abyss" || k=="fl-water" || k=="fl-swamp") {  
                     SetFloor(get_pos(), MakeFloor("fl-wood"));  
                     KillStone(get_pos());  
                 }  
             }  
         }  
     };  
   
     class WoodenStone_Growing : public Stone {  
         CLONEOBJ(WoodenStone_Growing);  
     public:  
         WoodenStone_Growing() : Stone("st-wood-growing") {}  
     private:  
         void init_model() { set_anim("st-wood-growing"); }  
         void animcb() {  
             Stone *st = world::MakeStone("st-wood");  
             world::SetStone(get_pos(), st);  
         }  
     };  
 }  
   
 //----------------------------------------  
 // ElectricStone  
 //  
 // Attributes:  
 //  
 // :charge  + - 0  
 //----------------------------------------  
 namespace  
 {  
     class ElectricStone : public Stone {  
         CLONEOBJ(ElectricStone);  
     public:  
         ElectricStone() : Stone("st-electric") {  
             set_attrib("charge", +1);  
         }  
     private:  
         void init_model() {  
         }  
     };  
 }  
   
 //----------------------------------------  
 // ScissorsStone  
 //----------------------------------------  
   
 /** \page st-scissors Scissors stone  
   
 This stone cuts \c all rubber bands attached to an actor that touches  
 it.  
   
 \image html st-scissors  
 */  
 namespace  
 {  
     class ScissorsStone : public Stone {  
         CLONEOBJ(ScissorsStone);  
     public:  
         ScissorsStone() : Stone("st-scissors") {}  
     private:  
         void actor_hit(const StoneContact &sc) {  
             world::KillRubberBand (sc.actor, (Stone*)0);  
             world::KillRubberBand (sc.actor, (Actor*)0);  
             play_sound("snip");  
             set_anim("st-scissors-snip");  
         }  
         void animcb() {  
             set_model("st-scissors");  
         }  
     };  
 }  
   
 //----------------------------------------  
 // RubberBand stone  
 //----------------------------------------  
   
 /** \page st-rubberband Rubberband stone  
   
 If hit by a marble, this stone first removes existing connections with  
 other rubberband stones and then attaches a new elastic between the  
 marble and itself.  Nothing happens if the marble was already attached  
 to this particular stone.  
   
 This stone can be moved if hit with a magic wand.  
   
 \subsection rubberbanda Attributes  
   
 - \c length  The natural length of the rubberband (default: 1)  
 - \c strength The strength of the rubberband (default: 10)  
   
 \image html st-rubberband.png  
 */  
 namespace  
 {  
     class RubberBandStone : public MovableStone {  
         CLONEOBJ(RubberBandStone);  
     public:  
         RubberBandStone () : MovableStone ("st-rubberband") {  
             set_attrib("length", 1.0);  
             set_attrib("strength", 10.0);  
         }  
     private:  
   
   
         void actor_hit(const StoneContact &sc) {  
             double strength = 10.0;  
             double length = 1.0;  
             double_attrib ("length", &length);  
             double_attrib ("strength", &strength);  
   
             if (!world::HasRubberBand (sc.actor, this)) {  
                 play_sound ("boing");  
                 world::KillRubberBand (sc.actor, (Stone*)0);  
                 world::AddRubberBand (sc.actor, this, strength, length);  
             }  
             if (wielded_item_is (sc.actor, "it-magicwand"))  
                 MovableStone::actor_hit(sc);  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // BolderStone  
 //----------------------------------------  
   
 /** \page st-bolder Bolder Stone  
   
 The bolder stone will move in one direction until another stone will  
 block.  When hit with a magic wand, the bolder stone reverse its  
 direction. When hitting a blocking stone it can activate switches or  
 oxyd stones.  
   
 \subsection boldera Attributes  
   
 - \c direction  \n NORTH, EAST, SOUTH, WEST  
   
 */  
 namespace  
 {  
     class BolderStone : public Stone, public TimeHandler  
     {  
         CLONEOBJ(BolderStone);  
     public:  
         BolderStone(Direction dir=NORTH) : Stone("st-bolder"), state(IDLE)  
         {  
             set_dir(dir);  
         }  
     private:  
         enum State { IDLE, MOVING } state;  
   
         Direction get_dir() const  
         {  
             return static_cast<Direction>(int_attrib("direction"));  
         }  
         void set_dir(Direction d) {set_attrib("direction", d);}  
   
         bool is_movable() { return true; }  
   
         void on_creation() {  
             if (state == IDLE)  
                 g_timer.set_alarm(this, 0.3, true);  
             Stone::on_creation();  
         }  
         void on_removal() {  
             if (state == IDLE)  
                 g_timer.remove_alarm(this);  
             Stone::on_removal();  
         }  
   
         void alarm() {  
             state              = MOVING;  
             GridPos   last_pos = get_pos();  
             Direction dir      = get_dir();  
   
             move_stone(dir);  
             state = IDLE;  
         }  
   
         void on_move() {  
             // send a message to the stone that's blocking our way  
             Direction dir = get_dir();  
   
             if (Stone *st = GetStone(move(get_pos(), dir))) {  
                 SendMessage(st, "trigger", Value(dir));  
             }  
         }  
   
         // Stone interface.  
         void init_model()  
         {  
             string mname = "st-bolder" + to_suffix(get_dir());  
             set_model(mname);  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
             Actor *a = sc.actor;  
   
             if (wielded_item_is(a, "it-magicwand")) {  
                 set_dir(reverse(get_dir()));  
                 init_model();  
             }  
         }  
   
         bool on_laserhit(Direction) {  
             set_dir(reverse(get_dir()));  
             init_model();  
             return false;  
         }  
   
         void on_impulse (const Impulse& impulse) {  
             if (impulse.sender && 0 == strncmp(impulse.sender->get_kind(), "st-rotator-", 11)) {  
                 set_dir(impulse.dir);  
             }  
             init_model();  
             move_stone(impulse.dir);  
         }  
   
         void actor_inside(Actor *a) {SendMessage(a, "shatter");}  
   
     };  
 }  
   
 //----------------------------------------  
 // BlockerStone  
 //----------------------------------------  
   
 /** \page st-blocker Blocker Stone  
   
 The BlockerStone acts like a normal stone until it is hit by a  
 BolderStone. Then it shrinks and morphs into a 'Blocker' item.  
   
 */  
   
 namespace {  
     class BlockerStone : public Stone  
     {  
         CLONEOBJ(BlockerStone);  
     public:  
         BlockerStone(bool solid)  
             : Stone(solid ? "st-blocker" : "st-blocker-growing")  
             , state(solid ? SOLID : GROWING)  
         {}  
   
     private:  
         enum State { SOLID, SHRINKING, GROWING } state;  
   
         void init_model() {  
             switch (state) {  
                 case SOLID:  
                     set_model("st-blocker");  
                     break;  
   
                 case SHRINKING:  
                     set_anim("st-blocker-shrinking");  
                     break;  
   
                 case GROWING:  
                     set_anim("st-blocker-growing");  
                     break;  
             }  
         }  
   
         void change_state(State newState) {  
             state = newState;  
             init_model();  
         }  
   
         void animcb() {  
             switch (state) {  
                 case SHRINKING: {  
                     Item *it = world::MakeItem("it-blocker-new");  
                     world::SetItem(get_pos(), it);  
                     TransferObjectName(this, it);  
                     world::KillStone(get_pos());  
                     break;  
                 }  
                 case GROWING:  
                     change_state(SOLID);  
                     break;  
             }  
         }  
   
         void message(const string &msg, const Value &) {  
             if (msg == "trigger" || msg == "signal") {  
                 if (state == SHRINKING) {  
                     change_state(GROWING);  
                 }  
                 else {  
                     change_state(SHRINKING);  
                 }  
             }  
         }  
   
         void actor_inside(Actor *a) {  
                 SendMessage(a, "shatter");  
         }  
   
         void actor_contact(Actor *a) {  
             if (state == GROWING) {  
                 SendMessage(a, "shatter");  
            }  
         }  
     };  
 };  
   
 //----------------------------------------  
 // PuzzleStones  
 //----------------------------------------  
   
 /** \page st-puzzle Puzzle Stone  
   
 These stones can be connected to other stones of the same type.  Any  
 of the four faces of the stone can have ``socket''.  If the touching  
 faces of two adjacent stones both have a socket, the two stones link  
 up and henceforth move as group.  
   
 A cluster of puzzle stones may for example look like this:  
   
 \verbatim  
 +---+---+---+---+  
 |   |   |   |   |  
 | --+-+-+---+-+ |  
 |   | | |   | | |  
 +---+-+-+---+-+-+  
     | | |   | | |  
     | | |   | | |  
     |   |   |   |  
     +---+   +---+  
 \endverbatim  
   
 This example actually presents the special case of a ``complete''  
 cluster.  A cluster is complete if none of its stones has an  
 unconnected socket.  If such a group is touched with a magic wand, all  
 its constituents explode.  [They should, anyway. This is not  
 implemented yet.]  
   
 \subsection puzzlea Attributes  
   
 - \b connections  
    number between 1 an 16.  Each bit in (connections-1) corresponds to  
    a socket on one of the four faces.  You will normally simply use  
    one of the Lua constants \c PUZ_0000 to \c PUZ_1111.  
   
 \subsection puzzlee Example  
 <table>  
 <tr>  
 <td> \image html st-puzzletempl_0001.png "PUZ_0000"  
 <td> \image html st-puzzletempl_0002.png "PUZ_0001"  
 <td> \image html st-puzzletempl_0003.png "PUZ_0010"  
 <td> \image html st-puzzletempl_0004.png "PUZ_0011"  
 <tr>  
 <td> \image html st-puzzletempl_0005.png "PUZ_0100"  
 <td> \image html st-puzzletempl_0006.png "PUZ_0101"  
 <td> \image html st-puzzletempl_0007.png "PUZ_0110"  
 <td> \image html st-puzzletempl_0008.png "PUZ_0111"  
 <tr>  
 <td> \image html st-puzzletempl_0009.png "PUZ_1000"  
 <td> \image html st-puzzletempl_0010.png "PUZ_1001"  
 <td> \image html st-puzzletempl_0011.png "PUZ_1010"  
 <td> \image html st-puzzletempl_0012.png "PUZ_1011"  
 <tr>  
 <td> \image html st-puzzletempl_0013.png "PUZ_1100"  
 <td> \image html st-puzzletempl_0014.png "PUZ_1101"  
 <td> \image html st-puzzletempl_0015.png "PUZ_1110"  
 <td> \image html st-puzzletempl_0016.png "PUZ_1111"  
 </tr>  
 </table>  
 */  
 namespace  
 {  
     class PuzzleStone : public Stone {  
         INSTANCELISTOBJ(PuzzleStone);  
     public:  
         PuzzleStone(int connections=0) : Stone("st-puzzle") {  
             set_attrib("connections", connections);  
         }  
     private:  
         typedef vector<GridPos> Cluster;  
   
         DirectionBits get_connections();  
   
         static bool visit_dir(vector<GridPos> &stack,  
                               GridPos curpos, Direction dir);  
         bool find_cluster(Cluster &);  
         void find_row_or_column_cluster(Cluster &c,  
                                         GridPos startpos, Direction dir);  
         bool cluster_complete();  
         void maybe_move_cluster(Cluster &c, bool is_complete, Direction dir);  
         void rotate_cluster(const Cluster &c);  
   
         bool on_laserhit(Direction dir) {  
             return get_connections() == 0;  
         }  
   
         virtual void on_impulse(const Impulse& impulse);  
   
         void init_model();  
         StoneResponse collision_response(const StoneContact &sc);  
         void actor_hit(const StoneContact &sc);  
         void actor_inside(Actor *a) {  
             if (get_connections() != NODIRBIT)  
                 SendMessage(a, "shatter");  
         }  
   
         // Variables  
         bool visited;           // flag for DFS  
     };  
 }  
   
 PuzzleStone::InstanceList PuzzleStone::instances;  
   
 DirectionBits  
 PuzzleStone::get_connections()  
 {  
     int conn=int_attrib("connections") - 1;  
     if (conn >=0 && conn <16)  
         return DirectionBits(conn);  
     else  
         return NODIRBIT;  
 }  
   
 bool  
 PuzzleStone::visit_dir(vector<GridPos> &stack, GridPos curpos, Direction dir)  
 {  
     GridPos newpos = move(curpos, dir);  
     PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(newpos));  
   
     if (!pz)  
         return false;  
   
     DirectionBits cfaces = pz->get_connections();  
   
     if (cfaces==NODIRBIT || has_dir(cfaces, reverse(dir))) {  
         // Puzzle stone at newpos is connected to stone at curpos  
         if (!pz->visited) {  
             pz->visited = true;  
             stack.push_back(newpos);  
         }  
         return true;  
     } else {  
         // The two stones are adjacent but not connected  
         return false;  
     }  
 }  
   
 /* Use a depth first search to determine the group of all stones that  
    are connected to the current stone.  Returns true if the cluster is  
    ``complete'' in the sense defined above. */  
 bool  
 PuzzleStone::find_cluster(Cluster &cluster)  
 {  
     for (unsigned i=0; i<instances.size(); ++i)  
         instances[i]->visited=false;  
   
     vector<GridPos> pos_stack;  
     bool is_complete = true;  
     pos_stack.push_back(get_pos());  
     this->visited = true;  
     while (!pos_stack.empty())  
     {  
         GridPos curpos = pos_stack.back();  
         pos_stack.pop_back();  
   
         PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(curpos));  
         assert(pz);  
   
         cluster.push_back(curpos);  
         DirectionBits cfaces = pz->get_connections();  
   
         if (cfaces==NODIRBIT)  
             cfaces = DirectionBits(NORTHBIT | SOUTHBIT | EASTBIT | WESTBIT);  
   
         if (has_dir(cfaces, NORTH))  
             is_complete &= visit_dir(pos_stack, curpos, NORTH);  
         if (has_dir(cfaces, EAST))  
             is_complete &= visit_dir(pos_stack, curpos, EAST);  
         if (has_dir(cfaces, SOUTH))  
             is_complete &= visit_dir(pos_stack, curpos, SOUTH);  
         if (has_dir(cfaces, WEST))  
             is_complete &= visit_dir(pos_stack, curpos, WEST);  
     }  
     return is_complete;  
 }  
   
 void  
 PuzzleStone::find_row_or_column_cluster(Cluster &c, GridPos startpos, Direction dir)  
 {  
     GridPos p = startpos;  
     while (dynamic_cast<PuzzleStone*>(GetStone(p))) {  
         c.push_back(p);  
         p.move(dir);  
     }  
 }  
   
 void  
 PuzzleStone::maybe_move_cluster(Cluster &c, bool is_complete, Direction dir)  
 {  
     sort(c.begin(), c.end());  
     Cluster mc(c);              // Moved cluster  
     Cluster diff;               // Difference between mc and c  
   
     for (unsigned i=0; i<mc.size(); ++i)  
         mc[i].move(dir);  
   
     set_difference(mc.begin(), mc.end(), c.begin(), c.end(),  
                    back_inserter(diff));  
   
     // Now check whether all stones can be placed at their new  
     // position  
     bool move_ok = true;  
     for (unsigned i=0; i<diff.size(); ++i)  
         if (GetStone(diff[i]) != 0)  
             move_ok = false;  
   
     if (!move_ok)  
         return;  
   
     // If the floor at the cluster's new position consists exclusively  
     // of abyss or water, create a bridge instead of moving the  
     // cluster.  
     bool create_bridge = false;  
   
 #if 0 // oxyd creates bridges of partial clusters (at least per.oxyd)  
     if (is_complete) {  
 #endif  
         create_bridge = true;  
         for (unsigned i=0; i<mc.size(); ++i) {  
             if (Floor *fl = GetFloor(mc[i])) {  
                 const string &k = fl->get_kind();  
                 if (!(k=="fl-abyss" || k=="fl-water")) {  
                     create_bridge=false;  
                     break;  
                 }  
             }  
         }  
 #if 0  
     }  
 #endif  
   
     // Finally, either move the whole cluster or create a bridge  
     play_sound("st-move");  
     if (create_bridge) {  
         for (unsigned i=0; i<c.size(); ++i) {  
             KillStone(c[i]);  
             SetFloor(mc[i], MakeFloor("fl-gray"));  
         }  
     } else {  
         vector<Stone*> clusterstones;  
         for (unsigned i=0; i<c.size(); ++i)  
             clusterstones.push_back(YieldStone(c[i]));  
   
         for (unsigned i=0; i<c.size(); ++i)  
             SetStone(mc[i], clusterstones[i]);  
     }  
   
     player::IncMoveCounter(c.size());  
 }  
   
 bool  
 PuzzleStone::cluster_complete()  
 {  
     Cluster c;  
     return find_cluster(c);  
 }  
   
 void  
 PuzzleStone::init_model()  
 {  
     string modelname = "st-puzzle";  
     char x[10];  
   
     // Every combination of ``sockets'' has its own model.  
     int modelno = int_attrib("connections");  
     sprintf(x, "%d", modelno);  
     modelname += x;  
     set_model(modelname);  
 }  
   
 void  
 PuzzleStone::rotate_cluster(const Cluster &c)  
 {  
     if (c.size() > 1) {  
         vector<Stone*> stones;  
         for (unsigned i=0; i<c.size(); ++i)  
             stones.push_back(YieldStone(c[i]));  
   
         // exclude last list item  
         for (unsigned i=0; i<c.size()-1; ++i)  
             SetStone(c[i+1], stones[i]);  
         SetStone(c[0], stones.back());  
     }  
 }  
   
 StoneResponse  
 PuzzleStone::collision_response(const StoneContact &sc)  
 {  
     if (get_connections() == NODIRBIT)  
         return STONE_PASS;  
     return STONE_REBOUND;  
 }  
   
 void  
 PuzzleStone::on_impulse(const Impulse& impulse)  
 {  
     Cluster c;  
     bool    is_complete = find_cluster(c);  
     maybe_move_cluster(c, is_complete, impulse.dir);  
 }  
   
   
   
 void  
 PuzzleStone::actor_hit(const StoneContact &sc)  
 {  
     if (get_connections() == NODIRBIT)  
         return;  
   
     Direction move_dir=get_push_direction(sc);  
   
     if (wielded_item_is(sc.actor, "it-magicwand")) {  
         if (cluster_complete()) {  
             // make all cluster stones explode  
             return;  
         }  
         else if (contact_face(sc) != NODIR) { // not touching a corner  
             Cluster c;  
             find_row_or_column_cluster(c, get_pos(), move_dir);  
   
             if (move_dir == NODIR) // not enough impulse to move the cluster  
                 rotate_cluster(c);  
             else  
                 ; // TODO: move the incomplete cluster  
         }  
     }  
     else if (move_dir != NODIR) {  
         sc.actor->send_impulse(get_pos(), move_dir);  
     }  
 }  
   
 //----------------------------------------  
 // DoorBase  
 //  
 // Base class for everything that behaves like a door, i.e., it has  
 // four states OPEN, CLOSED, OPENING, CLOSING.  
 //----------------------------------------  
 namespace  
 {  
     class DoorBase : public Stone {  
     protected:  
         DoorBase(const char *name) : Stone(name), state(CLOSED) {}  
         enum State { OPEN, CLOSED, OPENING, CLOSING };  
   
         State get_state() const { return state; }  
         void set_state(State st) { state=st; }  
   
         State state;  
   
     private:  
         virtual string model_basename() { return get_kind(); }  
         virtual void init_model();  
         virtual string opening_sound() const { return ""; }  
         virtual string closing_sound() const { return ""; }  
   
         void change_state(State newstate) ;  
         void message(const string &m, const Value &);  
   
         void actor_inside(Actor *a);  
         StoneResponse collision_response(const StoneContact &sc);  
         void actor_hit(const StoneContact &sc);  
         void actor_contact(Actor *a);  
   
         void animcb();  
         bool on_laserhit(Direction dir) {return state==OPEN;}  
     };  
 }  
   
 void  
 DoorBase::message(const string &m, const Value &)  
 {  
     if (m == "open" && (state==CLOSED || state==CLOSING))  
         change_state(OPENING);  
     else if (m=="close" && (state==OPEN || state==OPENING))  
         change_state(CLOSING);  
     else if (m=="openclose" || m=="signal") {  
         if (state==OPEN || state==OPENING)  
             change_state(CLOSING);  
         else  
             change_state(OPENING);  
     }  
 }  
   
 void  
 DoorBase::init_model()  
 {  
     string mname = model_basename();  
     if (state == CLOSED)  
         mname += "-closed";  
     else if (state==OPEN)  
         mname += "-open";  
     set_model(mname);  
 }  
   
 void  
 DoorBase::animcb()  
 {  
     if (state == OPENING)  
         change_state(OPEN);  
     else if (state == CLOSING)  
         change_state(CLOSED);  
 }  
   
   
 StoneResponse  
 DoorBase::collision_response(const StoneContact &sc)  
 {  
     return (state == OPEN) ? STONE_PASS:STONE_REBOUND;  
 }  
   
 void  
 DoorBase::actor_hit(const StoneContact &sc)  
 {  
     if (state == CLOSING)  
         SendMessage(sc.actor, "shatter");  
 }  
   
 void  
 DoorBase::change_state(State newstate)  
 {  
     string basename = model_basename();  
   
     switch (newstate) {  
     case OPEN:  
         set_model(basename+"-open");  
         laser::MaybeRecalcLight(get_pos());  
         break;  
     case CLOSED:  
         set_model(basename+"-closed");  
         laser::MaybeRecalcLight(get_pos());  
         break;  
     case OPENING:  
         play_sound(opening_sound().c_str());  
         if (state == CLOSING)  
             get_model()->reverse();  
         else  
             set_anim(basename+"-opening");  
         break;  
     case CLOSING:  
         play_sound(closing_sound().c_str());  
         if (state == OPENING)  
             get_model()->reverse();  
         else  
             set_anim(basename+"-closing");  
         break;  
     }  
     set_state(newstate);  
 }  
   
 void  
 DoorBase::actor_inside(Actor *a)  
 {  
     if (state == CLOSING)  
         SendMessage(a, "shatter");  
 }  
   
 void  
 DoorBase::actor_contact(Actor *a)  
 {  
     if (state == CLOSING)  
         SendMessage(a, "shatter");  
 }  
   
 //----------------------------------------  
 // Door  
 //  
 // Attributes:  
 //  
 // :type        h or v for a door that opens horizontally or vertically  
 //----------------------------------------  
 namespace  
 {  
     class Door : public DoorBase {  
         CLONEOBJ(Door);  
     public:  
         Door(const char *type="h") : DoorBase("st-door") {  
             set_attrib("type", type);  
         }  
     private:  
         virtual string opening_sound() const { return "dooropen"; }  
         virtual string closing_sound() const { return "doorclose"; }  
         virtual const char *collision_sound() { return "electric"; }  
         string get_type() const {  
             string type="h";  
             string_attrib("type", &type);  
             return type;  
         }  
   
         bool on_laserhit(Direction dir);  
   
         string model_basename() { return string("st-door")+get_type(); }  
         StoneResponse collision_response(const StoneContact &sc);  
     };  
   
     class Door_a : public DoorBase {  
         CLONEOBJ(Door_a);  
     public:  
         Door_a() : DoorBase("st-door_a") {}  
     };  
   
     class Door_b : public DoorBase {  
         CLONEOBJ(Door_b);  
     public:  
         Door_b() : DoorBase("st-door_b") {}  
     };  
   
     class Door_c : public DoorBase {  
         CLONEOBJ(Door_c);  
     public:  
         Door_c() : DoorBase("st-door_c") {}  
     };  
 }  
   
 bool  
 Door::on_laserhit(Direction dir) {  
     if (get_type() == "h")  
         return state==OPEN || dir==EAST || dir==WEST;  
     else  
         return state==OPEN || dir==NORTH || dir==SOUTH;  
 }  
   
 StoneResponse  
 Door::collision_response(const StoneContact &sc)  
 {  
     Direction cf = contact_face(sc);  
     if (state == OPEN)  
         return STONE_PASS;  
     else if (state == CLOSING)  
         return STONE_REBOUND;  
     else {  
         string t = get_type();  
         return ((t == "v" && (cf==WEST || cf==EAST)) ||  
                 (t == "h" && (cf==SOUTH || cf==NORTH)))  
             ? STONE_REBOUND  
             : STONE_PASS;  
     }  
 }  
   
 //----------------------------------------  
 // TimerStone  
 //  
 // Attributes:  
 //  
 // :interval        seconds between two "ticks"  
 // :loop  
 // :action,target   as usual  
 //----------------------------------------  
   
 /** \page st-timer Timer Stone  
   
 This stone can be used to trigger periodic events or to trigger one  
 single event after a certain amount of time.  
   
 \subsection timera Attributes  
   
 - \b on: 1 if the timer is running  
 - \b interval:  number of seconds before \b action is performed  
 - \b loop:      if 1, restart the timer after performing \b action  
 - \b action, \b target: as usual  
   
 \subsection timerm Messages  
   
 - \b on, \b off, \b onoff: as usual  
   
 \subsection timere Example  
   
 \verbatim  
 -- activate a laser after 5 seconds  
 set_stone("st-laser", 10,11, {name="laser"})  
 set_stone("st-timer", 10,10,  
           {loop=0, action="onoff", target="laser", interval=5})  
 \endverbatim  
 */  
 namespace  
 {  
     class TimerStone : public OnOffStone, public TimeHandler  
     {  
         CLONEOBJ(TimerStone);  
     public:  
         TimerStone() : OnOffStone("st-timer") {  
             set_attrib("interval", 1.0);  
             set_attrib("loop", 1.0);  
             set_attrib("on", 1.0);  
   
             // set_on(true);   DOESN'T WORK! calls init_model()  
         }  
     private:  
         double get_interval() const {  
             double interval = 100;  
             double_attrib("interval", &interval);  
             return interval;  
         }  
         void init_model() { set_model(is_on() ? "st-timer" : "st-timeroff"); }  
   
         void on_creation() {  
             set_alarm();  
             Stone::on_creation();  
         }  
   
         void set_alarm() {  
             if (is_on())  
                 g_timer.set_alarm(this, get_interval(), true);  
         }  
   
         void alarm() {  
             if (is_on())  
             {  
 //                 sound::PlaySound("st-timer");  
                 PerformAction(this, is_on());  
             }  
         }  
     };  
 }  
   
 //----------------------------------------  
 // Switch  
 //  
 // Attributes:  
 //  
 // :on              1 or 0  
 // :target,action   as usual  
 //----------------------------------------  
   
 /** \page st-switch Switch Stone  
   
 On actor hit this stone can trigger actions  
   
 \image html st-switch1.png  
 */  
 namespace  
 {  
     class SwitchStone : public OnOffStone {  
         CLONEOBJ(SwitchStone);  
     public:  
         SwitchStone() : OnOffStone("st-switch") {}  
     private:  
         void init_model() {  
             set_model(is_on() ? "st-switch1" : "st-switch0");  
         }  
   
         void actor_hit(const StoneContact &sc) {  
             set_on(!is_on());  
             PerformAction(this, is_on());  
             play_sound("st-switch");  
         }  
         const char *collision_sound() { return "st-metal"; }  
     };  
 }  
   
 //----------------------------------------  
 // Switch_black  
 //  
 // Attributes:  
 //  
 // :on              1 or 0  
 // :target,action   as usual  
 //----------------------------------------  
   
 //On actor (black only) hit this stone can trigger actions  
   
 namespace  
 {  
     class Switch_black : public OnOffStone {  
         CLONEOBJ(Switch_black);  
     public:  
         Switch_black() : OnOffStone("st-switch_black") {}  
     private:  
         void init_model() {  
             set_model(is_on() ? "st-switch_black1" : "st-switch_black0");  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
             if (sc.actor->get_attrib("blackball"))  
                 set_on(!is_on());  
             PerformAction(this, is_on());  
             play_sound("st-switch");  
         }  
   
         const char *collision_sound() { return "st-metal"; }  
     };  
 }  
   
 //----------------------------------------  
 // Switch_white  
 //  
 // Attributes:  
 //  
 // :on              1 or 0  
 // :target,action   as usual  
 //----------------------------------------  
   
 //On actor (white only) hit this stone can trigger actions  
   
 namespace  
 {  
     class Switch_white : public OnOffStone {  
         CLONEOBJ(Switch_white);  
     public:  
         Switch_white() : OnOffStone("st-switch_white") {}  
     private:  
         void init_model() {  
             set_model(is_on() ? "st-switch_white1" : "st-switch_white0");  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
             if (sc.actor->get_attrib("whiteball"))  
                 set_on(!is_on());  
             PerformAction(this, is_on());  
             play_sound("st-switch");  
         }  
   
         const char *collision_sound() { return "st-metal"; }  
     };  
 }  
   
 //----------------------------------------  
 // Timeswitch  
 //  
 // Attributes:  
 //  
 // :on              1 or 0  
 // :target,action   as usual  
 //----------------------------------------  
   
 /** \page st-switch Switch Stone  
   
 On actor hit this stone can trigger actions  
   
 \image html st-switch1.png  
 */  
 namespace  
 {  
     class TimeSwitch : public OnOffStone {  
         CLONEOBJ(TimeSwitch);  
     public:  
         TimeSwitch() : OnOffStone("st-timeswitch"), state(IDLE) {}  
     private:  
         enum State {IDLE, ON, OFF };  
         State state;  
         void change_state (State newstate)  
        {  
             if (newstate == IDLE) {  
                 state = IDLE;  
             }  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
   
         if ( state == IDLE) {  
             if (sc.actor)  
                 state = ON;  
                 set_on(!is_on());  
                 PerformAction(this, is_on());  
                 play_sound("st-switch");  
                 set_anim("st-time1switch");  
             }  
         }  
   
         void animcb() {  
             if (state == ON) {  
             set_on(!is_on());  
             PerformAction(this, is_on());  
             play_sound("st-switch");  
             change_state (IDLE);  
         }  
      }  
         const char *collision_sound() { return "st-metal"; }  
    };  
 }  
 //----------------------------------------  
 // FourSwitch  
 //  
 // Attributes:  
 //  
 // :on              1 or 0  
 // :target,action   as usual  
 //----------------------------------------  
   
 /** \page st-fourswitch Switch Stone  
   
 On actor hit this stone can trigger actions  
   
 \image html st-fourswitch.png  
 */  
   
 namespace  
 {  
     class FourSwitch : public OnOffStone {  
         CLONEOBJ(FourSwitch);  
     public:  
         FourSwitch() : OnOffStone("st-fourswitch"), m_direction(NORTH) {}  
     private:  
         Direction m_direction;  
   
         void init_model() {  
             switch (m_direction) {  
             case NORTH: set_model("st-fourswitch");   break;  
             case EAST:  set_model("st-fourswitch_e"); break;  
             case SOUTH: set_model("st-fourswitch_s"); break;  
             case WEST:  set_model("st-fourswitch_w"); break;  
             case NODIR: assert(0);  
             }  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
             set_on(!is_on());  
             PerformAction(this, is_on());  
             play_sound("st-switch");  
   
             m_direction = rotate(m_direction, true);  
             init_model();  
         }  
         const char *collision_sound() { return "st-metal"; }  
     };  
 }  
   
 //----------------------------------------  
 // Laser Switch  
 //----------------------------------------  
   
 /** \page st-laserswitch Laserswitch Stone  
   
 Activated by laser light this switch can trigger actions  
   
 */  
 namespace  
 {  
     class LaserSwitch : public laser::PhotoStone {  
         CLONEOBJ(LaserSwitch);  
     public:  
         LaserSwitch();  
         ~LaserSwitch();  
   
     private:  
         enum State { ON, OFF };  
         State state;  
   
         display::Model *model_bak;  
   
         // Stone interface  
         void actor_hit(const StoneContact &sc);  
         void on_creation();  
         void on_removal();  
         const char *collision_sound() { return "st-metal"; }  
   
         // PhotoStone interface.  
         void notify_laseron() { set_model("st-laserswitch1"); PerformAction(this, true);}  
         void notify_laseroff() { set_model("st-laserswitch0"); PerformAction(this, false);}  
     };  
 }  
   
 LaserSwitch::LaserSwitch()  
 : PhotoStone("st-laserswitch"), state(OFF), model_bak(0)  
 {  
 }  
   
 LaserSwitch::~LaserSwitch()  
 {  
     delete model_bak;  
 }  
   
 void  
 LaserSwitch::actor_hit(const StoneContact &sc)  
 {  
 }  
   
 void  
 LaserSwitch::on_creation()  
 {  
     if (model_bak) {  
         display::SetModel(GridLoc(GRID_STONES, get_pos()), model_bak);  
         model_bak=0;  
     }  
     else {  
         set_model("st-laserswitch0");  
     }  
   
     photo_activate();  
 }  
   
 void  
 LaserSwitch::on_removal()  
 {  
     photo_deactivate();  
     model_bak = display::YieldModel(GridLoc(GRID_STONES, get_pos()));  
 }  
   
 //----------------------------------------  
 // Floppy Switch  
 //  
 // Attributes:  
 //  
 // :on              1 or 0  
 // :target,action   as usual  
 //----------------------------------------  
 namespace  
 {  
     class FloppyStone : public OnOffStone  
     {  
         CLONEOBJ(FloppyStone);  
     public:  
         FloppyStone() : OnOffStone("st-floppy") {}  
     private:  
         void init_model() {  
             set_model(is_on() ? "st-floppy1" : "st-floppy0");  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
             if (player::Inventory *inv = get_inventory(sc.actor))  
             {  
                 if (is_on())  
                 {  
                     if (!inv->is_full()) {  
                         inv->add_item(MakeItem("it-floppy"));  
                         set_on(false);  
                         PerformAction(this, is_on());  
                     }  
                 }  
                 else if (wielded_item_is(sc.actor, "it-floppy"))  
                 {  
                     DisposeObject(inv->yield_first());  
                     set_on(true);  
                     PerformAction(this, is_on());  
                 }  
             }  
         }  
         const char *collision_sound() { return "st-metal"; }  
     };  
 }  
   
 //----------------------------------------  
 // ShogunStone  
 //  
 // Attributes:  
 //  
 // :holes            1..7  
 //----------------------------------------  
 namespace  
 {  
     class ShogunStone : public MovableStone {  
         CLONEOBJ(ShogunStone);  
   
         enum Holes { SMALL = 1, MEDIUM = 2, LARGE = 4};  
         static Holes smallest_hole(Holes s);  
         void set_holes(Holes h) { set_attrib("holes", h); }  
   
     public:  
         ShogunStone(int holes=SMALL) : MovableStone("st-shogun") {  
             set_holes(static_cast<Holes>(holes));  
         }  
     private:  
         Holes get_holes() const;  
         void notify_item();  
   
         void message(const string &m, const Value &) {  
             if (m == "renotify") { // request from ShogunDot (if set _after_ ShogunStone)  
                 notify_item();  
             }  
         }  
   
         void add_hole(Holes h) {  
             set_attrib("holes", get_holes() | h);  
             notify_item();  
             init_model();  
         }  
   
         void on_creation() {  
             init_model();  
             notify_item();  
         }  
   
         void on_impulse(const Impulse& impulse);  
   
         void init_model() {  
             char x[20];  
             sprintf(x, "st-shogun%d", int(get_holes()));  
             set_model(x);  
         }  
   
         void actor_hit(const StoneContact &sc);  
     };  
 }  
   
 ShogunStone::Holes  
 ShogunStone::get_holes() const  
 {  
     int h=int_attrib("holes");  
     if (h>=1 && h<=7)  
         return Holes(h);  
     else {  
         warning("Wrong 'holes' attribute (%i)", h);  
         return SMALL;  
     }  
 }  
   
 ShogunStone::Holes  
 ShogunStone::smallest_hole(Holes s)  
 {  
     if (s & SMALL) return SMALL;  
     if (s & MEDIUM) return MEDIUM;  
     if (s & LARGE) return LARGE;  
     assert(0);  
 }  
   
 void ShogunStone::notify_item ()  
 {  
     if (Item *it = GetItem(get_pos()))  
     {  
         switch (get_holes()) {  
         case SMALL:                    SendMessage(it, "shogun1"); break;  
         case (MEDIUM | SMALL):         SendMessage(it, "shogun2"); break;  
         case (LARGE | MEDIUM | SMALL): SendMessage(it, "shogun3"); break;  
         default:                       SendMessage(it, "noshogun"); break;  
         }  
     }  
 }  
   
 void  
 ShogunStone::actor_hit(const StoneContact &sc)  
 {  
     maybe_push_stone(sc);  
 }  
   
 void  
 ShogunStone::on_impulse(const Impulse& impulse)  
 {  
     GridPos destpos     = move(get_pos(), impulse.dir);  
     Holes holes         = get_holes();  
     Holes smallest      = smallest_hole(holes);  
     ShogunStone *target = 0;  
   
     if (Stone *st = GetStone(destpos)) {  
         target = dynamic_cast<ShogunStone*>(GetStone(destpos));  
   
         /* If the stone at `p' is not a shogun stone or if smallest hole  
            does not fit into target, do not transfer the smallest hole. */  
         if (!target || smallest >= smallest_hole(target->get_holes()))  
             return;  
     }  
   
     /* It's important, to remove the old stone before setting the new  
        one: otherwise it is possible to activate two triggers with one  
        shogun stone when shifting from one shogun target to a second  
        adjacent shogun target. */  
   
     // Remove/modify source stone:  
     if (Holes newholes = Holes(holes & ~smallest)) {  
         set_holes(newholes);  
         notify_item();  
         init_model();  
     }  
     else {  
         SendMessage(GetItem(get_pos()), "noshogun");  
         KillStone(get_pos());  
     }  
   
     // Modify/create target stone:  
     if (target) {  
         target->add_hole(smallest);  
         play_sound("st-magic");  
     }  
     else  // create new  
         SetStone(destpos, new ShogunStone(smallest));  
   
     player::IncMoveCounter();  
     play_sound("st-move");  
 }  
   
 //----------------------------------------  
 // FartStone  
 //----------------------------------------  
   
 /** \page st-fart Fart Stone  
   
 The fart stone has the unpleasant habit of "blowing off" when triggered  
 (by actor hit or signal) and will close all oxyd stones.  
   
 \subsection fartm Messages  
   
 - \b trigger: as usual  
   
 */  
   
 namespace  
 {  
     class FartStone : public Stone {  
         CLONEOBJ(FartStone);  
     public:  
         FartStone() : Stone("st-fart"), state(IDLE) {}  
     private:  
         enum State { IDLE, FARTING, BROKEN };  
         State state;  
   
         void fart() {  
             if (state == IDLE) {  
                 Object *ox = world::GetObjectTemplate("st-oxyd");  
                 SendMessage(ox, "closeall");  
                 play_sound("fart");  
                 set_anim("st-farting");  
                 state = FARTING;  
             }  
         }  
         void destroy() {  
   
         }  
         void animcb() {  
             if (state == FARTING) {  
                 state = IDLE;  
                 init_model();  
             }  
         }  
   
         void actor_hit(const StoneContact &sc) {  
             if (wielded_item_is(sc.actor, "it-hammer")) {  
                 // ### What we really want is either a broken "fart"  
                 // ### stone or an animation  
                 KillStone(get_pos());  
             } else {  
                 fart();  
             }  
         }  
         void message(const string &m, const Value &) {  
             if (m=="trigger")  
                 fart();  
         }  
     };  
 }  
   
 //----------------------------------------  
 // Thief Stone  
 //  
 // Takes one item from inventory after actor hit.  
 //  
 // TODO: It should steal afer some period of time  
 //----------------------------------------  
 namespace  
 {  
     class ThiefStone : public Stone {  
         CLONEOBJ(ThiefStone);  
     public:  
         ThiefStone() : Stone("st-thief"), state(IDLE) { }  
     private:  
         enum State { IDLE, STEALING } state;  
   
         void actor_hit(const StoneContact &sc) {  
             if (state==IDLE) {  
                 set_anim("st-thief-anim");  
                 if (player::Inventory *inv = get_inventory(sc.actor))  
                 {  
                     int i = int(inv->size() * (rand()/(RAND_MAX+1.0)));  
                     delete inv->yield_item(i);  
   
                     play_sound("thief");  
                     state = STEALING;  
                 }  
             }  
         }  
   
         void animcb() {  
             if (state == STEALING) {  
                 state = IDLE;  
                 init_model();  
             }  
         }  
   
         const char *collision_sound() {  
             return "st-thud";  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // ActorImpulseStone  
 //----------------------------------------  
 namespace  
 {  
     class ActorImpulseStone : public Stone {  
         CLONEOBJ(ActorImpulseStone);  
     public:  
         ActorImpulseStone() : Stone("st-actorimpulse"), state(IDLE) {}  
     private:  
         enum State { IDLE, PULSING, BROKEN };  
         State state;  
   
         void actor_hit (const StoneContact &sc) {  
             if (state == IDLE) {  
                 double forcefac = 200;  
                 sc.actor->add_force (forcefac * normalize(sc.normal));  
                 play_sound("impulse");  
                 set_anim("st-actorimpulse-anim");  
                 state = PULSING;  
             }  
         }  
   
         void animcb() {  
             if (state == PULSING) {  
                 state = IDLE;  
                 init_model();  
             }  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // ActorImpulseStone Invisible  
 //----------------------------------------  
 namespace  
 {  
     class ActorImpulseStoneInvisible : public Stone {  
         CLONEOBJ(ActorImpulseStoneInvisible);  
     public:  
         ActorImpulseStoneInvisible() : Stone("st-actorimpulse_invisible"), state(IDLE) {}  
     private:  
         enum State { IDLE, PULSING, BROKEN };  
         State state;  
   
         void actor_hit (const StoneContact &sc) {  
             if (state == IDLE) {  
                 double forcefac = 200;  
                 sc.actor->add_force (forcefac * normalize(sc.normal));  
                 play_sound("impulse");  
                 set_anim("st-actorimpulse-anim");  
                 state = PULSING;  
             }  
         }  
   
         void animcb() {  
             if (state == PULSING) {  
                 state = IDLE;  
                 init_model();  
             }  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // StoneImpulseStone  
 //  
 // Messages:  
 //  
 // :trigger  
 //----------------------------------------  
 namespace  
 {  
     class StoneImpulse_Base : public Stone {  
     protected:  
         StoneImpulse_Base(const char *kind) : Stone(kind), state(IDLE), incoming(NODIR)  
         {}  
   
         enum State { IDLE, PULSING, CLOSING };  
         State     state;  
         Direction incoming; // direction of incoming impulse (may be NODIR)  
   
         void change_state(State st);  
   
         virtual void on_impulse(const Impulse& impulse) {  
             incoming = impulse.dir;  
             change_state(PULSING);  
         }  
   
     private:  
   
         virtual void notify_state(State st) = 0;  
   
         void message(const string &m, const Value &value) {  
             if (m=="trigger") {  
                 incoming = (value.get_type() == Value::DOUBLE)  
                     ? Direction(value.get_double()+0.1)  
                     : NODIR;  
   
                 change_state(PULSING);  
             }  
         }  
   
         void animcb() {  
             if (state == PULSING)  
                 change_state (CLOSING);  
             else if (state == CLOSING)  
                 change_state (IDLE);  
         }  
   
         virtual bool on_laserhit(Direction dir) {  
             incoming = dir;  
             change_state(PULSING);  
             return false;  
         }  
     };  
   
 }  
   
 void  
 StoneImpulse_Base::change_state(State st)  
 {  
 //     warning("StoneImpulse_Base changes State %i -> %i", (int)state, (int)st);  
     if (st == state) return;  
   
     GridPos p = get_pos();  
     switch (st) {  
         case IDLE: {  
             init_model();  
             break;  
         }  
         case PULSING:  
             if (state != IDLE) {  
                 return;         // do not set new state  
             }  
             play_sound("impulse");  
             break;  
         case CLOSING: {  
             GridPos targetpos[4];  
             bool    haveStone[4];  
   
             for (int d = 0; d < 4; ++d) {  
                 targetpos[d] = move(p, Direction(d));  
                 haveStone[d] = GetStone(targetpos[d]) != 0;  
             }  
   
             for (int d = int(incoming)+1; d <= int(incoming)+4; ++d) {  
                 int D = d%4;  
                 if (haveStone[D]) {  
                     send_impulse(targetpos[D], Direction(D));  
                 }  
             }  
   
             incoming = NODIR;   // forget impulse direction  
             break;  
         }  
     }  
     state = st;  
   
     notify_state(state);  
 }  
   
   
 //----------------------------------------  
 // StoneImpulseStone (regular and hollow)  
 //  
 // Messages:  
 //  
 // :trigger  
 //----------------------------------------  
 namespace  
 {  
     class StoneImpulseStone : public StoneImpulse_Base {  
         CLONEOBJ(StoneImpulseStone);  
     public:  
         StoneImpulseStone() : StoneImpulse_Base("st-stoneimpulse")  
         {}  
   
     private:  
         void notify_state(State st) {  
             if (st == PULSING) set_anim("st-stoneimpulse-anim1");  
             else if (st==CLOSING) set_anim("st-stoneimpulse-anim2");  
         }  
   
         void actor_hit(const StoneContact &sc) {  
             change_state(PULSING);  
         }  
   
     };  
   
   
     class HollowStoneImpulseStone : public StoneImpulse_Base {  
         CLONEOBJ(HollowStoneImpulseStone);  
     public:  
         HollowStoneImpulseStone()  
         : StoneImpulse_Base("st-stoneimpulse-hollow") {}  
     private:  
         void notify_state(State st) {  
             if (st == PULSING) {  
                 laser::MaybeRecalcLight(get_pos());  
                 set_anim("st-stoneimpulse-hollow-anim1");  
             }  
             else if (st==CLOSING)  
                 set_anim("st-stoneimpulse-hollow-anim2");  
             else if (st == IDLE) {  
                 laser::MaybeRecalcLight(get_pos());  
             }  
         }  
   
         StoneResponse collision_response(const StoneContact &sc) {  
             return (state == IDLE) ? STONE_PASS : STONE_REBOUND;  
         }  
         void actor_inside(Actor *a) {  
             if( state == PULSING || state == CLOSING)  
                 SendMessage(a, "shatter");  
         }  
   
         bool on_laserhit(Direction) {  
             return (state==IDLE); // only let light pass if idle  
         }  
     };  
   
   
     class MovableImpulseStone : public StoneImpulse_Base {  
         CLONEOBJ(MovableImpulseStone);  
     public:  
         MovableImpulseStone() : StoneImpulse_Base("st-stoneimpulse_movable") {}  
   
     private:  
         void actor_inside (Actor *a) {  
             SendMessage(a, "shatter");  
         }  
   
         void notify_state(State st)  {  
             if (st == PULSING) set_anim("st-stoneimpulse-anim1");  
             else if (st==CLOSING) set_anim("st-stoneimpulse-anim2");  
         }  
   
         void init_model() {  
             set_model("st-stoneimpulse");  
         }  
   
         // Stone interface:  
   
         void actor_hit(const StoneContact &sc) {  
             if (!maybe_push_stone (sc)) {  
                 incoming = NODIR; // bad, but no real problem!  
                 change_state(PULSING);  
             }  
         }  
   
         void on_impulse(const Impulse& impulse) {  
             if (move_stone(impulse.dir)) {  
                 Actor *hitman = dynamic_cast<Actor*>(impulse.sender);  
                 if (hitman && wielded_item_is(hitman, "it-magicwand")) {  
                     return;     // do not change state to PULSING  
                 }  
             }  
             StoneImpulse_Base::on_impulse(impulse);  
         }  
   
         void on_move() {  
             // when moved, animation seems to be restarted  
             // and animcb never sets state to IDLE  
             change_state(IDLE);  
         }  
   
         bool is_movable() {  
             return true;  
         }  
     };  
 }  
   
 //----------------------------------------  
 // OxydStone  
 //----------------------------------------  
   
 /** \page st-oxyd Oxyd Stone  
   
 Oxyd stones are characterized by two attributes: Their flavor and  
 their color.  The flavor only affects the visual representation of  
 the stone; it can be either 'a' (opening like a flower) or 'b'  
 (displaying a fade-in animation).  The color attribute determines  
 the shape on the oxyd stone.  
   
 \b Note: You should usually not to create Oxyd stones manually  
 with \c set_stone(). Use the predefined \c oxyd() function instead.  
   
 \subsection oxyda Attributes  
   
 - \b flavor      "a", "b", "c", or "d"  
 - \b color       number between 0 and 7  
   
 \subsection oxydm Messages  
   
 - \b closeall    close all oxyd stones  
 - \b shuffle     interchange the colors of the oxyd stones in the current landscape  
 - \b trigger     open the stone  
   
 <table><tr>  
 <td>\image html st-oxyda.png "flavor A"  
 <td>\image html st-oxydb.png "flavor B"  
 <td>\image html st-oxydc.png "flavor C"  
 <td>\image html st-oxydd.png "flavor D"  
 </table>  
 */  
   
 namespace  
 {  
     class OxydStone : public laser::PhotoStone {  
         INSTANCELISTOBJ(OxydStone);  
     public:  
         OxydStone();  
         ~OxydStone();  
   
         static void shuffle_colors();  
     private:  
         enum State { CLOSED, OPEN, OPENING, CLOSING, BLINKING };  
         State state;  
   
         display::Model *model_bak;  
   
         static bool blinking_or_opening(OxydStone *a) {  
             return (a->state==BLINKING || a->state==OPENING);  
         }  
         static bool not_open(OxydStone *a) {  
             return !(a->state==OPEN || a->state==OPENING);  
         }  
   
         // Stone interface  
         void actor_hit(const StoneContact &sc);  
         void on_creation();  
         void on_removal();  
         const char *collision_sound() { return "st-stone"; }  
         void message(const string &m, const Value &);  
   
         // Animation callback  
         void animcb();  
   
         void maybe_open_stone();  
         void change_state(State newstate);  
   
         void notify_laseron() { maybe_open_stone(); }  
         void notify_laseroff() {}  
     };  
 }  
   
 OxydStone::InstanceList OxydStone::instances;  
   
 OxydStone::OxydStone()  
 : PhotoStone("st-oxyd")  
 , state(CLOSED)  
 , model_bak(0)  
 {  
     set_attrib("flavor", "b");  
     set_attrib("color", "0");  
 }  
   
 OxydStone::~OxydStone()  
 {  
     delete model_bak;  
 }  
   
 void  
 OxydStone::message(const string &m, const Value &)  
 {  
     if (m=="closeall")  
         for (unsigned i=0; i<instances.size(); ++i)  
             instances[i]->change_state(CLOSING);  
     else if (m=="shuffle")  
         shuffle_colors();  
     else if (m=="trigger")  
         maybe_open_stone();  
 }  
   
 void  
 OxydStone::shuffle_colors()  
 {  
     string icolor, acolor;  
     for (unsigned i=0; i<instances.size(); ++i) {  
         int a=int(instances.size() * (rand()/(RAND_MAX+1.0)));  
   
         instances[i]->string_attrib("color", &icolor);  
         instances[a]->string_attrib("color", &acolor);  
   
         instances[i]->set_attrib("color", acolor.c_str());  
         instances[a]->set_attrib("color", icolor.c_str());  
     }  
 }  
   
 void  
 OxydStone::change_state(State newstate)  
 {  
     string flavor = "a";  
     string color = "1";  
     string_attrib("flavor", &flavor);  
     string_attrib("color", &color);  
   
     string modelname = string("st-oxyd") + flavor + color;  
   
     State oldstate = state;  
     state = newstate;  
   
     switch (newstate) {  
     case CLOSED:  
         set_model(string("st-oxyd")+flavor);  
         break;  
   
     case BLINKING:  
         set_model(modelname + "-blink");  
         break;  
   
     case OPEN:  
         if (oldstate == CLOSED) {  
             play_sound("st-oxydopen");  
             play_sound("st-oxydopened");  
             set_anim(modelname+"-opening");  
         } else {  
             set_model(modelname + "-open");  
         }  
         /* If this was the last closed oxyd stone, finish the  
            level */  
         if (find_if(instances.begin(),instances.end(),not_open)  
             ==instances.end())  
         {  
             enigma::FinishLevel();  
         }  
         break;  
   
     case OPENING:  
         play_sound("st-oxydopen");  
         if (oldstate == CLOSED)  
             set_anim(modelname + "-opening");  
         else if (oldstate == CLOSING)  
             get_model()->reverse();  
   
         break;  
   
     case CLOSING:  
         if (oldstate == CLOSED || oldstate==CLOSING) {  
             state = oldstate;  
             return;  
         }  
   
         play_sound("st-oxydclose");  
         if (oldstate == OPENING)  
             get_model()->reverse();  
         else if (oldstate == BLINKING || oldstate == OPEN) {  
             set_anim(modelname + "-closing");  
         }  
         break;  
     }  
 }  
   
 void  
 OxydStone::animcb()  
 {  
     if (state == CLOSING)  
         change_state(CLOSED);  
     else if (state == OPENING)  
         change_state(BLINKING);  
     else if (state == OPEN)  
         change_state(OPEN); // set the right model  
 }  
   
 void  
 OxydStone::maybe_open_stone()  
 {  
     if (state == CLOSED || state == CLOSING) {  
         int mycolor = int_attrib("color");  
   
         // Is another oxyd stone currently blinking?  
         InstanceList::iterator i;  
         i=find_if(instances.begin(), instances.end(), blinking_or_opening);  
   
         if (i != instances.end()) {  
             // If colors match, open both stones. Close one of them  
             // otherwise  
             if (mycolor == (*i)->int_attrib("color")) {  
                 change_state(OPEN);  
                 (*i)->change_state(OPEN);  
             } else {  
                 (*i)->change_state(CLOSING);  
                 change_state(OPENING);  
             }  
         }  
         else {  
             // no blinking stone? -> make this one blink  
             change_state(OPENING);  
         }  
     }  
 }  
   
 void  
 OxydStone::actor_hit(const StoneContact &sc)  
 {  
     maybe_open_stone();  
 }  
   
 void  
 OxydStone::on_creation()  
 {  
     if (model_bak) {  
         display::SetModel(GridLoc(GRID_STONES, get_pos()), model_bak);  
         model_bak=0;  
     }  
     else  
     {  
         string flavor = "a";  
         string_attrib("flavor", &flavor);  
         set_model(string("st-oxyd") + flavor);  
     }  
   
     photo_activate();  
 }  
   
 void  
 OxydStone::on_removal()  
 {  
     photo_deactivate();  
     model_bak = display::YieldModel(GridLoc(GRID_STONES, get_pos()));  
 }  
   
 //----------------------------------------  
 // FakeOxydStone  
 //----------------------------------------  
   
 /** \page st-fakeoxyd Fake Oxyd Stone  
   
 These stones look like real Oxyd stones, but they only blink a little  
 when touched and do not open or have other special abilities.  
   
 \image html st-fakeoxyd-blink_0001.png  
 */  
 namespace  
 {  
     class FakeOxydStone : public Stone {  
         CLONEOBJ(FakeOxydStone);  
     public:  
         FakeOxydStone() : Stone("st-fakeoxyd"), state(IDLE) {}  
     private:  
         enum State { IDLE, BLINKING } state;  
         void actor_hit(const StoneContact &sc) {  
             if (state == IDLE) {  
                 set_anim("st-fakeoxyd-blink");  
                 state = BLINKING;  
             }  
         }  
         const char *collision_sound() {  
             return "st-fakeoxyd";  
         }  
         void animcb() {  
             set_model("st-fakeoxyd");  
             state = IDLE;  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // Black stones  
 //----------------------------------------  
 namespace  
 {  
     class BlackStone : public Stone {  
         CLONEOBJ(BlackStone);  
     public:  
         BlackStone() : Stone("st-black1") {}  
         BlackStone(const char *name) : Stone( name) {}  
         StoneResponse collision_response(const StoneContact &sc) {  
             if (sc.actor->get_attrib("blackball"))  
                 return STONE_PASS;  
             else  
                 return STONE_REBOUND;  
         }  
     };  
   
     class BlackStone2 : public BlackStone {  
         CLONEOBJ(BlackStone2);  
     public:  
         BlackStone2() : BlackStone("st-black2") {}  
     };  
   
     class BlackStone3 : public BlackStone {  
         CLONEOBJ(BlackStone3);  
     public:  
         BlackStone3() : BlackStone("st-black3") {}  
     };  
   
     class BlackStone4 : public BlackStone {  
         CLONEOBJ(BlackStone4);  
     public:  
         BlackStone4() : BlackStone("st-black4") {}  
     };  
   
 }  
   
 //----------------------------------------  
 // White stones  
 //----------------------------------------  
 namespace  
 {  
     class WhiteStone : public Stone {  
         CLONEOBJ(WhiteStone);  
     public:  
         WhiteStone() : Stone("st-white1") {}  
         WhiteStone(const char *name) : Stone( name) {}  
         StoneResponse collision_response(const StoneContact &sc) {  
             if (sc.actor->get_attrib("whiteball"))  
                 return STONE_PASS;  
             else  
                 return STONE_REBOUND;  
         }  
     };  
   
     class WhiteStone2 : public WhiteStone {  
         CLONEOBJ(WhiteStone2);  
     public:  
         WhiteStone2() : WhiteStone("st-white2") {}  
     };  
   
     class WhiteStone3 : public WhiteStone {  
         CLONEOBJ(WhiteStone3);  
     public:  
         WhiteStone3() : WhiteStone("st-white3") {}  
     };  
   
     class WhiteStone4 : public WhiteStone {  
         CLONEOBJ(WhiteStone4);  
     public:  
         WhiteStone4() : WhiteStone("st-white4") {}  
     };  
   
 }  
   
 //----------------------------------------  
 // YinYang stone 1  
 //----------------------------------------  
 namespace  
 {  
     class YinYangStone1 : public Stone {  
         CLONEOBJ(YinYangStone1);  
     public:  
         YinYangStone1() : Stone("st-yinyang1"), state(NOCOLOR) {}  
     private:  
         enum State { NOCOLOR, WHITE, BLACK } state;  
   
         void init_model() {  
             switch (state) {  
             case NOCOLOR: set_model("st-yinyang1"); break;  
             case WHITE: set_model("st-white1"); break;  
             case BLACK: set_model("st-black1"); break;  
             }  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
             if (state==NOCOLOR) {  
                 if (sc.actor->get_attrib("blackball")) {  
                     state = WHITE;  
                     init_model();  
                     play_sound("st-magic");  
                 }  
                 else if (sc.actor->get_attrib("whiteball")) {  
                     state = BLACK;  
                     init_model();  
                     play_sound("st-magic");  
                 }  
             }  
         }  
   
         StoneResponse collision_response(const StoneContact &sc)  
         {  
             if ((state==BLACK && sc.actor->get_attrib("blackball")) ||  
                 (state==WHITE && sc.actor->get_attrib("whiteball")))  
                 return STONE_PASS;  
             return STONE_REBOUND;  
         }  
     };  
 }  
   
 //----------------------------------------  
 // YinYang stone 2  
 //----------------------------------------  
 namespace  
 {  
     class YinYangStone2 : public Stone {  
         CLONEOBJ(YinYangStone2);  
     public:  
         YinYangStone2() : Stone("st-yinyang2"), state(NOCOLOR) {}  
     private:  
         enum State { NOCOLOR, WHITE, BLACK } state;  
   
         void init_model() {  
             switch (state) {  
             case NOCOLOR: set_model("st-yinyang2"); break;  
             case WHITE: set_model("st-white1"); break;  
             case BLACK: set_model("st-black1"); break;  
             }  
         }  
   
         void actor_hit(const StoneContact &sc)  
         {  
             if (state==NOCOLOR) {  
                 if (sc.actor->get_attrib("blackball")) {  
                     state = BLACK;  
                     init_model();  
                     play_sound("st-magic");  
                 }  
                 else if (sc.actor->get_attrib("whiteball")) {  
                     state = WHITE;  
                     init_model();  
                     play_sound("st-magic");  
                 }  
             }  
         }  
   
         StoneResponse collision_response(const StoneContact &sc)  
         {  
             if ((state==BLACK && sc.actor->get_attrib("blackball")) ||  
                 (state==WHITE && sc.actor->get_attrib("whiteball")))  
                 return STONE_PASS;  
             return STONE_REBOUND;  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // BombStone  
 //  
 // These stones add a bomb to the player's inventory when touched.  
 //----------------------------------------  
 namespace  
 {  
     class BombStone : public Stone {  
         CLONEOBJ(BombStone);  
         const char *collision_sound() {return "st-stone";}  
     public:  
         BombStone() : Stone("st-bombs"),state(IDLE) {}  
     private:  
         enum State { IDLE, BREAK };  
         State state;  
         void actor_hit(const StoneContact &sc);  
         void change_state (State newstate) {  
             if (state == IDLE && newstate==BREAK) {  
                 state = newstate;  
                 play_sound("explosion1");  
                 set_anim("st-bombs-anim");  
             }  
         }  
         void animcb() {  
             assert(state == BREAK);  
             GridPos p = get_pos();  
             SendMessage(GetStone(move(p, NORTH)), "bombstone");  
             SendMessage(GetStone(move(p, SOUTH)), "bombstone");  
             SendMessage(GetStone(move(p, EAST)), "bombstone");  
             SendMessage(GetStone(move(p, WEST)), "bombstone");  
             KillStone(p);  
             SetItem(p, MakeItem("it-explosion1"));  
         }  
         void BombStone::message(const string &msg, const Value &) {  
             if (msg =="expl" || msg =="bombstone")  
                 change_state(BREAK);  
         }  
     };  
 }  
 void  
 BombStone::actor_hit(const StoneContact &sc)  
 {  
     if (player::Inventory *inv = get_inventory(sc.actor))  
     {  
         if (!inv->is_full())  
         {  
             Item *it = MakeItem("it-blackbomb");  
             inv->add_item(it);  
         }  
     }  
 }  
   
 //----------------------------------------  
 // MagicStone  
 //----------------------------------------  
 namespace  
 {  
     class MagicStone : public Stone {  
         CLONEOBJ(MagicStone);  
     public:  
         MagicStone() : Stone("st-magic") {}  
     private:  
         void actor_hit(const StoneContact &sc) {  
             KillStone(get_pos());  
             display::GetStatusBar()->show_text("We don't sell books..", display::TEXT_2SECONDS);  
         }  
     };  
 }  
   
   
 //----------------------------------------  
 // DeathStone  
 //----------------------------------------  
   
 /** \page st-death Death's Head Stone  
   
 Simply kills all actors that touch it (except for actors that are  
 immune to these stones).  
   
 \image html st-death.png  
 */  
 namespace  
 {  
     class DeathStone : public Stone {  
         CLONEOBJ(DeathStone);  
     public:  
         DeathStone() : Stone("st-death"), active(false) {}  
     private:  
         bool active;  
         void actor_hit(const StoneContact &sc)  
         {  
             if (!active) {  
                 SendMessage(sc.actor, "shatter");  
                 active=true;  
                 set_anim("st-death-munch");  
             }  
         }  
         void animcb() { set_model("st-death"); active=false; }  
     };  
 }  
   
   
 //----------------------------------------  
 // DeathStone Invisible  
 //----------------------------------------  
   
 /** \page st-death_invisible Death's Head Stone invivible  
   
 Simply kills all actors that touch it (except for actors that are  
 immune to these stones). This variant is invisible.  
   
 \image html st-death.png  
 */  
 namespace  
 {  
     class DeathStoneInvisible : public Stone {  
         CLONEOBJ(DeathStoneInvisible);  
     public:  
         DeathStoneInvisible() : Stone("st-death_invisible"), active(false) {}  
     private:  
         bool active;  
         void actor_hit(const StoneContact &sc)  
         {  
             if (!active) {  
                 SendMessage(sc.actor, "shatter");  
                 active=true;  
                 set_anim("st-death-munch");  
             }  
         }  
         void animcb() { set_model("st-death_invisible"); active=false; }  
     };  
 }  
   
   
 //----------------------------------------  
 // Coin slot Switch  
 //----------------------------------------  
   
 /** \page st-coinslot Coin Slot Stone  
   
 Coin Slots are switches that must be activated by inserting coins.  
 The more coins you put in, the longer the switch will stay activated  
   
 \subsection coinslota Attributes  
   
 - \b cost            how many on-time bought for coin1  
 - \b interval        on-time remaining  
 - \b target, \b action   as usual  
   
 \subsection coinslote Example  
   
 \verbatim  
 set_stone("st-coinslot", 20,13,{action="openclose", target="door", cost=1})  
 \endverbatim  
 */  
   
 namespace  
 {  
     class CoinSlot : public OnOffStone, public TimeHandler {  
         CLONEOBJ(CoinSlot);  
     public:  
         CoinSlot();  
     private:  
         // Variables.  
         enum State { ACTIVE, INACTIVE } state;  
         double remaining_time;  
   
         // Private methods.  
         void init_model() {  
             set_model(state==ACTIVE ? "st-coinslot" : "st-coinslot");  
             // st-coinslot-filled  
         }  
   
         void actor_hit(const StoneContact &sc);  
         const char *collision_sound() { return "st-metal"; }  
   
         void animcb() { init_model(); }  
         void tick(double dtime);  
         void change_state(State newstate);  
     };  
 }  
   
 CoinSlot::CoinSlot()  
 : OnOffStone("st-coinslot"), state(INACTIVE), remaining_time(0)  
 {  
     set_attrib("cost", 1.0);  
 }  
   
 void  
 CoinSlot::change_state(State newstate)  
 {  
     if (state == newstate) return;  
   
     switch (newstate) {  
     case ACTIVE:  
         PerformAction(this, true);  
         g_timer.activate(this);  
         break;  
     case INACTIVE:  
         PerformAction(this, false);  
         g_timer.deactivate(this);  
         break;  
     }  
     state = newstate;  
 }  
   
 void  
 CoinSlot::tick(double dtime)  
 {  
     assert(remaining_time > 0);  
   
     remaining_time -= dtime;  
     if (remaining_time <= 0)  
     {  
         change_state(INACTIVE);  
     }  
 }  
   
 void  
 CoinSlot::actor_hit(const StoneContact &sc)  
 {  
     if (wielded_item_is(sc.actor, "it-coin"))  
     {  
         if (player::Inventory *inv = get_inventory(sc.actor))  
         {  
             play_sound("st-coinslot");  
             Item *it = inv->yield_first();  
   
             double coin_value = 0;  
             it->double_attrib("value", &coin_value);  
             remaining_time += coin_value;  
   
             set_anim("st-coin2slot");  
             change_state(ACTIVE);  
             delete it;  
         }  
     }  
 }  
   
   
 //----------------------------------------  
 // Turnstile  
 //----------------------------------------  
 namespace  
 {  
     class Turnstile_Arm;  
   
     /*  
     ** The stone at the center of a turnstile  
     */  
     class Turnstile_Pivot_Base : public Stone {  
     public:  
         Turnstile_Pivot_Base(const char *kind) : Stone (kind) {}  
   
     protected:  
         bool rotate(bool clockwise, Object *impulse_sender);  
   
         friend class Turnstile_Arm; // uses rotate  
   
     private:  
         DirectionBits arms_present() const;  
         bool          no_stone (int xoff, int yoff) const;  
   
         void set_arm (Direction dir);  
         void set_arms (DirectionBits arms);  
         void remove_arms (DirectionBits arms);  
   
         void animcb() { set_model(model()); }  
   
         void handleActorsAndItems(bool clockwise, Object *impulse_sender);  
   
         virtual const char *model() const    = 0;  
         virtual const char *anim() const     = 0;  
         virtual bool oxyd_compatible() const = 0;  
   
     };  
   
     class Turnstile_Pivot : public Turnstile_Pivot_Base {  
         CLONEOBJ(Turnstile_Pivot);  
     public:  
         Turnstile_Pivot() : Turnstile_Pivot_Base(model()) {}  
   
         const char *model() const { return "st-turnstile"; }  
         const char *anim() const  { return "st-turnstile-anim"; }  
         bool oxyd_compatible() const { return true; }  
     };  
   
     class Turnstile_Pivot_Green : public Turnstile_Pivot_Base {  
         CLONEOBJ(Turnstile_Pivot_Green);  
     public:  
         Turnstile_Pivot_Green() : Turnstile_Pivot_Base(model()) {}  
   
         const char *model() const { return "st-turnstile-green"; }  
         const char *anim() const  { return "st-turnstile-green-anim"; }  
         bool oxyd_compatible() const { return false; }  
     };  
   
     /*  
     ** The base class for any of the four arms of the turnstile  
     */  
     class Turnstile_Arm : public Stone {  
         virtual Direction get_dir() const = 0;  
   
         void actor_hit(const StoneContact &sc);  
         void on_impulse(const Impulse& impulse);  
   
         Turnstile_Pivot_Base *get_pivot() {  
             Stone *st = GetStone (move (get_pos(), reverse(get_dir())));  
             return dynamic_cast<Turnstile_Pivot_Base*>(st);  
         }  
     protected:  
         Turnstile_Arm (const char *kind) : Stone(kind)  
         {}  
     };  
   
     class Turnstile_N : public Turnstile_Arm {  
         CLONEOBJ(Turnstile_N);  
     public:  
         Turnstile_N(): Turnstile_Arm("st-turnstile-n") {}  
         Direction get_dir () const { return NORTH; }  
     };  
   
     class Turnstile_S : public Turnstile_Arm {  
         CLONEOBJ(Turnstile_S);  
         Direction get_dir () const { return SOUTH; }  
     public:  
         Turnstile_S(): Turnstile_Arm("st-turnstile-s") {}  
     };  
   
     class Turnstile_E : public Turnstile_Arm {  
         CLONEOBJ(Turnstile_E);  
         Direction get_dir () const { return EAST; }  
     public:  
         Turnstile_E(): Turnstile_Arm("st-turnstile-e") {}  
     };  
   
     class Turnstile_W : public Turnstile_Arm {  
         CLONEOBJ(Turnstile_W);  
         Direction get_dir () const { return WEST; }  
     public:  
         Turnstile_W(): Turnstile_Arm("st-turnstile-w") {}  
     };  
   
     /*  
     **  
     */  
     class Turnstile_Corner : public Stone {  
         CLONEOBJ(Turnstile_Corner);  
   
         void init_model() {  
             set_anim ("st-turnstile-corner");  
         }  
         void animcb() {  
             KillStone(get_pos());  
         }  
     public:  
         Turnstile_Corner() : Stone("st-turnstile-corner")  
         {}  
     };  
 }  
   
 // -------------------------------------  
 //      Turnstile_Arm implementation  
 // -------------------------------------  
   
 void  
 Turnstile_Arm::on_impulse(const Impulse& impulse) {  
     enum Action { ROTL, ROTR, stay };  
     static Action actions[4][4] = {  
         { stay, ROTL, stay, ROTR }, // west arm  
         { ROTR, stay, ROTL, stay }, // south arm  
         { stay, ROTR, stay, ROTL }, // east arm  
         { ROTL, stay, ROTR, stay } // north arm  
     };  
   
     Turnstile_Pivot_Base *pivot = get_pivot();  
   
     if (pivot) {  
         Action a = actions[get_dir()][impulse.dir];  
         if (a != stay) {  
             pivot->rotate(a == ROTR, impulse.sender); // ROTR is clockwise  
         }  
     }  
     else {  
         // Move arms not attached to a pivot individually  
         move_stone(impulse.dir);  
     }  
 }  
   
 void  
 Turnstile_Arm::actor_hit(const StoneContact &sc)  
 {  
     maybe_push_stone(sc);  
 }  
   
 // --------------------------------------------  
 //      Turnstile_Pivot_Base implementation  
 // --------------------------------------------  
   
 DirectionBits  
 Turnstile_Pivot_Base::arms_present() const  
 {  
     DirectionBits arms = NODIRBIT;  
     GridPos p = get_pos();  
     if (dynamic_cast<Turnstile_N*>(GetStone(move(p, NORTH))))  
         px::set_flags (arms, NORTHBIT);  
     if (dynamic_cast<Turnstile_S*>(GetStone(move(p, SOUTH))))  
         px::set_flags (arms, SOUTHBIT);  
     if (dynamic_cast<Turnstile_E*>(GetStone(move(p, EAST))))  
         px::set_flags (arms, EASTBIT);  
     if (dynamic_cast<Turnstile_W*>(GetStone(move(p, WEST))))  
         px::set_flags (arms, WESTBIT);  
     return arms;  
 }  
   
 bool  
 Turnstile_Pivot_Base::no_stone (int xoff, int yoff) const  
 {  
     GridPos p = get_pos();  
     p.x += xoff;  
     p.y += yoff;  
     return (0 == GetStone(p));  
 }  
   
 void  
 Turnstile_Pivot_Base::remove_arms (DirectionBits arms)  
 {  
     GridPos p = get_pos();  
     if (arms & NORTHBIT) KillStone (move (p, NORTH));  
     if (arms & EASTBIT) KillStone (move (p, EAST));  
     if (arms & SOUTHBIT) KillStone (move (p, SOUTH));  
     if (arms & WESTBIT) KillStone (move (p, WEST));  
 }  
   
 void  
 Turnstile_Pivot_Base::set_arms (DirectionBits arms)  
 {  
     if (arms & NORTHBIT) set_arm(NORTH);  
     if (arms & EASTBIT)  set_arm(EAST);  
     if (arms & SOUTHBIT) set_arm(SOUTH);  
     if (arms & WESTBIT)  set_arm(WEST);  
 }  
   
 void  
 Turnstile_Pivot_Base::set_arm (Direction dir)  
 {  
     char *names[4] = { "st-turnstile-w",  
                        "st-turnstile-s",  
                        "st-turnstile-e",  
                        "st-turnstile-n" };  
     Stone *st = MakeStone(names[dir]);  
     GridPos newp = move(get_pos(), dir);  
     SetStone (newp, st);  
   
     if (Item *it = GetItem(newp))  
         it->on_stonehit(st);  
   
 }  
   
 bool  
 Turnstile_Pivot_Base::rotate(bool clockwise, Object *impulse_sender)  
 {  
     DirectionBits arms       = arms_present();  
     bool          can_rotate = true;  
   
     if (clockwise)  {  
         if (arms & NORTHBIT) {  
             can_rotate &= no_stone(+1,-1);  
             if (! (arms & EASTBIT)) can_rotate &= no_stone(+1,0);  
         }  
         if (arms & WESTBIT) {  
             can_rotate &= no_stone(-1,-1);  
             if (! (arms & NORTHBIT)) can_rotate &= no_stone(0,-1);  
         }  
         if (arms & SOUTHBIT) {  
             can_rotate &= no_stone(-1,+1);  
             if (! (arms & WESTBIT)) can_rotate &= no_stone(-1,0);  
         }  
         if (arms & EASTBIT) {  
             can_rotate &= no_stone(+1,+1);  
             if (! (arms & SOUTHBIT)) can_rotate &= no_stone(0,+1);  
         }  
     }  
     else {  
         if (arms & NORTHBIT) {  
             can_rotate &= no_stone(-1,-1);  
             if (! (arms & WESTBIT)) can_rotate &= no_stone(-1,0);  
         }  
         if (arms & WESTBIT) {  
             can_rotate &= no_stone(-1,+1);  
             if (! (arms & SOUTHBIT)) can_rotate &= no_stone(0,+1);  
         }  
         if (arms & SOUTHBIT) {  
             can_rotate &= no_stone(+1,+1);  
             if (! (arms & EASTBIT)) can_rotate &= no_stone(+1,0);  
         }  
         if (arms & EASTBIT) {  
             can_rotate &= no_stone(+1,-1);  
             if (! (arms & NORTHBIT)) can_rotate &= no_stone(0,-1);  
         }  
     }  
   
     if (can_rotate) {  
         if (dynamic_cast<Actor*>(impulse_sender))  
             play_sound("st-magic");  
         play_sound("st-move");  
   
         set_anim(anim());  
   
         remove_arms(arms);  
         set_arms(enigma::rotate(arms, clockwise));  
         handleActorsAndItems(clockwise, impulse_sender);  
   
         player::IncMoveCounter();  
     }  
     return can_rotate;  
 }  
   
 void  
 Turnstile_Pivot_Base::handleActorsAndItems(bool clockwise, Object *impulse_sender) {  
     GridPos pv_pos = get_pos();  
     V2      pv_center(pv_pos.x+.5, pv_pos.y+.5);  
   
     const int to_index[3][3] = {  
         { 0, 7, 6 }, // x == 0  
         { 1,-1, 5 }, // x == 1  
         { 2, 3, 4 }  // x == 2  
     };  
     const int to_x[8] = { -1, 0, 1, 1, 1, 0, -1, -1 };  
     const int to_y[8] = { -1, -1, -1, 0, 1, 1, 1, 0 };  
   
     // for each field calculate whether an arm has passed by:  
     bool arm_seen[8];  
     {  
         const DirectionBits neededArm[2][8] = {  
             // anticlockwise :  
             { WESTBIT, NORTHBIT, NORTHBIT, EASTBIT, EASTBIT, SOUTHBIT, SOUTHBIT, WESTBIT },  
             // clockwise :  
             { NORTHBIT, NORTHBIT, EASTBIT, EASTBIT, SOUTHBIT, SOUTHBIT, WESTBIT, WESTBIT }  
         };  
         DirectionBits arms = arms_present(); // already the rotated state  
         assert(arms); // pivots w/o arms should not rotate  
   
         for (int i = 0; i<8; ++i) {  
             arm_seen[i] = arms & neededArm[clockwise][i];  
         }  
     }  
   
     // The turnstile itself already has been rotated.  
   
     // Handle items in range :  
     for (int i = 0; i<8; ++i) {  
         if (arm_seen[i]) {  
             GridPos pos(pv_pos.x+to_x[i], pv_pos.y+to_y[i]);  
             if (Item *it = GetItem(pos)) it->on_stonehit(this); // hit with pivot (shouldn't matter)  
         }  
     }  
   
     // Now handle all actors in range :  
     vector<Actor*> actorsInRange;  
   
     // tested range is sqrt(sqr(1.5)+sqr(0.5)) ( = radius hit by turnstile) + 19/64 ( = max. actor radius)  
     if (GetActorsInRange(pv_center, 1.879, actorsInRange)) {  
         for (vector<Actor*>::iterator ac = actorsInRange.begin(); ac != actorsInRange.end(); ++ac) {  
             const V2& ac_center = (*ac)->get_pos();  
             GridPos   ac_pos(ac_center);  
             int       dx        = ac_pos.x-pv_pos.x;  
             int       dy        = ac_pos.y-pv_pos.y;  
   
             if (dx<-1 || dx>1 || dy<-1 || dy>1) { // actor is outside of turnstile  
                 // @@@ FIXME: test if actor was hit by turnstile -> set actor speed  
             }  
             else {              // actor is inside the turnstile  
                 int idx_source = to_index[dx+1][dy+1];  
                 assert(idx_source != -1); // actor inside pivot -- impossible  
   
                 const int rot_index[4][8] = {  
                     { 6,  0, 0,  2, 2,  4, 4,  6 }, // anticlockwise  
                     { 2,  2, 4,  4, 6,  6, 0,  0 }, // clockwise  
                     { 6, -1, 0, -1, 2, -1, 4, -1 }, // anticlockwise (oxyd-compatible)  
                     { 2, -1, 4, -1, 6, -1, 0, -1 }, // clockwise (oxyd-compatible)  
                 };  
   
                 bool compatible = oxyd_compatible();  
                 int  idx_target = rot_index[clockwise+2*compatible][idx_source]; // destination index  
   
                 enum { STAY, WARP, SHATTER } action = STAY;  
   
                 if (compatible) {  
                     bool is_impulse_sender = (*ac == dynamic_cast<Actor*>(impulse_sender));  
   
                     if (is_impulse_sender) {  
                         action = WARP;  
                     }  
                     else { // other actors  
                         if (arm_seen[idx_source]) { // this actor has been hit by an arm  
                             action = SHATTER;  
                         }  
                     }  
                 }  
                 else {          // not oxyd-compatible  
                     if (arm_seen[idx_source]) {  
                         action = WARP;  
                     }  
                     // @@@ FIXME: shattered actors are warped  
                 }  
   
                 switch (action) {  
                     case WARP:  {  
                         GridPos ac_target_pos(pv_pos.x+to_x[idx_target], pv_pos.y+to_y[idx_target]);  
                         world::WarpActor(*ac, ac_target_pos.x+.5, ac_target_pos.y+.5);  
   
                         Stone *st = GetStone(ac_target_pos);  
                         if (!st) break;  
   
                         // destination is blocked  
   
                         Turnstile_Arm *arm = dynamic_cast<Turnstile_Arm*>(st);  
                         if (arm && !compatible) { // if blocking stone is turnstile arm -> hit it!  
                             const int impulse_dir[2][8] = {  
                                 // anticlockwise  
                                 { SOUTHBIT|WESTBIT, WESTBIT, NORTHBIT|WESTBIT, NORTHBIT,  
                                   NORTHBIT|EASTBIT, EASTBIT, SOUTHBIT|EASTBIT, SOUTHBIT },  
                                 // clockwise  
                                 { NORTHBIT|EASTBIT, EASTBIT, SOUTHBIT|EASTBIT, SOUTHBIT,  
                                   SOUTHBIT|WESTBIT, WESTBIT, NORTHBIT|WESTBIT, NORTHBIT }  
                             };  
   
                             DirectionBits possible_impulses =  
                                 static_cast<DirectionBits>(impulse_dir[clockwise][idx_target]);  
   
                             for (int d = 0; d<4; ++d) {  
                                 if (has_dir(possible_impulses, Direction(d))) {  
                                     (*ac)->send_impulse(ac_target_pos, Direction(d));  
                                 }  
                             }  
   
                             if (GetStone(ac_target_pos) == 0) { // arm disappeared  
                                 break;  
                             }  
                         }  
                         // fall-through (if arm didn't rotate or other stone was blocking)  
                     }  
                     case SHATTER: {  
                         SendMessage(*ac, "shatter");  
                         break;  
                     }  
                 }  
   
                 // @@@ FIXME: it's possible that two actors are moved to the same destination field.  
                 // In that case the second actor is put on top of the first actor  
                 // (happens only in non-oxyd-compat-mode with three balls or pullers/impulsestones)  
                 //  
                 // Note: With black and whiteball it's normally no problem,  
                 // because when one of the actors was moving, it's looking natural.  
                 // Problems occur when small balls come into play.  
             }  
         }  
     }  
   
 }  
   
 //----------------------------------------  
 // EasyModeStone  
 //  
 // I'm not quite sure what this one is supposed to do, but a stone  
 // like this appears in all Per.Oxyd landscapes that look different in  
 // easy mode.  For now, this stone simply does nothing.  
 //----------------------------------------  
 namespace  
 {  
     class EasyModeStone : public Stone {  
         SINGLETONOBJ(EasyModeStone);  
     public:  
         EasyModeStone() : Stone("st-easymode") {}  
   
         StoneResponse collision_response(const StoneContact &sc) {  
             return STONE_PASS;  
         }  
     };  
 }  
   
675  //----------------------------------------------------------------------  //----------------------------------------------------------------------
676  // OBJECT REPOSITORY  // OBJECT REPOSITORY
677  //----------------------------------------------------------------------  //----------------------------------------------------------------------
# Line 4489  ObjectRepos::ObjectRepos() Line 723  ObjectRepos::ObjectRepos()
723      add_templ("fl-gradient15", new Gradient(24));      add_templ("fl-gradient15", new Gradient(24));
724      add_templ("fl-gradient16", new Gradient(23));      add_templ("fl-gradient16", new Gradient(23));
725    
     // Stones  
     add_templ(new BlackStone);  
     add_templ(new BlackStone2);  
     add_templ(new BlackStone3);  
     add_templ(new BlackStone4);  
     add_templ(new BolderStone);  
     add_templ("st-bolder-n", new BolderStone(NORTH));  
     add_templ("st-bolder-e", new BolderStone(EAST));  
     add_templ("st-bolder-s", new BolderStone(SOUTH));  
     add_templ("st-bolder-w", new BolderStone(WEST));  
     add_templ(new BlockerStone(true));  
     add_templ(new BlockerStone(false));  
     add_templ(new BombStone);  
     add_templ(new BrickMagic);  
     add_templ(new ChameleonStone);  
     add_templ(new CoinSlot);  
     add_templ(new DeathStone);  
     add_templ(new DeathStoneInvisible);  
     add_templ(new Door);  
     add_templ("st-door-h", new Door("h"));  
     add_templ("st-door-v", new Door("v"));  
     add_templ(new Door_a);  
     add_templ(new Door_b);  
     add_templ(new Door_c);  
     add_templ(new FakeOxydStone);  
     add_templ(new FartStone);  
     add_templ(new FloppyStone);  
     add_templ(new HollowStoneImpulseStone);  
     add_templ(new InvisibleMagic);  
 //    add_templ(new KeyStone);  
     add_templ(new KeyStone_a);  
     add_templ(new KeyStone_b);  
     add_templ(new KeyStone_c);  
     add_templ(new MagicStone);  
   
     add_templ(new OneWayStone);  
     add_templ("st-oneway-n", new OneWayStone(NORTH));  
     add_templ("st-oneway-e", new OneWayStone(EAST));  
     add_templ("st-oneway-s", new OneWayStone(SOUTH));  
     add_templ("st-oneway-w", new OneWayStone(WEST));  
     add_templ(new OneWayStone_black);  
     add_templ("st-oneway_black-n", new OneWayStone_black(NORTH));  
     add_templ("st-oneway_black-e", new OneWayStone_black(EAST));  
     add_templ("st-oneway_black-s", new OneWayStone_black(SOUTH));  
     add_templ("st-oneway_black-w", new OneWayStone_black(WEST));  
     add_templ(new OneWayStone_white);  
     add_templ("st-oneway_white-n", new OneWayStone_white(NORTH));  
     add_templ("st-oneway_white-e", new OneWayStone_white(EAST));  
     add_templ("st-oneway_white-s", new OneWayStone_white(SOUTH));  
     add_templ("st-oneway_white-w", new OneWayStone_white(WEST));  
   
     add_templ(new OxydStone);  
   
     add_templ(new PuzzleStone);  
     add_templ("st-puzzle-hollow", new PuzzleStone(1));  
     add_templ("st-puzzle-n", new PuzzleStone(9));  
     add_templ("st-puzzle-e", new PuzzleStone(5));  
     add_templ("st-puzzle-s", new PuzzleStone(3));  
     add_templ("st-puzzle-w", new PuzzleStone(2));  
     add_templ("st-puzzle-ne", new PuzzleStone(13));  
     add_templ("st-puzzle-nw", new PuzzleStone(10));  
     add_templ("st-puzzle-es", new PuzzleStone(7));  
     add_templ("st-puzzle-sw", new PuzzleStone(4));  
     add_templ("st-puzzle-ns", new PuzzleStone(11));  
     add_templ("st-puzzle-ew", new PuzzleStone(6));  
     add_templ("st-puzzle-nes", new PuzzleStone(15));  
     add_templ("st-puzzle-new", new PuzzleStone(14));  
     add_templ("st-puzzle-nsw", new PuzzleStone(12));  
     add_templ("st-puzzle-esw", new PuzzleStone(8));  
     add_templ("st-puzzle-nesw", new PuzzleStone(16));  
   
     add_templ(new RubberBandStone);  
   
     add_templ(new ShogunStone);  
     add_templ("st-shogun-s", new ShogunStone(1));  
     add_templ("st-shogun-m", new ShogunStone(2));  
     add_templ("st-shogun-sm", new ShogunStone(3));  
     add_templ("st-shogun-l", new ShogunStone(4));  
     add_templ("st-shogun-sl", new ShogunStone(5));  
     add_templ("st-shogun-ml", new ShogunStone(6));  
     add_templ("st-shogun-sml", new ShogunStone(7));  
   
     add_templ(new ActorImpulseStone);  
     add_templ(new ActorImpulseStoneInvisible);  
     add_templ(new StoneImpulseStone);  
     add_templ(new MovableImpulseStone);  
     add_templ(new SwapStone);  
     add_templ(new SwitchStone);  
     add_templ(new Switch_black);  
     add_templ(new Switch_white);  
     add_templ(new TimeSwitch);  
     add_templ(new FourSwitch);  
     add_templ(new LaserSwitch);  
     add_templ(new ScissorsStone);  
     add_templ(new ThiefStone);  
     add_templ(new TimerStone);  
   
     add_templ (new Turnstile_Pivot); // oxyd-comaptible  
     add_templ (new Turnstile_Pivot_Green); // not oxyd-comaptible  
     add_templ (new Turnstile_N);  
     add_templ (new Turnstile_S);  
     add_templ (new Turnstile_E);  
     add_templ (new Turnstile_W);  
   
     add_templ(new WhiteStone);  
     add_templ(new WhiteStone2);  
     add_templ(new WhiteStone3);  
     add_templ(new WhiteStone4);  
     add_templ(new WoodenStone);  
     add_templ(new WoodenStone_Growing);  
     add_templ(new YinYangStone1);  
     add_templ(new YinYangStone2);  
   
     add_templ(new BlockStone);  
     add_templ(new Window);  
     add_templ(new Stone_break);  
     add_templ(new Stonebrush);  
     add_templ(new Break_invisible);  
     add_templ(new Break_acwhite);  
     add_templ(new Break_acblack);  
726  }  }
727    
728  ObjectRepos::~ObjectRepos()  ObjectRepos::~ObjectRepos()
# Line 4764  world::SendMessage(Object *o, const std: Line 878  world::SendMessage(Object *o, const std:
878  }  }
879    
880  void  void
 world::DefineSimpleStone(const std::string &kind,  
                          const std::string &sound)  
 {  
     Register(new SimpleStone(kind,sound));  
 }  
   
 void  
 world::DefineSimpleStoneHollow(const std::string &kind)  
 {  
     Register(new SimpleStoneHollow(kind));  
 }  
   
 void  
 world::DefineSimpleStoneGlass(const std::string &kind,  
                          const std::string &sound)  
 {  
     Register(new SimpleStoneGlass(kind,sound));  
 }  
   
 void  
 world::DefineSimpleStoneMovable(const std::string &kind,  
                          const std::string &sound)  
 {  
     Register(new SimpleStoneMovable(kind,sound));  
 }  
   
 void  
881  world::DefineSimpleFloor(const std::string &kind,  world::DefineSimpleFloor(const std::string &kind,
882                           double friction, double mousefactor)                           double friction, double mousefactor)
883  {  {

Legend:
Removed from v.1.82  
changed lines
  Added in v.1.83

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26