~ubuntu-branches/ubuntu/edgy/enigma/edgy

« back to all changes in this revision

Viewing changes to src/stones_complex.cc

  • Committer: Bazaar Package Importer
  • Author(s): Erich Schubert
  • Date: 2005-08-28 15:30:09 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20050828153009-sky64kb6tcq37xt5
Tags: 0.92.1-1
* New upstream subversion checkout
* Remove menu.s3m, which we are allowed to distributed but not to modify
  also copyright notice is confusing... (Closes: #321669)
* Rebuild with new libzipios (Closes: #325405)
  I hope this works without a versioned build-dependency
* Added "enigma replaces enigma-data" for upgrades (Closes: #308558)
* Added notes about the fonts copyright.
* updated to policy 3.6.2.1 (no changes)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * Copyright (C) 2002,2003,2004 Daniel Heck
3
 
 *
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.
8
 
 *
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.
13
 
 *
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.
17
 
 *
18
 
 * $Id: stones_complex.cc,v 1.59 2004/05/22 13:04:27 dheck Exp $
19
 
 */
20
 
 
21
 
#include "laser.hh"
22
 
#include "sound.hh"
23
 
#include "server.hh"
24
 
#include "player.hh"
25
 
 
26
 
#include "stones_internal.hh"
27
 
 
28
 
#include "px/tools.hh"
29
 
 
30
 
#include <algorithm>
31
 
#include <cassert>
32
 
#include <iostream>
33
 
 
34
 
using namespace std;
35
 
using namespace world;
36
 
using namespace stones;
37
 
 
38
 
 
39
 
/* -------------------- RotatorStone -------------------- */
40
 
namespace
41
 
{
42
 
    class RotatorStone : public Stone {
43
 
    public:
44
 
        RotatorStone(bool clockwise_, bool movable_)
45
 
        : Stone("st-rotator"), clockwise(clockwise_), movable(movable_)
46
 
        {}
47
 
 
48
 
    private:
49
 
        static const double RATE;
50
 
        static const double IMPULSE_DELAY;
51
 
 
52
 
        bool clockwise;
53
 
        bool movable;
54
 
 
55
 
        Stone *clone() { return new RotatorStone(clockwise, movable); }
56
 
        void dispose() { delete this; }
57
 
 
58
 
        void send_impulses() {
59
 
            GridPos p = get_pos();
60
 
 
61
 
            if (clockwise) {
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);
66
 
            } else {
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);
71
 
            }
72
 
        }
73
 
 
74
 
 
75
 
        void init_model() {
76
 
            set_anim(clockwise ? "st-rotator-right" : "st-rotator-left");
77
 
        }
78
 
        void animcb() {
79
 
            init_model();
80
 
            send_impulses();
81
 
        }
82
 
 
83
 
        bool is_movable () const { return movable; }
84
 
 
85
 
        void actor_hit (const StoneContact &sc) {
86
 
            if (player::wielded_item_is(sc.actor, "it-wrench")) {
87
 
                clockwise = !clockwise;
88
 
                init_model();
89
 
            }
90
 
 
91
 
            if (movable)
92
 
                maybe_push_stone(sc);
93
 
        }
94
 
 
95
 
        void on_impulse(const Impulse& impulse) {
96
 
            if (movable)
97
 
                move_stone(impulse.dir);
98
 
        }
99
 
 
100
 
        void on_laserhit(Direction) {
101
 
            clockwise = !clockwise;
102
 
            init_model();
103
 
        }
104
 
    };
105
 
 
106
 
    const double RotatorStone::RATE          = 1.0;
107
 
    const double RotatorStone::IMPULSE_DELAY = 0.1;
108
 
}
109
 
 
110
 
 
111
 
/* -------------------- PullStone -------------------- */
112
 
 
113
 
// When pushed this stone acts like pulled.
114
 
// When pushed by an actor it exchanges its position with the actor.
115
 
 
116
 
namespace
117
 
{
118
 
    struct PulledActor {
119
 
        // Variables
120
 
        Actor *actor;
121
 
        V2     speed;
122
 
 
123
 
        // Constructor
124
 
        PulledActor(Actor *actor_, const V2& speed_)
125
 
        : actor(actor_), speed(speed_)
126
 
        {
127
 
        }
128
 
 
129
 
    };
130
 
 
131
 
    class PullInfo {
132
 
        list<PulledActor>  actors;
133
 
        YieldedGridStone  *ystone;
134
 
 
135
 
    public:
136
 
        PullInfo(Stone *st)
137
 
        : ystone(st ? new YieldedGridStone(st) : 0)
138
 
        {}
139
 
        ~PullInfo() { delete ystone; }
140
 
 
141
 
        void add_actor(Actor *actor, const V2& speed) {
142
 
            actors.push_back(PulledActor(actor, speed));
143
 
        }
144
 
 
145
 
        list<PulledActor>& get_actors() { return actors; }
146
 
 
147
 
        void set_stone(GridPos pos) { if (ystone) ystone->set_stone(pos); }
148
 
        void dispose() { if (ystone) ystone->dispose(); }
149
 
    };
150
 
 
151
 
 
152
 
    class PullStone : public Stone, public TimeHandler {
153
 
        // Variables.
154
 
        enum State { IDLE, MOVING, VANISHED } state;
155
 
        Direction  m_movedir;
156
 
        PullInfo  *pull_info;   // information about moved objects (only valid during pull)
157
 
 
158
 
    public:
159
 
        PullStone();
160
 
        ~PullStone();
161
 
 
162
 
    private:
163
 
        // Object interface.
164
 
        PullStone *clone();
165
 
        void       dispose();
166
 
 
167
 
        // Stone interface.
168
 
        bool is_movable () const {
169
 
            return state == IDLE;
170
 
        }
171
 
        void actor_hit(const StoneContact &sc) {
172
 
            if (state == IDLE)
173
 
                maybe_push_stone(sc);
174
 
        }
175
 
        void on_impulse(const Impulse& impulse);
176
 
        bool is_removable() const {
177
 
            return state == IDLE;
178
 
        }
179
 
 
180
 
        // TimeHandler interface.
181
 
        void alarm();
182
 
 
183
 
        // Functions.
184
 
        void change_state(State new_state);
185
 
        void set_move_state(bool appearing, Direction move_dir);
186
 
 
187
 
    };
188
 
}
189
 
 
190
 
PullStone::PullStone()
191
 
: Stone("st-pull"), state(IDLE), m_movedir(NODIR) , pull_info(0)
192
 
{}
193
 
 
194
 
PullStone::~PullStone() {
195
 
    delete pull_info;
196
 
}
197
 
 
198
 
PullStone *PullStone::clone() {
199
 
    PullStone *other = new PullStone(*this);
200
 
    other->pull_info = 0;
201
 
    return other;
202
 
}
203
 
 
204
 
void PullStone::dispose() {
205
 
    if (state == MOVING && pull_info != 0)
206
 
        pull_info->dispose();
207
 
    delete this;
208
 
}
209
 
 
210
 
void PullStone::set_move_state (bool appearing, Direction move_dir) {
211
 
    if (appearing) {
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);
216
 
    }
217
 
    else
218
 
        m_movedir = reverse(move_dir);
219
 
    change_state(MOVING);
220
 
}
221
 
 
222
 
void PullStone::change_state (State new_state) {
223
 
    switch (new_state) {
224
 
    case IDLE: set_model("st-pull"); break;
225
 
    case MOVING: {
226
 
            string mname = string("st-pull") + to_suffix(m_movedir);
227
 
            set_model(mname);
228
 
            break;
229
 
        }
230
 
    case VANISHED: break;
231
 
    }
232
 
    state = new_state;
233
 
}
234
 
 
235
 
void PullStone::alarm() {
236
 
    assert(state == MOVING);
237
 
    GridPos oldpos = move (get_pos(), reverse(m_movedir));
238
 
 
239
 
    // remove the disappearing half of the PullStone :
240
 
    PullStone *oldStone = dynamic_cast<PullStone*>(GetStone(oldpos));
241
 
    assert(oldStone);
242
 
    oldStone->change_state(VANISHED);
243
 
    KillStone(oldpos);
244
 
 
245
 
    if (pull_info) {            // have other objects been moved ?
246
 
        pull_info->set_stone(oldpos); // re-sets any pulled stone
247
 
 
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;
253
 
        }
254
 
        delete pull_info;
255
 
        pull_info = 0;
256
 
    }
257
 
 
258
 
    change_state(IDLE);
259
 
}
260
 
 
261
 
void PullStone::on_impulse(const Impulse& impulse) 
262
 
{
263
 
    if (state != IDLE)
264
 
        return;
265
 
 
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);
270
 
 
271
 
    if (other_stone &&
272
 
        (!other_stone->is_removable() || IsLevelBorder(newPos))) {
273
 
        return;                 // avoid unremoveable and border stones
274
 
    }
275
 
 
276
 
    PullStone *newStone = this->clone();
277
 
 
278
 
    if (other_stone) {
279
 
        // yield other_stone:
280
 
        newStone->pull_info = new PullInfo(other_stone);
281
 
    }
282
 
    else {
283
 
        newStone->pull_info = new PullInfo(0);
284
 
    }
285
 
 
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) {
292
 
        Actor     *actor     = *i;
293
 
        ActorInfo *ai        = actor->get_actorinfo();
294
 
        GridPos    actor_pos(ai->pos);
295
 
 
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)
298
 
            V2 vel      = ai->vel;
299
 
            V2 mid_dest = ai->pos+warp; // position during move
300
 
 
301
 
            WarpActor(actor, mid_dest[0], mid_dest[1], false);
302
 
 
303
 
//             if (length(vel) < 15.0) // speed up actor
304
 
//                 vel += 1.7*warp;
305
 
 
306
 
            newStone->pull_info->add_actor(actor, vel);
307
 
        }
308
 
    }
309
 
 
310
 
    SetStone(newPos, newStone);
311
 
    newStone->set_move_state(true, move_dir);
312
 
    set_move_state(false, move_dir);
313
 
 
314
 
    sound_event("moveslow");
315
 
}
316
 
 
317
 
 
318
 
/* -------------------- Oneway stones -------------------- */
319
 
 
320
 
// These stone can only be passed in one direction.
321
 
 
322
 
namespace
323
 
{
324
 
    class OneWayBase : public Stone {
325
 
    protected:
326
 
        OneWayBase(const char *kind, Direction dir);
327
 
 
328
 
        void init_model();
329
 
        void message(const string& msg, const Value &val);
330
 
 
331
 
        void actor_hit (const StoneContact&);
332
 
        StoneResponse collision_response(const StoneContact &sc);
333
 
        bool is_floating() const { return true; }
334
 
 
335
 
        Direction get_orientation() const {
336
 
            return Direction(int_attrib("orientation"));
337
 
        }
338
 
        void set_orientation(Direction dir) {
339
 
            set_attrib("orientation", Value(dir));
340
 
        }
341
 
 
342
 
        virtual bool actor_may_pass (Actor *a) = 0;
343
 
    };
344
 
 
345
 
    class OneWayStone : public OneWayBase {
346
 
    public:
347
 
        OneWayStone(Direction dir=SOUTH) : OneWayBase("st-oneway", dir) {}
348
 
    private:
349
 
        CLONEOBJ(OneWayStone);
350
 
        virtual bool actor_may_pass (Actor */*a*/) { return true; }
351
 
    };
352
 
 
353
 
 
354
 
    class OneWayStone_black : public OneWayBase {
355
 
    public:
356
 
        OneWayStone_black(Direction dir=SOUTH)
357
 
            : OneWayBase("st-oneway_black",dir) {}
358
 
    private:
359
 
        CLONEOBJ(OneWayStone_black);
360
 
        virtual bool actor_may_pass (Actor *a) {
361
 
            return a->get_attrib("blackball") != 0;
362
 
        }
363
 
        void actor_hit (const StoneContact&) {
364
 
            // do nothing if hit by actor
365
 
        }
366
 
    };
367
 
 
368
 
    class OneWayStone_white : public OneWayBase {
369
 
    public:
370
 
        OneWayStone_white(Direction dir=SOUTH)
371
 
            : OneWayBase("st-oneway_white", dir) {}
372
 
    private:
373
 
        CLONEOBJ(OneWayStone_white);
374
 
        virtual bool actor_may_pass (Actor *a) {
375
 
            return a->get_attrib("whiteball") != 0;
376
 
        }
377
 
        void actor_hit (const StoneContact&) {
378
 
            // do nothing if hit by actor
379
 
        }
380
 
    };
381
 
}
382
 
 
383
 
