/* * 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 "stones_internal.hh" #include "server.hh" #include "client.hh" #include "player.hh" #include "Inventory.hh" using namespace std; using namespace world; using namespace stones; /* -------------------- Helper routines -------------------- */ /*! Determine whether the actor hitting the stone can move stone and return either the direction the stone should move or NODIR. */ Direction stones::get_push_direction (const StoneContact &sc) { ActorInfo *ai = sc.actor->get_actorinfo(); Direction dir = contact_face(sc); // Make sure the speed component towards the face of the stone is // large enough and pointing towards the stone. if (dir!=enigma::NODIR && ai->vel * sc.normal < -4) return reverse(dir); return NODIR; } /* Move a stone (by sending an impulse) Called when an actor hits a stone. */ bool stones::maybe_push_stone (const StoneContact &sc) { Direction dir = get_push_direction(sc); if (dir != enigma::NODIR) { sc.actor->send_impulse(sc.stonepos, dir); return GetStone(sc.stonepos) == 0; // return true only if stone vanished } return false; } //====================================================================== // STONES //====================================================================== /** \page lstones Available Stones Oxyd Stones: - \ref st-oxyd - \ref st-fakeoxyd - \ref st-fart Movable stones: - \ref st-brownie - \ref st-wood - \ref st-block Stones that can trigger actions: - \ref st-switch - \ref st-fourswitch - \ref st-laserswitch - \ref st-key - \ref st-coinslot - \ref st-timer Stones that can change their behaviour: - \ref st-brick_magic - \ref st-stonebrush - \ref st-invisible_magic - \ref st-break_invisible Lasers and Mirrors: - \ref st-laser - \ref st-pmirror - \ref st-3mirror Other stones: - \ref st-death - \ref st-swap - \ref st-bolder - \ref st-puzzle - \ref st-stone_break - \ref st-window - \ref st-break_acwhite - \ref st-break_acblack - \ref st-oneway - \ref st-oneway_black - \ref st-oneway_white - \ref st-chameleon */ Stone::Stone() {} Stone::Stone(const char * kind) : GridObject (kind) {} Stone::~Stone() { revokeDelayedImpulses(this); } const StoneTraits &Stone::get_traits() const { static StoneTraits default_traits = { "INVALID", st_INVALID, stf_none, material_stone, 1.0 }; return default_traits; } const char *Stone::get_kind() const { const StoneTraits &tr = get_traits(); if (tr.id != st_INVALID) return tr.name; else return Object::get_kind(); } StoneResponse Stone::collision_response(const StoneContact &) { return STONE_REBOUND; } void Stone::actor_hit(const StoneContact &sc) { if (is_movable()) stones::maybe_push_stone (sc); } void Stone::actor_touch(const StoneContact &sc) { } void Stone::on_impulse(const Impulse& impulse) { if (is_movable()) move_stone(impulse.dir); } const char * Stone::collision_sound() { return "stone"; } /* Move a stone (regardless whether it is_movable() or not) if the destination field is free. Returns: true if stone has been moved. Note: This should be used by on_impulse() to perform a move. */ bool Stone::move_stone(GridPos newPos, const char *soundevent) { GridPos p = get_pos(); if (!GetStone(newPos)) { sound_event (soundevent); MoveStone(p, newPos); server::IncMoveCounter(); on_move(); if (Item *it = GetItem(newPos)) it->on_stonehit(this); return true; } return false; } bool Stone::move_stone(Direction dir) { return move_stone(move(get_pos(), dir), "movesmall"); } void Stone::on_move() { if (!is_floating()) ShatterActorsInsideField (get_pos()); } /* Multiplies velocity with the attribute-matrix hit_distortion_[xx,xy,yx,yy] and factor hit_factor If components are not set, use ((1,0),(0,1)) as default matrix, resp. defaultfactor as hit_factor. */ ecl::V2 Stone::distortedVelocity (ecl::V2 vel, double defaultfactor = 1.0) { ecl::V2 newvel(0,0); if(const Value *xx = this->get_attrib("hit_distortion_xx")) newvel[0] += to_double(*xx) * vel[0]; else newvel[0] += vel[0]; if(const Value *xy = this->get_attrib("hit_distortion_xy")) newvel[0] += to_double(*xy) * vel[1]; if(const Value *yx = this->get_attrib("hit_distortion_yx")) newvel[1] += to_double(*yx) * vel[0]; if(const Value *yy = this->get_attrib("hit_distortion_yy")) newvel[1] += to_double(*yy) * vel[1]; else newvel[1] += vel[1]; if (const Value *factor = this->get_attrib("hit_factor")) newvel *= to_double(*factor); else newvel *= defaultfactor; return newvel; } // ******************************************************************************* // Stones under development : /* -------------------- Explosion stone -------------------- */ namespace { class ExplosionStone : public Stone { CLONEOBJ(ExplosionStone); StoneResponse collision_response(const StoneContact &) { return STONE_PASS; } void actor_contact (Actor *a) { SendMessage(a, "shatter"); } void init_model() { set_anim("st-explosion"); } void animcb() { KillStone(get_pos()); } public: ExplosionStone(): Stone("st-explosion") {} }; } /* -------------------- Charge stone -------------------- */ // Attributes: // // :charge + - 0 namespace { class ChargeStone : public Stone { CLONEOBJ(ChargeStone); public: ChargeStone(const char *kind, double charge) : Stone(kind) { set_attrib("charge", charge); } private: double get_charge() { double q = 0; double_attrib("charge", &q); return max(-1.0, min(1.0, q)); } void animcb() { init_model(); } void actor_hit (const StoneContact &sc) { ActorInfo *ai = sc.actor->get_actorinfo(); ai->charge = get_charge(); set_anim(string(get_kind())+"-anim"); } }; } /* -------------------- SpitterStone -------------------- */ namespace { class SpitterStone : public Stone { CLONEOBJ(SpitterStone); enum State { IDLE, LOADING, SPITTING }; // Variables State state; V2 ball_velocity; // Functions. void animcb(); void actor_hit (const StoneContact &sc); public: SpitterStone () : Stone("st-spitter"), state (IDLE) { } }; } void SpitterStone::animcb() { switch (state) { case IDLE: ASSERT(0, XLevelRuntime, "SpitterStone: animcb called with inconsistent state"); case LOADING: { Actor *ball = MakeActor (ac_cannonball); ActorInfo *ai = ball->get_actorinfo(); V2 center = get_pos().center(); state = SPITTING; ai->vel = ball_velocity; world::AddActor (center[0], center[1], ball); set_anim ("st-spitter-spitting"); break; } case SPITTING: init_model(); state = IDLE; break; } } void SpitterStone::actor_hit (const StoneContact &sc) { if (state != IDLE) return; if (enigma::Inventory *inv = player::GetInventory(sc.actor)) { int lifepos = inv->find("it-extralife"); if (lifepos != -1) { delete inv->yield_item(lifepos); player::RedrawInventory (inv); ball_velocity = distortedVelocity(sc.actor->get_vel(), 1.0); state = LOADING; set_anim ("st-spitter-loading"); } } } /* -------------------- YieldedGridStone -------------------- */ YieldedGridStone::YieldedGridStone(Stone *st) : stone(st) { GridPos pos = stone->get_pos(); model = display::YieldModel(GridLoc(GRID_STONES, pos)); YieldStone(pos); } YieldedGridStone::~YieldedGridStone() { ASSERT(!stone, XLevelRuntime, "YieldedGridStone: destructor called though stone still exists"); ASSERT(!model, XLevelRuntime, "YieldedGridStone: destructor called though model still exists"); } void YieldedGridStone::set_stone(GridPos pos) { SetStone(pos, stone); display::SetModel(GridLoc(GRID_STONES, stone->get_pos()), model); stone->on_move(); stone = 0; model = 0; } void YieldedGridStone::dispose() { stone->dispose(); delete model; stone = 0; model = 0; } /* -------------------- Oxyd compatibility stones -------------------- */ namespace { /* I have no idea what these stones are _really_ supposed to do; they seemingly do not appear in the landscape and they create normal floor tiles on creation. Other than that... who knows... */ class Peroxyd_0xb8 : public Stone { CLONEOBJ(Peroxyd_0xb8); public: Peroxyd_0xb8() : Stone("st-peroxyd-0xb8") {} void on_creation (GridPos p) { SetFloor (p, MakeFloor ("fl-normal")); KillStone(p); } }; class Peroxyd_0xb9 : public Stone { CLONEOBJ(Peroxyd_0xb9); public: Peroxyd_0xb9() : Stone("st-peroxyd-0xb9") {} void on_creation (GridPos p) { SetFloor (p, MakeFloor ("fl-normal")); KillStone(p); } }; class Oxyd_0x18 : public Stone { CLONEOBJ(Oxyd_0x18); public: Oxyd_0x18() : Stone("st-oxyd-0x18") { } void on_creation (GridPos p) { KillStone (p); } }; } /* -------------------- Flash stone -------------------- */ namespace { class FlashStone : public Stone { CLONEOBJ(FlashStone); void actor_hit (const StoneContact &sc) { if (Actor *other = FindOtherMarble(sc.actor)) { other->add_force (distortedVelocity(sc.actor->get_vel(), 20)); } } public: FlashStone() : Stone ("st-flash") {} }; } /* -------------------- Surprise stone -------------------- */ namespace { class SurpriseStone : public Stone { CLONEOBJ (SurpriseStone); public: SurpriseStone() : Stone("st-surprise") {} void actor_hit (const StoneContact &) { static const char *stonename[] = { "st-grate1", "st-death", "st-surprise", "st-glass1_hole", "st-magic", "st-knight", "st-thief", "st-plain_break", "st-plain_breaking" }; int idx = enigma::IntegerRand (1, 9) - 1; sound_event ("stonetransform"); ReplaceStone (get_pos(), MakeStone (stonename[idx])); } }; } /* -------------------- Coffee stone -------------------- */ namespace { class CoffeeStone : public Stone { CLONEOBJ(CoffeeStone); public: CoffeeStone() : Stone ("st-coffee") { } void actor_hit (const StoneContact &) { sound_event ("stonetransform"); ReplaceStone(get_pos(), MakeStone("st-glass_move")); } }; } /* -------------------- Breaking stone -------------------- */ namespace { class BreakingStone : public Stone { CLONEOBJ(BreakingStone); void init_model() { sound_event("stonedestroy"); set_anim("st-breaking"); } void animcb() { KillStone(get_pos()); } public: BreakingStone() : Stone("st-breaking") { } }; } /* -------------------- Bug stone -------------------- */ namespace { class BugStone : public Stone { CLONEOBJ(BugStone); public: BugStone() : Stone("st-bug") { } void actor_hit (const StoneContact &sc) { if (get_id(sc.actor) == ac_bug) { ReplaceStone(get_pos(), MakeStone("st-breaking")); } } }; } /* -------------------- Plain stones -------------------- */ /* These stones mimic the behaviour of the plain-looking stones in Oxyd. */ namespace { class PlainStone : public Stone { CLONEOBJ(PlainStone); void on_laserhit (Direction) { ReplaceStone (get_pos(), MakeStone("st-plain_cracked")); } const char *collision_sound() {return "stone";} virtual Value message (const string &msg, const Value &) { if (msg == "trigger" || msg == "signal") { ReplaceStone(get_pos(), MakeStone("st-plain_hole")); } return Value(); } void actor_hit (const StoneContact &sc) { if (player::WieldedItemIs (sc.actor, "it-pencil")) { enigma::Inventory *inv = player::GetInventory(sc.actor); if (inv && inv->size() > 0) { delete inv->yield_item(0); player::RedrawInventory(inv); sound_event("stonepaint"); ReplaceStone(get_pos(), MakeStone("st-firebreak")); } } else Stone::actor_hit(sc); } public: PlainStone() : Stone("st-plain") {} }; class PlainStone_Hollow : public Stone { CLONEOBJ(PlainStone_Hollow); virtual Value message (const string &msg, const Value &) { if (msg == "trigger" || msg == "signal") { ReplaceStone(get_pos(), MakeStone("st-plain")); } return Value(); } StoneResponse collision_response(const StoneContact &) { return STONE_PASS; } bool is_floating() const { return true; } public: PlainStone_Hollow() : Stone("st-plain_hole") { } }; class PlainStone_Breaking : public Stone { CLONEOBJ(PlainStone_Breaking); void init_model() { set_anim("st-plain_breaking"); } void animcb() { KillStone(get_pos()); } const char *collision_sound() {return "metal";} public: PlainStone_Breaking() : Stone ("st-plain_breaking") { } }; class PlainStone_Breakable : public Stone { CLONEOBJ(PlainStone_Breakable); const char *collision_sound() {return "metal";} void break_me() { sound_event ("stonedestroy"); ReplaceStone(get_pos(), MakeStone ("st-plain_breaking")); } void on_laserhit (Direction) { break_me(); } virtual Value message (const string &msg, const Value &) { if (msg =="ignite" || msg == "expl" || msg == "bombstone") break_me(); return Value(); } void actor_hit (const StoneContact &sc) { if (player::WieldedItemIs (sc.actor, "it-hammer")) { break_me(); } } void on_floor_change() { if (Floor *fl = GetFloor (get_pos())) if (fl->is_kind("fl-abyss")) ReplaceStone (get_pos(), MakeStone("st-plain_falling")); } public: PlainStone_Breakable() : Stone("st-plain_break") { } }; class PlainStone_Cracked : public Stone { CLONEOBJ(PlainStone_Cracked); void break_me() { sound_event ("stonedestroy"); ReplaceStone(get_pos(), MakeStone("st-plain_breaking")); } void actor_hit (const StoneContact &sc) { if (player::WieldedItemIs (sc.actor, "it-hammer")) { break_me(); } } virtual Value message (const string &msg, const Value &) { if (msg =="ignite" || msg == "expl" || msg == "bombstone") break_me(); return Value(); } const char *collision_sound() {return "metal";} public: PlainStone_Cracked() : Stone("st-plain_cracked") { } }; class PlainStone_Falling : public Stone { CLONEOBJ(PlainStone_Falling); void init_model() { set_anim("st-plain_falling"); } void animcb() { sound_event ("stonedestroy"); KillStone(get_pos()); } public: PlainStone_Falling() : Stone("st-plain_falling") { } }; class PlainStone_Movable : public Stone { CLONEOBJ(PlainStone_Movable); void break_me() { sound_event ("stonedestroy"); ReplaceStone(get_pos(), MakeStone ("st-plain_breaking")); } virtual Value message (const string &msg, const Value &) { if (msg =="ignite" || msg == "expl" || msg == "bombstone") break_me(); return Value(); } void on_move() { Stone::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; } void actor_hit (const StoneContact &sc) { if (player::WieldedItemIs (sc.actor, "it-pencil")) { enigma::Inventory *inv = player::GetInventory(sc.actor); if (inv && inv->size() > 0) { delete inv->yield_item(0); player::RedrawInventory(inv); sound_event("stonepaint"); ReplaceStone(get_pos(), MakeStone("st-firebreak_move")); } } else Stone::actor_hit(sc); } public: PlainStone_Movable() : Stone("st-plain_move") { } }; } /* -------------------- Black- and Whiteballs Stones -------------------- */ namespace { class BlackBallsStone : public Stone { CLONEOBJ(BlackBallsStone); virtual Value on_message (const Message &m) { GridPos p = get_pos(); Actor *a = world::CurrentCollisionActor; if (a && get_id(a) == ac_blackball) { if (p.y == m.gridpos.y) { SendMessage (GetStone (move (p, EAST)), "signal", 1.0); SendMessage (GetStone (move (p, WEST)), "signal", 1.0); SendMessage (GetStone (move (p, NORTH)), "signal", 0.0); SendMessage (GetStone (move (p, SOUTH)), "signal", 0.0); } else { SendMessage (GetStone (move (p, EAST)), "signal", 0.0); SendMessage (GetStone (move (p, WEST)), "signal", 0.0); SendMessage (GetStone (move (p, NORTH)), "signal", 1.0); SendMessage (GetStone (move (p, SOUTH)), "signal", 1.0); } } return Value(); } public: BlackBallsStone() : Stone ("st-blackballs") { } }; class WhiteBallsStone : public Stone { CLONEOBJ(WhiteBallsStone); virtual Value on_message (const Message &m) { GridPos p = get_pos(); Actor *a = world::CurrentCollisionActor; if (a && get_id(a) == ac_whiteball) { if (p.y == m.gridpos.y) { SendMessage (GetStone (move (p, EAST)), "signal", 1.0); SendMessage (GetStone (move (p, WEST)), "signal", 1.0); SendMessage (GetStone (move (p, NORTH)), "signal", 0.0); SendMessage (GetStone (move (p, SOUTH)), "signal", 0.0); } else { SendMessage (GetStone (move (p, EAST)), "signal", 0.0); SendMessage (GetStone (move (p, WEST)), "signal", 0.0); SendMessage (GetStone (move (p, NORTH)), "signal", 1.0); SendMessage (GetStone (move (p, SOUTH)), "signal", 1.0); } } return Value(); } public: WhiteBallsStone() : Stone ("st-whiteballs") { } }; } /* -------------------- Unimplemented stones -------------------- */ namespace { class FakeOxydA : public Stone { CLONEOBJ(FakeOxydA); public: FakeOxydA() : Stone("st-fakeoxyda") { } void actor_hit (const StoneContact &) { sound_event ("stonetransform"); ReplaceStone(get_pos(), MakeStone("st-glass1_move")); } }; } // -------------------------------------------------------------------------------- extern void InitSwitches(); void stones::Init() { // Register(new ...); Register (new ExplosionStone); Register (new ChargeStone ("st-chargeplus", +1.0)); Register (new ChargeStone ("st-chargeminus", -1.0)); Register (new ChargeStone ("st-chargezero", 0.0)); Register (new SpitterStone); Register (new Peroxyd_0xb8); Register (new Peroxyd_0xb9); Register (new Oxyd_0x18); Register (new FlashStone); Register (new SurpriseStone); Register (new CoffeeStone); Register (new BlackBallsStone); Register (new WhiteBallsStone); Register (new FakeOxydA); Register (new BreakingStone); Register (new BugStone); Register (new PlainStone); Register (new PlainStone_Hollow); Register (new PlainStone_Breakable); Register (new PlainStone_Breaking); Register (new PlainStone_Cracked); Register (new PlainStone_Movable); Register (new PlainStone_Falling); // Init stones from stones_simple.cc and stones_complex.cc: Init_simple(); Init_complex(); InitSwitches(); }