2
* Copyright (C) 2002,2003,2004 Daniel Heck
4
* This program is free software; you can redistribute it and/ or
5
* modify it under the terms of the GNU General Public License
6
* as published by the Free Software Foundation; either version 2
7
* of the License, or (at your option) any later version.
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
14
* You should have received a copy of the GNU General Public License along
15
* with this program; if not, write to the Free Software Foundation, Inc.,
16
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
18
* $Id: stones_complex.cc,v 1.59 2004/05/22 13:04:27 dheck Exp $
26
#include "stones_internal.hh"
28
#include "px/tools.hh"
35
using namespace world;
36
using namespace stones;
39
/* -------------------- RotatorStone -------------------- */
42
class RotatorStone : public Stone {
44
RotatorStone(bool clockwise_, bool movable_)
45
: Stone("st-rotator"), clockwise(clockwise_), movable(movable_)
49
static const double RATE;
50
static const double IMPULSE_DELAY;
55
Stone *clone() { return new RotatorStone(clockwise, movable); }
56
void dispose() { delete this; }
58
void send_impulses() {
59
GridPos p = get_pos();
62
send_impulse (move(p, NORTH), EAST, IMPULSE_DELAY);
63
send_impulse (move(p, EAST), SOUTH, IMPULSE_DELAY);
64
send_impulse (move(p, SOUTH), WEST, IMPULSE_DELAY);
65
send_impulse (move(p, WEST), NORTH, IMPULSE_DELAY);
67
send_impulse (move(p, NORTH), WEST, IMPULSE_DELAY);
68
send_impulse (move(p, EAST), NORTH, IMPULSE_DELAY);
69
send_impulse (move(p, SOUTH), EAST, IMPULSE_DELAY);
70
send_impulse (move(p, WEST), SOUTH, IMPULSE_DELAY);
76
set_anim(clockwise ? "st-rotator-right" : "st-rotator-left");
83
bool is_movable () const { return movable; }
85
void actor_hit (const StoneContact &sc) {
86
if (player::wielded_item_is(sc.actor, "it-wrench")) {
87
clockwise = !clockwise;
95
void on_impulse(const Impulse& impulse) {
97
move_stone(impulse.dir);
100
void on_laserhit(Direction) {
101
clockwise = !clockwise;
106
const double RotatorStone::RATE = 1.0;
107
const double RotatorStone::IMPULSE_DELAY = 0.1;
111
/* -------------------- PullStone -------------------- */
113
// When pushed this stone acts like pulled.
114
// When pushed by an actor it exchanges its position with the actor.
124
PulledActor(Actor *actor_, const V2& speed_)
125
: actor(actor_), speed(speed_)
132
list<PulledActor> actors;
133
YieldedGridStone *ystone;
137
: ystone(st ? new YieldedGridStone(st) : 0)
139
~PullInfo() { delete ystone; }
141
void add_actor(Actor *actor, const V2& speed) {
142
actors.push_back(PulledActor(actor, speed));
145
list<PulledActor>& get_actors() { return actors; }
147
void set_stone(GridPos pos) { if (ystone) ystone->set_stone(pos); }
148
void dispose() { if (ystone) ystone->dispose(); }
152
class PullStone : public Stone, public TimeHandler {
154
enum State { IDLE, MOVING, VANISHED } state;
156
PullInfo *pull_info; // information about moved objects (only valid during pull)
168
bool is_movable () const {
169
return state == IDLE;
171
void actor_hit(const StoneContact &sc) {
173
maybe_push_stone(sc);
175
void on_impulse(const Impulse& impulse);
176
bool is_removable() const {
177
return state == IDLE;
180
// TimeHandler interface.
184
void change_state(State new_state);
185
void set_move_state(bool appearing, Direction move_dir);
190
PullStone::PullStone()
191
: Stone("st-pull"), state(IDLE), m_movedir(NODIR) , pull_info(0)
194
PullStone::~PullStone() {
198
PullStone *PullStone::clone() {
199
PullStone *other = new PullStone(*this);
200
other->pull_info = 0;
204
void PullStone::dispose() {
205
if (state == MOVING && pull_info != 0)
206
pull_info->dispose();
210
void PullStone::set_move_state (bool appearing, Direction move_dir) {
212
m_movedir = move_dir;
213
// only the half-stone on the new field gets an alarm
214
// wherein it completes the move
215
GameTimer.set_alarm(this, 0.09, false);
218
m_movedir = reverse(move_dir);
219
change_state(MOVING);
222
void PullStone::change_state (State new_state) {
224
case IDLE: set_model("st-pull"); break;
226
string mname = string("st-pull") + to_suffix(m_movedir);
230
case VANISHED: break;
235
void PullStone::alarm() {
236
assert(state == MOVING);
237
GridPos oldpos = move (get_pos(), reverse(m_movedir));
239
// remove the disappearing half of the PullStone :
240
PullStone *oldStone = dynamic_cast<PullStone*>(GetStone(oldpos));
242
oldStone->change_state(VANISHED);
245
if (pull_info) { // have other objects been moved ?
246
pull_info->set_stone(oldpos); // re-sets any pulled stone
248
// set pulled actor(s):
249
list<PulledActor>::iterator e = pull_info->get_actors().end();
250
for (list<PulledActor>::iterator i = pull_info->get_actors().begin(); i != e; ++i) {
251
PulledActor& pulled = *i;
252
pulled.actor->get_actorinfo()->vel = pulled.speed;
261
void PullStone::on_impulse(const Impulse& impulse)
266
Direction move_dir = reverse(impulse.dir);
267
const GridPos& oldPos = get_pos();
268
GridPos newPos = move(oldPos, move_dir);
269
Stone *other_stone = GetStone(newPos);
272
(!other_stone->is_removable() || IsLevelBorder(newPos))) {
273
return; // avoid unremoveable and border stones
276
PullStone *newStone = this->clone();
279
// yield other_stone:
280
newStone->pull_info = new PullInfo(other_stone);
283
newStone->pull_info = new PullInfo(0);
286
// search for affected actors
287
vector<Actor*> found_actors;
288
const double range_one_field = 1.415; // approx. 1 field [ > sqrt(1+1) ]
289
GetActorsInRange(newPos.center(), range_one_field, found_actors);
290
vector<Actor*>::iterator e = found_actors.end();
291
for (vector<Actor*>::iterator i = found_actors.begin(); i != e; ++i) {
293
ActorInfo *ai = actor->get_actorinfo();
294
GridPos actor_pos(ai->pos);
296
if (actor_pos == newPos) { // if the actor is in the dest field
297
V2 warp((oldPos.x-newPos.x)*0.4, (oldPos.y-newPos.y)*0.4); // half move vector (of actor)
299
V2 mid_dest = ai->pos+warp; // position during move
301
WarpActor(actor, mid_dest[0], mid_dest[1], false);
303
// if (length(vel) < 15.0) // speed up actor
306
newStone->pull_info->add_actor(actor, vel);
310
SetStone(newPos, newStone);
311
newStone->set_move_state(true, move_dir);
312
set_move_state(false, move_dir);
314
sound_event("moveslow");
318
/* -------------------- Oneway stones -------------------- */
320
// These stone can only be passed in one direction.
324
class OneWayBase : public Stone {
326
OneWayBase(const char *kind, Direction dir);
329
void message(const string& msg, const Value &val);
331
void actor_hit (const StoneContact&);
332
StoneResponse collision_response(const StoneContact &sc);
333
bool is_floating() const { return true; }
335
Direction get_orientation() const {
336
return Direction(int_attrib("orientation"));
338
void set_orientation(Direction dir) {
339
set_attrib("orientation", Value(dir));
342
virtual bool actor_may_pass (Actor *a) = 0;
345
class OneWayStone : public OneWayBase {
347
OneWayStone(Direction dir=SOUTH) : OneWayBase("st-oneway", dir) {}
349
CLONEOBJ(OneWayStone);
350
virtual bool actor_may_pass (Actor */*a*/) { return true; }
354
class OneWayStone_black : public OneWayBase {
356
OneWayStone_black(Direction dir=SOUTH)
357
: OneWayBase("st-oneway_black",dir) {}
359
CLONEOBJ(OneWayStone_black);
360
virtual bool actor_may_pass (Actor *a) {
361
return a->get_attrib("blackball") != 0;
363
void actor_hit (const StoneContact&) {
364
// do nothing if hit by actor
368
class OneWayStone_white : public OneWayBase {
370
OneWayStone_white(Direction dir=SOUTH)
371
: OneWayBase("st-oneway_white", dir) {}
373
CLONEOBJ(OneWayStone_white);
374
virtual bool actor_may_pass (Actor *a) {
375
return a->get_attrib("whiteball") != 0;
377
void actor_hit (const StoneContact&) {
378
// do nothing if hit by actor
383
OneWayBase::OneWayBase(const char *kind, Direction dir)
386
set_orientation(dir);
389
void OneWayBase::init_model()
391
string mname = get_kind();
392
mname += to_suffix(get_orientation());
396
void OneWayBase::message(const string& msg, const Value &val) {
397
if (msg == "direction" && val.get_type() == Value::DOUBLE) {
398
set_orientation(to_direction(val));
401
else if (msg == "signal" || msg == "flip") {
402
Direction dir = get_orientation();
403
set_orientation(reverse(dir));
408
void OneWayBase::actor_hit(const StoneContact &sc) {
409
Direction o=get_orientation();
411
if (has_dir(contact_faces(sc), o)) {
412
if (player::wielded_item_is(sc.actor, "it-magicwand")) {
413
set_orientation(reverse(o));
419
StoneResponse OneWayBase::collision_response(const StoneContact &sc) {
420
DirectionBits dirs=contact_faces(sc);
421
Direction o=get_orientation();
423
if (!sc.actor->is_flying() && actor_may_pass(sc.actor))
424
return has_dir(dirs,o) ? STONE_REBOUND : STONE_PASS;
426
return STONE_REBOUND;
430
/* -------------------- BolderStone -------------------- */
432
/** \page st-bolder Bolder Stone
434
The bolder stone will move in one direction until another stone will
435
block. When hit with a magic wand, the bolder stone reverse its
436
direction. When hitting a blocking stone it can activate switches or
439
\subsection boldera Attributes
441
- \c direction \n NORTH, EAST, SOUTH, WEST
446
class BolderStone : public Stone {
447
CLONEOBJ(BolderStone);
449
BolderStone(Direction dir=NORTH)
450
: Stone("st-bolder"), state(IDLE)
452
set_attrib("direction", dir);
453
// do not use set_dir, because this will set the state to ACTIVE
458
ACTIVE, // may send trigger into direction
459
IDLE, // already sent trigger w/o success
460
FALLING // falling into abyss
464
Direction get_dir() const {
465
return static_cast<Direction>(int_attrib("direction"));
467
void set_dir(Direction d) {
469
state = ACTIVE; // if turned by it-magicwand -> allow triggering
470
set_attrib("direction", d);
473
void on_floor_change() {
474
Floor *fl = GetFloor(get_pos());
475
if (fl->is_kind("fl-abyss")) {
481
bool have_obstacle (Direction dir) {
482
return GetStone(move(get_pos(), dir)) != 0;
485
void trigger_obstacle (Direction dir) {
486
if (Stone *st = GetStone(move(get_pos(), dir))) {
487
SendMessage(st, "trigger", Value(dir));
493
trigger_obstacle(get_dir());
499
string mname = "st-bolder" + to_suffix(get_dir());
500
if (state == FALLING)
501
mname += "-fall-anim";
506
display::Model *m = get_model();
507
Direction dir = get_dir();
510
KillStone(get_pos());
515
if (!have_obstacle(dir)) {
517
trigger_obstacle(dir);
519
// if (Model *m = get_model())
525
trigger_obstacle(dir);
526
if (!move_stone(dir)) {
527
// if (state == MOVING) // state may be FALLING
536
bool is_movable() const { return state != FALLING; }
538
void actor_hit(const StoneContact &sc) {
539
if (player::wielded_item_is(sc.actor, "it-magicwand")) {
540
set_dir(reverse(get_dir()));
545
void on_laserhit(Direction) {
546
set_dir(reverse(get_dir()));
549
// @@@ FIXME: the direction should only be inverted on NEW laserbeam
550
// not on every light-recalc. Need to use PhotoCell!
553
void on_impulse (const Impulse& impulse) {
554
if (state == FALLING)
557
if (impulse.sender && impulse.sender->is_kind("st-rotator")) {
558
set_dir(impulse.dir);
561
move_stone(impulse.dir);
564
void message(const string& msg, const Value &val) {
565
if (msg == "direction" && state != FALLING) {
566
set_dir (to_direction(val));
574
/* -------------------- BlockerStone -------------------- */
576
/** \page st-blocker Blocker Stone
578
The BlockerStone acts like a normal stone until it is hit by a
579
BolderStone. Then it shrinks and morphs into a 'Blocker' item.
585
class BlockerStone : public Stone {
586
CLONEOBJ(BlockerStone);
588
BlockerStone(bool solid)
589
: Stone(solid ? "st-blocker" : "st-blocker-growing"),
590
state(solid ? SOLID : GROWING)
594
enum State { SOLID, SHRINKING, GROWING } state;
599
set_model("st-blocker");
603
set_anim("st-blocker-shrinking");
607
set_anim("st-blocker-growing");
612
void change_state(State newState) {
613
if (state != newState) {
614
if (state == GROWING && newState == SHRINKING) {
616
get_model()->reverse();
618
else if (state == SHRINKING && newState == GROWING) {
620
get_model()->reverse();
625
if (newState == SOLID) {
626
set_attrib("kind", "st-blocker");
635
Item *it = world::MakeItem("it-blocker-new");
636
world::SetItem(get_pos(), it);
637
TransferObjectName(this, it);
638
world::KillStone(get_pos());
650
void message(const string &msg, const Value &val) {
651
if (msg == "trigger" || msg == "openclose") {
652
if (state == SHRINKING) {
653
change_state(GROWING);
656
change_state(SHRINKING);
659
else if (msg == "signal") {
660
int value = to_int(val);
661
// warning("received signal (value=%i)", value);
662
if (value) { // value == 1 -> shrink
663
if (state != SHRINKING)
664
change_state(SHRINKING);
666
else { // value == 0 -> grow
667
if (state == SHRINKING)
668
change_state(GROWING);
671
else if (msg == "open") { // aka "shrink"
672
if (state != SHRINKING)
673
change_state(SHRINKING);
675
else if (msg == "close") { // aka "grow"
676
if (state == SHRINKING)
677
change_state(GROWING);
681
void actor_contact(Actor *a) {
682
if (state == GROWING) {
683
SendMessage(a, "shatter");
686
void actor_inside(Actor *a) {
687
if (state == GROWING) {
688
SendMessage(a, "shatter");
695
/* -------------------- Volcano -------------------- */
698
class VolcanoStone : public Stone {
699
CLONEOBJ(VolcanoStone);
701
enum State {INACTIVE, ACTIVE, FINISHED, BREAKING};
702
VolcanoStone( State initstate=INACTIVE) : Stone("st-volcano"), state( initstate) {}
709
case INACTIVE: set_model( "st-plain"); break;
710
case ACTIVE: set_anim( "st-farting"); break;
711
case BREAKING: set_anim("st-stone_break-anim"); break;
716
if (state == ACTIVE) {
718
GridPos p = get_pos();
719
if (DoubleRand(0, 1) > 0.7) spread (move(p, NORTH));
720
if (DoubleRand(0, 1) > 0.7) spread (move(p, EAST));
721
if (DoubleRand(0, 1) > 0.7) spread (move(p, SOUTH));
722
if (DoubleRand(0, 1) > 0.7) spread (move(p, WEST));
724
// Be finished at random time
725
if (DoubleRand(0, 1) > 0.95)
728
} else if( state == BREAKING) {
729
KillStone( get_pos());
733
void message(const string &msg, const Value &) {
734
if (msg == "trigger") {
735
if (state == INACTIVE) {
742
void spread( GridPos p) {
743
Stone *st = GetStone(p);
745
Item *it = MakeItem("it-seed_volcano");
747
SendMessage( it, "grow");
751
void actor_hit(const StoneContact &sc) {
754
if( state == ACTIVE && player::wielded_item_is(a, "it-hammer")) {
763
/* -------------------- ConnectiveStone -------------------- */
765
// base class for PuzzleStone and BigBrick
768
class ConnectiveStone : public Stone {
770
ConnectiveStone(const char *kind, int connections)
773
set_attrib("connections", connections);
776
DirectionBits get_connections() const;
780
virtual int get_modelno() const;
785
ConnectiveStone::get_connections() const
787
int conn=int_attrib("connections") - 1;
788
if (conn >=0 && conn <16)
789
return DirectionBits(conn);
794
void ConnectiveStone::init_model() {
795
set_model(get_kind()+px::strf("%d", get_modelno()));
798
int ConnectiveStone::get_modelno() const {
799
return int_attrib("connections");
803
/* -------------------- BigBrick -------------------- */
805
// BigBricks allow to build stones of any size
809
class BigBrick : public ConnectiveStone {
812
BigBrick(int connections)
813
: ConnectiveStone("st-bigbrick", connections)
819
/* -------------------- Puzzle stones -------------------- */
821
/** \page st-puzzle Puzzle Stone
823
Puzzle stones can be connected to other stones of the same type. Any
824
of the four faces of the stone can have ``socket''. If the adjoining
825
faces of two neighboring stones both have a socket, the two stones
826
link up and henceforth move as group.
828
A cluster of puzzle stones may for example look like this:
842
This example actually presents the special case of a ``complete''
843
cluster. A cluster is complete if none of its stones has an
846
When touched with a magic wand the puzzle stones rotate
849
\subsection puzzlea Attributes
852
number between 1 an 16. Each bit in (connections-1) corresponds to
853
a socket on one of the four faces. You will normally simply use
854
one of the Lua constants \c PUZ_0000 to \c PUZ_1111.
857
If 1 then the puzzle stones act oxyd-compatible: Complete clusters
858
explode, when they get touched. All other puzzle stones rotate row-
859
or columnwise. Groups of oxyd-compatible puzzle stones are shuffled
860
randomly at level startup.
862
\subsection puzzlee Example
865
<td> \image html st-puzzletempl_0001.png "PUZ_0000"
866
<td> \image html st-puzzletempl_0002.png "PUZ_0001"
867
<td> \image html st-puzzletempl_0003.png "PUZ_0010"
868
<td> \image html st-puzzletempl_0004.png "PUZ_0011"
870
<td> \image html st-puzzletempl_0005.png "PUZ_0100"
871
<td> \image html st-puzzletempl_0006.png "PUZ_0101"
872
<td> \image html st-puzzletempl_0007.png "PUZ_0110"
873
<td> \image html st-puzzletempl_0008.png "PUZ_0111"
875
<td> \image html st-puzzletempl_0009.png "PUZ_1000"
876
<td> \image html st-puzzletempl_0010.png "PUZ_1001"
877
<td> \image html st-puzzletempl_0011.png "PUZ_1010"
878
<td> \image html st-puzzletempl_0012.png "PUZ_1011"
880
<td> \image html st-puzzletempl_0013.png "PUZ_1100"
881
<td> \image html st-puzzletempl_0014.png "PUZ_1101"
882
<td> \image html st-puzzletempl_0015.png "PUZ_1110"
883
<td> \image html st-puzzletempl_0016.png "PUZ_1111"
889
class PuzzleStone : public ConnectiveStone, public TimeHandler, public world::PhotoCell {
890
INSTANCELISTOBJ(PuzzleStone);
892
PuzzleStone(int connections, bool oxyd1_compatible_);
894
typedef vector<GridPos> Cluster;
896
/* ---------- Private methods ---------- */
898
bool oxyd1_compatible() const { return int_attrib("oxyd") != 0; }
900
static bool visit_dir(vector<GridPos> &stack, GridPos curpos,
902
static void visit_adjacent(vector<GridPos>& stack, GridPos curpos,
903
Direction dir, int wanted_oxyd_attrib);
905
bool find_cluster(Cluster &);
906
void find_adjacents(Cluster &);
907
void find_row_or_column_cluster(Cluster &c, GridPos startpos,
908
Direction dir, int wanted_oxyd_attrib);
910
bool cluster_complete();
911
bool can_move_cluster (Cluster &c, Direction dir);
912
void maybe_move_cluster(Cluster &c, bool is_complete, bool actor_with_wand,
914
void rotate_cluster(const Cluster &c);
915
void maybe_rotate_cluster(Direction dir);
917
int get_modelno() const;
919
void trigger_explosion(double delay);
920
static void trigger_explosion_at(GridPos p, double delay, int wanted_oxyd_attrib);
922
bool explode_complete_cluster();
924
/* ---------- TimeHandler interface ---------- */
928
/* ---------- PhotoCell interface ---------- */
930
void on_recalc_start();
931
void on_recalc_finish();
933
/* ---------- Stone interface ---------- */
935
void message(const string& msg, const Value &val);
937
void on_creation (GridPos p);
938
void on_removal (GridPos p);
939
void on_impulse (const Impulse& impulse);
940
void on_laserhit (Direction dir);
942
bool is_floating() const;
944
StoneResponse collision_response(const StoneContact &sc);
945
void actor_hit (const StoneContact &sc);
946
void actor_contact (Actor *a);
948
/* ---------- Variables ---------- */
949
bool visited; // flag for DFS
950
enum { IDLE, EXPLODING } state;
951
DirectionBits illumination; // last state of surrounding laser beams
955
PuzzleStone::InstanceList PuzzleStone::instances;
957
PuzzleStone::PuzzleStone(int connections, bool oxyd1_compatible_)
958
: ConnectiveStone("st-puzzle", connections),
960
illumination (NODIRBIT)
962
set_attrib("oxyd", int(oxyd1_compatible_));
967
PuzzleStone::visit_dir(vector<GridPos> &stack, GridPos curpos, Direction dir)
969
GridPos newpos = move(curpos, dir);
970
PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(newpos));
975
DirectionBits cfaces = pz->get_connections();
977
if (cfaces==NODIRBIT || has_dir(cfaces, reverse(dir))) {
978
// Puzzle stone at newpos is connected to stone at curpos
981
stack.push_back(newpos);
985
// The two stones are adjacent but not connected
990
/* Use a depth first search to determine the group of all stones that
991
are connected to the current stone. Returns true if the cluster is
992
``complete'' in the sense defined above. */
993
bool PuzzleStone::find_cluster(Cluster &cluster) {
994
for (unsigned i=0; i<instances.size(); ++i)
995
instances[i]->visited=false;
997
vector<GridPos> pos_stack;
998
bool is_complete = true;
999
pos_stack.push_back(get_pos());
1000
this->visited = true;
1001
while (!pos_stack.empty())
1003
GridPos curpos = pos_stack.back();
1004
pos_stack.pop_back();
1006
PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(curpos));
1009
cluster.push_back(curpos);
1010
DirectionBits cfaces = pz->get_connections();
1012
if (cfaces==NODIRBIT)
1013
cfaces = DirectionBits(NORTHBIT | SOUTHBIT | EASTBIT | WESTBIT);
1015
if (has_dir(cfaces, NORTH))
1016
is_complete &= visit_dir(pos_stack, curpos, NORTH);
1017
if (has_dir(cfaces, EAST))
1018
is_complete &= visit_dir(pos_stack, curpos, EAST);
1019
if (has_dir(cfaces, SOUTH))
1020
is_complete &= visit_dir(pos_stack, curpos, SOUTH);
1021
if (has_dir(cfaces, WEST))
1022
is_complete &= visit_dir(pos_stack, curpos, WEST);
1027
void PuzzleStone::visit_adjacent (vector<GridPos>& stack, GridPos curpos,
1028
Direction dir, int wanted_oxyd_attrib)
1030
GridPos newpos = move(curpos, dir);
1031
if (PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(newpos))) {
1033
if (wanted_oxyd_attrib == pz->int_attrib("oxyd")) {
1035
stack.push_back(newpos);
1041
/* Use a depth first search to determine the group of all puzzle stones
1042
with the same "oxyd" attrib that are adjacent to the current stone
1043
(or to any other member of the group).
1045
void PuzzleStone::find_adjacents(Cluster &cluster) {
1046
for (unsigned i=0; i<instances.size(); ++i)
1047
instances[i]->visited=false;
1049
vector<GridPos> pos_stack;
1050
pos_stack.push_back(get_pos());
1051
this->visited = true;
1053
int wanted_oxyd_attrib = int_attrib("oxyd");
1055
while (!pos_stack.empty()) {
1056
GridPos curpos = pos_stack.back();
1057
pos_stack.pop_back();
1059
cluster.push_back(curpos);
1060
visit_adjacent(pos_stack, curpos, NORTH, wanted_oxyd_attrib);
1061
visit_adjacent(pos_stack, curpos, SOUTH, wanted_oxyd_attrib);
1062
visit_adjacent(pos_stack, curpos, EAST, wanted_oxyd_attrib);
1063
visit_adjacent(pos_stack, curpos, WEST, wanted_oxyd_attrib);
1067
/* searches from 'startpos' into 'dir' for puzzle-stones.
1068
wanted_oxyd_attrib == -1 -> take any puzzle stone
1069
else -> take only puzzle stones of same type
1072
void PuzzleStone::find_row_or_column_cluster(Cluster &c, GridPos startpos,
1073
Direction dir, int wanted_oxyd_attrib)
1075
assert(dir != NODIR);
1077
GridPos p = startpos;
1078
while (Stone *puzz = dynamic_cast<PuzzleStone*>(GetStone(p))) {
1079
if (wanted_oxyd_attrib != -1 && wanted_oxyd_attrib != puzz->int_attrib("oxyd"))
1080
break; // stop when an unrequested puzzle stone type is readed
1086
bool PuzzleStone::can_move_cluster (Cluster &c, Direction dir)
1088
sort(c.begin(), c.end());
1089
Cluster mc(c); // Moved cluster
1090
Cluster diff; // Difference between mc and c
1092
for (unsigned i=0; i<mc.size(); ++i)
1095
set_difference(mc.begin(), mc.end(), c.begin(), c.end(),
1096
back_inserter(diff));
1098
// Now check whether all stones can be placed at their new
1100
bool move_ok = true;
1101
for (unsigned i=0; i<diff.size(); ++i)
1102
if (GetStone(diff[i]) != 0)
1108
void PuzzleStone::maybe_move_cluster(Cluster &c, bool is_complete,
1109
bool actor_with_wand, Direction dir)
1111
sort(c.begin(), c.end());
1112
Cluster mc(c); // Moved cluster
1113
Cluster diff; // Difference between mc and c
1115
for (unsigned i=0; i<mc.size(); ++i)
1118
set_difference(mc.begin(), mc.end(), c.begin(), c.end(),
1119
back_inserter(diff));
1121
// Now check whether all stones can be placed at their new
1123
bool move_ok = true;
1124
for (unsigned i=0; i<diff.size(); ++i)
1125
if (GetStone(diff[i]) != 0)
1131
// If the floor at a complete cluster's new position consists
1132
// exclusively of abyss or water, create a bridge instead of
1133
// moving the cluster.
1135
// For partial clusters build bridges only on water and if the
1136
// wielded item is NOT the magic wand.
1138
bool create_bridge = true;
1140
for (unsigned i=0; create_bridge && i<mc.size(); ++i) {
1141
if (Floor *fl = GetFloor(mc[i])) {
1142
if (fl->is_kind("fl-abyss")) {
1144
create_bridge = false;
1146
else if (fl->is_kind("fl-water")) {
1147
if (!is_complete && actor_with_wand)
1148
create_bridge = false;
1151
create_bridge = false;
1155
// Finally, either move the whole cluster or create a bridge
1156
sound_event("movebig");
1157
if (create_bridge) {
1158
for (unsigned i=0; i<c.size(); ++i) {
1160
SetFloor(mc[i], MakeFloor("fl-gray"));
1164
vector<Stone*> clusterstones;
1165
for (unsigned i=0; i<c.size(); ++i)
1166
clusterstones.push_back(YieldStone(c[i]));
1168
for (unsigned i=0; i<c.size(); ++i) {
1169
SetStone(mc[i], clusterstones[i]);
1170
clusterstones[i]->on_move();
1174
server::IncMoveCounter(c.size());
1177
bool PuzzleStone::cluster_complete() {
1179
return find_cluster(c);
1182
int PuzzleStone::get_modelno() const {
1183
int modelno = int_attrib("connections");
1184
if (oxyd1_compatible()) modelno += 16;
1188
void PuzzleStone::rotate_cluster(const Cluster &c) {
1189
unsigned size = c.size();
1191
int cn = GetStone(c[size-1])->int_attrib("connections");
1192
for (unsigned i=size-1; i>0; --i) {
1193
PuzzleStone *st = dynamic_cast<PuzzleStone*> (GetStone (c[i]));
1194
st->set_attrib ("connections", GetStone(c[i-1])->int_attrib ("connections"));
1197
GetStone(c[0])->set_attrib ("connections", cn);
1198
dynamic_cast<PuzzleStone*> (GetStone(c[0]))->init_model();
1202
StoneResponse PuzzleStone::collision_response(const StoneContact &/*sc*/) {
1203
if (get_connections() == NODIRBIT)
1205
return STONE_REBOUND;
1208
void PuzzleStone::trigger_explosion(double delay) {
1209
if (state == IDLE) {
1211
GameTimer.set_alarm(this, delay, false);
1215
void PuzzleStone::trigger_explosion_at (GridPos p, double delay,
1216
int wanted_oxyd_attrib)
1218
PuzzleStone *puzz = dynamic_cast<PuzzleStone*>(GetStone(p));
1219
if (puzz && wanted_oxyd_attrib == puzz->int_attrib("oxyd")) {
1220
// explode adjacent puzzle stones of same type
1221
puzz->trigger_explosion(delay);
1225
void PuzzleStone::explode() {
1226
GridPos p = get_pos();
1227
int ox_attr = int_attrib("oxyd");
1229
// exchange puzzle stone with explosion
1230
sound_event("stonedestroy");
1231
SetStone(p, MakeStone("st-explosion"));
1233
// trigger all adjacent puzzle stones :
1234
const double DEFAULT_DELAY = 0.2;
1235
trigger_explosion_at(move(p, NORTH), DEFAULT_DELAY, ox_attr);
1236
trigger_explosion_at(move(p, SOUTH), DEFAULT_DELAY, ox_attr);
1237
trigger_explosion_at(move(p, EAST), DEFAULT_DELAY, ox_attr);
1238
trigger_explosion_at(move(p, WEST), DEFAULT_DELAY, ox_attr);
1240
// @@@ FIXME: At the moment it's possible to push partial puzzle stones
1241
// next to an already exploding cluster. Then the part will explode as well.
1242
// Possible fix : mark whole cluster as "EXPLODING_SOON" when explosion is initiated
1244
// ignite adjacent fields
1245
// SendExplosionEffect(p, DYNAMITE);
1248
void PuzzleStone::alarm() {
1252
void PuzzleStone::message(const string& msg, const Value &val) {
1253
if (msg == "scramble") {
1254
// oxyd levels contain explicit information on how to
1255
// scramble puzzle stones. According to that information
1256
// a "scramble" message is send to specific puzzle stones
1257
// together with information about the direction.
1259
// enigma levels may create scramble messages using
1260
// AddScramble() and SetScrambleIntensity()
1262
Direction dir = to_direction(val);
1264
find_row_or_column_cluster(c, get_pos(), dir, oxyd1_compatible());
1266
int size = c.size();
1268
// warning("received 'scramble'. dir=%s size=%i", to_suffix(dir).c_str(), size);
1271
int count = IntegerRand(0, size-1);
1276
warning("useless scramble (cluster size=%i)", size);
1281
void PuzzleStone::on_impulse(const Impulse& impulse)
1283
// if (!oxyd1_compatible() && state == IDLE) {
1284
if (state == IDLE) {
1286
bool is_complete = find_cluster(c);
1287
bool actor_with_wand = false;
1289
if (Actor *ac = dynamic_cast<Actor*>(impulse.sender))
1290
actor_with_wand = player::wielded_item_is(ac, "it-magicwand");
1292
maybe_move_cluster(c, is_complete, actor_with_wand, impulse.dir);
1296
bool PuzzleStone::explode_complete_cluster()
1298
// @@@ FIXME: explode_complete_cluster should mark the whole cluster
1299
// as "EXPLODING_SOON" (otherwise it may be changed before it explodes completely)
1301
assert(state == IDLE);
1302
bool exploded = false;
1305
if (find_cluster(complete)) {
1307
find_adjacents(all);
1309
// If all adjacent stones build one complete cluster explode it
1310
if (all.size() == complete.size()) {
1311
explode(); // explode complete cluster
1315
assert(all.size() > complete.size());
1316
if (!oxyd1_compatible()) {
1317
// check if 'all' is made up of complete clusters :
1319
sort(all.begin(), all.end());
1322
sort(complete.begin(), complete.end());
1324
// remove one complete cluster from 'all'
1327
set_symmetric_difference(all.begin(), all.end(),
1328
complete.begin(), complete.end(),
1329
back_inserter(rest));
1330
// now rest contains 'all' minus 'complete'
1334
if (all.empty()) { // none left -> all were complete
1339
// look for next complete cluster :
1342
PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(all[0]));
1344
if (!pz->find_cluster(complete)) {
1345
break; // incomplete cluster found -> don't explode
1351
// warning("exploding complete cluster");
1361
bool PuzzleStone::is_floating() const {
1362
return get_connections() == 0;
1365
void PuzzleStone::maybe_rotate_cluster(Direction dir)
1369
find_row_or_column_cluster(c, get_pos(), dir, int_attrib ("oxyd"));
1370
if (c.size() >= 2) {
1371
// warning("ok -> rotate");
1377
void PuzzleStone::on_creation (GridPos p) {
1379
ConnectiveStone::on_creation (p);
1380
illumination = NODIRBIT;
1383
void PuzzleStone::on_removal(GridPos p) {
1385
ConnectiveStone::on_removal(p);
1388
void PuzzleStone::on_laserhit (Direction dir) {
1389
px::set_flags (illumination, to_bits(reverse(dir)));
1392
void PuzzleStone::on_recalc_start() {
1393
illumination = NODIRBIT;
1396
void PuzzleStone::on_recalc_finish() {
1397
if (illumination != (ALL_DIRECTIONS+1) &&
1398
illumination != NODIRBIT &&
1401
if (!explode_complete_cluster() && oxyd1_compatible()) {
1402
if (illumination & NORTHBIT) maybe_rotate_cluster(SOUTH);
1403
if (illumination & SOUTHBIT) maybe_rotate_cluster(NORTH);
1404
if (illumination & EASTBIT) maybe_rotate_cluster(WEST);
1405
if (illumination & WESTBIT) maybe_rotate_cluster(EAST);
1410
void PuzzleStone::actor_hit(const StoneContact &sc)
1412
if (get_connections() == NODIRBIT)
1413
return; // Puzzle stone is hollow
1415
if (state == EXPLODING)
1421
Direction rotate_dir = reverse (contact_face (sc));
1422
Direction move_dir = get_push_direction(sc);
1424
if (oxyd1_compatible()) {
1427
if (explode_complete_cluster())
1430
// 1) If unconnected puzzle stones -> try to move it
1431
if (c.size() == 1 && move_dir != NODIR) {
1432
// if cluster contains single stone
1433
// -> move it if dest pos is free
1434
GridPos dest = move(c[0], move_dir);
1435
if (GetStone(dest) == 0) {
1436
Stone *puzz = YieldStone(c[0]);
1437
SetStone(dest, puzz);
1438
sound_event ("movesmall");
1440
maybe_rotate_cluster (rotate_dir);
1442
// 2) If more than one stone,
1444
maybe_rotate_cluster (rotate_dir);
1449
bool has_magic_wand = player::wielded_item_is(sc.actor, "it-magicwand");
1451
// 1) Try to start explosion of complete cluster
1452
if (has_magic_wand && explode_complete_cluster())
1455
// 2) Failed? Try to move the cluster
1456
if (move_dir != NODIR && can_move_cluster (c, move_dir)) {
1457
sc.actor->send_impulse(get_pos(), move_dir);
1461
// 3) Last chance: try to rotate the row or column
1463
maybe_rotate_cluster (rotate_dir);
1467
void PuzzleStone::actor_contact (Actor *a)
1469
if (state == EXPLODING)
1470
SendMessage (a, "shatter");
1475
/* -------------------- DoorBase -------------------- */
1477
// Base class for everything that behaves like a door, i.e., it has
1478
// four states OPEN, CLOSED, OPENING, CLOSING.
1481
class DoorBase : public Stone {
1483
enum State { OPEN, CLOSED, OPENING, CLOSING } state;
1485
DoorBase(const char *name, State initstate=CLOSED)
1486
: Stone(name), state(initstate)
1489
State get_state() const { return state; }
1490
void set_state(State st) { state=st; }
1493
// DoorBase interface
1494
virtual string model_basename() { return get_kind(); }
1495
virtual void init_model();
1496
virtual string opening_sound() const { return ""; }
1497
virtual string closing_sound() const { return ""; }
1500
void change_state(State newstate) ;
1501
void message(const string &m, const Value &);
1503
StoneResponse collision_response(const StoneContact &sc);
1508
virtual bool is_transparent (Direction) const
1509
{ return state==OPEN; }
1511
virtual bool is_sticky (const Actor *) const
1516
void DoorBase::message(const string &m, const Value &val) {
1517
State newstate = state;
1518
int ival = to_int (val);
1522
else if (m == "close")
1524
else if (m == "openclose")
1525
newstate = (state==OPEN || state==OPENING) ? CLOSING : OPENING;
1526
else if (m == "signal")
1527
newstate = ival > 0 ? OPENING : CLOSING;
1529
if (newstate==OPENING && (state==CLOSED || state==CLOSING))
1530
change_state(OPENING);
1531
else if (newstate==CLOSING && (state==OPEN || state==OPENING))
1532
change_state(CLOSING);
1535
void DoorBase::init_model() {
1536
string mname = model_basename();
1537
if (state == CLOSED)
1539
else if (state==OPEN)
1544
void DoorBase::animcb() {
1545
if (state == OPENING)
1547
else if (state == CLOSING)
1548
change_state(CLOSED);
1552
DoorBase::collision_response(const StoneContact &/*sc*/)
1554
return (state == OPEN) ? STONE_PASS:STONE_REBOUND;
1557
void DoorBase::change_state(State newstate)
1559
string basename = model_basename();
1563
set_model(basename+"-open");
1564
lasers::MaybeRecalcLight(get_pos());
1567
set_model(basename+"-closed");
1568
world::ShatterActorsInsideField (get_pos());
1569
lasers::MaybeRecalcLight(get_pos()); // maybe superfluous
1572
sound_event (opening_sound().c_str());
1573
if (state == CLOSING)
1574
get_model()->reverse();
1576
set_anim(basename+"-opening");
1579
sound_event (closing_sound().c_str());
1580
if (state == OPENING)
1581
get_model()->reverse();
1583
set_anim(basename+"-closing");
1584
world::ShatterActorsInsideField (get_pos());
1585
lasers::MaybeRecalcLight(get_pos());
1588
set_state(newstate);
1592
/* -------------------- Door -------------------- */
1596
// :type h or v for a door that opens horizontally or vertically
1599
class Door : public DoorBase {
1602
Door(const char *type="h", bool open=false)
1603
: DoorBase("st-door", open ? OPEN : CLOSED)
1605
set_attrib("type", type);
1608
virtual string opening_sound() const { return "dooropen"; }
1609
virtual string closing_sound() const { return "doorclose"; }
1610
virtual const char *collision_sound() { return "electric"; }
1611
string get_type() const {
1613
string_attrib("type", &type);
1617
bool is_transparent (Direction) const;
1618
bool is_floating () const {
1619
return true; // don't let door press buttons
1622
void actor_hit(const StoneContact &)
1624
if (Item *it = GetItem (get_pos()))
1625
PerformAction (it, true);
1628
string model_basename() { return string("st-door")+get_type(); }
1629
StoneResponse collision_response(const StoneContact &sc);
1632
class Door_a : public DoorBase {
1635
Door_a() : DoorBase("st-door_a") {}
1638
class Door_b : public DoorBase {
1641
Door_b() : DoorBase("st-door_b") {}
1644
class Door_c : public DoorBase {
1647
Door_c() : DoorBase("st-door_c") {}
1651
bool Door::is_transparent (Direction dir) const {
1652
if (get_type() == "h")
1653
return state==OPEN || dir==EAST || dir==WEST;
1655
return state==OPEN || dir==NORTH || dir==SOUTH;
1659
Door::collision_response(const StoneContact &sc)
1661
Direction cf = contact_face(sc);
1664
else if (state == CLOSING)
1665
return STONE_REBOUND;
1667
string t = get_type();
1668
return ((t == "v" && (cf==WEST || cf==EAST)) ||
1669
(t == "h" && (cf==SOUTH || cf==NORTH)))
1676
/* -------------------- ShogunStone -------------------- */
1683
class ShogunStone : public Stone {
1684
CLONEOBJ(ShogunStone);
1686
enum Holes { SMALL = 1, MEDIUM = 2, LARGE = 4};
1687
static Holes smallest_hole(Holes s);
1688
void set_holes(Holes h) { set_attrib("holes", h); }
1691
ShogunStone(int holes=SMALL) : Stone("st-shogun") {
1692
set_holes(static_cast<Holes>(holes));
1695
Holes get_holes() const;
1698
void message(const string &m, const Value &) {
1699
if (m == "init") { // request from ShogunDot (if set _after_ ShogunStone)
1704
void add_hole(Holes h) {
1705
set_attrib("holes", get_holes() | h);
1710
void on_creation (GridPos p) {
1715
void on_impulse(const Impulse& impulse);
1718
set_model(px::strf("st-shogun%d", int(get_holes())));
1721
bool is_movable() const { return false; }
1723
void actor_hit (const StoneContact &sc) {
1724
maybe_push_stone (sc);
1729
ShogunStone::Holes ShogunStone::get_holes() const {
1730
int h=int_attrib("holes");
1734
warning("Wrong 'holes' attribute (%i)", h);
1739
ShogunStone::Holes ShogunStone::smallest_hole(Holes s) {
1740
if (s & SMALL) return SMALL;
1741
if (s & MEDIUM) return MEDIUM;
1742
if (s & LARGE) return LARGE;
1746
void ShogunStone::notify_item ()
1748
if (Item *it = GetItem(get_pos())) {
1749
switch (get_holes()) {
1750
case SMALL: SendMessage(it, "shogun1"); break;
1751
case (MEDIUM | SMALL): SendMessage(it, "shogun2"); break;
1752
case (LARGE | MEDIUM | SMALL): SendMessage(it, "shogun3"); break;
1753
default: SendMessage(it, "noshogun"); break;
1758
void ShogunStone::on_impulse(const Impulse& impulse) {
1759
GridPos destpos = move(get_pos(), impulse.dir);
1760
Holes holes = get_holes();
1761
Holes smallest = smallest_hole(holes);
1762
ShogunStone *target = 0;
1764
if (Stone *st = GetStone(destpos)) {
1765
target = dynamic_cast<ShogunStone*>(st);
1767
/* If the stone at `p' is not a shogun stone or if smallest hole
1768
does not fit into target, do not transfer the smallest hole. */
1769
if (!target || smallest >= smallest_hole(target->get_holes()))
1773
/* It's important to remove the old stone before setting the new
1774
one: otherwise it is possible to activate two triggers with one
1775
shogun stone when shifting from one shogun target to a second
1776
adjacent shogun target. */
1778
GridPos my_pos = get_pos();
1781
// Remove/modify source stone:
1782
if (Holes newholes = Holes(holes & ~smallest)) {
1783
set_holes(newholes);
1788
string_attrib("name", &old_name); // store name of disappearing stone
1789
SendMessage(GetItem(my_pos), "noshogun");
1793
// Modify/create target stone:
1795
target->add_hole(smallest);
1796
// target->sound_event("st-magic");
1797
// sound::PlaySound("st-magic", my_pos.center()); // object already disappeared
1799
else { // create new
1800
target = new ShogunStone(smallest);
1801
SetStone(destpos, target);
1805
if (!old_name.empty())
1806
NameObject(target, old_name);
1808
server::IncMoveCounter();
1809
sound::SoundEvent ("movesmall", my_pos.center());
1814
/* -------------------- Stone impulse stones -------------------- */
1821
class StoneImpulse_Base : public Stone {
1823
StoneImpulse_Base(const char *kind) : Stone(kind), state(IDLE), incoming(NODIR)
1826
enum State { IDLE, PULSING, CLOSING };
1828
Direction incoming; // direction of incoming impulse (may be NODIR)
1830
void change_state(State st);
1832
virtual void on_impulse(const Impulse& impulse) {
1833
incoming = impulse.dir;
1834
change_state(PULSING);
1839
virtual void notify_state(State st) = 0;
1841
void message(const string &m, const Value &value) {
1843
incoming = (value.get_type() == Value::DOUBLE)
1844
? Direction(value.get_double()+0.1)
1847
change_state(PULSING);
1849
else if (m == "signal" && to_double (value) != 0) {
1851
change_state (PULSING);
1856
if (state == PULSING)
1857
change_state (CLOSING);
1858
else if (state == CLOSING)
1859
change_state (IDLE);
1862
void on_laserhit(Direction dir) {
1864
change_state(PULSING);
1870
void StoneImpulse_Base::change_state(State new_state) {
1871
if (new_state == state) return;
1873
GridPos p = get_pos();
1874
switch (new_state) {
1877
notify_state(state);
1881
if (state != IDLE) {
1882
return; // do not set new state
1885
notify_state(state);
1886
sound_event("impulse");
1889
GridPos targetpos[4];
1892
// set CLOSING model _before_ sending impulses !!!
1893
// (any impulse might have side effects that move this stone)
1896
notify_state(state);
1898
for (int d = 0; d < 4; ++d) {
1899
targetpos[d] = move(p, Direction(d));
1900
haveStone[d] = GetStone(targetpos[d]) != 0;
1903
for (int d = int(incoming)+1; d <= int(incoming)+4; ++d) {
1906
send_impulse(targetpos[D], Direction(D));
1910
incoming = NODIR; // forget impulse direction
1919
class StoneImpulseStone : public StoneImpulse_Base {
1920
CLONEOBJ(StoneImpulseStone);
1922
StoneImpulseStone() : StoneImpulse_Base("st-stoneimpulse")
1926
void notify_state(State st) {
1932
set_anim("st-stoneimpulse-anim1");
1935
set_anim("st-stoneimpulse-anim2");
1940
void actor_hit(const StoneContact &/*sc*/) {
1941
change_state(PULSING);
1947
class HollowStoneImpulseStone : public StoneImpulse_Base {
1948
CLONEOBJ(HollowStoneImpulseStone);
1950
HollowStoneImpulseStone()
1951
: StoneImpulse_Base("st-stoneimpulse-hollow") {}
1953
void notify_state(State st) {
1957
lasers::MaybeRecalcLight(get_pos());
1960
lasers::MaybeRecalcLight(get_pos());
1961
set_anim("st-stoneimpulse-hollow-anim1");
1964
set_anim("st-stoneimpulse-hollow-anim2");
1969
StoneResponse collision_response(const StoneContact &/*sc*/) {
1970
return (state == IDLE) ? STONE_PASS : STONE_REBOUND;
1972
void actor_inside(Actor *a) {
1973
if (state == PULSING || state == CLOSING)
1974
SendMessage(a, "shatter");
1977
bool is_floating () const {
1981
void on_laserhit (Direction) {
1982
// hollow StoneImpulseStones cannot be activated using lasers
1987
class MovableImpulseStone : public StoneImpulse_Base {
1988
CLONEOBJ(MovableImpulseStone);
1990
MovableImpulseStone()
1991
: StoneImpulse_Base("st-stoneimpulse_movable"),
1998
void notify_state(State st) {
2003
change_state(PULSING);
2009
set_anim("st-stoneimpulse-anim1");
2012
set_anim("st-stoneimpulse-anim2");
2018
set_model("st-stoneimpulse");
2023
void actor_hit(const StoneContact &sc) {
2024
if (!maybe_push_stone (sc)) {
2025
incoming = NODIR; // bad, but no real problem!
2027
change_state(PULSING);
2031
void on_impulse(const Impulse& impulse) {
2032
State oldstate = state;
2034
if (move_stone(impulse.dir)) {
2035
notify_state(oldstate); // restart anim if it was animated before move
2037
Actor *hitman = dynamic_cast<Actor*>(impulse.sender);
2038
if (hitman && player::wielded_item_is(hitman, "it-magicwand")) {
2039
return; // do not change state to PULSING
2044
change_state(PULSING);
2048
if (state != PULSING)
2049
repulse = true; // pulse again
2053
bool is_movable() const {
2054
// moving the stone is handled explicitly in actor_hit()
2055
return false; //true;
2064
/* -------------------- Oxyd stone -------------------- */
2066
/** \page st-oxyd Oxyd Stone
2068
Oxyd stones are characterized by two attributes: Their flavor and
2069
their color. The flavor only affects the visual representation of
2070
the stone; it can be either 'a' (opening like a flower) or 'b'
2071
(displaying a fade-in animation). The color attribute determines
2072
the shape on the oxyd stone.
2074
\b Note: You should usually not to create Oxyd stones manually
2075
with \c set_stone(). Use the predefined \c oxyd() function instead.
2077
\subsection oxyda Attributes
2079
- \b flavor "a", "b", "c", or "d"
2080
- \b color number between 0 and 7
2082
\subsection oxydm Messages
2084
- \b closeall close all oxyd stones
2085
- \b shuffle interchange the colors of the oxyd stones in the current landscape
2086
- \b trigger open the stone
2089
<td>\image html st-oxyda.png "flavor A"
2090
<td>\image html st-oxydb.png "flavor B"
2091
<td>\image html st-oxydc.png "flavor C"
2092
<td>\image html st-oxydd.png "flavor D"
2098
class OxydStone : public PhotoStone {
2099
INSTANCELISTOBJ(OxydStone);
2103
static void shuffle_colors();
2105
enum State { CLOSED, OPEN, OPENING, CLOSING, BLINKING };
2109
void actor_hit(const StoneContact &sc);
2110
void on_creation (GridPos p);
2111
void on_removal (GridPos p);
2112
const char *collision_sound() { return "stone"; }
2113
void message(const string &m, const Value &);
2116
// PhotoStone interface
2117
void notify_laseron() { maybe_open_stone(); }
2118
void notify_laseroff() {}
2120
// Animation callback
2124
void maybe_open_stone();
2125
void change_state(State newstate);
2128
static bool blinking(OxydStone *a) {
2129
return (a->state==BLINKING);
2131
static bool blinking_or_opening(OxydStone *a) {
2132
return (a->state==BLINKING || a->state == OPENING);
2134
static bool not_open(OxydStone *a) {
2135
return !(a->state==OPEN || a->state==OPENING);
2141
OxydStone::InstanceList OxydStone::instances;
2143
OxydStone::OxydStone()
2144
: PhotoStone("st-oxyd"),
2147
set_attrib("flavor", "b");
2148
set_attrib("color", "0");
2151
void OxydStone::message(const string &m, const Value &val) {
2152
if (m=="closeall") {
2153
for (unsigned i=0; i<instances.size(); ++i)
2154
instances[i]->change_state(CLOSING);
2156
else if (m=="shuffle")
2158
else if (m=="trigger" || m=="spitter")
2160
else if (m=="signal" && to_int(val) != 0)
2162
else if (m=="init") {
2163
// odd number of oxyd stones in the level? no problem, turn a
2164
// random one into a fake oxyd
2166
if (instances.size() % 2) {
2167
/// "odd number of oxyd stones\n";
2172
void OxydStone::shuffle_colors() {
2173
vector<int> closed_oxyds;
2174
unsigned isize = instances.size();
2175
for (unsigned i=0; i<isize; ++i) {
2176
if (instances[i]->state == CLOSED) {
2177
closed_oxyds.push_back(i);
2181
unsigned size = closed_oxyds.size();
2183
for (unsigned i = 0; i<size; ++i) {
2184
unsigned a = IntegerRand(0, size-2);
2185
if (a >= i) ++a; // make a always different from j
2187
OxydStone *o1 = instances[closed_oxyds[i]];
2188
OxydStone *o2 = instances[closed_oxyds[a]];
2190
string icolor, acolor;
2191
o1->string_attrib("color", &icolor);
2192
o2->string_attrib("color", &acolor);
2194
o1->set_attrib("color", acolor.c_str());
2195
o2->set_attrib("color", icolor.c_str());
2200
void OxydStone::change_state(State newstate)
2202
string flavor = "a";
2204
string_attrib("flavor", &flavor);
2205
string_attrib("color", &color);
2207
string modelname = string("st-oxyd") + flavor + color;
2209
State oldstate = state;
2214
set_model(string("st-oxyd")+flavor);
2218
set_model(modelname + "-blink");
2222
if (oldstate == CLOSED) {
2223
sound_event("oxydopen");
2224
sound_event("oxydopened");
2225
set_anim(modelname+"-opening");
2227
set_model(modelname + "-open");
2229
/* If this was the last closed oxyd stone, finish the
2231
if (find_if(instances.begin(),instances.end(),not_open)
2234
server::FinishLevel();
2239
sound_event("oxydopen");
2240
if (oldstate == CLOSED)
2241
set_anim(modelname + "-opening");
2242
else if (oldstate == CLOSING)
2243
get_model()->reverse();
2248
if (oldstate == CLOSED || oldstate==CLOSING) {
2253
sound_event("oxydclose");
2254
if (oldstate == OPENING)
2255
get_model()->reverse();
2256
else if (oldstate == BLINKING || oldstate == OPEN) {
2257
set_anim(modelname + "-closing");
2263
void OxydStone::animcb() {
2264
if (state == CLOSING)
2265
change_state(CLOSED);
2266
else if (state == OPENING)
2267
change_state(BLINKING);
2268
else if (state == OPEN)
2269
change_state(OPEN); // set the right model
2272
void OxydStone::maybe_open_stone() {
2273
if (state == CLOSED || state == CLOSING) {
2274
int mycolor = int_attrib("color");
2276
// Is another oxyd stone currently blinking?
2277
InstanceList::iterator i;
2278
i=find_if(instances.begin(), instances.end(), blinking_or_opening);
2280
if (i != instances.end()) {
2284
if (server::GameCompatibility != GAMET_ENIGMA) {
2285
// If colors match and stone (*i) is already blinking,
2286
// open both stones. Close one of them otherwise.
2287
// (This is the Oxyd behaviour; it doesn't work with
2288
// some Enigma levels.)
2289
can_open = (mycolor == (*i)->int_attrib("color") && (*i)->state==BLINKING);
2292
can_open = (mycolor == (*i)->int_attrib("color"));
2296
(*i)->change_state(OPEN);
2298
(*i)->change_state(CLOSING);
2299
change_state(OPENING);
2303
// no blinking stone? -> make this one blink
2304
change_state(OPENING);
2309
void OxydStone::actor_hit(const StoneContact &/*sc*/) {
2313
void OxydStone::on_creation (GridPos)
2315
string flavor = "a";
2316
string_attrib("flavor", &flavor);
2317
set_model(string("st-oxyd") + flavor);
2321
void OxydStone::on_removal(GridPos p)
2328
/* -------------------- Turnstiles -------------------- */
2331
class Turnstile_Arm;
2334
** The stone at the center of a turnstile
2336
class Turnstile_Pivot_Base : public Stone {
2338
Turnstile_Pivot_Base(const char *kind);
2341
bool rotate(bool clockwise, Object *impulse_sender);
2343
friend class Turnstile_Arm; // uses rotate
2347
virtual void on_message (const Message &m);
2348
virtual void animcb();
2351
DirectionBits arms_present() const;
2352
bool no_stone (int xoff, int yoff) const;
2353
void set_arm (Direction dir, RBI_vector &rubs);
2354
void remove_arms (DirectionBits arms);
2355
void rotate_arms (DirectionBits arms, bool clockwise);
2356
void handleActorsAndItems(bool clockwise, Object *impulse_sender);
2358
// Turnstile_Pivot_Base interface
2359
virtual const char *model() const = 0;
2360
virtual const char *anim() const = 0;
2361
virtual bool oxyd_compatible() const = 0;
2367
class Turnstile_Pivot : public Turnstile_Pivot_Base {
2368
CLONEOBJ(Turnstile_Pivot);
2370
Turnstile_Pivot() : Turnstile_Pivot_Base(model()) {}
2372
const char *model() const { return "st-turnstile"; }
2373
const char *anim() const { return "st-turnstile-anim"; }
2374
bool oxyd_compatible() const { return true; }
2377
class Turnstile_Pivot_Green : public Turnstile_Pivot_Base {
2378
CLONEOBJ(Turnstile_Pivot_Green);
2380
Turnstile_Pivot_Green() : Turnstile_Pivot_Base(model()) {}
2382
const char *model() const { return "st-turnstile-green"; }
2383
const char *anim() const { return "st-turnstile-green-anim"; }
2384
bool oxyd_compatible() const { return false; }
2388
** The base class for any of the four arms of the turnstile
2390
class Turnstile_Arm : public Stone {
2391
virtual Direction get_dir() const = 0;
2393
void actor_hit(const StoneContact &sc);
2394
void on_impulse(const Impulse& impulse);
2396
Turnstile_Pivot_Base *get_pivot() {
2397
Stone *st = GetStone (move (get_pos(), reverse(get_dir())));
2398
return dynamic_cast<Turnstile_Pivot_Base*>(st);
2401
bool is_movable () const { return true; }
2403
Turnstile_Arm (const char *kind) : Stone(kind)
2407
class Turnstile_N : public Turnstile_Arm {
2408
CLONEOBJ(Turnstile_N);
2410
Turnstile_N(): Turnstile_Arm("st-turnstile-n") {}
2411
Direction get_dir () const { return NORTH; }
2414
class Turnstile_S : public Turnstile_Arm {
2415
CLONEOBJ(Turnstile_S);
2416
Direction get_dir () const { return SOUTH; }
2418
Turnstile_S(): Turnstile_Arm("st-turnstile-s") {}
2421
class Turnstile_E : public Turnstile_Arm {
2422
CLONEOBJ(Turnstile_E);
2423
Direction get_dir () const { return EAST; }
2425
Turnstile_E(): Turnstile_Arm("st-turnstile-e") {}
2428
class Turnstile_W : public Turnstile_Arm {
2429
CLONEOBJ(Turnstile_W);
2430
Direction get_dir () const { return WEST; }
2432
Turnstile_W(): Turnstile_Arm("st-turnstile-w") {}
2438
class Turnstile_Corner : public Stone {
2439
CLONEOBJ(Turnstile_Corner);
2442
set_anim ("st-turnstile-corner");
2445
KillStone(get_pos());
2448
Turnstile_Corner() : Stone("st-turnstile-corner")
2454
/* -------------------- Turnstile_Arm -------------------- */
2456
void Turnstile_Arm::on_impulse(const Impulse& impulse) {
2457
enum Action { ROTL, ROTR, stay };
2458
static Action actions[4][4] = {
2459
{ stay, ROTL, stay, ROTR }, // west arm
2460
{ ROTR, stay, ROTL, stay }, // south arm
2461
{ stay, ROTR, stay, ROTL }, // east arm
2462
{ ROTL, stay, ROTR, stay } // north arm
2465
Turnstile_Pivot_Base *pivot = get_pivot();
2468
Action a = actions[get_dir()][impulse.dir];
2470
pivot->rotate(a == ROTR, impulse.sender); // ROTR is clockwise
2474
// Move arms not attached to a pivot individually
2475
move_stone(impulse.dir);
2479
void Turnstile_Arm::actor_hit(const StoneContact &sc)
2481
maybe_push_stone(sc);
2484
// --------------------------------------------
2485
// Turnstile_Pivot_Base implementation
2486
// --------------------------------------------
2488
Turnstile_Pivot_Base::Turnstile_Pivot_Base(const char *kind)
2493
void Turnstile_Pivot_Base::animcb()
2499
void Turnstile_Pivot_Base::on_message (const Message &m)
2501
if (m.message == "signal") {
2502
int val = to_int (m.value);
2512
Turnstile_Pivot_Base::arms_present() const
2514
DirectionBits arms = NODIRBIT;
2515
GridPos p = get_pos();
2516
if (dynamic_cast<Turnstile_N*>(GetStone(move(p, NORTH))))
2517
px::set_flags (arms, NORTHBIT);
2518
if (dynamic_cast<Turnstile_S*>(GetStone(move(p, SOUTH))))
2519
px::set_flags (arms, SOUTHBIT);
2520
if (dynamic_cast<Turnstile_E*>(GetStone(move(p, EAST))))
2521
px::set_flags (arms, EASTBIT);
2522
if (dynamic_cast<Turnstile_W*>(GetStone(move(p, WEST))))
2523
px::set_flags (arms, WESTBIT);
2527
bool Turnstile_Pivot_Base::no_stone (int xoff, int yoff) const {
2528
GridPos p = get_pos();
2531
return (0 == GetStone(p));
2534
void Turnstile_Pivot_Base::remove_arms (DirectionBits arms) {
2535
GridPos p = get_pos();
2536
if (arms & NORTHBIT) KillStone (move (p, NORTH));
2537
if (arms & EASTBIT) KillStone (move (p, EAST));
2538
if (arms & SOUTHBIT) KillStone (move (p, SOUTH));
2539
if (arms & WESTBIT) KillStone (move (p, WEST));
2542
void Turnstile_Pivot_Base::rotate_arms (DirectionBits arms, bool clockwise) {
2543
GridPos p = get_pos();
2550
if (arms & NORTHBIT) GiveRubberBands(GetStone(move (p, NORTH)), Nrubs);
2551
if (arms & EASTBIT) GiveRubberBands(GetStone(move (p, EAST)), Erubs);
2552
if (arms & SOUTHBIT) GiveRubberBands(GetStone(move (p, SOUTH)), Srubs);
2553
if (arms & WESTBIT) GiveRubberBands(GetStone(move (p, WEST)), Wrubs);
2558
if (arms & NORTHBIT) set_arm(EAST, Nrubs);
2559
if (arms & EASTBIT) set_arm(SOUTH, Erubs);
2560
if (arms & SOUTHBIT) set_arm(WEST, Srubs);
2561
if (arms & WESTBIT) set_arm(NORTH, Wrubs);
2564
if (arms & NORTHBIT) set_arm(WEST, Nrubs);
2565
if (arms & EASTBIT) set_arm(NORTH, Erubs);
2566
if (arms & SOUTHBIT) set_arm(EAST, Srubs);
2567
if (arms & WESTBIT) set_arm(SOUTH, Wrubs);
2571
void Turnstile_Pivot_Base::set_arm (Direction dir, RBI_vector &rubs) {
2572
const char *names[4] = { "st-turnstile-w", "st-turnstile-s",
2573
"st-turnstile-e", "st-turnstile-n" };
2574
Stone *st = MakeStone(names[dir]);
2575
GridPos newp = move(get_pos(), dir);
2576
SetStone (newp, st);
2578
if (Item *it = GetItem(newp))
2579
it->on_stonehit(st);
2582
for (RBI_vector::iterator i = rubs.begin(); i != rubs.end(); ++i)
2583
AddRubberBand (i->act, st, i->data);
2586
bool Turnstile_Pivot_Base::rotate(bool clockwise, Object *impulse_sender) {
2590
DirectionBits arms = arms_present();
2591
bool can_rotate = true;
2594
if (arms & NORTHBIT) {
2595
can_rotate &= no_stone(+1,-1);
2596
if (! (arms & EASTBIT)) can_rotate &= no_stone(+1,0);
2598
if (arms & WESTBIT) {
2599
can_rotate &= no_stone(-1,-1);
2600
if (! (arms & NORTHBIT)) can_rotate &= no_stone(0,-1);
2602
if (arms & SOUTHBIT) {
2603
can_rotate &= no_stone(-1,+1);
2604
if (! (arms & WESTBIT)) can_rotate &= no_stone(-1,0);
2606
if (arms & EASTBIT) {
2607
can_rotate &= no_stone(+1,+1);
2608
if (! (arms & SOUTHBIT)) can_rotate &= no_stone(0,+1);
2612
if (arms & NORTHBIT) {
2613
can_rotate &= no_stone(-1,-1);
2614
if (! (arms & WESTBIT)) can_rotate &= no_stone(-1,0);
2616
if (arms & WESTBIT) {
2617
can_rotate &= no_stone(-1,+1);
2618
if (! (arms & SOUTHBIT)) can_rotate &= no_stone(0,+1);
2620
if (arms & SOUTHBIT) {
2621
can_rotate &= no_stone(+1,+1);
2622
if (! (arms & EASTBIT)) can_rotate &= no_stone(+1,0);
2624
if (arms & EASTBIT) {
2625
can_rotate &= no_stone(+1,-1);
2626
if (! (arms & NORTHBIT)) can_rotate &= no_stone(0,-1);
2632
sound_event ("turnstileright");
2634
sound_event ("turnstileleft");
2635
// if (dynamic_cast<Actor*>(impulse_sender)) {
2637
sound_event("movesmall");
2641
rotate_arms(arms, clockwise);
2642
handleActorsAndItems(clockwise, impulse_sender);
2644
PerformAction (this, 1-clockwise);
2645
server::IncMoveCounter();
2651
bool calc_arm_seen (bool cw, DirectionBits arms, int field) {
2652
// for each field calculate whether an arm has passed by, first
2653
// counterclockwise and then clockwise:
2654
const DirectionBits neededArm[2][8] = {
2655
{WESTBIT, NORTHBIT, NORTHBIT, EASTBIT, EASTBIT, SOUTHBIT, SOUTHBIT, WESTBIT},
2656
{NORTHBIT, NORTHBIT, EASTBIT, EASTBIT, SOUTHBIT, SOUTHBIT, WESTBIT, WESTBIT}
2658
return arms & neededArm[cw][field];
2662
void Turnstile_Pivot_Base::handleActorsAndItems(bool clockwise, Object *impulse_sender) {
2663
const int to_index[3][3] = { // (read this transposed)
2664
{ 0, 7, 6 }, // x == 0
2665
{ 1,-1, 5 }, // x == 1
2666
{ 2, 3, 4 } // x == 2
2668
const int to_x[8] = { -1, 0, 1, 1, 1, 0, -1, -1 };
2669
const int to_y[8] = { -1, -1, -1, 0, 1, 1, 1, 0 };
2672
DirectionBits arms = arms_present(); // Note: already the rotated state
2673
for (int i = 0; i<8; ++i)
2674
arm_seen[i] = calc_arm_seen (clockwise, arms, i);
2676
// ---------- Handle items in range ----------
2677
GridPos pv_pos = get_pos();
2678
for (int i = 0; i<8; ++i)
2680
GridPos item_pos(pv_pos.x+to_x[i], pv_pos.y+to_y[i]);
2681
if (Item *it = GetItem(item_pos))
2682
it->on_stonehit(this); // hit with pivot (shouldn't matter)
2685
// ---------- Handle actors in range ----------
2686
vector<Actor*> actorsInRange;
2688
// tested range is sqrt(sqr(1.5)+sqr(0.5))
2689
// = (radius swept by turnstile) + 19/64 ( = max. actor radius)
2690
if (!GetActorsInRange(pv_pos.center(), 1.879, actorsInRange))
2693
vector<Actor*>::iterator i = actorsInRange.begin(), end = actorsInRange.end();
2694
for (; i != end; ++i) {
2696
const V2 &ac_center = ac->get_pos();
2697
GridPos ac_pos(ac_center);
2698
int dx = ac_pos.x-pv_pos.x;
2699
int dy = ac_pos.y-pv_pos.y;
2701
// ignore if actor is not inside the turnstile
2702
if (dx<-1 || dx>1 || dy<-1 || dy>1)
2705
int idx_source = to_index[dx+1][dy+1];
2706
if (idx_source == -1)
2707
continue; // actor inside pivot -- should not happen
2709
const int rot_index[4][8] = {
2710
{ 6, 0, 0, 2, 2, 4, 4, 6 }, // anticlockwise
2711
{ 2, 2, 4, 4, 6, 6, 0, 0 }, // clockwise
2712
{ 6, -1, 0, -1, 2, -1, 4, -1 }, // anticlockwise (oxyd-compatible)
2713
{ 2, -1, 4, -1, 6, -1, 0, -1 }, // clockwise (oxyd-compatible)
2716
bool compatible = oxyd_compatible();
2717
int idx_target = rot_index[clockwise+2*compatible][idx_source]; // destination index
2718
bool do_warp = arm_seen[idx_source]; // move the actor along with the turnstile?
2721
// Move only the actor that hit the turnstile in Oxyd mode
2722
do_warp = (ac == dynamic_cast<Actor*>(impulse_sender));
2723
if (!do_warp && arm_seen[idx_source])
2724
SendMessage(ac, "shatter"); // hit by an arm
2730
GridPos ac_target_pos(pv_pos.x+to_x[idx_target], pv_pos.y+to_y[idx_target]);
2731
world::WarpActor(ac, ac_target_pos.x+.5, ac_target_pos.y+.5, false);
2733
if (Stone *st = GetStone(ac_target_pos)) {
2735
// destination is blocked
2737
Turnstile_Arm *arm = dynamic_cast<Turnstile_Arm*>(st);
2738
if (arm && !compatible) { // if blocking stone is turnstile arm -> hit it!
2739
const int impulse_dir[2][8] = {
2741
{ SOUTHBIT|WESTBIT, WESTBIT, NORTHBIT|WESTBIT, NORTHBIT,
2742
NORTHBIT|EASTBIT, EASTBIT, SOUTHBIT|EASTBIT, SOUTHBIT },
2744
{ NORTHBIT|EASTBIT, EASTBIT, SOUTHBIT|EASTBIT, SOUTHBIT,
2745
SOUTHBIT|WESTBIT, WESTBIT, NORTHBIT|WESTBIT, NORTHBIT }
2748
DirectionBits possible_impulses =
2749
static_cast<DirectionBits>(impulse_dir[clockwise][idx_target]);
2751
for (int d = 0; d<4; ++d)
2752
if (has_dir(possible_impulses, Direction(d)))
2753
ac->send_impulse(ac_target_pos, Direction(d));
2755
// if (GetStone(ac_target_pos) == 0) // arm disappeared
2761
// @@@ FIXME: it's possible that two actors are moved to the same
2762
// destination field. In that case the second actor is put on top of
2763
// the first actor (happens only in non-oxyd-compat-mode with three
2764
// balls or pullers/impulsestones)
2766
// Note: With black and whiteball it's normally no problem, because
2767
// when one of the actors was moving, it's looking natural. Problems
2768
// occur when small balls come into play.
2773
/* -------------------- Mail stone -------------------- */
2777
class MailStone : public Stone {
2778
CLONEOBJ(MailStone);
2782
MailStone (const char *kind, Direction dir);
2783
void actor_hit (const StoneContact &sc);
2785
GridPos find_pipe_endpoint();
2787
static void setup();
2791
void MailStone::setup()
2793
Register (new MailStone ("st-mail-n", NORTH));
2794
Register (new MailStone ("st-mail-e", EAST));
2795
Register (new MailStone ("st-mail-s", SOUTH));
2796
Register (new MailStone ("st-mail-w", WEST));
2799
MailStone::MailStone (const char *kind, Direction dir)
2800
: Stone(kind), m_dir(dir)
2804
void MailStone::actor_hit (const StoneContact &sc)
2806
if (player::Inventory *inv = player::GetInventory(sc.actor)) {
2807
if (Item *it = inv->get_item(0)) {
2808
GridPos p = find_pipe_endpoint();
2809
if (world::IsInsideLevel(p) && it->can_drop_at (p)) {
2810
it = inv->yield_first();
2811
it->drop(sc.actor, p);
2817
GridPos MailStone::find_pipe_endpoint()
2819
GridPos p = get_pos();
2820
Direction move_dir = m_dir;
2822
while (move_dir != NODIR) {
2824
if (Item *it = world::GetItem(p)) {
2825
switch (get_id(it)) {
2827
if (!(move_dir == EAST || move_dir == WEST))
2831
if (!(move_dir == SOUTH || move_dir == NORTH))
2835
if (move_dir == SOUTH) move_dir = EAST;
2836
else if (move_dir == WEST) move_dir = NORTH;
2837
else move_dir = NODIR;
2840
if (move_dir == NORTH) move_dir = EAST;
2841
else if (move_dir == WEST) move_dir = SOUTH;
2842
else move_dir = NODIR;
2845
if (move_dir == NORTH) move_dir = WEST;
2846
else if (move_dir == EAST) move_dir = SOUTH;
2847
else move_dir = NODIR;
2850
if (move_dir == SOUTH) move_dir = WEST;
2851
else if (move_dir == EAST) move_dir = NORTH;
2852
else move_dir = NODIR;
2855
move_dir = NODIR;; // end of pipe reached
2863
// --------------------------------------------------------------------------------
2865
void stones::Init_complex()
2867
Register(new BolderStone);
2868
Register("st-bolder-n", new BolderStone(NORTH));
2869
Register("st-bolder-e", new BolderStone(EAST));
2870
Register("st-bolder-s", new BolderStone(SOUTH));
2871
Register("st-bolder-w", new BolderStone(WEST));
2873
Register(new BlockerStone(true));
2874
Register(new BlockerStone(false));
2877
Register("st-door-h", new Door("h"));
2878
Register("st-door-v", new Door("v"));
2879
Register("st-door-h-open", new Door("h", true));
2880
Register("st-door-v-open", new Door("v", true));
2881
Register(new Door_a);
2882
Register(new Door_b);
2883
Register(new Door_c);
2885
Register(new HollowStoneImpulseStone);
2889
Register(new MovableImpulseStone);
2891
Register(new OneWayStone);
2892
Register("st-oneway-n", new OneWayStone(NORTH));
2893
Register("st-oneway-e", new OneWayStone(EAST));
2894
Register("st-oneway-s", new OneWayStone(SOUTH));
2895
Register("st-oneway-w", new OneWayStone(WEST));
2896
Register(new OneWayStone_black);
2897
Register("st-oneway_black-n", new OneWayStone_black(NORTH));
2898
Register("st-oneway_black-e", new OneWayStone_black(EAST));
2899
Register("st-oneway_black-s", new OneWayStone_black(SOUTH));
2900
Register("st-oneway_black-w", new OneWayStone_black(WEST));
2901
Register(new OneWayStone_white);
2902
Register("st-oneway_white-n", new OneWayStone_white(NORTH));
2903
Register("st-oneway_white-e", new OneWayStone_white(EAST));
2904
Register("st-oneway_white-s", new OneWayStone_white(SOUTH));
2905
Register("st-oneway_white-w", new OneWayStone_white(WEST));
2907
Register(new OxydStone);
2909
Register (new PullStone);
2911
Register( new VolcanoStone);
2912
Register("st-volcano_inactive", new VolcanoStone(VolcanoStone::INACTIVE));
2913
Register("st-volcano_active", new VolcanoStone(VolcanoStone::ACTIVE));
2915
// PerOxyd/Enigma compatible puzzle stones:
2916
Register(new PuzzleStone(0, false));
2917
Register("st-puzzle-hollow", new PuzzleStone(1, false));
2918
Register("st-puzzle-n", new PuzzleStone(9, false));
2919
Register("st-puzzle-e", new PuzzleStone(5, false));
2920
Register("st-puzzle-s", new PuzzleStone(3, false));
2921
Register("st-puzzle-w", new PuzzleStone(2, false));
2922
Register("st-puzzle-ne", new PuzzleStone(13, false));
2923
Register("st-puzzle-nw", new PuzzleStone(10, false));
2924
Register("st-puzzle-es", new PuzzleStone(7, false));
2925
Register("st-puzzle-sw", new PuzzleStone(4, false));
2926
Register("st-puzzle-ns", new PuzzleStone(11, false));
2927
Register("st-puzzle-ew", new PuzzleStone(6, false));
2928
Register("st-puzzle-nes", new PuzzleStone(15, false));
2929
Register("st-puzzle-new", new PuzzleStone(14, false));
2930
Register("st-puzzle-nsw", new PuzzleStone(12, false));
2931
Register("st-puzzle-esw", new PuzzleStone(8, false));
2932
Register("st-puzzle-nesw", new PuzzleStone(16, false));
2934
// Oxyd1 compatible puzzle stones:
2935
Register("st-puzzle2-hollow", new PuzzleStone(1, true));
2936
Register("st-puzzle2-n", new PuzzleStone(9, true));
2937
Register("st-puzzle2-e", new PuzzleStone(5, true));
2938
Register("st-puzzle2-s", new PuzzleStone(3, true));
2939
Register("st-puzzle2-w", new PuzzleStone(2, true));
2940
Register("st-puzzle2-ne", new PuzzleStone(13, true));
2941
Register("st-puzzle2-nw", new PuzzleStone(10, true));
2942
Register("st-puzzle2-es", new PuzzleStone(7, true));
2943
Register("st-puzzle2-sw", new PuzzleStone(4, true));
2944
Register("st-puzzle2-ns", new PuzzleStone(11, true));
2945
Register("st-puzzle2-ew", new PuzzleStone(6, true));
2946
Register("st-puzzle2-nes", new PuzzleStone(15, true));
2947
Register("st-puzzle2-new", new PuzzleStone(14, true));
2948
Register("st-puzzle2-nsw", new PuzzleStone(12, true));
2949
Register("st-puzzle2-esw", new PuzzleStone(8, true));
2950
Register("st-puzzle2-nesw", new PuzzleStone(16, true));
2952
Register("st-bigbrick", new BigBrick(1));
2953
Register("st-bigbrick-n", new BigBrick(9));
2954
Register("st-bigbrick-e", new BigBrick(5));
2955
Register("st-bigbrick-s", new BigBrick(3));
2956
Register("st-bigbrick-w", new BigBrick(2));
2957
Register("st-bigbrick-ne", new BigBrick(13));
2958
Register("st-bigbrick-nw", new BigBrick(10));
2959
Register("st-bigbrick-es", new BigBrick(7));
2960
Register("st-bigbrick-sw", new BigBrick(4));
2961
Register("st-bigbrick-ns", new BigBrick(11));
2962
Register("st-bigbrick-ew", new BigBrick(6));
2963
Register("st-bigbrick-nes", new BigBrick(15));
2964
Register("st-bigbrick-new", new BigBrick(14));
2965
Register("st-bigbrick-nsw", new BigBrick(12));
2966
Register("st-bigbrick-esw", new BigBrick(8));
2967
Register("st-bigbrick-nesw", new BigBrick(16));
2969
Register ("st-rotator-right", new RotatorStone(true, false));
2970
Register ("st-rotator-left", new RotatorStone(false, false));
2971
Register ("st-rotator_move-right", new RotatorStone(true, true));
2972
Register ("st-rotator_move-left", new RotatorStone(false, true));
2974
Register(new ShogunStone);
2975
Register("st-shogun-s", new ShogunStone(1));
2976
Register("st-shogun-m", new ShogunStone(2));
2977
Register("st-shogun-sm", new ShogunStone(3));
2978
Register("st-shogun-l", new ShogunStone(4));
2979
Register("st-shogun-sl", new ShogunStone(5));
2980
Register("st-shogun-ml", new ShogunStone(6));
2981
Register("st-shogun-sml", new ShogunStone(7));
2983
Register(new StoneImpulseStone);
2985
Register (new Turnstile_Pivot); // oxyd-comaptible
2986
Register (new Turnstile_Pivot_Green); // not oxyd-comaptible
2987
Register (new Turnstile_N);
2988
Register (new Turnstile_S);
2989
Register (new Turnstile_E);
2990
Register (new Turnstile_W);