OneWayBase::OneWayBase(const char *kind, Direction dir) 
384
 
: Stone(kind)
385
 
{
386
 
    set_orientation(dir);
387
 
}
388
 
 
389
 
void OneWayBase::init_model()
390
 
{
391
 
    string mname = get_kind();
392
 
    mname += to_suffix(get_orientation());
393
 
    set_model (mname);
394
 
}
395
 
 
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));
399
 
        init_model();
400
 
    }
401
 
    else if (msg == "signal" || msg == "flip") {
402
 
        Direction dir = get_orientation();
403
 
        set_orientation(reverse(dir));
404
 
        init_model();
405
 
    }
406
 
}
407
 
 
408
 
void OneWayBase::actor_hit(const StoneContact &sc) {
409
 
    Direction o=get_orientation();
410
 
 
411
 
    if (has_dir(contact_faces(sc), o)) {
412
 
        if (player::wielded_item_is(sc.actor, "it-magicwand")) {
413
 
            set_orientation(reverse(o));
414
 
            init_model();
415
 
        }
416
 
    }
417
 
}
418
 
 
419
 
StoneResponse OneWayBase::collision_response(const StoneContact &sc) {
420
 
    DirectionBits dirs=contact_faces(sc);
421
 
    Direction o=get_orientation();
422
 
 
423
 
    if (!sc.actor->is_flying() && actor_may_pass(sc.actor))
424
 
        return has_dir(dirs,o) ? STONE_REBOUND : STONE_PASS;
425
 
    else
426
 
        return STONE_REBOUND;
427
 
}
428
 
 
429
 
 
430
 
/* -------------------- BolderStone -------------------- */
431
 
 
432
 
/** \page st-bolder Bolder Stone
433
 
 
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
437
 
oxyd stones.
438
 
 
439
 
\subsection boldera Attributes
440
 
 
441
 
- \c direction  \n NORTH, EAST, SOUTH, WEST
442
 
 
443
 
*/
444
 
namespace
445
 
{
446
 
    class BolderStone : public Stone {
447
 
        CLONEOBJ(BolderStone);
448
 
    public:
449
 
        BolderStone(Direction dir=NORTH)
450
 
        : Stone("st-bolder"), state(IDLE)
451
 
        {
452
 
            set_attrib("direction", dir);
453
 
            // do not use set_dir, because this will set the state to ACTIVE
454
 
        }
455
 
 
456
 
    private:
457
 
        enum State {
458
 
            ACTIVE,             // may send trigger into direction
459
 
            IDLE,               // already sent trigger w/o success
460
 
            FALLING             // falling into abyss
461
 
        } state;
462
 
 
463
 
 
464
 
        Direction get_dir() const {
465
 
            return static_cast<Direction>(int_attrib("direction"));
466
 
        }
467
 
        void set_dir(Direction d) {
468
 
            if (d != get_dir())
469
 
                state = ACTIVE; // if turned by it-magicwand -> allow triggering
470
 
            set_attrib("direction", d);
471
 
        }
472
 
 
473
 
        void on_floor_change() {
474
 
            Floor *fl = GetFloor(get_pos());
475
 
            if (fl->is_kind("fl-abyss")) {
476
 
                state = FALLING;
477
 
                init_model();
478
 
            }
479
 
        }
480
 
 
481
 
        bool have_obstacle (Direction dir) {
482
 
            return GetStone(move(get_pos(), dir)) != 0;
483
 
        }
484
 
 
485
 
        void trigger_obstacle (Direction dir) {
486
 
            if (Stone *st = GetStone(move(get_pos(), dir))) {
487
 
                SendMessage(st, "trigger", Value(dir));
488
 
            }
489
 
        }
490
 
 
491
 
        void on_move() {
492
 
            state = ACTIVE;
493
 
            trigger_obstacle(get_dir());
494
 
            Stone::on_move();
495
 
        }
496
 
 
497
 
        // Stone interface.
498
 
        void init_model() {
499
 
            string mname  = "st-bolder" + to_suffix(get_dir());
500
 
            if (state == FALLING)
501
 
                mname += "-fall-anim";
502
 
            set_anim(mname);
503
 
        }
504
 
 
505
 
        void animcb() {
506
 
            display::Model *m = get_model();
507
 
            Direction dir = get_dir();
508
 
            switch (state) {
509
 
            case FALLING:
510
 
                KillStone(get_pos());
511
 
//                init_model();
512
 
                break;
513
 
 
514
 
            case IDLE:
515
 
                if (!have_obstacle(dir)) {
516
 
                    state = ACTIVE;
517
 
                    trigger_obstacle(dir);
518
 
                }
519
 
//                 if (Model *m = get_model())
520
 
                m->restart();
521
 
//                init_model();
522
 
                break;
523
 
 
524
 
            case ACTIVE: {
525
 
                trigger_obstacle(dir);
526
 
                if (!move_stone(dir)) {
527
 
//                    if (state == MOVING) // state may be FALLING
528
 
                        state = IDLE;
529
 
                }
530
 
                init_model();
531
 
                break;
532
 
            }
533
 
            }
534
 
        }
535
 
 
536
 
        bool is_movable() const { return state != FALLING; }
537
 
 
538
 
        void actor_hit(const StoneContact &sc) {
539
 
            if (player::wielded_item_is(sc.actor, "it-magicwand")) {
540
 
                set_dir(reverse(get_dir()));
541
 
                init_model();
542
 
            }
543
 
        }
544
 
 
545
 
        void on_laserhit(Direction) {
546
 
            set_dir(reverse(get_dir()));
547
 
            init_model();
548
 
 
549
 
            // @@@ FIXME:  the direction should only be inverted on NEW laserbeam
550
 
            // not on every light-recalc. Need to use PhotoCell!
551
 
        }
552
 
 
553
 
        void on_impulse (const Impulse& impulse) {
554
 
            if (state == FALLING)
555
 
                return;
556
 
 
557
 
            if (impulse.sender && impulse.sender->is_kind("st-rotator")) {
558
 
                set_dir(impulse.dir);
559
 
            }
560
 
            init_model();
561
 
            move_stone(impulse.dir);
562
 
        }
563
 
 
564
 
        void message(const string& msg, const Value &val) {
565
 
            if (msg == "direction" && state != FALLING) {
566
 
                set_dir (to_direction(val));
567
 
                init_model();
568
 
            }
569
 
        }
570
 
    };
571
 
}
572
 
 
573
 
 
574
 
/* -------------------- BlockerStone -------------------- */
575
 
 
576
 
/** \page st-blocker Blocker Stone
577
 
 
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.
580
 
 
581
 
*/
582
 
 
583
 
namespace
584
 
{
585
 
    class BlockerStone : public Stone {
586
 
        CLONEOBJ(BlockerStone);
587
 
    public:
588
 
        BlockerStone(bool solid)
589
 
        : Stone(solid ? "st-blocker" : "st-blocker-growing"), 
590
 
          state(solid ? SOLID : GROWING)
591
 
        {}
592
 
 
593
 
    private:
594
 
        enum State { SOLID, SHRINKING, GROWING } state;
595
 
 
596
 
        void init_model() {
597
 
            switch (state) {
598
 
            case SOLID:
599
 
                set_model("st-blocker");
600
 
                break;
601
 
 
602
 
            case SHRINKING:
603
 
                set_anim("st-blocker-shrinking");
604
 
                break;
605
 
 
606
 
            case GROWING:
607
 
                set_anim("st-blocker-growing");
608
 
                break;
609
 
            }
610
 
        }
611
 
 
612
 
        void change_state(State newState) {
613
 
            if (state != newState) {
614
 
                if (state == GROWING && newState == SHRINKING) {
615
 
                    state = SHRINKING;
616
 
                    get_model()->reverse();
617
 
                }
618
 
                else if (state == SHRINKING && newState == GROWING) {
619
 
                    state = GROWING;
620
 
                    get_model()->reverse();
621
 
                }
622
 
                else {
623
 
                    state = newState;
624
 
                    init_model();
625
 
                    if (newState == SOLID) {
626
 
                        set_attrib("kind", "st-blocker");
627
 
                    }
628
 
                }
629
 
            }
630
 
        }
631
 
 
632
 
        void animcb() {
633
 
            switch (state) {
634
 
            case SHRINKING: {
635
 
                Item *it = world::MakeItem("it-blocker-new");
636
 
                world::SetItem(get_pos(), it);
637
 
                TransferObjectName(this, it);
638
 
                world::KillStone(get_pos());
639
 
                break;
640
 
            }
641
 
            case GROWING:
642
 
                change_state(SOLID);
643
 
                break;
644
 
            default :
645
 
                assert(0);
646
 
                break;
647
 
            }
648
 
        }
649
 
 
650
 
        void message(const string &msg, const Value &val) {
651
 
            if (msg == "trigger" || msg == "openclose") {
652
 
                if (state == SHRINKING) {
653
 
                    change_state(GROWING);
654
 
                }
655
 
                else {
656
 
                    change_state(SHRINKING);
657
 
                }
658
 
            }
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);
665
 
                }
666
 
                else {          // value == 0 -> grow
667
 
                    if (state == SHRINKING)
668
 
                        change_state(GROWING);
669
 
                }
670
 
            }
671
 
            else if (msg == "open") { // aka "shrink"
672
 
                if (state != SHRINKING)
673
 
                    change_state(SHRINKING);
674
 
            }
675
 
            else if (msg == "close") { // aka "grow"
676
 
                if (state == SHRINKING)
677
 
                    change_state(GROWING);
678
 
            }
679
 
        }
680
 
 
681
 
        void actor_contact(Actor *a) {
682
 
            if (state == GROWING) {
683
 
                SendMessage(a, "shatter");
684
 
            }
685
 
        }
686
 
        void actor_inside(Actor *a) {
687
 
            if (state == GROWING) {
688
 
                SendMessage(a, "shatter");
689
 
            }
690
 
        }
691
 
    };
692
 
}
693
 
 
694
 
 
695
 
/* -------------------- Volcano -------------------- */
696
 
namespace
697
 
{
698
 
    class VolcanoStone : public Stone {
699
 
        CLONEOBJ(VolcanoStone);
700
 
    public:
701
 
        enum State {INACTIVE, ACTIVE, FINISHED, BREAKING};
702
 
        VolcanoStone( State initstate=INACTIVE) : Stone("st-volcano"), state( initstate) {}
703
 
    private:
704
 
        enum State state;
705
 
 
706
 
        void init_model() {
707
 
            switch( state) {
708
 
            case FINISHED:
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;
712
 
            }
713
 
        }
714
 
 
715
 
        void animcb() {
716
 
            if (state == ACTIVE) {
717
 
                // Spread
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));
723
 
 
724
 
                // Be finished at random time
725
 
                if (DoubleRand(0, 1) > 0.95)
726
 
                    state = FINISHED;
727
 
                init_model();
728
 
            } else if( state == BREAKING) {
729
 
                KillStone( get_pos());
730
 
            }
731
 
        }
