Files
commandergenius/project/jni/application/enigma/src/stones.cpp
2010-10-13 17:30:44 +03:00

844 lines
22 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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();
}