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" |
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). */ |
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 |
//---------------------------------------------------------------------- |
//---------------------------------------------------------------------- |
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() |
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 |
{ |
{ |