/* * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * * $Id: stones.cc,v 1.29 2004/03/15 20:15:34 dheck Exp $ */ #include "stones_internal.hh" #include "server.hh" #include "client.hh" #include "player.hh" #include 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) {} 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(Direction dir) { GridPos p = get_pos(); GridPos newPos = move(p, dir); if (!GetStone(newPos)) { sound_event ("movesmall"); MoveStone(p, newPos); server::IncMoveCounter(); on_move(); if (Item *it = GetItem(newPos)) it->on_stonehit(this); return true; } return false; } void Stone::on_move() { if (!is_floating()) ShatterActorsInsideField (get_pos()); } // ******************************************************************************* // 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); 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 (player::Inventory *inv = player::GetInventory(sc.actor)) { int lifepos = inv->find("it-extralife"); if (lifepos != -1) { delete inv->yield_item(lifepos); ball_velocity = sc.actor->get_vel(); 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); assert(!model); } 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 (20*sc.actor->get_vel()); } } 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() { 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";} void message (const string &msg, const Value &) { if (msg == "trigger" || msg == "signal") { ReplaceStone(get_pos(), MakeStone("st-plain_hole")); } } public: PlainStone() : Stone("st-plain") {} }; class PlainStone_Hollow : public Stone { CLONEOBJ(PlainStone_Hollow); void message (const string &msg, const Value &) { if (msg == "trigger" || msg == "signal") { ReplaceStone(get_pos(), MakeStone("st-plain")); } } 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(); } void message (const string &msg, const Value &) { if (msg =="ignite" || msg == "expl" || msg == "bombstone") break_me(); } void actor_hit (const StoneContact &sc) { if (player::wielded_item_is(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::wielded_item_is(sc.actor, "it-hammer")) { break_me(); } } void message (const string &msg, const Value &) { if (msg =="ignite" || msg == "expl" || msg == "bombstone") break_me(); } 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")); } void message (const string &msg, const Value &) { if (msg =="ignite" || msg == "expl" || msg == "bombstone") break_me(); } 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: PlainStone_Movable() : Stone("st-plain_move") { } }; } namespace { class BlackBallsStone : public Stone { CLONEOBJ(BlackBallsStone); void 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); } } } public: BlackBallsStone() : Stone ("st-blackballs") { } }; class WhiteBallsStone : public Stone { CLONEOBJ(WhiteBallsStone); void 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); } } } 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")); } }; } // -------------------------------------------------------------------------------- void stones::Init() { extern void InitSwitches(); // 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(); }