732
 
 
733
 
        void message(const string &msg, const Value &) {
734
 
            if (msg == "trigger") {
735
 
                if (state == INACTIVE) {
736
 
                    state = ACTIVE;
737
 
                    init_model();
738
 
                }
739
 
            }
740
 
        }
741
 
 
742
 
        void spread( GridPos p) {
743
 
            Stone *st = GetStone(p);
744
 
            if( !st) {
745
 
                Item *it = MakeItem("it-seed_volcano");
746
 
                SetItem( p, it);
747
 
                SendMessage( it, "grow");
748
 
            }
749
 
        }
750
 
 
751
 
        void actor_hit(const StoneContact &sc) {
752
 
            Actor *a = sc.actor;
753
 
 
754
 
            if( state == ACTIVE && player::wielded_item_is(a, "it-hammer")) {
755
 
                state = BREAKING;
756
 
                init_model();
757
 
            }
758
 
        }
759
 
    };
760
 
}
761
 
 
762
 
 
763
 
/* -------------------- ConnectiveStone -------------------- */
764
 
 
765
 
// base class for PuzzleStone and BigBrick
766
 
 
767
 
namespace {
768
 
    class ConnectiveStone : public Stone {
769
 
    public:
770
 
        ConnectiveStone(const char *kind, int connections)
771
 
        : Stone(kind)
772
 
        {
773
 
            set_attrib("connections", connections);
774
 
        }
775
 
 
776
 
        DirectionBits get_connections() const;
777
 
    protected:
778
 
        void init_model();
779
 
    private:
780
 
        virtual int get_modelno() const;
781
 
    };
782
 
}
783
 
 
784
 
DirectionBits
785
 
ConnectiveStone::get_connections() const
786
 
{
787
 
    int conn=int_attrib("connections") - 1;
788
 
    if (conn >=0 && conn <16)
789
 
        return DirectionBits(conn);
790
 
    else
791
 
        return NODIRBIT;
792
 
}
793
 
 
794
 
void ConnectiveStone::init_model() {
795
 
    set_model(get_kind()+px::strf("%d", get_modelno()));
796
 
}
797
 
 
798
 
int ConnectiveStone::get_modelno() const {
799
 
    return int_attrib("connections");
800
 
}
801
 
 
802
 
 
803
 
/* -------------------- BigBrick -------------------- */
804
 
 
805
 
// BigBricks allow to build stones of any size
806
 
 
807
 
namespace 
808
 
{
809
 
    class BigBrick : public ConnectiveStone {
810
 
        CLONEOBJ(BigBrick);
811
 
    public:
812
 
        BigBrick(int connections)
813
 
        : ConnectiveStone("st-bigbrick", connections)
814
 
        {}
815
 
    };
816
 
}
817
 
 
818
 
 
819
 
/* -------------------- Puzzle stones -------------------- */ 
820
 
 
821
 
/** \page st-puzzle Puzzle Stone
822
 
 
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.
827
 
 
828
 
A cluster of puzzle stones may for example look like this:
829
 
 
830
 
\verbatim
831
 
+---+---+---+---+
832
 
|   |   |   |   |
833
 
| --+-+-+---+-+ |
834
 
|   | | |   | | |
835
 
+---+-+-+---+-+-+
836
 
    | | |   | | |
837
 
    | | |   | | |
838
 
    |   |   |   |
839
 
    +---+   +---+
840
 
\endverbatim
841
 
 
842
 
This example actually presents the special case of a ``complete''
843
 
cluster.  A cluster is complete if none of its stones has an
844
 
unconnected socket.
845
 
 
846
 
When touched with a magic wand the puzzle stones rotate
847
 
row- or columnwise.  
848
 
 
849
 
\subsection puzzlea Attributes
850
 
 
851
 
- \b connections
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.
855
 
 
856
 
- \b oxyd
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.
861
 
 
862
 
\subsection puzzlee Example
863
 
<table>
864
 
<tr>
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"
869
 
<tr>
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"
874
 
<tr>
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"
879
 
<tr>
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"
884
 
</tr>
885
 
</table>
886
 
*/
887
 
namespace
888
 
{
889
 
    class PuzzleStone : public ConnectiveStone, public TimeHandler, public world::PhotoCell {
890
 
        INSTANCELISTOBJ(PuzzleStone);
891
 
    public:
892
 
        PuzzleStone(int connections, bool oxyd1_compatible_);
893
 
    private:
894
 
        typedef vector<GridPos> Cluster;
895
 
 
896
 
        /* ---------- Private methods ---------- */
897
 
 
898
 
        bool oxyd1_compatible() const { return int_attrib("oxyd") != 0; }
899
 
 
900
 
        static bool visit_dir(vector<GridPos> &stack, GridPos curpos,
901
 
                              Direction dir);
902
 
        static void visit_adjacent(vector<GridPos>& stack, GridPos curpos,
903
 
                                   Direction dir, int wanted_oxyd_attrib);
904
 
 
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);
909
 
 
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, 
913
 
                                Direction dir);
914
 
        void rotate_cluster(const Cluster &c);
915
 
        void maybe_rotate_cluster(Direction dir);
916
 
 
917
 
        int get_modelno() const;
918
 
 
919
 
        void        trigger_explosion(double delay);
920
 
        static void trigger_explosion_at(GridPos p, double delay, int wanted_oxyd_attrib);
921
 
        void        explode();
922
 
        bool        explode_complete_cluster();
923
 
 
924
 
        /* ---------- TimeHandler interface ---------- */
925
 
 
926
 
        void alarm();
927
 
 
928
 
        /* ---------- PhotoCell interface ---------- */
929
 
 
930
 
        void on_recalc_start();
931
 
        void on_recalc_finish();
932
 
 
933
 
        /* ---------- Stone interface ---------- */
934
 
 
935
 
        void message(const string& msg, const Value &val);
936
 
 
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);
941
 
 
942
 
        bool is_floating() const;
943
 
 
944
 
        StoneResponse collision_response(const StoneContact &sc);
945
 
        void actor_hit (const StoneContact &sc);
946
 
        void actor_contact (Actor *a);
947
 
 
948
 
        /* ---------- Variables ---------- */
949
 
        bool visited;           // flag for DFS
950
 
        enum { IDLE, EXPLODING } state;
951
 
        DirectionBits illumination; // last state of surrounding laser beams
952
 
    };
953
 
}
954
 
 
955
 
PuzzleStone::InstanceList PuzzleStone::instances;
956
 
 
957
 
PuzzleStone::PuzzleStone(int connections, bool oxyd1_compatible_)
958
 
: ConnectiveStone("st-puzzle", connections), 
959
 
  state (IDLE), 
960
 
  illumination (NODIRBIT)
961
 
{
962
 
    set_attrib("oxyd", int(oxyd1_compatible_));
963
 
}
964
 
 
965
 
 
966
 
bool
967
 
PuzzleStone::visit_dir(vector<GridPos> &stack, GridPos curpos, Direction dir)
968
 
{
969
 
    GridPos newpos = move(curpos, dir);
970
 
    PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(newpos));
971
 
 
972
 
    if (!pz)
973
 
        return false;
974
 
 
975
 
    DirectionBits cfaces = pz->get_connections();
976
 
 
977
 
    if (cfaces==NODIRBIT || has_dir(cfaces, reverse(dir))) {
978
 
        // Puzzle stone at newpos is connected to stone at curpos
979
 
        if (!pz->visited) {
980
 
            pz->visited = true;
981
 
            stack.push_back(newpos);
982
 
        }
983
 
        return true;
984
 
    } else {
985
 
        // The two stones are adjacent but not connected
986
 
        return false;
987
 
    }
988
 
}
989
 
 
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;
996
 
 
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())
1002
 
    {
1003
 
        GridPos curpos = pos_stack.back();
1004
 
        pos_stack.pop_back();
1005
 
 
1006
 
        PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(curpos));
1007
 
        assert(pz);
1008
 
 
1009
 
        cluster.push_back(curpos);
1010
 
        DirectionBits cfaces = pz->get_connections();
1011
 
 
1012
 
        if (cfaces==NODIRBIT)
1013
 
            cfaces = DirectionBits(NORTHBIT | SOUTHBIT | EASTBIT | WESTBIT);
1014
 
 
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);
1023
 
    }
1024
 
    return is_complete;
1025
 
}
1026
 
 
1027
 
void PuzzleStone::visit_adjacent (vector<GridPos>& stack, GridPos curpos, 
1028
 
                                  Direction dir, int wanted_oxyd_attrib)
1029
 
{
1030
 
    GridPos newpos = move(curpos, dir);
1031
 
    if (PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(newpos))) {
1032
 
        if (!pz->visited) {
1033
 
            if (wanted_oxyd_attrib == pz->int_attrib("oxyd")) {
1034
 
                pz->visited = true;
1035
 
                stack.push_back(newpos);
1036
 
            }
1037
 
        }
1038
 
    }
1039
 
}
1040
 
 
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).
1044
 
*/
1045
 
void PuzzleStone::find_adjacents(Cluster &cluster) {
1046
 
    for (unsigned i=0; i<instances.size(); ++i)
1047
 
        instances[i]->visited=false;
1048
 
 
1049
 
    vector<GridPos> pos_stack;
1050
 
    pos_stack.push_back(get_pos());
1051
 
    this->visited = true;
1052
 
 
1053
 
    int wanted_oxyd_attrib = int_attrib("oxyd");
1054
 
 
1055
 
    while (!pos_stack.empty()) {
1056
 
        GridPos curpos = pos_stack.back();
1057
 
        pos_stack.pop_back();
1058
 
 
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);
1064
 
    }
1065
 
}
1066
 
 
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
1070
 
*/
1071
 
 
1072
 
void PuzzleStone::find_row_or_column_cluster(Cluster &c, GridPos startpos, 
1073
 
                                             Direction dir, int wanted_oxyd_attrib)
1074
 
{
1075
 
    assert(dir != NODIR);
1076
 
 
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
1081
 
        c.push_back(p);
1082
 
        p.move(dir);
1083
 
    }
1084
 
}
1085
 
 
1086
 
bool PuzzleStone::can_move_cluster (Cluster &c, Direction dir)
1087
 
{
1088
 
    sort(c.begin(), c.end());
1089
 
    Cluster mc(c);              // Moved cluster
1090
 
    Cluster diff;               // Difference between mc and c
1091
 
 
1092
 
    for (unsigned i=0; i<mc.size(); ++i)
1093
 
        mc[i].move(dir);
1094
 
 
1095
 
    set_difference(mc.begin(), mc.end(), c.begin(), c.end(),
1096
 
                   back_inserter(diff));
1097
 
 
1098
 
    // Now check whether all stones can be placed at their new
1099
 
    // position
1100
 
    bool move_ok = true;
1101
 
    for (unsigned i=0; i<diff.size(); ++i)
1102
 
        if (GetStone(diff[i]) != 0)
1103
 
            move_ok = false;
1104
 
 
1105
 
    return move_ok;    
1106
 
}
1107
 
 
1108
 
void PuzzleStone::maybe_move_cluster(Cluster &c, bool is_complete, 
1109
 
                                     bool actor_with_wand, Direction dir)
