2326 lines
65 KiB
C++
2326 lines
65 KiB
C++
/*
|
||
* Copyright (C) 2002,2003,2004 Daniel Heck
|
||
*
|
||
* This program is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU General Public License
|
||
* as published by the Free Software Foundation; either version 2
|
||
* of the License, or (at your option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License along
|
||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
*
|
||
*/
|
||
|
||
#include "errors.hh"
|
||
#include "laser.hh"
|
||
#include "server.hh"
|
||
#include "player.hh"
|
||
#include "client.hh"
|
||
#include "Inventory.hh"
|
||
|
||
#include "stones_internal.hh"
|
||
|
||
using namespace std;
|
||
using namespace world;
|
||
using namespace stones;
|
||
|
||
|
||
/* -------------------- SimpleStoneTraits -------------------- */
|
||
|
||
namespace
|
||
{
|
||
/*! This class stores some atrributes for SimpleStones. Only one
|
||
instance is created for each type of SimpleStone. */
|
||
|
||
struct SimpleStoneTraits {
|
||
string sound; // collision sound
|
||
bool hollow; // whether stone is hollow
|
||
bool glass; // whether it's a glass stone
|
||
|
||
// static list of all allocated SimpleStoneTraits (never freed yet)
|
||
static vector<SimpleStoneTraits*> simple_stone_traits;
|
||
|
||
SimpleStoneTraits(const string& sound_, bool hollow_, bool glass_)
|
||
: sound(sound_) , hollow(hollow_) , glass(glass_)
|
||
{}
|
||
|
||
public:
|
||
SimpleStoneTraits() {}
|
||
|
||
// static void clear() {
|
||
// vector<SimpleStoneTraits*>::iterator i = simple_stone_traits.begin();
|
||
// vector<SimpleStoneTraits*>::iterator e = simple_stone_traits.end();
|
||
// for (; i != e; ++e)
|
||
// delete simple_stone_traits[i];
|
||
// simple_stone_traits.clear();
|
||
// }
|
||
|
||
static const SimpleStoneTraits* Register(const string& sound_, bool hollow_, bool glass_) {
|
||
simple_stone_traits.push_back(new SimpleStoneTraits(sound_, hollow_, glass_));
|
||
return simple_stone_traits.back();
|
||
}
|
||
};
|
||
|
||
vector<SimpleStoneTraits*> SimpleStoneTraits::simple_stone_traits;
|
||
}
|
||
|
||
|
||
/* -------------------- SimpleStone -------------------- */
|
||
|
||
namespace
|
||
{
|
||
class SimpleStone : public Stone {
|
||
public:
|
||
SimpleStone(const string &knd, const string & snd,
|
||
bool hollow, bool is_glass)
|
||
: Stone(knd.c_str()),
|
||
traits(SimpleStoneTraits::Register(snd, hollow, is_glass)),
|
||
sunglasses(false)
|
||
{}
|
||
private:
|
||
SimpleStone(const SimpleStone& other)
|
||
: Stone(other.get_kind()), traits(other.traits),
|
||
sunglasses(false)
|
||
{}
|
||
|
||
Stone *clone() { return new SimpleStone(*this); }
|
||
void dispose() { delete this; }
|
||
|
||
const char *collision_sound() {
|
||
return traits->sound.c_str();
|
||
}
|
||
|
||
/** Different kinds of glassstones:
|
||
* Stone: visible: invisible: lasertransparent:
|
||
* st-glass - pass Y
|
||
* st-glass1 - pass Y
|
||
* st-glass1_hole pass pass Y
|
||
* st-glass2 - pass N
|
||
* st-glass2_hole pass pass Y
|
||
* st-glass3 - - Y
|
||
*/
|
||
StoneResponse collision_response(const StoneContact &sc) {
|
||
if (traits->hollow)
|
||
return STONE_PASS;
|
||
if (sc.actor->is_invisible() && ((traits->glass && !is_kind("st-glass3")) || is_kind("st-beads")) )
|
||
return STONE_PASS;
|
||
return Stone::collision_response(sc);
|
||
}
|
||
|
||
bool is_sticky (const Actor *actor) const {
|
||
if (traits->glass || is_kind("st-beads"))
|
||
return !actor->is_invisible();
|
||
return Stone::is_sticky(actor);
|
||
}
|
||
|
||
bool is_floating() const {
|
||
return traits->hollow;
|
||
}
|
||
|
||
bool is_transparent (Direction dir) const {
|
||
if (traits->hollow || (traits->glass && !is_kind("st-glass2")) )
|
||
return true;
|
||
return Stone::is_transparent(dir);
|
||
}
|
||
|
||
virtual Value on_message (const Message &m)
|
||
{
|
||
if (traits->hollow && m.message == "glasses") {
|
||
if (to_int(m.value)) {
|
||
if (!sunglasses) {
|
||
sunglasses = true;
|
||
set_model( "invisible");
|
||
}
|
||
}
|
||
else {
|
||
if (sunglasses) {
|
||
sunglasses = false;
|
||
set_model (this->get_kind());
|
||
}
|
||
}
|
||
}
|
||
return Value();
|
||
}
|
||
|
||
const SimpleStoneTraits *traits; // owned by simple_stone_traits
|
||
bool sunglasses; // seen through glasses
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- SimpleStoneMovable -------------------- */
|
||
|
||
namespace
|
||
{
|
||
class SimpleStoneMovable : public Stone {
|
||
public:
|
||
SimpleStoneMovable(const string &knd, const string & snd, bool is_glass)
|
||
: Stone (knd.c_str()),
|
||
traits(SimpleStoneTraits::Register(snd, false, is_glass))
|
||
{}
|
||
|
||
private:
|
||
SimpleStoneMovable(const SimpleStoneMovable& other)
|
||
: Stone (other.get_kind()),
|
||
traits (other.traits)
|
||
{}
|
||
|
||
Stone *clone() { return new SimpleStoneMovable(*this); }
|
||
void dispose() { delete this; }
|
||
|
||
const char *collision_sound() {
|
||
return traits->sound.c_str();
|
||
}
|
||
|
||
/** Different kinds of movable glassstones:
|
||
* Stone: visible: invisible: lasertransparent:
|
||
* st-glass_move push pass Y
|
||
* st-glass1_move push push Y
|
||
* st-glass2_move push push N
|
||
*/
|
||
StoneResponse collision_response(const StoneContact &sc) {
|
||
if (traits->glass && sc.actor->is_invisible() && is_kind("st-glass_move"))
|
||
return STONE_PASS;
|
||
return Stone::collision_response(sc);
|
||
}
|
||
|
||
bool is_sticky (const Actor *actor) const {
|
||
if (traits->glass)
|
||
return !actor->is_invisible();
|
||
return Stone::is_sticky(actor);
|
||
}
|
||
|
||
bool is_transparent (Direction dir) const {
|
||
if (traits->glass && !is_kind("st-glass2_move"))
|
||
return true;
|
||
return Stone::is_transparent(dir);
|
||
}
|
||
|
||
bool is_movable() const { return true; }
|
||
|
||
const SimpleStoneTraits *traits; // owned by simple_stone_traits
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- DummyStone -------------------- */
|
||
|
||
namespace
|
||
{
|
||
/*! Used for debugging; Prints its own oxyd code when hit. */
|
||
class DummyStone : public Stone {
|
||
CLONEOBJ(DummyStone);
|
||
public:
|
||
DummyStone() : Stone("st-dummy") {}
|
||
private:
|
||
|
||
StoneResponse collision_response(const StoneContact &/*sc*/) {
|
||
static int lastCode = -1;
|
||
int code = int_attrib("code");
|
||
if (code != lastCode) {
|
||
fprintf(stderr, "Collision with stone 0x%02x\n", code);
|
||
lastCode = code;
|
||
}
|
||
return STONE_REBOUND;
|
||
}
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- EasyModeStone -------------------- */
|
||
|
||
/** \page st-easymode Easy-Mode Stone
|
||
|
||
In easy game mode this stone converts the floor at its
|
||
position to fl-normal.
|
||
In normal game mode the stone kills any item at its position.
|
||
Then in both modes it kills itself.
|
||
|
||
E.g. it can be used to hide water-barriers or to insert helper
|
||
items that make the level easier in easy game mode.
|
||
|
||
\subsection easye Example
|
||
\verbatim
|
||
set_stone("st-easymode", 10,10)
|
||
\endverbatim
|
||
|
||
\ref it-easymode
|
||
*/
|
||
|
||
namespace
|
||
{
|
||
class EasyModeStone : public Stone {
|
||
CLONEOBJ(EasyModeStone);
|
||
DECL_TRAITS;
|
||
|
||
virtual Value message(const std::string &msg, const Value&) {
|
||
if (msg == "init") {
|
||
if (server::GetDifficulty() == DIFFICULTY_EASY) {
|
||
SetFloor (get_pos(), MakeFloor ("fl-normal"));
|
||
} else {
|
||
KillItem (get_pos());
|
||
}
|
||
KillStone (get_pos());
|
||
}
|
||
return Value();
|
||
}
|
||
public:
|
||
EasyModeStone()
|
||
{}
|
||
};
|
||
DEF_TRAITS(EasyModeStone, "st-easymode", st_easymode);
|
||
}
|
||
|
||
|
||
/* -------------------- Grates -------------------- */
|
||
|
||
namespace
|
||
{
|
||
class GrateBase : public Stone {
|
||
public:
|
||
GrateBase(const char *kind) : Stone(kind) {}
|
||
private:
|
||
bool is_floating() const { return true; }
|
||
bool is_transparent (Direction) const { return true; }
|
||
|
||
virtual StoneResponse collision_response(const StoneContact &sc) {
|
||
if (server::GameCompatibility == GAMET_OXYD1) {
|
||
return STONE_PASS;
|
||
}
|
||
// tested with per.oxyd
|
||
return sc.actor->is_on_floor() ? STONE_PASS : STONE_REBOUND;
|
||
}
|
||
};
|
||
|
||
class Grate1 : public GrateBase {
|
||
CLONEOBJ(Grate1);
|
||
public:
|
||
Grate1() : GrateBase("st-grate1") {}
|
||
};
|
||
|
||
class Grate2 : public GrateBase {
|
||
CLONEOBJ(Grate2);
|
||
public:
|
||
Grate2() : GrateBase("st-grate2") {}
|
||
};
|
||
|
||
/*! Horses and small marbles can move through this stone, but
|
||
normal marbles can't. */
|
||
class Grate3 : public GrateBase {
|
||
CLONEOBJ(Grate3);
|
||
public:
|
||
Grate3() : GrateBase("st-grate3") {}
|
||
|
||
StoneResponse collision_response(const StoneContact &sc) {
|
||
ActorID id = get_id(sc.actor);
|
||
if (id == ac_horse || id == ac_meditation || id == ac_killerball)
|
||
return STONE_PASS;
|
||
else
|
||
return STONE_REBOUND;
|
||
}
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- Chameleon Stone -------------------- */
|
||
|
||
namespace
|
||
{
|
||
class ChameleonStone : public Stone {
|
||
CLONEOBJ(ChameleonStone);
|
||
DECL_TRAITS;
|
||
|
||
void init_model() {
|
||
string modelname = "fl-gray";
|
||
if (Floor *fl = GetFloor(get_pos()))
|
||
modelname = fl->get_kind();
|
||
set_model(modelname);
|
||
}
|
||
|
||
bool is_floating() const
|
||
{ return true; }
|
||
|
||
StoneResponse collision_response(const StoneContact &)
|
||
{ return STONE_PASS; }
|
||
|
||
public:
|
||
ChameleonStone()
|
||
{}
|
||
};
|
||
DEF_TRAITS(ChameleonStone, "st-chameleon", st_chameleon);
|
||
}
|
||
|
||
|
||
/* -------------------- SwapStone -------------------- */
|
||
|
||
namespace
|
||
{
|
||
class SwapStone : public Stone, public TimeHandler {
|
||
public:
|
||
SwapStone();
|
||
~SwapStone();
|
||
private:
|
||
// Object interface
|
||
SwapStone *clone();
|
||
void dispose();
|
||
|
||
// GridObject interface
|
||
void init_model();
|
||
void on_removal(GridPos p);
|
||
|
||
// Stone interface
|
||
void on_impulse (const Impulse &impulse);
|
||
bool is_removable() const { return state == IDLE; }
|
||
void actor_hit (const StoneContact &sc);
|
||
|
||
// TimeHandler interface
|
||
void alarm();
|
||
|
||
// Variables :
|
||
enum State { IDLE, COME, GO } state;
|
||
YieldedGridStone *in_exchange_with;
|
||
Direction move_dir;
|
||
};
|
||
}
|
||
|
||
SwapStone::SwapStone()
|
||
: Stone("st-swap"),
|
||
state(IDLE),
|
||
in_exchange_with(0),
|
||
move_dir(NODIR)
|
||
{}
|
||
|
||
SwapStone::~SwapStone() {
|
||
GameTimer.remove_alarm(this);
|
||
}
|
||
|
||
SwapStone *SwapStone::clone() {
|
||
SwapStone *other = new SwapStone(*this);
|
||
other->in_exchange_with = 0;
|
||
return other;
|
||
}
|
||
|
||
void SwapStone::dispose() {
|
||
if (state == COME && in_exchange_with != 0) {
|
||
in_exchange_with->dispose();
|
||
delete in_exchange_with;
|
||
}
|
||
delete this;
|
||
}
|
||
|
||
void SwapStone::on_removal(GridPos p) {
|
||
if (state == COME) {
|
||
GameTimer.remove_alarm(this);
|
||
}
|
||
}
|
||
|
||
/* Animation finished; put the "swapped" stone to its new position. */
|
||
void SwapStone::alarm()
|
||
{
|
||
GridPos oldPos = move(get_pos(), reverse(move_dir));
|
||
|
||
// Set the swapped stone (this also kills the old (inactive) swap stone)
|
||
in_exchange_with->set_stone(oldPos);
|
||
delete in_exchange_with;
|
||
in_exchange_with = 0;
|
||
|
||
state = IDLE;
|
||
init_model();
|
||
// sound_event ("moveslow");
|
||
}
|
||
|
||
void SwapStone::on_impulse(const Impulse& impulse)
|
||
{
|
||
if (state == IDLE) {
|
||
GridPos oldp = get_pos();
|
||
GridPos newp = move(oldp, impulse.dir);
|
||
|
||
// never swap beyond the world and for non enigma modes do not swap to
|
||
// border as well.
|
||
if (IsInsideLevel(newp) &&
|
||
(server::GameCompatibility == GAMET_ENIGMA || !IsLevelBorder(newp))) {
|
||
Stone *other = GetStone(newp);
|
||
if (other && other->is_removable()) {
|
||
SwapStone *newStone = new SwapStone;
|
||
newStone->state = COME;
|
||
newStone->in_exchange_with = new YieldedGridStone(other); // yields 'other'
|
||
newStone->move_dir = impulse.dir;
|
||
|
||
GameTimer.set_alarm(newStone, 0.1, false);
|
||
|
||
SetStone(newp, newStone);
|
||
|
||
state = GO;
|
||
move_dir = impulse.dir;
|
||
init_model();
|
||
|
||
sound_event ("moveslow");
|
||
server::IncMoveCounter(1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void SwapStone::actor_hit(const StoneContact &sc) {
|
||
Direction dir = get_push_direction (sc);
|
||
if (dir != NODIR) {
|
||
send_impulse(get_pos(), dir);
|
||
}
|
||
}
|
||
|
||
void SwapStone::init_model() {
|
||
static const char *models_come[] = { "st-swap-w", "st-swap-s", "st-swap-e", "st-swap-n" };
|
||
static const char *models_go[] = { "st-swap-e", "st-swap-n", "st-swap-w", "st-swap-s" };
|
||
|
||
const char *model = 0;
|
||
switch (state) {
|
||
case IDLE: model = "st-swap"; break;
|
||
case COME: model = models_come[move_dir]; break;
|
||
case GO: model = models_go[move_dir]; break;
|
||
}
|
||
|
||
set_model(model);
|
||
}
|
||
|
||
|
||
/* -------------------- BlockStone -------------------- */
|
||
|
||
namespace
|
||
{
|
||
class BlockStone : public Stone {
|
||
CLONEOBJ(BlockStone);
|
||
public:
|
||
BlockStone() : Stone("st-block") {}
|
||
private:
|
||
V2 get_center() const {
|
||
return get_pos().center();
|
||
}
|
||
|
||
void on_floor_change() {
|
||
if (Floor *fl=GetFloor(get_pos())) {
|
||
const string &k = fl->get_kind();
|
||
if (k=="fl-water" || k=="fl-abyss" || k == "fl-swamp") {
|
||
client::Msg_Sparkle (get_center());
|
||
KillStone(get_pos());
|
||
}
|
||
}
|
||
}
|
||
bool is_movable () const { return true; }
|
||
};
|
||
|
||
};
|
||
|
||
|
||
/* -------------------- Window -------------------- */
|
||
|
||
/** \page st-window Breakable Stone
|
||
|
||
Hit this window heavily with your marble to blast it into smithereens.
|
||
|
||
\image html st-window.png
|
||
*/
|
||
|
||
namespace
|
||
{
|
||
class Window : public Stone {
|
||
CLONEOBJ(Window);
|
||
DECL_TRAITS;
|
||
const char *collision_sound() {return "glass";}
|
||
|
||
bool is_transparent (Direction) const { return true; }
|
||
bool is_floating() const { return state != IDLE; }
|
||
enum State { IDLE, BREAK } state;
|
||
|
||
void actor_hit(const StoneContact &sc) {
|
||
Actor *a = sc.actor;
|
||
if (state == IDLE) {
|
||
double impulse = -(a->get_vel() * sc.normal) * get_mass(a);
|
||
if (impulse > 35) {
|
||
SendMessage(a, "shatter");
|
||
}
|
||
|
||
if (impulse > 25) {
|
||
sound_event ("shatter");
|
||
state = BREAK;
|
||
set_anim("st-window-anim");
|
||
}
|
||
}
|
||
}
|
||
void animcb() {
|
||
KillStone(get_pos());
|
||
}
|
||
public:
|
||
Window() : state(IDLE) {}
|
||
};
|
||
DEF_TRAITS(Window, "st-window", st_window);
|
||
}
|
||
|
||
// -----------------------
|
||
// BreakableStone
|
||
// -----------------------
|
||
// base class for Stone_break, Break_Bolder, Break_acwhite and Break_acblack
|
||
//
|
||
// breakable stones can be destroyed using
|
||
// hammer, laser, dynamite, bombs or bombstones
|
||
|
||
namespace
|
||
{
|
||
class BreakableStone : public Stone {
|
||
public:
|
||
BreakableStone(const char *kind) : Stone(kind), state(IDLE) {}
|
||
protected:
|
||
void break_me() {
|
||
if (state == IDLE) {
|
||
state = BREAK;
|
||
sound_event ("stonedestroy");
|
||
set_anim(get_break_anim());
|
||
}
|
||
}
|
||
private:
|
||
const char *collision_sound() { return "stone"; }
|
||
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (may_be_broken_by(sc.actor))
|
||
break_me();
|
||
}
|
||
void on_laserhit(Direction) {
|
||
break_me();
|
||
}
|
||
void animcb() {
|
||
KillStone(get_pos());
|
||
}
|
||
virtual Value message(const string &msg, const Value &) {
|
||
if (msg =="ignite" || msg == "expl" || msg == "bombstone")
|
||
break_me();
|
||
return Value();
|
||
}
|
||
|
||
virtual string get_break_anim() const {
|
||
return string(get_kind())+"-anim";
|
||
}
|
||
virtual bool may_be_broken_by(Actor *a) const = 0;
|
||
|
||
// variables:
|
||
|
||
enum State { IDLE, BREAK };
|
||
State state;
|
||
};
|
||
}
|
||
|
||
//----------------------------------------
|
||
// Stone_break
|
||
//----------------------------------------
|
||
|
||
/** \page st-stone_break Breakable Stone
|
||
|
||
This stone can be destroyed by an actor having a
|
||
hammer and by laser, dynamite, bombs and bombstones.
|
||
|
||
\subsection stone_breake Example
|
||
\verbatim
|
||
set_stone("st-stone_break", 10,10)
|
||
\endverbatim
|
||
|
||
\image html st-stone_break.png
|
||
*/
|
||
namespace
|
||
{
|
||
class Stone_break : public BreakableStone {
|
||
CLONEOBJ(Stone_break);
|
||
public:
|
||
Stone_break(const char *kind) : BreakableStone(kind) { }
|
||
private:
|
||
bool may_be_broken_by(Actor *a) const {
|
||
return player::WieldedItemIs (a, "it-hammer");
|
||
}
|
||
};
|
||
|
||
class LaserBreakable : public BreakableStone {
|
||
CLONEOBJ (LaserBreakable);
|
||
|
||
void actor_hit(const StoneContact &) {
|
||
}
|
||
bool may_be_broken_by(Actor *) const {
|
||
return false;
|
||
}
|
||
public:
|
||
|
||
LaserBreakable(): BreakableStone("st-laserbreak") {
|
||
}
|
||
};
|
||
}
|
||
|
||
|
||
//----------------------------------------
|
||
// Break_bolder
|
||
//----------------------------------------
|
||
|
||
/** \page st-break_bolder Breakable Stone
|
||
|
||
This stone can be destroyed by an actor having a hammer and by laser,
|
||
dynamite, bombs and bombstones, bolder
|
||
|
||
\subsection break_bolder Example
|
||
\verbatim
|
||
set_stone("st-break_bolder", 10,10)
|
||
\endverbatim
|
||
|
||
\image html st-break_bolder.png
|
||
*/
|
||
namespace
|
||
{
|
||
class Break_bolder : public BreakableStone {
|
||
CLONEOBJ(Break_bolder);
|
||
public:
|
||
Break_bolder() : BreakableStone("st-break_bolder") {}
|
||
private:
|
||
bool may_be_broken_by(Actor *a) const {
|
||
return player::WieldedItemIs (a, "it-hammer");
|
||
}
|
||
virtual Value message(const string &msg, const Value &) {
|
||
if (msg == "trigger")
|
||
break_me();
|
||
return Value();
|
||
}
|
||
};
|
||
}
|
||
|
||
//----------------------------------------
|
||
// Stone_movebreak
|
||
//----------------------------------------
|
||
|
||
/** \page st-rock3_movebreak Breakable Movable Stone
|
||
|
||
This stone can be destroyed by an actor having a
|
||
hammer and by laser, dynamite, bombs and bombstones.
|
||
|
||
\subsection stone_breake Example
|
||
\verbatim
|
||
set_stone("st-rock3_movebreak", 10,10)
|
||
\endverbatim
|
||
|
||
\image html st-rock3.png
|
||
*/
|
||
namespace
|
||
{
|
||
class Stone_movebreak : public BreakableStone {
|
||
CLONEOBJ(Stone_movebreak);
|
||
public:
|
||
Stone_movebreak() : BreakableStone("st-rock3_movebreak") {}
|
||
private:
|
||
|
||
void on_laserhit(Direction) {
|
||
}
|
||
|
||
string get_break_anim() const {
|
||
return "st-rock3_break-anim";
|
||
}
|
||
bool may_be_broken_by(Actor *a) const {
|
||
return player::WieldedItemIs (a, "it-hammer");
|
||
}
|
||
|
||
bool is_movable() const { return true; }
|
||
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (may_be_broken_by(sc.actor))
|
||
break_me();
|
||
// else
|
||
// maybe_push_stone (sc);
|
||
}
|
||
void on_impulse(const Impulse& impulse) {
|
||
move_stone(impulse.dir);
|
||
}
|
||
|
||
};
|
||
}
|
||
|
||
//----------------------------------------
|
||
// Break_acwhite
|
||
//----------------------------------------
|
||
|
||
/** \page st-break_acwhite Breakable Stone
|
||
|
||
This stone can be destroyed by actor (whiteball) having a
|
||
hammer and by laser, dynamite, bombs and bombstones.
|
||
|
||
\subsection break_acwhite Example
|
||
\verbatim
|
||
set_stone("st-break_acwhite", 10,10)
|
||
\endverbatim
|
||
|
||
\image html st-break_acwhite.png
|
||
*/
|
||
namespace
|
||
{
|
||
class Break_acwhite : public BreakableStone {
|
||
CLONEOBJ(Break_acwhite);
|
||
public:
|
||
Break_acwhite() : BreakableStone("st-break_acwhite") {}
|
||
private:
|
||
bool may_be_broken_by(Actor *a) const {
|
||
return a->get_attrib("whiteball") &&
|
||
player::WieldedItemIs (a, "it-hammer");
|
||
}
|
||
};
|
||
}
|
||
|
||
//----------------------------------------
|
||
// Break_acblack
|
||
//----------------------------------------
|
||
|
||
/** \page st-break_acblack Breakable Stone
|
||
|
||
This stone can be destroyed by actor (blackball) having a
|
||
hammer.
|
||
|
||
\subsection break_acblack Example
|
||
\verbatim
|
||
set_stone("st-break_acblack", 10,10)
|
||
\endverbatim
|
||
|
||
\image html st-break_acblack.png
|
||
*/
|
||
namespace
|
||
{
|
||
class Break_acblack : public BreakableStone {
|
||
CLONEOBJ(Break_acblack);
|
||
public:
|
||
Break_acblack() : BreakableStone("st-break_acblack") {}
|
||
private:
|
||
bool may_be_broken_by(Actor *a) const {
|
||
return a->get_attrib("blackball") &&
|
||
player::WieldedItemIs (a, "it-hammer");
|
||
}
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- BrickMagic -------------------- */
|
||
|
||
/** \page st-brick_magic Magic Brick Stone
|
||
|
||
This stone does initially look like a "st-brick". If touched by the
|
||
actor, having a magic wand, it turns into a "st-glass" stone and
|
||
allows lasers to go through it.
|
||
|
||
\subsection brick_magicke Example
|
||
\verbatim
|
||
set_stone("st-brick_magic", 10,10)
|
||
\endverbatim
|
||
|
||
\image html st-brick.png
|
||
*/
|
||
namespace
|
||
{
|
||
class BrickMagic : public Stone {
|
||
CLONEOBJ(BrickMagic);
|
||
const char *collision_sound() {return "stone";}
|
||
public:
|
||
BrickMagic() : Stone("st-brick_magic") {}
|
||
private:
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (player::WieldedItemIs (sc.actor, "it-magicwand")) {
|
||
sound_event ("stonepaint");
|
||
ReplaceStone (get_pos(), MakeStone("st-glass"));
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- Stonebrush -------------------- */
|
||
|
||
/** \page st-stonebrush Brush Stone
|
||
|
||
This stone is initially invisible. If touched by an actor
|
||
having a brush it turns into a "st-rock4".
|
||
|
||
\subsection stonebrushe Example
|
||
\verbatim
|
||
set_stone("st-stonebrush", 10,10)
|
||
\endverbatim
|
||
|
||
\image html st-rock4.png
|
||
*/
|
||
namespace
|
||
{
|
||
class Stonebrush : public Stone {
|
||
CLONEOBJ(Stonebrush);
|
||
const char *collision_sound() {return "stone";}
|
||
public:
|
||
Stonebrush() : Stone("st-stonebrush"), state(INVISIBLE) {}
|
||
private:
|
||
enum State { INVISIBLE, BRUSH } state;
|
||
|
||
void actor_hit(const StoneContact &sc) {
|
||
if( state == INVISIBLE) {
|
||
if (player::WieldedItemIs (sc.actor, "it-brush")) {
|
||
sound_event ("stonepaint");
|
||
state = BRUSH;
|
||
if (server::GameCompatibility == GAMET_PEROXYD) {
|
||
set_model("st-likeoxydc-open");
|
||
}
|
||
else {
|
||
set_model("st-rock4");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
//----------------------------------------
|
||
// Break_invisible
|
||
//----------------------------------------
|
||
|
||
/** \page st-break_invisible Brush Stone
|
||
|
||
This stone is initially invisible. If touched by an actor having a
|
||
brush it turns into a "st_stone_break". This stone can be destroyed
|
||
by an actor having a hammer.
|
||
|
||
\subsection break_invisible Example
|
||
\verbatim
|
||
set_stone("st-break_invisible", 10,10)
|
||
\endverbatim
|
||
|
||
\image html st-stone_break.png
|
||
*/
|
||
namespace
|
||
{
|
||
class Break_invisible : public Stone {
|
||
CLONEOBJ(Break_invisible);
|
||
const char *collision_sound() {return "stone";}
|
||
public:
|
||
Break_invisible() : Stone("st-break_invisible"), state(INVISIBLE) {}
|
||
private:
|
||
enum State { INVISIBLE, BRUSH, DESTROY };
|
||
State state;
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (state == INVISIBLE) {
|
||
if (player::WieldedItemIs (sc.actor, "it-brush")) {
|
||
sound_event ("stonepaint");
|
||
state = BRUSH;
|
||
set_model("st-stone_break");
|
||
}
|
||
}
|
||
else if (state == BRUSH) {
|
||
if (player::WieldedItemIs (sc.actor, "it-hammer")) {
|
||
sound_event ("stonedestroy");
|
||
state = DESTROY;
|
||
set_anim("st-stone_break-anim");
|
||
}
|
||
}
|
||
}
|
||
void animcb() {
|
||
if (state == DESTROY)
|
||
KillStone(get_pos());
|
||
}
|
||
};
|
||
}
|
||
|
||
//----------------------------------------
|
||
// Invisible Magic
|
||
//----------------------------------------
|
||
|
||
/** \page st-invisible_magic Magic Invisible Stone
|
||
|
||
This stone is initially invisible, and laserlight can pass through.
|
||
If touched by an actor having a magic wand, it will mutate into a
|
||
"st-greenbrown" and laserlight is blocked.
|
||
|
||
\subsection invisible_magice Example
|
||
\verbatim
|
||
set_stone("st-invisible_magic", 10,10)
|
||
\endverbatim
|
||
|
||
\image html st-greenbrown.png
|
||
*/
|
||
namespace
|
||
{
|
||
class InvisibleMagic : public Stone {
|
||
CLONEOBJ(InvisibleMagic);
|
||
const char *collision_sound() {return "cloth";}
|
||
public:
|
||
InvisibleMagic() : Stone("st-invisible_magic"), state(INVISIBLE) {}
|
||
private:
|
||
enum State { INVISIBLE, STONE } state;
|
||
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (state == INVISIBLE) {
|
||
if (player::WieldedItemIs (sc.actor, "it-magicwand")) {
|
||
sound_event ("stonepaint");
|
||
state = STONE;
|
||
set_model("st-greenbrown");
|
||
lasers::MaybeRecalcLight(get_pos());
|
||
}
|
||
}
|
||
}
|
||
bool is_transparent (Direction) const { return state==INVISIBLE; }
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- Wooden stone -------------------- */
|
||
|
||
/** \page st-wood Wooden Stone
|
||
|
||
This stone is movable. If moved into abyss, water or swamp it builds
|
||
a wooden plank.
|
||
|
||
\subsection woode Example
|
||
\verbatim
|
||
set_stone("st-wood", 10,10)
|
||
\endverbatim
|
||
|
||
Note: There are two flavors of st-wood which may be specified
|
||
by using st-wood1 or st-wood2, and a two related kinds: st-flrock,
|
||
which creates the unburnable fl-rock and denies fire under it, and
|
||
the burnable st-hay, which leaves the burnable but stable fl-hay
|
||
behind.
|
||
|
||
\image html st-wood.png
|
||
*/
|
||
namespace
|
||
{
|
||
class WoodenStone : public Stone {
|
||
CLONEOBJ(WoodenStone);
|
||
public:
|
||
WoodenStone(const char *kind, const char *floorkind_, bool blockfire_ = false) :
|
||
Stone(kind), floorkind(floorkind_), blockfire(blockfire_) {}
|
||
|
||
private:
|
||
const char *floorkind;
|
||
bool blockfire;
|
||
|
||
void maybe_fall_or_stopfire() {
|
||
GridPos p = get_pos();
|
||
if (world::IsLevelBorder(p))
|
||
return;
|
||
if (Floor *fl = GetFloor(p)) {
|
||
const string &k = fl->get_kind();
|
||
if(blockfire)
|
||
SendMessage(fl, "stopfire");
|
||
if (k == "fl-abyss" || k == "fl-water" || k == "fl-swamp") {
|
||
SetFloor(p, MakeFloor(floorkind));
|
||
KillStone(p);
|
||
}
|
||
}
|
||
}
|
||
|
||
virtual Value message (const string &msg, const Value &) {
|
||
if (msg == "fire" && !blockfire) {
|
||
KillStone(get_pos());
|
||
return Value(1.0); // allow fire to spread
|
||
} else if (msg == "heat" && blockfire) {
|
||
return Value(1.0); // block fire
|
||
} else if (msg == "fall")
|
||
maybe_fall_or_stopfire();
|
||
return Value();
|
||
}
|
||
|
||
// in oxyd1 only fall when moving
|
||
void on_move() {
|
||
Stone::on_move();
|
||
if (server::GameCompatibility == GAMET_OXYD1)
|
||
maybe_fall_or_stopfire();
|
||
}
|
||
|
||
// other oxyds versions: fall everytime the floor changes
|
||
void on_floor_change() {
|
||
if (server::GameCompatibility != GAMET_OXYD1)
|
||
maybe_fall_or_stopfire();
|
||
}
|
||
bool is_movable () const { return true; }
|
||
};
|
||
|
||
/*! When st-wood is created it randomly becomes st-wood1 or
|
||
st-wood2. */
|
||
class RandomWoodenStone : public Stone {
|
||
public:
|
||
RandomWoodenStone() : Stone("st-wood") {}
|
||
private:
|
||
Stone *clone() {
|
||
if(IntegerRand(0,1) == 0)
|
||
return new WoodenStone("st-wood1", "fl-stwood1");
|
||
else
|
||
return new WoodenStone("st-wood2", "fl-stwood2");
|
||
}
|
||
void dispose() {delete this;}
|
||
};
|
||
}
|
||
|
||
//----------------------------------------
|
||
// Growing stones used by it-seed
|
||
//----------------------------------------
|
||
namespace
|
||
{
|
||
class WoodenStone_Growing : public Stone {
|
||
CLONEOBJ(WoodenStone_Growing);
|
||
public:
|
||
WoodenStone_Growing() : Stone("st-wood-growing") {}
|
||
private:
|
||
void init_model() { set_anim("st-wood-growing"); }
|
||
void animcb() {
|
||
Stone *st = world::MakeStone ("st-wood");
|
||
world::ReplaceStone (get_pos(), st);
|
||
SendMessage (st, "fall"); // instantly builds a bridge on fl-swamp etc
|
||
}
|
||
void actor_contact(Actor *a) {SendMessage(a, "shatter");}
|
||
void actor_inside(Actor *a) {SendMessage(a, "shatter");}
|
||
void actor_hit(const StoneContact &sc) {SendMessage(sc.actor, "shatter");}
|
||
};
|
||
|
||
class GreenbrownStone_Growing : public Stone {
|
||
CLONEOBJ(GreenbrownStone_Growing);
|
||
public:
|
||
GreenbrownStone_Growing() : Stone("st-greenbrown-growing") {}
|
||
private:
|
||
void init_model() { set_anim("st-greenbrown-growing"); }
|
||
void animcb() {
|
||
Stone *st = world::MakeStone("st-greenbrown");
|
||
world::ReplaceStone(get_pos(), st);
|
||
}
|
||
void actor_contact(Actor *a) {SendMessage(a, "shatter");}
|
||
void actor_inside(Actor *a) {SendMessage(a, "shatter");}
|
||
void actor_hit(const StoneContact &sc) {SendMessage(sc.actor, "shatter");}
|
||
};
|
||
|
||
class VolcanoStone_Growing : public Stone {
|
||
CLONEOBJ(VolcanoStone_Growing);
|
||
public:
|
||
VolcanoStone_Growing() : Stone("st-volcano-growing") {}
|
||
private:
|
||
void init_model() { set_anim("st-volcano-growing"); }
|
||
void animcb() {
|
||
Stone *st = world::MakeStone("st-volcano_active");
|
||
world::ReplaceStone(get_pos(), st);
|
||
}
|
||
void actor_contact(Actor *a) {SendMessage(a, "shatter");}
|
||
void actor_inside(Actor *a) {SendMessage(a, "shatter");}
|
||
void actor_hit(const StoneContact &sc) {SendMessage(sc.actor, "shatter");}
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- Scissors stone -------------------- */
|
||
|
||
/** \page st-scissors Scissors stone
|
||
|
||
This stone cuts \c all rubber bands attached to an actor that touches
|
||
it.
|
||
|
||
\image html st-scissors
|
||
*/
|
||
namespace
|
||
{
|
||
class ScissorsStone : public Stone {
|
||
CLONEOBJ(ScissorsStone);
|
||
DECL_TRAITS;
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (world::KillRubberBands (sc.actor))
|
||
world::PerformAction(this, false);
|
||
sound_event("scissors");
|
||
set_anim("st-scissors-snip");
|
||
}
|
||
void animcb() {
|
||
set_model("st-scissors");
|
||
}
|
||
public:
|
||
ScissorsStone()
|
||
{}
|
||
};
|
||
DEF_TRAITS(ScissorsStone, "st-scissors", st_scissors);
|
||
}
|
||
|
||
|
||
/* -------------------- Rubberband stone -------------------- */
|
||
|
||
/** \page st-rubberband Rubberband stone
|
||
|
||
If hit by a marble, this stone first removes existing connections with
|
||
other rubberband stones and then attaches a new elastic between the
|
||
marble and itself. Nothing happens if the marble was already attached
|
||
to this particular stone.
|
||
|
||
This stone can be moved if hit with a magic wand.
|
||
|
||
\subsection rubberbanda Attributes
|
||
|
||
- \c length The natural length of the rubberband (default: 1)
|
||
- \c strength The strength of the rubberband (default: 10)
|
||
|
||
\image html st-rubberband.png
|
||
*/
|
||
namespace
|
||
{
|
||
class RubberBandStone : public Stone {
|
||
CLONEOBJ(RubberBandStone);
|
||
DECL_TRAITS;
|
||
|
||
void actor_hit(const StoneContact &sc) {
|
||
double strength = 10.0;
|
||
double length = 1.0;
|
||
double minlength = 0.0;
|
||
double_attrib ("strength", &strength);
|
||
double_attrib ("length", &length);
|
||
double_attrib ("minlength", &minlength);
|
||
|
||
world::RubberBandData rbd;
|
||
rbd.strength = strength;
|
||
rbd.length = length;
|
||
rbd.minlength = minlength;
|
||
|
||
// The mode attribute "scissor" defines, if when touching an st-rubberband,
|
||
// other rubberbands to the actor will be cut of or not, true means they will. true is default.
|
||
enigma::Value const *scissorValue = get_attrib("scissor");
|
||
bool isScissor = (scissorValue == NULL)? true : to_bool(*scissorValue);
|
||
|
||
if (!world::HasRubberBand (sc.actor, this)) {
|
||
sound_event ("rubberband");
|
||
if (isScissor) {
|
||
world::KillRubberBand (sc.actor, (Stone*)0);
|
||
}
|
||
world::AddRubberBand (sc.actor, this, rbd);
|
||
}
|
||
// if (player::wielded_item_is (sc.actor, "it-magicwand"))
|
||
maybe_push_stone (sc);
|
||
}
|
||
|
||
void on_impulse (const Impulse& impulse) {
|
||
Actor *a = dynamic_cast<Actor *> (impulse.sender);
|
||
if (a && player::WieldedItemIs (a, "it-magicwand"))
|
||
move_stone(impulse.dir);
|
||
}
|
||
|
||
|
||
|
||
public:
|
||
RubberBandStone () {
|
||
set_attrib("length", 1.0);
|
||
set_attrib("strength", 10.0);
|
||
}
|
||
};
|
||
DEF_TRAITS(RubberBandStone, "st-rubberband", st_rubberband);
|
||
}
|
||
|
||
|
||
/* -------------------- Timer stone -------------------- */
|
||
|
||
// Attributes:
|
||
//
|
||
// :interval seconds between two "ticks"
|
||
// :loop
|
||
// :action,target as usual
|
||
// :invisible
|
||
|
||
/** \page st-timer Timer Stone
|
||
|
||
This stone can be used to trigger periodic events or to trigger one
|
||
single event after a certain amount of time.
|
||
|
||
\subsection timera Attributes
|
||
|
||
- \b on: 1 if the timer is running
|
||
- \b interval: number of seconds before \b action is performed
|
||
- \b loop: if 1, restart the timer after performing \b action
|
||
- \b action, \b target: as usual
|
||
- \b invisible : if 1, stone is invisible
|
||
|
||
\subsection timerm Messages
|
||
|
||
- \b on, \b off, \b onoff: as usual
|
||
|
||
\subsection timere Example
|
||
|
||
\verbatim
|
||
-- activate a laser after 5 seconds
|
||
set_stone("st-laser", 10,11, {name="laser"})
|
||
set_stone("st-timer", 10,10,
|
||
{loop=0, action="onoff", target="laser", interval=5})
|
||
\endverbatim
|
||
*/
|
||
namespace
|
||
{
|
||
class TimerStone : public OnOffStone, public TimeHandler
|
||
{
|
||
CLONEOBJ(TimerStone);
|
||
public:
|
||
TimerStone() : OnOffStone("st-timer"), m_signalvalue(1) {
|
||
set_attrib("interval", 1.0);
|
||
set_attrib("loop", 1.0);
|
||
set_attrib("on", 1.0);
|
||
set_attrib("invisible", 0.0);
|
||
|
||
// set_on(true); DOESN'T WORK! calls init_model()
|
||
}
|
||
|
||
virtual ~TimerStone();
|
||
private:
|
||
int m_signalvalue;
|
||
|
||
double get_interval() const {
|
||
double interval = 100;
|
||
double_attrib("interval", &interval);
|
||
return interval;
|
||
}
|
||
void init_model() {
|
||
if (int_attrib("invisible")) {
|
||
set_model("invisible");
|
||
}
|
||
else {
|
||
set_model(is_on() ? "st-timer" : "st-timeroff");
|
||
}
|
||
}
|
||
|
||
void on_creation (GridPos p) {
|
||
set_alarm();
|
||
Stone::on_creation (p);
|
||
}
|
||
|
||
void set_alarm() {
|
||
if (is_on())
|
||
GameTimer.set_alarm(this, get_interval(), true);
|
||
}
|
||
|
||
void alarm() {
|
||
if (is_on()) {
|
||
// sound::PlaySound("st-timer");
|
||
PerformAction(this, m_signalvalue != 0);
|
||
m_signalvalue = 1-m_signalvalue;
|
||
}
|
||
}
|
||
|
||
void notify_onoff (bool newon) {
|
||
if (newon)
|
||
set_alarm();
|
||
else
|
||
GameTimer.remove_alarm(this);
|
||
}
|
||
};
|
||
|
||
TimerStone::~TimerStone() {
|
||
GameTimer.remove_alarm(this);
|
||
}
|
||
}
|
||
|
||
|
||
/* -------------------- FartStone -------------------- */
|
||
|
||
/** \page st-fart Fart Stone
|
||
|
||
The fart stone has the unpleasant habit of "blowing off" when
|
||
triggered (by actor contact or signal) and will close all oxyd stones.
|
||
|
||
\subsection fartm Messages
|
||
|
||
- \b trigger: as usual
|
||
|
||
*/
|
||
|
||
namespace
|
||
{
|
||
class FartStone : public Stone {
|
||
CLONEOBJ(FartStone);
|
||
DECL_TRAITS;
|
||
|
||
enum State { IDLE, FARTING, BREAKING };
|
||
State state;
|
||
bool rememberBreaking; // set true if an explosion or break-message
|
||
// or such occured while farting
|
||
|
||
void change_state(State newstate);
|
||
void animcb();
|
||
void actor_hit(const StoneContact &sc);
|
||
virtual Value message(const string &m, const Value &val);
|
||
|
||
void on_laserhit(Direction) {
|
||
change_state(BREAKING);
|
||
}
|
||
public:
|
||
FartStone() : state(IDLE), rememberBreaking(false)
|
||
{}
|
||
};
|
||
DEF_TRAITS(FartStone, "st-fart", st_fart);
|
||
}
|
||
|
||
void FartStone::change_state(State newstate)
|
||
{
|
||
if (state == newstate)
|
||
return;
|
||
|
||
switch (newstate) {
|
||
case IDLE:
|
||
state = IDLE;
|
||
init_model();
|
||
if (rememberBreaking)
|
||
change_state(BREAKING);
|
||
break;
|
||
case FARTING:
|
||
case BREAKING:
|
||
if (state == IDLE) {
|
||
Object *ox = world::GetObjectTemplate("st-oxyd");
|
||
SendMessage(ox, "closeall");
|
||
sound_event("fart");
|
||
if (newstate == BREAKING) {
|
||
sound_event ("stonedestroy");
|
||
set_anim ("st-fartbreak-anim");
|
||
}
|
||
else
|
||
set_anim("st-farting");
|
||
state = newstate;
|
||
} else if (state == FARTING && newstate == BREAKING)
|
||
rememberBreaking = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
void FartStone::animcb()
|
||
{
|
||
if (state == FARTING)
|
||
change_state(IDLE);
|
||
else if (state == BREAKING)
|
||
KillStone(get_pos());
|
||
}
|
||
|
||
void FartStone::actor_hit(const StoneContact &sc)
|
||
{
|
||
if (player::WieldedItemIs (sc.actor, "it-hammer"))
|
||
change_state(BREAKING);
|
||
else
|
||
change_state(FARTING);
|
||
}
|
||
Value FartStone::message (const string &m, const Value &val)
|
||
{
|
||
if (m == "signal" && to_int(val) != 0)
|
||
change_state(FARTING);
|
||
else if (m=="trigger")
|
||
change_state(FARTING);
|
||
else if (m == "ignite" || m == "expl")
|
||
change_state(BREAKING);
|
||
return Value();
|
||
}
|
||
|
||
|
||
|
||
|
||
/* -------------------- Thief -------------------- */
|
||
namespace
|
||
{
|
||
/*! Steals one item from the player's inventory when hit. */
|
||
class ThiefStone : public Stone {
|
||
CLONEOBJ(ThiefStone);
|
||
DECL_TRAITS;
|
||
|
||
enum State { IDLE, EMERGING, RETREATING, CAPTURED } state;
|
||
Actor *m_affected_actor;
|
||
Item * bag;
|
||
public:
|
||
ThiefStone();
|
||
virtual ~ThiefStone();
|
||
private:
|
||
void steal_from_player();
|
||
|
||
void actor_hit(const StoneContact &sc);
|
||
// even a slight touch should steal from the actor:
|
||
void actor_touch(const StoneContact &sc) { actor_hit(sc); }
|
||
void animcb();
|
||
virtual Value message(const string &msg, const Value &v);
|
||
|
||
const char *collision_sound() { return "cloth"; }
|
||
int affected_player;
|
||
};
|
||
DEF_TRAITS(ThiefStone, "st-thief", st_thief);
|
||
}
|
||
|
||
ThiefStone::ThiefStone()
|
||
: state(IDLE), m_affected_actor (0), affected_player (-1), bag(NULL) {}
|
||
|
||
ThiefStone::~ThiefStone() {
|
||
if (bag != NULL)
|
||
delete bag;
|
||
}
|
||
|
||
void ThiefStone::actor_hit(const StoneContact &sc) {
|
||
ActorID id = get_id(sc.actor);
|
||
if (state == IDLE) {
|
||
set_anim("st-thief-emerge");
|
||
state = EMERGING;
|
||
m_affected_actor = sc.actor;
|
||
affected_player = -1;
|
||
m_affected_actor->int_attrib("player", &affected_player);
|
||
}
|
||
}
|
||
|
||
void ThiefStone::animcb() {
|
||
switch (state) {
|
||
case EMERGING:
|
||
steal_from_player();
|
||
state = RETREATING;
|
||
set_anim("st-thief-retreat");
|
||
break;
|
||
case RETREATING:
|
||
state = IDLE;
|
||
init_model();
|
||
break;
|
||
case CAPTURED:
|
||
KillStone(get_pos());
|
||
break;
|
||
default:
|
||
ASSERT(0, XLevelRuntime, "ThiefStone: animcb called with inconsistent state");
|
||
}
|
||
}
|
||
|
||
void ThiefStone::steal_from_player()
|
||
{
|
||
// the actor that hit the thief may no longer exist!
|
||
if (m_affected_actor && affected_player >= 0 &&
|
||
player::HasActor(affected_player, m_affected_actor) &&
|
||
!m_affected_actor->has_shield()) {
|
||
enigma::Inventory *inv = player::GetInventory(m_affected_actor);
|
||
if (inv && inv->size() > 0) {
|
||
if (bag == NULL)
|
||
bag = world::MakeItem(it_bag);
|
||
int i = IntegerRand (0, int (inv->size()-1));
|
||
dynamic_cast<ItemHolder *>(bag)->add_item(inv->yield_item(i));
|
||
player::RedrawInventory (inv);
|
||
sound_event("thief");
|
||
}
|
||
}
|
||
}
|
||
|
||
Value ThiefStone::message(const string &msg, const Value &v) {
|
||
if(msg == "capture" && state == IDLE) {
|
||
state = CAPTURED;
|
||
Item * it = world::GetItem(get_pos());
|
||
|
||
// add items on grid pos that can be picked up to our bag
|
||
if (it != NULL && !(it->get_traits().flags & itf_static) && bag != NULL) {
|
||
dynamic_cast<ItemHolder *>(bag)->add_item(world::YieldItem(get_pos()));
|
||
}
|
||
// drop bag if pos is not occupied by a static item
|
||
if (world::GetItem(get_pos()) == NULL)
|
||
world::SetItem(get_pos(), bag);
|
||
bag = NULL;
|
||
set_anim(string(get_kind()) + "-captured");
|
||
return Value(1);
|
||
} else
|
||
return Stone::message(msg, v);
|
||
}
|
||
|
||
// -------------------------
|
||
// ActorImpulseBase
|
||
// -------------------------
|
||
|
||
namespace
|
||
{
|
||
class ActorImpulseBase : public Stone {
|
||
public:
|
||
ActorImpulseBase(const char *kind) : Stone(kind), state(IDLE) {
|
||
// set_attrib("force", Value());
|
||
}
|
||
|
||
protected:
|
||
virtual void actor_hit (const StoneContact &sc) {
|
||
if (state == IDLE) {
|
||
// actor_hit is called before reflect, but the force added below
|
||
// is applied to actor after the reflection.
|
||
|
||
double forcefac = server::BumperForce;
|
||
double_attrib("force", &forcefac);
|
||
|
||
V2 vec = normalize(sc.actor->get_pos() - get_pos().center());
|
||
sc.actor->add_force (distortedVelocity(vec, forcefac));
|
||
|
||
sound_event("bumper");
|
||
set_anim("st-actorimpulse-anim");
|
||
state = PULSING;
|
||
}
|
||
}
|
||
|
||
private:
|
||
virtual const char *collision_sound() {
|
||
return "";
|
||
}
|
||
|
||
void animcb() {
|
||
if (state == PULSING) {
|
||
state = IDLE;
|
||
init_model();
|
||
}
|
||
}
|
||
|
||
// Variables
|
||
enum State { IDLE, PULSING, BROKEN };
|
||
State state;
|
||
};
|
||
|
||
|
||
class ActorImpulseStone : public ActorImpulseBase {
|
||
CLONEOBJ(ActorImpulseStone);
|
||
|
||
int m_signalidx;
|
||
|
||
virtual Value message (const string &msg, const Value &) {
|
||
if (msg == "signal") {
|
||
world::EmitSignalByIndex (this, m_signalidx, 0);
|
||
m_signalidx += 1;
|
||
if (!world::EmitSignalByIndex (this, m_signalidx, 1)) {
|
||
m_signalidx = 0;
|
||
world::EmitSignalByIndex (this, m_signalidx, 1);
|
||
}
|
||
} else if (msg == "init") {
|
||
world::EmitSignalByIndex (this, m_signalidx, 1);
|
||
}
|
||
return Value();
|
||
}
|
||
|
||
public:
|
||
ActorImpulseStone() : ActorImpulseBase("st-actorimpulse"),
|
||
m_signalidx(0)
|
||
{}
|
||
};
|
||
|
||
|
||
class ActorImpulseStoneInvisible : public ActorImpulseBase {
|
||
CLONEOBJ(ActorImpulseStoneInvisible);
|
||
public:
|
||
ActorImpulseStoneInvisible() : ActorImpulseBase("st-actorimpulse_invisible") {}
|
||
|
||
void actor_hit(const StoneContact& sc) {
|
||
if (player::WieldedItemIs (sc.actor, "it-brush")) {
|
||
Stone *st = MakeStone("st-actorimpulse");
|
||
SetStone(get_pos(), st);
|
||
st->actor_hit(sc);
|
||
}
|
||
else
|
||
ActorImpulseBase::actor_hit(sc);
|
||
}
|
||
};
|
||
}
|
||
|
||
//----------------------------------------
|
||
// FakeOxydStone
|
||
//----------------------------------------
|
||
|
||
/** \page st-fakeoxyd Fake Oxyd Stone
|
||
|
||
These stones look like real Oxyd stones, but they only blink a little
|
||
when touched and do not open or have other special abilities.
|
||
|
||
\image html st-fakeoxyd-blink_0001.png
|
||
*/
|
||
namespace
|
||
{
|
||
class FakeOxydStone : public Stone {
|
||
CLONEOBJ(FakeOxydStone);
|
||
public:
|
||
FakeOxydStone() : Stone("st-fakeoxyd"), state(IDLE) {
|
||
set_attrib("blinking", 0.0);
|
||
}
|
||
private:
|
||
enum State { IDLE, BLINKING } state;
|
||
void actor_hit(const StoneContact &/*sc*/) {
|
||
if (state == IDLE) {
|
||
set_anim("st-fakeoxyd-blink");
|
||
sound_event ("fakeoxyd");
|
||
state = BLINKING;
|
||
set_attrib("blinking", 1.0);
|
||
}
|
||
}
|
||
const char *collision_sound() {
|
||
return "metal";
|
||
}
|
||
void animcb() {
|
||
set_model("st-fakeoxyd");
|
||
state = IDLE;
|
||
set_attrib("blinking", 0.0);
|
||
}
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- Black Stones -------------------- */
|
||
namespace
|
||
{
|
||
class BlackWhiteStone : public Stone {
|
||
CLONEOBJ(BlackWhiteStone);
|
||
DECL_TRAITS_ARRAY(8, m_type);
|
||
int m_type;
|
||
|
||
BlackWhiteStone(int type) : m_type(type)
|
||
{}
|
||
|
||
StoneResponse collision_response(const StoneContact &sc) {
|
||
if (m_type < 4) {
|
||
return (sc.actor->get_attrib("blackball")) ?
|
||
STONE_PASS : STONE_REBOUND;
|
||
}
|
||
else {
|
||
return (sc.actor->get_attrib("whiteball")) ?
|
||
STONE_PASS : STONE_REBOUND;
|
||
}
|
||
}
|
||
|
||
virtual Value on_message (const Message &m) {
|
||
if (m.message == "signal" || m.message == "trigger") {
|
||
// toggle between black and white stone
|
||
m_type = (m_type + 4) % 8;
|
||
init_model();
|
||
}
|
||
return Value();
|
||
}
|
||
|
||
bool is_floating() const { return true; }
|
||
bool is_transparent (Direction) const { return true; }
|
||
|
||
public:
|
||
static void setup() {
|
||
for (int i=0; i<8; ++i)
|
||
RegisterStone (new BlackWhiteStone(i));
|
||
}
|
||
};
|
||
|
||
StoneTraits BlackWhiteStone::traits[8] = {
|
||
{"st-black1", st_black1, stf_transparent},
|
||
{"st-black2", st_black2, stf_transparent},
|
||
{"st-black3", st_black3, stf_transparent},
|
||
{"st-black4", st_black4, stf_transparent},
|
||
{"st-white1", st_white1, stf_transparent},
|
||
{"st-white2", st_white2, stf_transparent},
|
||
{"st-white3", st_white3, stf_transparent},
|
||
{"st-white4", st_white4, stf_transparent},
|
||
};
|
||
|
||
}
|
||
|
||
|
||
/* -------------------- YinYang stones -------------------- */
|
||
namespace
|
||
{
|
||
class YinYangStone : public Stone {
|
||
public:
|
||
YinYangStone(const char *kind) : Stone(kind)
|
||
{}
|
||
|
||
protected:
|
||
void turn_white(const char *stonename = "st-white1") {
|
||
sound_event("yinyang");
|
||
ReplaceStone (get_pos(), MakeStone(stonename));
|
||
}
|
||
void turn_black(const char *stonename = "st-black1") {
|
||
sound_event("yinyang");
|
||
ReplaceStone (get_pos(), MakeStone(stonename));
|
||
}
|
||
};
|
||
|
||
class YinYangStone1 : public YinYangStone {
|
||
CLONEOBJ(YinYangStone1);
|
||
public:
|
||
YinYangStone1() : YinYangStone("st-yinyang1") {}
|
||
|
||
private:
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (sc.actor->get_attrib("blackball")) turn_white();
|
||
else if (sc.actor->get_attrib("whiteball")) turn_black();
|
||
}
|
||
};
|
||
|
||
class YinYangStone2 : public YinYangStone {
|
||
CLONEOBJ(YinYangStone2);
|
||
public:
|
||
YinYangStone2() : YinYangStone("st-yinyang2") {}
|
||
private:
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (sc.actor->get_attrib("blackball")) turn_black();
|
||
else if (sc.actor->get_attrib("whiteball")) turn_white();
|
||
}
|
||
};
|
||
|
||
|
||
/*! A Per.Oxyd compatible YinYang stone that must be activated
|
||
with a magic wand or a brush. */
|
||
class YinYangStone3 : public YinYangStone {
|
||
CLONEOBJ(YinYangStone3);
|
||
public:
|
||
YinYangStone3() : YinYangStone("st-yinyang3") {}
|
||
private:
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (player::WieldedItemIs (sc.actor, "it-magicwand") ||
|
||
player::WieldedItemIs (sc.actor, "it-brush"))
|
||
{
|
||
if (sc.actor->get_attrib("blackball"))
|
||
turn_white("st-white4");
|
||
else if (sc.actor->get_attrib("whiteball"))
|
||
turn_black("st-black4");
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- BombStone -------------------- */
|
||
|
||
namespace
|
||
{
|
||
/*! This stone add a bomb to the player's inventory when touched. */
|
||
class BombStone : public Stone {
|
||
CLONEOBJ(BombStone);
|
||
const char *collision_sound() {return "stone";}
|
||
public:
|
||
BombStone(const char* kind_, const char* itemkind_) :
|
||
Stone(kind_), state(IDLE), itemkind(itemkind_) {}
|
||
private:
|
||
enum State { IDLE, BREAK };
|
||
State state;
|
||
const char* itemkind;
|
||
void actor_hit (const StoneContact &sc);
|
||
void change_state (State newstate);
|
||
void animcb();
|
||
virtual Value message (const string &msg, const Value &);
|
||
};
|
||
}
|
||
|
||
void BombStone::change_state (State newstate)
|
||
{
|
||
if (state == IDLE && newstate==BREAK) {
|
||
string model = get_kind();
|
||
state = newstate;
|
||
sound_event ("stonedestroy");
|
||
set_anim(model + "-anim");
|
||
}
|
||
}
|
||
|
||
void BombStone::animcb()
|
||
{
|
||
ASSERT(state == BREAK, XLevelRuntime, "BombStone: animcb called with inconsistent state");
|
||
GridPos p = get_pos();
|
||
SendExplosionEffect(p, EXPLOSION_BOMBSTONE);
|
||
KillStone(p);
|
||
if(Item *it = GetItem(get_pos())) {
|
||
SendMessage(it, "ignite");
|
||
} else
|
||
SetItem(p, it_explosion1);
|
||
}
|
||
|
||
Value BombStone::message(const string &msg, const Value &)
|
||
{
|
||
if (msg =="expl" || msg =="bombstone")
|
||
change_state(BREAK);
|
||
return Value();
|
||
}
|
||
|
||
void BombStone::actor_hit(const StoneContact &sc)
|
||
{
|
||
if (enigma::Inventory *inv = player::GetInventory(sc.actor)) {
|
||
if (!inv->is_full()) {
|
||
Item *it = MakeItem(itemkind);
|
||
inv->add_item(it);
|
||
player::RedrawInventory (inv);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/* -------------------- MagicStone -------------------- */
|
||
namespace
|
||
{
|
||
class MagicStone : public Stone {
|
||
CLONEOBJ(MagicStone);
|
||
DECL_TRAITS;
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (sc.actor->get_attrib("player") &&
|
||
sc.actor->get_vel() * sc.normal < -4)
|
||
{
|
||
KillStone(get_pos());
|
||
client::Msg_ShowText ("We don't sell books..", false, 2.0);
|
||
}
|
||
}
|
||
public:
|
||
MagicStone()
|
||
{}
|
||
};
|
||
DEF_TRAITS(MagicStone, "st-magic", st_magic);
|
||
}
|
||
|
||
|
||
/* -------------------- DeathStone -------------------- */
|
||
|
||
/** \page st-death Death's Head Stone
|
||
|
||
Simply kills all actors that touch it (except for actors that are
|
||
immune to these stones).
|
||
|
||
\image html st-death.png
|
||
*/
|
||
namespace
|
||
{
|
||
class DeathStone : public Stone {
|
||
CLONEOBJ(DeathStone);
|
||
DECL_TRAITS;
|
||
bool active;
|
||
|
||
void actor_hit(const StoneContact &sc) {
|
||
SendMessage(sc.actor, "shatter");
|
||
if (!active) {
|
||
active=true;
|
||
set_anim("st-death-anim");
|
||
}
|
||
}
|
||
|
||
// even a slight touch should shatter the actor:
|
||
void actor_touch(const StoneContact &sc) { actor_hit(sc); }
|
||
|
||
protected:
|
||
void animcb() { set_model("st-death"); active=false; }
|
||
public:
|
||
DeathStone() : active(false)
|
||
{}
|
||
};
|
||
DEF_TRAITS(DeathStone, "st-death", st_death);
|
||
}
|
||
|
||
|
||
/* -------------------- Invisible DeathStone -------------------- */
|
||
|
||
/** \page st-death_invisible Death's Head Stone invivible
|
||
|
||
Simply kills all actors that touch it (except for actors that are
|
||
immune to these stones). This variant is invisible.
|
||
|
||
\image html st-death.png
|
||
*/
|
||
namespace
|
||
{
|
||
class DeathStoneInvisible : public DeathStone {
|
||
CLONEOBJ(DeathStoneInvisible);
|
||
DECL_TRAITS;
|
||
|
||
bool visible; // seen through glasses
|
||
|
||
void set_visible_model() {
|
||
set_model(visible ? "st-death" : "st-death_invisible");
|
||
}
|
||
|
||
void animcb() {
|
||
DeathStone::animcb();
|
||
set_visible_model();
|
||
}
|
||
|
||
virtual Value message(const string& msg, const Value &val) {
|
||
if (msg == "glasses") {
|
||
if (to_int(val)) {
|
||
if (!visible) {
|
||
visible = true;
|
||
set_visible_model();
|
||
}
|
||
}
|
||
else {
|
||
if (visible) {
|
||
visible = false;
|
||
set_visible_model();
|
||
}
|
||
}
|
||
}
|
||
return Value();
|
||
}
|
||
public:
|
||
DeathStoneInvisible() : visible(false) {}
|
||
};
|
||
DEF_TRAITS(DeathStoneInvisible, "st-death_invisible", st_death_invisible);
|
||
}
|
||
|
||
|
||
/* -------------------- Brake stone -------------------- */
|
||
|
||
/** \page st-brake Brake
|
||
|
||
Blocks bolder stones and other movable stones. Can be picked up.
|
||
|
||
\image html st-brake.png
|
||
*/
|
||
|
||
namespace
|
||
{
|
||
class BrakeStone : public Stone {
|
||
CLONEOBJ(BrakeStone);
|
||
public:
|
||
BrakeStone() : Stone("st-brake") {}
|
||
|
||
void on_creation (GridPos p) {
|
||
Stone::on_creation(p);
|
||
|
||
Item *it = GetItem(p);
|
||
if (it && it->is_kind("it-blocker")) {
|
||
KillItem(p);
|
||
// sound_event ("explosion1");
|
||
}
|
||
}
|
||
|
||
StoneResponse collision_response(const StoneContact &/*sc*/) {
|
||
return STONE_PASS;
|
||
}
|
||
|
||
void actor_inside(Actor *a) {
|
||
const double BRAKE_RADIUS = 0.3;
|
||
GridPos p = get_pos();
|
||
double dist = length(a->get_pos() - p.center());
|
||
|
||
if (dist < BRAKE_RADIUS) {
|
||
player::PickupStoneAsItem(a, p);
|
||
}
|
||
}
|
||
|
||
void explode() {
|
||
GridPos p = get_pos();
|
||
KillStone(p);
|
||
SetItem(p, it_explosion1);
|
||
}
|
||
|
||
void on_laserhit(Direction) {
|
||
explode();
|
||
}
|
||
|
||
virtual Value message(const string &msg, const Value &) {
|
||
if (msg == "expl") {
|
||
explode();
|
||
}
|
||
return Value();
|
||
}
|
||
|
||
bool is_sticky(const Actor *) const
|
||
{ return false; }
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- Disco stones -------------------- */
|
||
namespace
|
||
{
|
||
class DiscoStone : public Stone {
|
||
CLONEOBJ(DiscoStone);
|
||
public:
|
||
DiscoStone(const char *kind) : Stone(kind) {}
|
||
|
||
StoneResponse collision_response(const StoneContact &) {
|
||
return STONE_PASS;
|
||
}
|
||
|
||
virtual void lighten() {}
|
||
virtual void darken() {}
|
||
protected:
|
||
static void visit_lighten(GridPos p) {
|
||
if (DiscoStone *st = dynamic_cast<DiscoStone*>(GetStone(p)))
|
||
st->lighten();
|
||
}
|
||
static void visit_darken(GridPos p) {
|
||
if (DiscoStone *st = dynamic_cast<DiscoStone*>(GetStone(p)))
|
||
st->darken();
|
||
}
|
||
|
||
private:
|
||
bool is_floating() const { return true; }
|
||
|
||
virtual Value message(const string &msg, const Value &val) {
|
||
if (msg == "signal") {
|
||
int ival = to_int (val);
|
||
if (ival > 0)
|
||
lighten();
|
||
else
|
||
darken();
|
||
}
|
||
else if (msg == "lighten")
|
||
lighten();
|
||
else if (msg == "darken")
|
||
darken();
|
||
return Value();
|
||
}
|
||
};
|
||
class DiscoLight : public DiscoStone {
|
||
CLONEOBJ(DiscoLight);
|
||
virtual void darken() {
|
||
GridPos p = get_pos();
|
||
SetStone (p, MakeStone("st-disco-medium"));
|
||
visit_darken (move(p, NORTH));
|
||
visit_darken (move(p, EAST));
|
||
visit_darken (move(p, SOUTH));
|
||
visit_darken (move(p, WEST));
|
||
}
|
||
public:
|
||
DiscoLight() : DiscoStone("st-disco-light") {}
|
||
};
|
||
class DiscoMedium : public DiscoStone {
|
||
CLONEOBJ(DiscoMedium);
|
||
public:
|
||
DiscoMedium() : DiscoStone("st-disco-medium") {}
|
||
virtual void lighten() {
|
||
GridPos p = get_pos();
|
||
SetStone (p, MakeStone("st-disco-light"));
|
||
visit_lighten (move(p, NORTH));
|
||
visit_lighten (move(p, EAST));
|
||
visit_lighten (move(p, SOUTH));
|
||
visit_lighten (move(p, WEST));
|
||
}
|
||
virtual void darken() {
|
||
GridPos p = get_pos();
|
||
SetStone (p, MakeStone("st-disco-dark"));
|
||
visit_darken (move(p, NORTH));
|
||
visit_darken (move(p, EAST));
|
||
visit_darken (move(p, SOUTH));
|
||
visit_darken (move(p, WEST));
|
||
}
|
||
};
|
||
|
||
class DiscoDark : public DiscoStone {
|
||
CLONEOBJ(DiscoDark);
|
||
virtual void lighten() {
|
||
GridPos p = get_pos();
|
||
SetStone (p, MakeStone("st-disco-medium"));
|
||
visit_lighten (move(p, NORTH));
|
||
visit_lighten (move(p, EAST));
|
||
visit_lighten (move(p, SOUTH));
|
||
visit_lighten (move(p, WEST));
|
||
}
|
||
virtual void darken() {}
|
||
public:
|
||
DiscoDark() : DiscoStone("st-disco-dark") {}
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- Knight stone -------------------- */
|
||
namespace
|
||
{
|
||
class Knight : public Stone {
|
||
CLONEOBJ(Knight);
|
||
DECL_TRAITS;
|
||
|
||
int subtype;
|
||
enum {MIN_SUBTYPE=1, MAX_SUBTYPE=5};
|
||
|
||
StoneResponse collision_response(const StoneContact &) {
|
||
return (subtype == MAX_SUBTYPE) ? STONE_PASS : STONE_REBOUND;
|
||
}
|
||
void actor_hit(const StoneContact &sc)
|
||
{
|
||
if (subtype != MAX_SUBTYPE) {
|
||
if (player::WieldedItemIs (sc.actor, "it-sword")) {
|
||
subtype += 1;
|
||
if (subtype == MAX_SUBTYPE) {
|
||
client::Msg_ShowText ("All right, we'll call it a draw", false, 4.0);
|
||
}
|
||
init_model();
|
||
} else {
|
||
SendMessage(sc.actor, "shatter");
|
||
}
|
||
}
|
||
}
|
||
void init_model() {
|
||
set_model(ecl::strf("st-knight%d", subtype));
|
||
}
|
||
bool is_floating() const { return subtype == MAX_SUBTYPE; }
|
||
public:
|
||
Knight() : subtype (MIN_SUBTYPE) {}
|
||
};
|
||
DEF_TRAITS(Knight, "st-knight", st_knight);
|
||
}
|
||
|
||
|
||
/* -------------------- Polarization Switch stone -------------------- */
|
||
namespace
|
||
{
|
||
class PolarSwitchStone : public OnOffStone {
|
||
CLONEOBJ(PolarSwitchStone);
|
||
DECL_TRAITS;
|
||
public:
|
||
PolarSwitchStone() : OnOffStone("st-polarswitch") {}
|
||
private:
|
||
void actor_hit(const StoneContact &sc) { set_on(!is_on()); }
|
||
void init_model() { set_model(is_on() ? "st-glass1" : "st-glass2"); }
|
||
bool is_transparent(Direction) const { return this->is_on(); }
|
||
void notify_onoff(bool) { lasers::MaybeRecalcLight(this->get_pos()); }
|
||
|
||
StoneResponse collision_response(const StoneContact &sc) {
|
||
if (sc.actor->is_invisible())
|
||
return STONE_PASS;
|
||
return Stone::collision_response(sc);
|
||
}
|
||
|
||
bool is_sticky (const Actor *actor) const { return !actor->is_invisible(); }
|
||
};
|
||
DEF_TRAITS(PolarSwitchStone, "st-polarswitch", st_polarswitch);
|
||
}
|
||
|
||
|
||
/* -------------------- Fire breakable stones -------------------- */
|
||
|
||
/* These stones mimic the behaviour of the plain-looking stones in
|
||
Oxyd. */
|
||
namespace
|
||
{
|
||
class Stone_firebreak : public Stone {
|
||
CLONEOBJ(Stone_firebreak);
|
||
|
||
const char *collision_sound() {return "stone";}
|
||
|
||
void break_me() {
|
||
sound_event("stonedestroy");
|
||
ReplaceStone(get_pos(), MakeStone("st-plain_breaking"));
|
||
}
|
||
|
||
virtual Value message (const string &msg, const Value &) {
|
||
if (msg =="heat" || msg == "fire") {
|
||
break_me();
|
||
return Value(1.0);
|
||
}
|
||
return Value();
|
||
}
|
||
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (player::WieldedItemIs(sc.actor, "it-brush")) {
|
||
sound_event("stonepaint");
|
||
ReplaceStone(get_pos(), MakeStone("st-plain"));
|
||
} else
|
||
Stone::actor_hit(sc);
|
||
}
|
||
|
||
public:
|
||
Stone_firebreak() : Stone("st-firebreak") { }
|
||
};
|
||
|
||
class Stone_movefirebreak : public Stone {
|
||
CLONEOBJ(Stone_movefirebreak);
|
||
|
||
void break_me() {
|
||
sound_event("stonedestroy");
|
||
ReplaceStone(get_pos(), MakeStone("st-plain_breaking"));
|
||
}
|
||
|
||
virtual Value message (const string &msg, const Value &) {
|
||
if (msg =="fire")
|
||
break_me();
|
||
return Value();
|
||
}
|
||
|
||
void actor_hit(const StoneContact &sc) {
|
||
if (player::WieldedItemIs(sc.actor, "it-brush")) {
|
||
sound_event("stonepaint");
|
||
ReplaceStone(get_pos(), MakeStone("st-plain_move"));
|
||
} else
|
||
Stone::actor_hit(sc);
|
||
}
|
||
|
||
void on_move() {
|
||
GridPos p = get_pos();
|
||
if (Floor *fl = GetFloor (p)) {
|
||
if (fl->is_kind("fl-abyss")) {
|
||
ReplaceStone (p, MakeStone("st-plain_falling"));
|
||
}
|
||
else if (fl->is_kind("fl-swamp") || fl->is_kind("fl-water")) {
|
||
sound_event ("drown");
|
||
client::Msg_Sparkle (p.center());
|
||
KillStone (p);
|
||
}
|
||
}
|
||
}
|
||
|
||
bool is_movable () const { return true; }
|
||
|
||
public:
|
||
Stone_movefirebreak() : Stone("st-firebreak_move") { }
|
||
};
|
||
}
|
||
|
||
|
||
/* -------------------- Functions -------------------- */
|
||
|
||
void world::DefineSimpleStone(const std::string &kind,
|
||
const std::string &sound,
|
||
int hollow, int glass)
|
||
{
|
||
Register(new SimpleStone(kind, sound, hollow != 0, glass != 0));
|
||
}
|
||
|
||
void world::DefineSimpleStoneMovable(const std::string &kind,
|
||
const std::string &sound,
|
||
int glass)
|
||
{
|
||
Register(new SimpleStoneMovable(kind, sound, glass != 0));
|
||
}
|
||
|
||
void stones::Init_simple()
|
||
{
|
||
Register(new ActorImpulseStone);
|
||
Register(new ActorImpulseStoneInvisible);
|
||
|
||
BlackWhiteStone::setup();
|
||
|
||
Register(new BlockStone);
|
||
Register(new BombStone("st-bombs", "it-blackbomb"));
|
||
//Register(new BombStone("st-dynamite", "it-dynamite"));
|
||
//Register(new BombStone("st-whitebombs", "it-whitebomb"));
|
||
Register(new BrakeStone);
|
||
|
||
Register(new Break_acblack);
|
||
Register(new Break_acwhite);
|
||
Register(new Break_bolder);
|
||
Register(new Break_invisible);
|
||
|
||
Register(new BrickMagic);
|
||
|
||
RegisterStone (new ChameleonStone);
|
||
|
||
RegisterStone (new DeathStone);
|
||
RegisterStone (new DeathStoneInvisible);
|
||
Register(new DiscoLight);
|
||
Register(new DiscoMedium);
|
||
Register(new DiscoDark);
|
||
Register(new DummyStone);
|
||
RegisterStone (new EasyModeStone);
|
||
Register(new FakeOxydStone);
|
||
Register(new FartStone);
|
||
Register(new Grate1);
|
||
Register(new Grate2);
|
||
Register(new Grate3);
|
||
Register(new InvisibleMagic);
|
||
Register(new Knight);
|
||
Register(new LaserBreakable);
|
||
Register(new MagicStone);
|
||
Register(new RubberBandStone);
|
||
Register(new ScissorsStone);
|
||
Register(new Stone_break("st-stone_break"));
|
||
Register(new Stone_break("st-rock3_break"));
|
||
Register(new Stone_break("st-break_gray"));
|
||
Register(new Stone_movebreak);
|
||
Register(new Stonebrush);
|
||
Register(new SwapStone);
|
||
|
||
Register(new ThiefStone);
|
||
Register(new TimerStone);
|
||
Register(new Window);
|
||
|
||
Register(new RandomWoodenStone); // random flavor
|
||
Register(new WoodenStone("st-wood1", "fl-stwood1")); // horizontal planks
|
||
Register(new WoodenStone("st-wood2", "fl-stwood2")); // vertical planks
|
||
Register(new WoodenStone("st-flrock", "fl-rock", true));
|
||
Register(new WoodenStone("st-flhay", "fl-hay"));
|
||
Register(new WoodenStone_Growing);
|
||
Register(new GreenbrownStone_Growing);
|
||
Register(new VolcanoStone_Growing);
|
||
|
||
Register(new YinYangStone1);
|
||
Register(new YinYangStone2);
|
||
Register(new YinYangStone3);
|
||
|
||
Register(new PolarSwitchStone);
|
||
|
||
Register(new Stone_firebreak);
|
||
Register(new Stone_movefirebreak);
|
||
}
|