1110
 
{
1111
 
    sort(c.begin(), c.end());
1112
 
    Cluster mc(c);              // Moved cluster
1113
 
    Cluster diff;               // Difference between mc and c
1114
 
 
1115
 
    for (unsigned i=0; i<mc.size(); ++i)
1116
 
        mc[i].move(dir);
1117
 
 
1118
 
    set_difference(mc.begin(), mc.end(), c.begin(), c.end(),
1119
 
                   back_inserter(diff));
1120
 
 
1121
 
    // Now check whether all stones can be placed at their new
1122
 
    // position
1123
 
    bool move_ok = true;
1124
 
    for (unsigned i=0; i<diff.size(); ++i)
1125
 
        if (GetStone(diff[i]) != 0)
1126
 
            move_ok = false;
1127
 
 
1128
 
    if (!move_ok)
1129
 
        return;
1130
 
 
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.
1134
 
    //
1135
 
    // For partial clusters build bridges only on water and if the
1136
 
    // wielded item is NOT the magic wand.
1137
 
 
1138
 
    bool create_bridge = true;
1139
 
 
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")) {
1143
 
                if (!is_complete)
1144
 
                    create_bridge = false;
1145
 
            }
1146
 
            else if (fl->is_kind("fl-water")) {
1147
 
                if (!is_complete && actor_with_wand) 
1148
 
                    create_bridge = false;
1149
 
            }
1150
 
            else
1151
 
                create_bridge = false;
1152
 
        }
1153
 
    }
1154
 
 
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) {
1159
 
            KillStone(c[i]);
1160
 
            SetFloor(mc[i], MakeFloor("fl-gray"));
1161
 
        }
1162
 
    } 
1163
 
    else {
1164
 
        vector<Stone*> clusterstones;
1165
 
        for (unsigned i=0; i<c.size(); ++i)
1166
 
            clusterstones.push_back(YieldStone(c[i]));
1167
 
 
1168
 
        for (unsigned i=0; i<c.size(); ++i) {
1169
 
            SetStone(mc[i], clusterstones[i]);
1170
 
            clusterstones[i]->on_move();
1171
 
        }
1172
 
    }
1173
 
 
1174
 
    server::IncMoveCounter(c.size());
1175
 
}
1176
 
 
1177
 
bool PuzzleStone::cluster_complete() {
1178
 
    Cluster c;
1179
 
    return find_cluster(c);
1180
 
}
1181
 
 
1182
 
int PuzzleStone::get_modelno() const {
1183
 
    int modelno = int_attrib("connections");
1184
 
    if (oxyd1_compatible()) modelno += 16;
1185
 
    return modelno;
1186
 
}
1187
 
 
1188
 
void PuzzleStone::rotate_cluster(const Cluster &c) {
1189
 
    unsigned size = c.size();
1190
 
    if (size > 1) {
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"));
1195
 
            st->init_model();
1196
 
        }
1197
 
        GetStone(c[0])->set_attrib ("connections", cn);
1198
 
        dynamic_cast<PuzzleStone*> (GetStone(c[0]))->init_model();
1199
 
    }
1200
 
}
1201
 
 
1202
 
StoneResponse PuzzleStone::collision_response(const StoneContact &/*sc*/) {
1203
 
    if (get_connections() == NODIRBIT)
1204
 
        return STONE_PASS;
1205
 
    return STONE_REBOUND;
1206
 
}
1207
 
 
1208
 
void PuzzleStone::trigger_explosion(double delay) {
1209
 
    if (state == IDLE) {
1210
 
        state = EXPLODING;
1211
 
        GameTimer.set_alarm(this, delay, false);
1212
 
    }
1213
 
}
1214
 
 
1215
 
void PuzzleStone::trigger_explosion_at (GridPos p, double delay,
1216
 
                                        int wanted_oxyd_attrib)
1217
 
{
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);
1222
 
    }
1223
 
}
1224
 
 
1225
 
void PuzzleStone::explode() {
1226
 
    GridPos p       = get_pos();
1227
 
    int     ox_attr = int_attrib("oxyd");
1228
 
 
1229
 
    // exchange puzzle stone with explosion
1230
 
    sound_event("stonedestroy");
1231
 
    SetStone(p, MakeStone("st-explosion"));
1232
 
 
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);
1239
 
 
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
1243
 
 
1244
 
    // ignite adjacent fields
1245
 
//     SendExplosionEffect(p, DYNAMITE);
1246
 
}
1247
 
 
1248
 
void PuzzleStone::alarm() {
1249
 
    explode();
1250
 
}
1251
 
 
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.
1258
 
        //
1259
 
        // enigma levels may create scramble messages using
1260
 
        // AddScramble() and SetScrambleIntensity()
1261
 
 
1262
 
        Direction dir = to_direction(val);
1263
 
        Cluster   c;
1264
 
        find_row_or_column_cluster(c, get_pos(), dir, oxyd1_compatible());
1265
 
 
1266
 
        int size = c.size();
1267
 
 
1268
 
        // warning("received 'scramble'. dir=%s size=%i", to_suffix(dir).c_str(), size);
1269
 
 
1270
 
        if (size >= 2) {
1271
 
            int count = IntegerRand(0, size-1);
1272
 
            while (count--)
1273
 
                rotate_cluster(c);
1274
 
        }
1275
 
        else {
1276
 
            warning("useless scramble (cluster size=%i)", size);
1277
 
        }
1278
 
    }
1279
 
}
1280
 
 
1281
 
void PuzzleStone::on_impulse(const Impulse& impulse) 
1282
 
{
1283
 
//    if (!oxyd1_compatible() && state == IDLE) {
1284
 
    if (state == IDLE) {
1285
 
        Cluster c;
1286
 
        bool    is_complete     = find_cluster(c);
1287
 
        bool    actor_with_wand = false;
1288
 
 
1289
 
        if (Actor *ac = dynamic_cast<Actor*>(impulse.sender)) 
1290
 
            actor_with_wand = player::wielded_item_is(ac, "it-magicwand");
1291
 
 
1292
 
        maybe_move_cluster(c, is_complete, actor_with_wand, impulse.dir);
1293
 
    }
1294
 
}
1295
 
 
1296
 
bool PuzzleStone::explode_complete_cluster() 
1297
 
{
1298
 
    // @@@ FIXME: explode_complete_cluster should mark the whole cluster
1299
 
    // as "EXPLODING_SOON" (otherwise it may be changed before it explodes completely)
1300
 
 
1301
 
    assert(state == IDLE);
1302
 
    bool exploded = false;
1303
 
 
1304
 
    Cluster complete;
1305
 
    if (find_cluster(complete)) {
1306
 
        Cluster all;
1307
 
        find_adjacents(all);
1308
 
 
1309
 
        // If all adjacent stones build one complete cluster explode it
1310
 
        if (all.size() == complete.size()) {
1311
 
            explode();          // explode complete cluster
1312
 
            exploded = true;
1313
 
        }
1314
 
        else {
1315
 
            assert(all.size() > complete.size());
1316
 
            if (!oxyd1_compatible()) {
1317
 
                // check if 'all' is made up of complete clusters :
1318
 
 
1319
 
                sort(all.begin(), all.end());
1320
 
 
1321
 
                while (1) {
1322
 
                    sort(complete.begin(), complete.end());
1323
 
 
1324
 
                    // remove one complete cluster from 'all'
1325
 
                    {
1326
 
                        Cluster rest;
1327
 
                        set_symmetric_difference(all.begin(), all.end(),
1328
 
                                                 complete.begin(), complete.end(),
1329
 
                                                 back_inserter(rest));
1330
 
                        // now rest contains 'all' minus 'complete'
1331
 
                        swap(all, rest);
1332
 
                    }
1333
 
 
1334
 
                    if (all.empty()) { // none left -> all were complete
1335
 
                        exploded = true;
1336
 
                        break;
1337
 
                    }
1338
 
 
1339
 
                    // look for next complete cluster :
1340
 
                    complete.clear();
1341
 
                    {
1342
 
                        PuzzleStone *pz = dynamic_cast<PuzzleStone*>(GetStone(all[0]));
1343
 
                        assert(pz);
1344
 
                        if (!pz->find_cluster(complete)) {
1345
 
                            break; // incomplete cluster found -> don't explode
1346
 
                        }
1347
 
                    }
1348
 
                }
1349
 
 
1350
 
                if (exploded) {
1351
 
//                     warning("exploding complete cluster");
1352
 
                    explode();
1353
 
                }
1354
 
            }
1355
 
        }
1356
 
    }
1357
 
 
1358
 
    return exploded;
1359
 
}
1360
 
 
1361
 
bool PuzzleStone::is_floating() const {
1362
 
    return get_connections() == 0;
1363
 
}
1364
 
 
1365
 
void PuzzleStone::maybe_rotate_cluster(Direction dir) 
1366
 
{
1367
 
    if (dir != NODIR) {
1368
 
        Cluster c;
1369
 
        find_row_or_column_cluster(c, get_pos(), dir, int_attrib ("oxyd"));
1370
 
        if (c.size() >= 2) {
1371
 
//             warning("ok -> rotate");
1372
 
            rotate_cluster(c);
1373
 
        }
1374
 
    }
1375
 
}
1376
 
 
1377
 
void PuzzleStone::on_creation (GridPos p) {
1378
 
    photo_activate();
1379
 
    ConnectiveStone::on_creation (p);
1380
 
    illumination = NODIRBIT;
1381
 
}
1382
 
 
1383
 
void PuzzleStone::on_removal(GridPos p) {
1384
 
    photo_deactivate();
1385
 
    ConnectiveStone::on_removal(p);
1386
 
}
1387
 
 
1388
 
void PuzzleStone::on_laserhit (Direction dir) {
1389
 
    px::set_flags (illumination, to_bits(reverse(dir)));
1390
 
}
1391
 
 
1392
 
void PuzzleStone::on_recalc_start() {
1393
 
    illumination = NODIRBIT;
1394
 
}
1395
 
 
1396
 
void PuzzleStone::on_recalc_finish() {
1397
 
    if (illumination != (ALL_DIRECTIONS+1) && 
1398
 
        illumination != NODIRBIT && 
1399
 
        state == IDLE) 
1400
 
    {
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);
1406
 
        }
1407
 
    }
1408
 
}
1409
 
 
1410
 
void PuzzleStone::actor_hit(const StoneContact &sc) 
1411
 
{
1412
 
    if (get_connections() == NODIRBIT)
1413
 
        return;                 // Puzzle stone is hollow
1414
 
 
1415
 
    if (state == EXPLODING)
1416
 
        return;
1417
 
 
1418
 
    Cluster c;
1419
 
    find_cluster (c);
1420
 
 
1421
 
    Direction rotate_dir = reverse (contact_face (sc));
1422
 
    Direction move_dir = get_push_direction(sc);
1423
 
 
1424
 
    if (oxyd1_compatible()) {
1425
 
        // Oxyd 1
1426
 
 
1427
 
        if (explode_complete_cluster())
1428
 
            return;
1429
 
 
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");
1439
 
            } else
1440
 
                maybe_rotate_cluster (rotate_dir);
1441
 
        } 
1442
 
        // 2) If more than one stone, 
1443
 
        else 
1444
 
            maybe_rotate_cluster (rotate_dir);
1445
 
    }
1446
 
    else {
1447
 
        // Not Oxyd 1
1448
 
 
1449
 
        bool has_magic_wand = player::wielded_item_is(sc.actor, "it-magicwand");
1450
 
 
1451
 
        // 1) Try to start explosion of complete cluster
1452
 
        if (has_magic_wand && explode_complete_cluster())
1453
 
            return;
1454
 
 
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);
1458
 
            return;
1459
 
        }
1460
 
 
1461
 
        // 3) Last chance: try to rotate the row or column 
1462
 
        if (has_magic_wand)
1463
 
            maybe_rotate_cluster (rotate_dir);
1464
 
    }
1465
 
}
1466
 
 
1467
 
void PuzzleStone::actor_contact (Actor *a)
1468
 
{
1469
 
    if (state == EXPLODING)
1470
 
        SendMessage (a, "shatter");
1471
 
}
1472
 
 
1473
 
 
1474
 
 
1475
 
/* -------------------- DoorBase -------------------- */
1476
 
 
1477
 
// Base class for everything that behaves like a door, i.e., it has
1478
 
// four states OPEN, CLOSED, OPENING, CLOSING.
1479
 
namespace
1480
 
{
1481
 
    class DoorBase : public Stone {
1482
 
    protected:
1483
 
        enum State { OPEN, CLOSED, OPENING, CLOSING } state;
1484
 
 
1485
 
        DoorBase(const char *name, State initstate=CLOSED)
1486
 
        : Stone(name), state(initstate)
1487
 
        {}
1488
 
 
1489
 
        State get_state() const { return state; }
1490
 
        void set_state(State st) { state=st; }
1491
 
 
1492
 
    private:
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 ""; }
1498
 
 
1499
 
        // Private methods
1500
 
        void change_state(State newstate) ;
1501
 
        void message(const string &m, const Value &);
1502
 
 
1503
 
        StoneResponse collision_response(const StoneContact &sc);
1504
 
 
1505
 
        void animcb();
1506
 
 
1507
 
        // Stone interface
1508
 
        virtual bool is_transparent (Direction) const 
1509
 
        { return state==OPEN; }
1510
 
        
1511
 
        virtual bool is_sticky (const Actor *) const 
1512
 
        { return false; }
1513
 
    };
1514
 
}
1515
 
 
1516
 
void DoorBase::message(const string &m, const Value &val) {
1517
 
    State newstate = state;
1518
 
    int ival = to_int (val);
1519
 
 
1520
 
    if (m == "open")
1521
 
        newstate = OPENING;
1522
 
    else if (m == "close")
1523
 
        newstate = CLOSING;
1524
 
    else if (m == "openclose")
1525
 
        newstate = (state==OPEN || state==OPENING) ? CLOSING : OPENING;
1526
 
    else if (m == "signal")
1527
 
        newstate = ival > 0 ? OPENING : CLOSING;
1528
 
 
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);
1533
 
}
1534
 
 
1535
 
void DoorBase::init_model() {
1536
 
    string mname = model_basename();
1537
 
    if (state == CLOSED)
1538
 
        mname += "-closed";
1539
 
    else if (state==OPEN)
1540
 
        mname += "-open";
1541
 
    set_model(mname);
1542
 
}
1543
 
 
1544
 
void DoorBase::animcb() {
1545
 
    if (state == OPENING)
1546
 
        change_state(OPEN);
1547
 
    else if (state == CLOSING)
1548
 
        change_state(CLOSED);
1549
 
}
1550
 
 
1551
 
StoneResponse
1552
 
DoorBase::collision_response(const StoneContact &/*sc*/)
1553
 
{
1554
 
    return (state == OPEN) ? STONE_PASS:STONE_REBOUND;
1555
 
}
1556
 
 
1557
 
void DoorBase::change_state(State newstate) 
1558
 
{
1559
 
    string basename = model_basename();
1560
 
 
1561
 
    switch (newstate) {
1562
 
    case OPEN:
1563
 
        set_model(basename+"-open");
1564
 
        lasers::MaybeRecalcLight(get_pos());
1565
 
        break;
1566
 
    case CLOSED:
1567
 
        set_model(basename+"-closed");
1568
 
        world::ShatterActorsInsideField (get_pos());
1569
 
        lasers::MaybeRecalcLight(get_pos()); // maybe superfluous
1570
 
        break;
1571
 
    case OPENING:
1572
 
        sound_event (opening_sound().c_str());
1573
 
        if (state == CLOSING)
1574
 
            get_model()->reverse();
1575
 
        else
1576
 
            set_anim(basename+"-opening");
1577
 
        break;
1578
 
    case CLOSING:
1579
 
        sound_event (closing_sound().c_str());
1580
 
        if (state == OPENING)
1581
 
            get_model()->reverse();
1582
 
        else
1583
 
            set_anim(basename+"-closing");
1584
 
        world::ShatterActorsInsideField (get_pos());
1585
 
        lasers::MaybeRecalcLight(get_pos());
1586
 
        break;
1587
 
    }
1588
 
    set_state(newstate);
1589
 
}
1590
 
 
1591
 
 
1592
 
/* -------------------- Door -------------------- */
1593
 
 
1594
 
// Attributes:
1595
 
//
1596
 
// :type        h or v for a door that opens horizontally or vertically
1597
 
namespace
1598
 
{
1599
 
    class Door : public DoorBase {
1600
 
        CLONEOBJ(Door);
1601
 
    public:
1602
 
        Door(const char *type="h", bool open=false)
1603
 
        : DoorBase("st-door", open ? OPEN : CLOSED)
1604
 
        {
1605
 
            set_attrib("type", type);
1606
 
        }
1607
 
    private:
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 {
1612
 
            string type="h";
1613
 
            string_attrib("type", &type);
1614
 
            return type;
1615
 
        }
1616
 
 
1617
 
        bool is_transparent (Direction) const;
1618
 
        bool is_floating () const { 
1619
 
            return true;        // don't let door press buttons
1620
 
        }
1621
 
 
1622
 
        void actor_hit(const StoneContact &)
1623
 
        {
1624
 
            if (Item *it = GetItem (get_pos()))
1625
 
                PerformAction (it, true);
1626
 
        }
1627
 
 
1628
 
        string model_basename() { return string("st-door")+get_type(); }
1629
 
        StoneResponse collision_response(const StoneContact &sc);
1630
 
    };
1631
 
 
1632
 
    class Door_a : public DoorBase {
1633
 
        CLONEOBJ(Door_a);
1634
 
    public:
1635
 
        Door_a() : DoorBase("st-door_a") {}
1636
 
    };
1637
 
 
1638
 
    class Door_b : public DoorBase {
1639
 
        CLONEOBJ(Door_b);
1640
 
    public:
1641
 
        Door_b() : DoorBase("st-door_b") {}
1642
 
    };
1643
 
 
1644
 
    class Door_c : public DoorBase {
1645
 
        CLONEOBJ(Door_c);
1646
 
    public:
1647
 
        Door_c() : DoorBase("st-door_c") {}
1648
 
    };
1649
 
}
1650
 
 
1651
 
bool Door::is_transparent (Direction dir) const {
1652
 
    if (get_type() == "h")
1653
 
        return state==OPEN || dir==EAST || dir==WEST;
1654
 
    else
1655
 
        return state==OPEN || dir==NORTH || dir==SOUTH;
1656
 
}
1657
 
 
1658
 
StoneResponse
1659
 
Door::collision_response(const StoneContact &sc)
1660
 
{
1661
 
    Direction cf = contact_face(sc);
1662
 
    if (state == OPEN)
1663
 
        return STONE_PASS;
1664
 
    else if (state == CLOSING)
1665
 
        return STONE_REBOUND;
1666
 
    else {
1667
 
        string t = get_type();
1668
 
        return ((t == "v" && (cf==WEST || cf==EAST)) ||
1669
 
                (t == "h" && (cf==SOUTH || cf==NORTH)))
1670
 
            ? STONE_REBOUND
1671
 
            : STONE_PASS;
1672
 
    }
1673
 
}
1674
 
 
1675
 
 
1676
 
/* -------------------- ShogunStone -------------------- */
1677
 
 
1678
 
// Attributes:
1679
 
//
1680
 
// :holes            1..7
1681
 
namespace
1682
 
{
1683
 
    class ShogunStone : public Stone {
1684
 
        CLONEOBJ(ShogunStone);
1685
 
 
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); }
1689
 
 
1690
 
    public:
1691
 
        ShogunStone(int holes=SMALL) : Stone("st-shogun") {
1692
 
            set_holes(static_cast<Holes>(holes));
1693
 
        }
1694
 
    private:
1695
 
        Holes get_holes() const;
1696
 
        void notify_item();
1697
 
 
1698
 
        void message(const string &m, const Value &) {
1699
 
            if (m == "init") { // request from ShogunDot (if set _after_ ShogunStone)
1700
 
                notify_item();
1701
 
            }
1702
 
        }
1703
 
 
1704
 
        void add_hole(Holes h) {
1705
 
            set_attrib("holes", get_holes() | h);
1706
 
            notify_item();
1707
 
            init_model();
1708
 
        }
1709
 
 
1710
 
        void on_creation (GridPos p) {
1711
 
            init_model();
1712
 
            notify_item();
1713
 
        }
1714
 
 
1715
 
        void on_impulse(const Impulse& impulse);
1716
 
 
1717
 
        void init_model() {
1718
 
            set_model(px::strf("st-shogun%d", int(get_holes())));
1719
 
        }
1720
 
 
1721
 
        bool is_movable() const { return false; }
1722
 
 
1723
 
        void actor_hit (const StoneContact &sc) {
1724
 
            maybe_push_stone (sc);
1725
 
        }
1726
 
    };
1727
 
}
1728
 
 
1729
 
ShogunStone::Holes ShogunStone::get_holes() const {
1730
 
    int h=int_attrib("holes");
1731
 
    if (h>=1 && h<=7)
1732
 
        return Holes(h);
1733
 
    else {
1734
 
        warning("Wrong 'holes' attribute (%i)", h);
1735
 
        return SMALL;
1736
 
    }
1737
 
}
1738
 
 
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;
1743
 
    assert(0);
1744
 
}
1745
 
 
1746
 
void ShogunStone::notify_item ()
1747
 
{
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;
1754
 
        }
1755
 
    }
1756
 
}
1757
 
 
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;
1763
 
 
1764
 
    if (Stone *st = GetStone(destpos)) {
1765
 
        target = dynamic_cast<ShogunStone*>(st);
1766
 
 
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()))
1770
 
            return;
1771
 
    }
1772
 
 
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. */
1777
 
 
1778
 
    GridPos my_pos = get_pos();
1779
 
    string  old_name;
1780
 
 
1781
 
    // Remove/modify source stone:
1782
 
    if (Holes newholes = Holes(holes & ~smallest)) {
1783
 
        set_holes(newholes);
1784
 
        notify_item();
1785
 
        init_model();
1786
 
    }
1787
 
    else {
1788
 
        string_attrib("name", &old_name); // store name of disappearing stone
1789
 
        SendMessage(GetItem(my_pos), "noshogun");
1790
 
        KillStone(my_pos);
1791
 
    }
1792
 
 
1793
 
    // Modify/create target stone:
1794
 
    if (target) {
1795
 
        target->add_hole(smallest);
1796
 
//         target->sound_event("st-magic");
1797
 
//         sound::PlaySound("st-magic", my_pos.center()); // object already disappeared
1798
 
    }
1799
 
    else {                       // create new
1800
 
        target = new ShogunStone(smallest);
1801
 
        SetStone(destpos, target);
1802
 
        target->on_move();
1803
 
    }
1804
 
 
1805
 
    if (!old_name.empty())
1806
 
        NameObject(target, old_name);
1807
 
 
1808
 
    server::IncMoveCounter();
1809
 
    sound::SoundEvent ("movesmall", my_pos.center());
1810
 
}
1811
 
 
1812
 
 
1813
 
 
1814
 
/* -------------------- Stone impulse stones -------------------- */
1815
 
 
1816
 
// Messages:
1817
 
//
1818
 
// :trigger
1819
 
namespace
1820
 
{
1821
 
    class StoneImpulse_Base : public Stone {
1822
 
    protected:
1823
 
        StoneImpulse_Base(const char *kind) : Stone(kind), state(IDLE), incoming(NODIR)
1824
 
        {}
1825
 
 
1826
 
        enum State { IDLE, PULSING, CLOSING };
1827
 
        State     state;
1828
 
        Direction incoming; // direction of incoming impulse (may be NODIR)
1829
 
 
1830
 
        void change_state(State st);
1831
 
 
1832
 
        virtual void on_impulse(const Impulse& impulse) {
1833
 
            incoming = impulse.dir;
1834
 
            change_state(PULSING);
1835
 
        }
1836
 
 
1837
 
    private:
1838
 
 
1839
 
        virtual void notify_state(State st) = 0;
1840
 
 
1841
 
        void message(const string &m, const Value &value) {
1842
 
            if (m=="trigger") {
1843
 
                incoming = (value.get_type() == Value::DOUBLE)
1844
 
                    ? Direction(value.get_double()+0.1)
1845
 
                    : NODIR;
1846
 
 
1847
 
                change_state(PULSING);
1848
 
            }
1849
 
            else if (m == "signal" && to_double (value) != 0) {
1850
 
                incoming = NODIR;
1851
 
                change_state (PULSING);
1852
 
            }
1853
 
        }
1854
 
 
1855
 
        void animcb() {
1856
 
            if (state == PULSING)
1857
 
                change_state (CLOSING);
1858
 
            else if (state == CLOSING)
1859
 
                change_state (IDLE);
1860
 
        }
1861
 
 
1862
 
        void on_laserhit(Direction dir) {
1863
 
            incoming = dir;
1864
 
            change_state(PULSING);
1865
 
        }
1866
 
    };
1867
 
 
1868
 
}
1869
 
 
1870
 
void StoneImpulse_Base::change_state(State new_state) {
1871
 
    if (new_state == state) return;
1872
 
 
1873
 
    GridPos p = get_pos();
1874
 
    switch (new_state) {
1875
 
        case IDLE: {
1876
 
            state = new_state;
1877
 
            notify_state(state);
1878
 
            break;
1879
 
        }
1880
 
        case PULSING:
1881
 
            if (state != IDLE) {
1882
 
                return;         // do not set new state
1883
 
            }
1884
 
            state = new_state;
1885
 
            notify_state(state);
1886
 
            sound_event("impulse");
1887
 
            break;
1888
 
        case CLOSING: {
1889
 
            GridPos targetpos[4];
1890
 
            bool    haveStone[4];
1891
 
 
1892
 
            // set CLOSING model _before_ sending impulses !!!
1893
 
            // (any impulse might have side effects that move this stone)
1894
 
 
1895
 
            state = new_state;
1896
 
            notify_state(state);
1897
 
 
1898
 
            for (int d = 0; d < 4; ++d) {
1899
 
                targetpos[d] = move(p, Direction(d));
1900
 
                haveStone[d] = GetStone(targetpos[d]) != 0;
1901
 
            }
1902
 
 
1903
 
            for (int d = int(incoming)+1; d <= int(incoming)+4; ++d) {
1904
 
                int D = d%4;
1905
 
                if (haveStone[D]) {
1906
 
                    send_impulse(targetpos[D], Direction(D));
1907
 
                }
1908
 
            }
1909
 
 
1910
 
            incoming = NODIR;   // forget impulse direction
1911
 
            break;
1912
 
        }
1913
 
    }
1914
 
}
1915
 
 
1916
 
 
1917
 
namespace
1918
 
{
1919
 
    class StoneImpulseStone : public StoneImpulse_Base {
1920
 
        CLONEOBJ(StoneImpulseStone);
1921
 
    public:
1922
 
        StoneImpulseStone() : StoneImpulse_Base("st-stoneimpulse")
1923
 
        {}
1924
 
 
1925
 
    private:
1926
 
        void notify_state(State st) {
1927
 
            switch (st) {
1928
 
            case IDLE:
1929
 
                init_model();
1930
 
                break;
1931
 
            case PULSING:
1932
 
                set_anim("st-stoneimpulse-anim1");
1933
 
                break;
1934
 
            case CLOSING:
1935
 
                set_anim("st-stoneimpulse-anim2");
1936
 
                break;
1937
 
            }
1938
 
        }
1939
 
 
1940
 
        void actor_hit(const StoneContact &/*sc*/) {
1941
 
            change_state(PULSING);
1942
 
        }
1943
 
 
1944
 
    };
1945
 
 
1946
 
 
1947
 
    class HollowStoneImpulseStone : public StoneImpulse_Base {
1948
 
        CLONEOBJ(HollowStoneImpulseStone);
1949
 
    public:
1950
 
        HollowStoneImpulseStone()
1951
 
        : StoneImpulse_Base("st-stoneimpulse-hollow") {}
1952
 
    private:
1953
 
        void notify_state(State st) {
1954
 
            switch (st) {
1955
 
            case IDLE:
1956
 
                init_model();
1957
 
                lasers::MaybeRecalcLight(get_pos());
1958
 
                break;
1959
 
            case PULSING:
1960
 
                lasers::MaybeRecalcLight(get_pos());
1961
 
                set_anim("st-stoneimpulse-hollow-anim1");
1962
 
                break;
1963
 
            case CLOSING:
1964
 
                set_anim("st-stoneimpulse-hollow-anim2");
1965
 
                break;
1966
 
            }
1967
 
        }
1968
 
 
1969
 
        StoneResponse collision_response(const StoneContact &/*sc*/) {
1970
 
            return (state == IDLE) ? STONE_PASS : STONE_REBOUND;
1971
 
        }
1972
 
        void actor_inside(Actor *a) {
1973
 
            if (state == PULSING || state == CLOSING)
1974
 
                SendMessage(a, "shatter");
1975
 
        }
1976
 
 
1977
 
        bool is_floating () const {
1978
 
            return true;
1979
 
        }
1980
 
 
1981
 
        void on_laserhit (Direction) {
1982
 
            // hollow StoneImpulseStones cannot be activated using lasers
1983
 
        }
1984
 
    };
1985
 
 
1986
 
 
1987
 
    class MovableImpulseStone : public StoneImpulse_Base {
1988
 
        CLONEOBJ(MovableImpulseStone);
1989
 
    public:
1990
 
        MovableImpulseStone()
1991
 
        : StoneImpulse_Base("st-stoneimpulse_movable"), 
1992
 
          repulse(false)
1993
 
        {
1994
 
        }
1995
 
 
1996
 
    private:
1997
 
 
1998
 
        void notify_state(State st) {
1999
 
            switch (st) {
2000
 
            case IDLE:
2001
 
                if (repulse) {
2002
 
                    repulse = false;
2003
 
                    change_state(PULSING);
2004
 
                }
2005
 
                else
2006
 
                    init_model();
2007
 
                break;
2008
 
            case PULSING:
2009
 
                set_anim("st-stoneimpulse-anim1");
2010
 
                break;
2011
 
            case CLOSING:
2012
 
                set_anim("st-stoneimpulse-anim2");
2013
 
                break;
2014
 
            }
2015
 
        }
2016
 
 
2017
 
        void init_model() {
2018
 
            set_model("st-stoneimpulse");
2019
 
        }
2020
 
 
2021
 
        // Stone interface:
2022
 
 
2023
 
        void actor_hit(const StoneContact &sc) {
2024
 
            if (!maybe_push_stone (sc)) {
2025
 
                incoming = NODIR; // bad, but no real problem!
2026
 
                if (state == IDLE)
2027
 
                    change_state(PULSING);
2028
 
            }
2029
 
        }
2030
 
 
2031
 
        void on_impulse(const Impulse& impulse) {
2032
 
            State oldstate = state;
2033
 
 
2034
 
            if (move_stone(impulse.dir)) {
2035
 
                notify_state(oldstate); // restart anim if it was animated before move
2036
 
 
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
2040
 
                }
2041
 
            }
2042
 
 
2043
 
            if (state == IDLE)
2044
 
                change_state(PULSING);
2045
 
        }
2046
 
 
2047
 
        void on_move() {
2048
 
            if (state != PULSING)
2049
 
                repulse = true; // pulse again
2050
 
            Stone::on_move();
2051
 
        }
2052
 
 
2053
 
        bool is_movable() const {
2054
 
            // moving the stone is handled explicitly in actor_hit()
2055
 
            return false; //true;
2056
 
        }
2057
 
 
2058
 
        // Variables.
2059
 
        bool repulse;
2060
 
    };
2061
 
}
2062
 
 
2063
 
 
2064
 
/* -------------------- Oxyd stone -------------------- */
2065
 
 
2066
 
/** \page st-oxyd Oxyd Stone
2067
 
 
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.
2073
 
 
2074
 
\b Note: You should usually not to create Oxyd stones manually
2075
 
with \c set_stone(). Use the predefined \c oxyd() function instead.
2076
 
 
2077
 
\subsection oxyda Attributes
2078
 
 
2079
 
- \b flavor      "a", "b", "c", or "d"
2080
 
- \b color       number between 0 and 7
2081
 
 
2082
 
\subsection oxydm Messages
2083
 
 
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
2087
 
 
2088
 
<table><tr>
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"
2093
 
</table>
2094
 
*/
2095
 
 
2096
 
namespace
2097
 
{
2098
 
    class OxydStone : public PhotoStone {
2099
 
        INSTANCELISTOBJ(OxydStone);
2100
 
    public:
2101
 
        OxydStone();
2102
 
 
2103
 
        static void shuffle_colors();
2104
 
    private:
2105
 
        enum State { CLOSED, OPEN, OPENING, CLOSING, BLINKING };
2106
 
        State state;
2107
 
 
2108
 
        // Stone interface
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 &);
2114
 
 
2115
 
 
2116
 
        // PhotoStone interface
2117
 
        void notify_laseron() { maybe_open_stone(); }
2118
 
        void notify_laseroff() {}
2119
 
 
2120
 
        // Animation callback
2121
 
        void animcb();
2122
 
 
2123
 
        // Private methods
2124
 
        void maybe_open_stone();
2125
 
        void change_state(State newstate);
2126
 
 
2127
 
 
2128
 
        static bool blinking(OxydStone *a) {
2129
 
            return (a->state==BLINKING);
2130
 
        }
2131
 
        static bool blinking_or_opening(OxydStone *a) {
2132
 
            return (a->state==BLINKING || a->state == OPENING);
2133
 
        }
2134
 
        static bool not_open(OxydStone *a) {
2135
 
            return !(a->state==OPEN || a->state==OPENING);
2136
 
        }
2137
 
 
2138
 
    };
2139
 
}
2140
 
 
2141
 
OxydStone::InstanceList OxydStone::instances;
2142
 
 
2143
 
OxydStone::OxydStone()
2144
 
: PhotoStone("st-oxyd"),
2145
 
  state(CLOSED)
2146
 
{
2147
 
    set_attrib("flavor", "b");
2148
 
    set_attrib("color", "0");
2149
 
}
2150
 
 
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);
2155
 
    }
2156
 
    else if (m=="shuffle")
2157
 
        shuffle_colors();
2158
 
    else if (m=="trigger" || m=="spitter")
2159
 
        maybe_open_stone();
2160
 
    else if (m=="signal" && to_int(val) != 0)
2161
 
        maybe_open_stone();
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
2165
 
 
2166
 
        if (instances.size() % 2) {
2167
 
            /// "odd number of oxyd stones\n";
2168
 
        }
2169
 
    }
2170
 
}
2171
 
 
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);
2178
 
        }
2179
 
    }
2180
 
 
2181
 
    unsigned size = closed_oxyds.size();
2182
 
    if (size>1) {
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
2186
 
 
2187
 
            OxydStone *o1 = instances[closed_oxyds[i]];
2188
 
            OxydStone *o2 = instances[closed_oxyds[a]];
2189
 
 
2190
 
            string icolor, acolor;
2191
 
            o1->string_attrib("color", &icolor);
2192
 
            o2->string_attrib("color", &acolor);
2193
 
 
2194
 
            o1->set_attrib("color", acolor.c_str());
2195
 
            o2->set_attrib("color", icolor.c_str());
2196
 
        }
2197
 
    }
2198
 
}
2199
 
 
2200
 
void OxydStone::change_state(State newstate) 
2201
 
{
2202
 
    string flavor = "a";
2203
 
    string color = "1";
2204
 
    string_attrib("flavor", &flavor);
2205
 
    string_attrib("color", &color);
2206
 
 
2207
 
    string modelname = string("st-oxyd") + flavor + color;
2208
 
 
2209
 
    State oldstate = state;
2210
 
    state = newstate;
2211
 
 
2212
 
    switch (newstate) {
2213
 
    case CLOSED:
2214
 
        set_model(string("st-oxyd")+flavor);
2215
 
        break;
2216
 
 
2217
 
    case BLINKING:
2218
 
        set_model(modelname + "-blink");
2219
 
        break;
2220
 
 
2221
 
    case OPEN:
2222
 
        if (oldstate == CLOSED) {
2223
 
            sound_event("oxydopen");
2224
 
            sound_event("oxydopened");
2225
 
            set_anim(modelname+"-opening");
2226
 
        } else {
2227
 
            set_model(modelname + "-open");
2228
 
        }
2229
 
        /* If this was the last closed oxyd stone, finish the
2230
 
           level */
2231
 
        if (find_if(instances.begin(),instances.end(),not_open)
2232
 
            ==instances.end())
2233
 
        {
2234
 
            server::FinishLevel();
2235
 
        }
2236
 
        break;
2237
 
 
2238
 
    case OPENING:
2239
 
        sound_event("oxydopen");
2240
 
        if (oldstate == CLOSED)
2241
 
            set_anim(modelname + "-opening");
2242
 
        else if (oldstate == CLOSING)
2243
 
            get_model()->reverse();
2244
 
 
2245
 
        break;
2246
 
 
2247
 
    case CLOSING:
2248
 
        if (oldstate == CLOSED || oldstate==CLOSING) {
2249
 
            state = oldstate;
2250
 
            return;
2251
 
        }
2252
 
 
2253
 
        sound_event("oxydclose");
2254
 
        if (oldstate == OPENING)
2255
 
            get_model()->reverse();
2256
 
        else if (oldstate == BLINKING || oldstate == OPEN) {
2257
 
            set_anim(modelname + "-closing");
2258
 
        }
2259
 
        break;
2260
 
    }
2261
 
}
2262
 
 
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
2270
 
}
2271
 
 
2272
 
void OxydStone::maybe_open_stone() {
2273
 
    if (state == CLOSED || state == CLOSING) {
2274
 
        int mycolor = int_attrib("color");
2275
 
 
2276
 
        // Is another oxyd stone currently blinking?
2277
 
        InstanceList::iterator i;
2278
 
        i=find_if(instances.begin(), instances.end(), blinking_or_opening);
2279
 
 
2280
 
        if (i != instances.end()) {
2281
 
 
2282
 
            bool can_open;
2283
 
 
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);
2290
 
            }
2291
 
            else 
2292
 
                can_open = (mycolor == (*i)->int_attrib("color"));
2293
 
 
2294
 
            if (can_open) {
2295
 
                change_state(OPEN);
2296
 
                (*i)->change_state(OPEN);
2297
 
            } else {
2298
 
                (*i)->change_state(CLOSING);
2299
 
                change_state(OPENING);
2300
 
            }
2301
 
        }
2302
 
        else {
2303
 
            // no blinking stone? -> make this one blink
2304
 
            change_state(OPENING);
2305
 
        }
2306
 
    }
2307
 
}
2308
 
 
2309
 
void OxydStone::actor_hit(const StoneContact &/*sc*/) {
2310
 
    maybe_open_stone();
2311
 
}
2312
 
 
2313
 
void OxydStone::on_creation (GridPos) 
2314
 
{
2315
 
    string flavor = "a";
2316
 
    string_attrib("flavor", &flavor);
2317
 
    set_model(string("st-oxyd") + flavor);
2318
 
    photo_activate();
2319
 
}
2320
 
 
2321
 
void OxydStone::on_removal(GridPos p) 
2322
 
{
2323
 
    photo_deactivate();
2324
 
    kill_model (p);
2325
 
}
2326
 
 
2327
 
 
2328
 
/* -------------------- Turnstiles -------------------- */
2329
 
namespace
2330
 
{
2331
 
    class Turnstile_Arm;
2332
 
 
2333
 
    /*
2334
 
    ** The stone at the center of a turnstile
2335
 
    */
2336
 
    class Turnstile_Pivot_Base : public Stone {
2337
 
    public:
2338
 
        Turnstile_Pivot_Base(const char *kind);
2339
 
 
2340
 
    protected:
2341
 
        bool rotate(bool clockwise, Object *impulse_sender);
2342
 
 
2343
 
        friend class Turnstile_Arm; // uses rotate
2344
 
 
2345
 
    private:
2346
 
        // Object interface
2347
 
        virtual void on_message (const Message &m);
2348
 
        virtual void animcb();
2349
 
 
2350
 
        // Private methods
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);
2357
 
 
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;
2362
 
 
2363
 
        // Variables
2364
 
        bool active;
2365
 
    };
2366
 
 
2367
 
    class Turnstile_Pivot : public Turnstile_Pivot_Base {
2368
 
        CLONEOBJ(Turnstile_Pivot);
2369
 
    public:
2370
 
        Turnstile_Pivot() : Turnstile_Pivot_Base(model()) {}
2371
 
 
2372
 
        const char *model() const { return "st-turnstile"; }
2373
 
        const char *anim() const  { return "st-turnstile-anim"; }
2374
 
        bool oxyd_compatible() const { return true; }
2375
 
    };
2376
 
 
2377
 
    class Turnstile_Pivot_Green : public Turnstile_Pivot_Base {
2378
 
        CLONEOBJ(Turnstile_Pivot_Green);
2379
 
    public:
2380
 
        Turnstile_Pivot_Green() : Turnstile_Pivot_Base(model()) {}
2381
 
 
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; }
2385
 
    };
2386
 
 
2387
 
    /*
2388
 
    ** The base class for any of the four arms of the turnstile
2389
 
    */
2390
 
    class Turnstile_Arm : public Stone {
2391
 
        virtual Direction get_dir() const = 0;
2392
 
 
2393
 
        void actor_hit(const StoneContact &sc);
2394
 
        void on_impulse(const Impulse& impulse);
2395
 
 
2396
 
        Turnstile_Pivot_Base *get_pivot() {
2397
 
            Stone *st = GetStone (move (get_pos(), reverse(get_dir())));
2398
 
            return dynamic_cast<Turnstile_Pivot_Base*>(st);
2399
 
        }
2400
 
 
2401
 
        bool is_movable () const { return true; }
2402
 
    protected:
2403
 
        Turnstile_Arm (const char *kind) : Stone(kind)
2404
 
        {}
2405
 
    };
2406
 
 
2407
 
    class Turnstile_N : public Turnstile_Arm {
2408
 
        CLONEOBJ(Turnstile_N);
2409
 
    public:
2410
 
        Turnstile_N(): Turnstile_Arm("st-turnstile-n") {}
2411
 
        Direction get_dir () const { return NORTH; }
2412
 
    };
2413
 
 
2414
 
    class Turnstile_S : public Turnstile_Arm {
2415
 
        CLONEOBJ(Turnstile_S);
2416
 
        Direction get_dir () const { return SOUTH; }
2417
 
    public:
2418
 
        Turnstile_S(): Turnstile_Arm("st-turnstile-s") {}
2419
 
    };
2420
 
 
2421
 
    class Turnstile_E : public Turnstile_Arm {
2422
 
        CLONEOBJ(Turnstile_E);
2423
 
        Direction get_dir () const { return EAST; }
2424
 
    public:
2425
 
        Turnstile_E(): Turnstile_Arm("st-turnstile-e") {}
2426
 
    };
2427
 
 
2428
 
    class Turnstile_W : public Turnstile_Arm {
2429
 
        CLONEOBJ(Turnstile_W);
2430
 
        Direction get_dir () const { return WEST; }
2431
 
    public:
2432
 
        Turnstile_W(): Turnstile_Arm("st-turnstile-w") {}
2433
 
    };
2434
 
 
2435
 
    /*
2436
 
    **
2437
 
    */
2438
 
    class Turnstile_Corner : public Stone {
2439
 
        CLONEOBJ(Turnstile_Corner);
2440
 
 
2441
 
        void init_model() {
2442
 
            set_anim ("st-turnstile-corner");
2443
 
        }
2444
 
        void animcb() {
2445
 
            KillStone(get_pos());
2446
 
        }
2447
 
    public:
2448
 
        Turnstile_Corner() : Stone("st-turnstile-corner")
2449
 
        {}
2450
 
    };
2451
 
}
2452
 
 
2453
 
 
2454
 
/* -------------------- Turnstile_Arm -------------------- */
2455
 
 
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
2463
 
    };
2464
 
 
2465
 
    Turnstile_Pivot_Base *pivot = get_pivot();
2466
 
 
2467
 
    if (pivot) {
2468
 
        Action a = actions[get_dir()][impulse.dir];
2469
 
        if (a != stay) {
2470
 
            pivot->rotate(a == ROTR, impulse.sender); // ROTR is clockwise
2471
 
        }
2472
 
    }
2473
 
    else {
2474
 
        // Move arms not attached to a pivot individually
2475
 
        move_stone(impulse.dir);
2476
 
    }
2477
 
}
2478
 
 
2479
 
void Turnstile_Arm::actor_hit(const StoneContact &sc) 
2480
 
{
2481
 
    maybe_push_stone(sc);
2482
 
}
2483
 
 
2484
 
// --------------------------------------------
2485
 
//      Turnstile_Pivot_Base implementation
2486
 
// --------------------------------------------
2487
 
 
2488
 
Turnstile_Pivot_Base::Turnstile_Pivot_Base(const char *kind) 
2489
 
: Stone (kind),
2490
 
  active (false)
2491
 
{}
2492
 
 
2493
 
void Turnstile_Pivot_Base::animcb() 
2494
 
2495
 
    set_model(model()); 
2496
 
    active = false;
2497
 
}
2498
 
 
2499
 
void Turnstile_Pivot_Base::on_message (const Message &m)
2500
 
{
2501
 
    if (m.message == "signal") {
2502
 
        int val = to_int (m.value);
2503
 
        if (val == 1)
2504
 
            rotate(false, 0);
2505
 
        else
2506
 
            rotate(true, 0);
2507
 
    }
2508
 
}
2509
 
 
2510
 
 
2511
 
DirectionBits
2512
 
Turnstile_Pivot_Base::arms_present() const
2513
 
{
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);
2524
 
    return arms;
2525
 
}
2526
 
 
2527
 
bool Turnstile_Pivot_Base::no_stone (int xoff, int yoff) const {
2528
 
    GridPos p = get_pos();
2529
 
    p.x += xoff;
2530
 
    p.y += yoff;
2531
 
    return (0 == GetStone(p));
2532
 
}
2533
 
 
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));
2540
 
}
2541
 
 
2542
 
void Turnstile_Pivot_Base::rotate_arms (DirectionBits arms, bool clockwise) {
2543
 
    GridPos p = get_pos();
2544
 
 
2545
 
    RBI_vector Nrubs;
2546
 
    RBI_vector Erubs;
2547
 
    RBI_vector Srubs;
2548
 
    RBI_vector Wrubs;
2549
 
 
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);
2554
 
 
2555
 
    remove_arms(arms);
2556
 
 
2557
 
    if (clockwise) {
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);
2562
 
    }
2563
 
    else {
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);
2568
 
    }
2569
 
}
2570
 
 
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);
2577
 
 
2578
 
    if (Item *it = GetItem(newp))
2579
 
        it->on_stonehit(st);
2580
 
 
2581
 
    if (!rubs.empty())
2582
 
        for (RBI_vector::iterator i = rubs.begin(); i != rubs.end(); ++i)
2583
 
            AddRubberBand (i->act, st, i->data);
2584
 
}
2585
 
 
2586
 
bool Turnstile_Pivot_Base::rotate(bool clockwise, Object *impulse_sender) {
2587
 
    if (active)
2588
 
        return false;
2589
 
 
2590
 
    DirectionBits arms       = arms_present();
2591
 
    bool          can_rotate = true;
2592
 
 
2593
 
    if (clockwise)  {
2594
 
        if (arms & NORTHBIT) {
2595
 
            can_rotate &= no_stone(+1,-1);
2596
 
            if (! (arms & EASTBIT)) can_rotate &= no_stone(+1,0);
2597
 
        }
2598
 
        if (arms & WESTBIT) {
2599
 
            can_rotate &= no_stone(-1,-1);
2600
 
            if (! (arms & NORTHBIT)) can_rotate &= no_stone(0,-1);
2601
 
        }
2602
 
        if (arms & SOUTHBIT) {
2603
 
            can_rotate &= no_stone(-1,+1);
2604
 
            if (! (arms & WESTBIT)) can_rotate &= no_stone(-1,0);
2605
 
        }
2606
 
        if (arms & EASTBIT) {
2607
 
            can_rotate &= no_stone(+1,+1);
2608
 
            if (! (arms & SOUTHBIT)) can_rotate &= no_stone(0,+1);
2609
 
        }
2610
 
    }
2611
 
    else {
2612
 
        if (arms & NORTHBIT) {
2613
 
            can_rotate &= no_stone(-1,-1);
2614
 
            if (! (arms & WESTBIT)) can_rotate &= no_stone(-1,0);
2615
 
        }
2616
 
        if (arms & WESTBIT) {
2617
 
            can_rotate &= no_stone(-1,+1);
2618
 
            if (! (arms & SOUTHBIT)) can_rotate &= no_stone(0,+1);
2619
 
        }
2620
 
        if (arms & SOUTHBIT) {
2621
 
            can_rotate &= no_stone(+1,+1);
2622
 
            if (! (arms & EASTBIT)) can_rotate &= no_stone(+1,0);
2623
 
        }
2624
 
        if (arms & EASTBIT) {
2625
 
            can_rotate &= no_stone(+1,-1);
2626
 
            if (! (arms & NORTHBIT)) can_rotate &= no_stone(0,-1);
2627
 
        }
2628
 
    }
2629
 
 
2630
 
    if (can_rotate) {
2631
 
        if (clockwise)
2632
 
            sound_event ("turnstileright");
2633
 
        else 
2634
 
            sound_event ("turnstileleft");
2635
 
//         if (dynamic_cast<Actor*>(impulse_sender)) {
2636
 
//         }
2637
 
        sound_event("movesmall");
2638
 
 
2639
 
        active = true;
2640
 
        set_anim(anim());
2641
 
        rotate_arms(arms, clockwise);
2642
 
        handleActorsAndItems(clockwise, impulse_sender);
2643
 
 
2644
 
        PerformAction (this, 1-clockwise);
2645
 
        server::IncMoveCounter();
2646
 
    }
2647
 
    return can_rotate;
2648
 
}
2649
 
 
2650
 
namespace {
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}
2657
 
        };
2658
 
        return arms & neededArm[cw][field];
2659
 
    }
2660
 
}
2661
 
 
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
2667
 
    };
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 };
2670
 
 
2671
 
    bool arm_seen[8];
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);
2675
 
 
2676
 
    // ---------- Handle items in range ----------
2677
 
    GridPos pv_pos = get_pos();
2678
 
    for (int i = 0; i<8; ++i) 
2679
 
        if (arm_seen[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)
2683
 
        }
2684
 
 
2685
 
    // ---------- Handle actors in range ----------
2686
 
    vector<Actor*> actorsInRange;
2687
 
 
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))
2691
 
        return;
2692
 
 
2693
 
    vector<Actor*>::iterator i = actorsInRange.begin(), end = actorsInRange.end();
2694
 
    for (; i != end; ++i) {
2695
 
        Actor *ac = *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;
2700
 
 
2701
 
        // ignore if actor is not inside the turnstile
2702
 
        if (dx<-1 || dx>1 || dy<-1 || dy>1)
2703
 
            continue;
2704
 
 
2705
 
        int idx_source = to_index[dx+1][dy+1];
2706
 
        if (idx_source == -1) 
2707
 
            continue;       // actor inside pivot -- should not happen
2708
 
 
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)
2714
 
        };
2715
 
 
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?
2719
 
 
2720
 
        if (compatible) {
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
2725
 
        }
2726
 
 
2727
 
        if (!do_warp) 
2728
 
            continue;
2729
 
 
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);
2732
 
 
2733
 
        if (Stone *st = GetStone(ac_target_pos)) {
2734
 
 
2735
 
            // destination is blocked
2736
 
 
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] = {
2740
 
                    // anticlockwise
2741
 
                    { SOUTHBIT|WESTBIT, WESTBIT, NORTHBIT|WESTBIT, NORTHBIT,
2742
 
                      NORTHBIT|EASTBIT, EASTBIT, SOUTHBIT|EASTBIT, SOUTHBIT },
2743
 
                    // clockwise
2744
 
                    { NORTHBIT|EASTBIT, EASTBIT, SOUTHBIT|EASTBIT, SOUTHBIT,
2745
 
                      SOUTHBIT|WESTBIT, WESTBIT, NORTHBIT|WESTBIT, NORTHBIT }
2746
 
                };
2747
 
 
2748
 
                DirectionBits possible_impulses =
2749
 
                    static_cast<DirectionBits>(impulse_dir[clockwise][idx_target]);
2750
 
 
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));
2754
 
 
2755
 
//                 if (GetStone(ac_target_pos) == 0)  // arm disappeared
2756
 
//                     break;
2757
 
            }
2758
 
        }
2759
 
    }
2760
 
 
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)
2765
 
//
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.
2769
 
 
2770
 
}
2771
 
 
2772
 
 
2773
 
/* -------------------- Mail stone -------------------- */
2774
 
 
2775
 
namespace
2776
 
{
2777
 
    class MailStone : public Stone {
2778
 
        CLONEOBJ(MailStone);
2779
 
        Direction m_dir;
2780
 
 
2781
 
 
2782
 
        MailStone (const char *kind, Direction dir);
2783
 
        void actor_hit (const StoneContact &sc);
2784
 
 
2785
 
        GridPos find_pipe_endpoint();
2786
 
    public:
2787
 
        static void setup();
2788
 
    };
2789
 
}
2790
 
 
2791
 
void MailStone::setup()
2792
 
{
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));
2797
 
}
2798
 
 
2799
 
MailStone::MailStone (const char *kind, Direction dir)
2800
 
: Stone(kind), m_dir(dir)
2801
 
{}
2802
 
 
2803
 
 
2804
 
void MailStone::actor_hit (const StoneContact &sc) 
2805
 
{
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);
2812
 
            }
2813
 
        }
2814
 
    }
2815
 
}
2816
 
 
2817
 
GridPos MailStone::find_pipe_endpoint() 
2818
 
{
2819
 
    GridPos p = get_pos();
2820
 
    Direction move_dir = m_dir;
2821
 
 
2822
 
    while (move_dir != NODIR) {
2823
 
        p.move (move_dir);
2824
 
        if (Item *it = world::GetItem(p)) {
2825
 
            switch (get_id(it)) {
2826
 
            case it_pipe_h:
2827
 
                if (!(move_dir == EAST || move_dir == WEST))
2828
 
                    move_dir = NODIR;
2829
 
                break;
2830
 
            case it_pipe_v:
2831
 
                if (!(move_dir == SOUTH || move_dir == NORTH))
2832
 
                    move_dir = NODIR;
2833
 
                break;
2834
 
            case it_pipe_ne:
2835
 
                if (move_dir == SOUTH)     move_dir = EAST;
2836
 
                else if (move_dir == WEST) move_dir = NORTH;
2837
 
                else                       move_dir = NODIR;
2838
 
                break;
2839
 
            case it_pipe_es:
2840
 
                if (move_dir == NORTH)     move_dir = EAST;
2841
 
                else if (move_dir == WEST) move_dir = SOUTH;
2842
 
                else                       move_dir = NODIR;
2843
 
                break;
2844
 
            case it_pipe_sw:
2845
 
                if (move_dir == NORTH)     move_dir = WEST;
2846
 
                else if (move_dir == EAST) move_dir = SOUTH;
2847
 
                else                       move_dir = NODIR;
2848
 
                break;
2849
 
            case it_pipe_wn:
2850
 
                if (move_dir == SOUTH)     move_dir = WEST;
2851
 
                else if (move_dir == EAST) move_dir = NORTH;
2852
 
                else                       move_dir = NODIR;
2853
 
                break;
2854
 
            default:
2855
 
                move_dir = NODIR;; // end of pipe reached
2856
 
            }
2857
 
        } else
2858
 
            move_dir = NODIR;
2859
 
    }
2860
 
    return p;
2861
 
}
2862
 
 
2863
 
// --------------------------------------------------------------------------------
2864
 
 
2865
 
void stones::Init_complex()
2866
 
{
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));
2872
 
 
2873
 
    Register(new BlockerStone(true));
2874
 
    Register(new BlockerStone(false));
2875
 
 
2876
 
    Register(new Door);
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);
2884
 
 
2885
 
    Register(new HollowStoneImpulseStone);
2886
 
 
2887
 
    MailStone::setup();
2888
 
 
2889
 
    Register(new MovableImpulseStone);
2890
 
 
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));
2906
 
 
2907
 
    Register(new OxydStone);
2908
 
 
2909
 
    Register (new PullStone);
2910
 
 
2911
 
    Register( new VolcanoStone);
2912
 
    Register("st-volcano_inactive", new VolcanoStone(VolcanoStone::INACTIVE));
2913
 
    Register("st-volcano_active", new VolcanoStone(VolcanoStone::ACTIVE));
2914
 
 
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));
2933
 
 
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));
2951
 
 
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));
2968
 
 
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));
2973
 
 
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));
2982
 
 
2983
 
    Register(new StoneImpulseStone);
2984
 
 
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);
2991
 
}