1183 lines
30 KiB
C++
1183 lines
30 KiB
C++
/*
|
||
* Copyright (C) 2002,2003,2004,2005 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 "enigma.hh"
|
||
#include "player.hh"
|
||
#include "sound.hh"
|
||
#include "server.hh"
|
||
#include "world.hh"
|
||
|
||
#include <iostream>
|
||
#include <set>
|
||
|
||
using namespace std;
|
||
using namespace enigma;
|
||
using namespace world;
|
||
using ecl::V2;
|
||
|
||
#include "actors_internal.hh"
|
||
|
||
|
||
|
||
/* -------------------- Helper functions -------------------- */
|
||
|
||
|
||
#define DECL_TRAITS \
|
||
static ActorTraits traits; \
|
||
const ActorTraits &get_traits() const { return traits; } \
|
||
|
||
|
||
|
||
/* -------------------- Actor -------------------- */
|
||
|
||
Actor::Actor (const ActorTraits &tr)
|
||
: Object(tr.name),
|
||
m_actorinfo(),
|
||
m_sprite(),
|
||
startingpos(),
|
||
respawnpos(), use_respawnpos(false),
|
||
spikes(false), controllers (0)
|
||
{
|
||
set_attrib("mouseforce", 0.0);
|
||
|
||
// copy default properties to dynamic properties
|
||
m_actorinfo.mass = tr.default_mass;
|
||
m_actorinfo.radius = tr.radius;
|
||
|
||
ASSERT(m_actorinfo.radius <= get_max_radius(), XLevelRuntime, "Actor: radius of actor too large");
|
||
}
|
||
|
||
void Actor::on_collision (Actor *)
|
||
{
|
||
}
|
||
|
||
ActorInfo *Actor::get_actorinfo() {
|
||
return &m_actorinfo;
|
||
}
|
||
|
||
const ActorInfo &Actor::get_actorinfo() const {
|
||
return m_actorinfo;
|
||
}
|
||
|
||
const ecl::V2 &Actor::get_pos() const
|
||
{
|
||
return m_actorinfo.pos;
|
||
}
|
||
|
||
|
||
double Actor::get_max_radius() {
|
||
return 24/64.0;
|
||
}
|
||
|
||
void Actor::think(double /*dtime*/) {
|
||
const Field *f = GetField (get_gridpos());
|
||
if (f) {
|
||
Floor *fl = f->floor;
|
||
Item *it = f->item;
|
||
bool item_covers_floor = (it && it->covers_floor(m_actorinfo.pos));
|
||
if (!item_covers_floor && fl && this->is_on_floor())
|
||
fl->actor_contact(this);
|
||
}
|
||
}
|
||
|
||
void Actor::set_respawnpos(const V2& p)
|
||
{
|
||
respawnpos = p;
|
||
use_respawnpos = true;
|
||
}
|
||
|
||
void Actor::remove_respawnpos() {
|
||
use_respawnpos = false;
|
||
}
|
||
|
||
void Actor::find_respawnpos() {
|
||
V2& what_pos = use_respawnpos ? respawnpos : startingpos;
|
||
FreeRespawnLocationFinder unblocked(what_pos, *this);
|
||
what_pos = unblocked.get_position();
|
||
}
|
||
|
||
const V2& Actor::get_respawnpos() const {
|
||
return use_respawnpos ? respawnpos : startingpos;
|
||
}
|
||
|
||
const V2 &Actor::get_startpos() const {
|
||
return startingpos;
|
||
}
|
||
|
||
void Actor::respawn() {
|
||
V2 p =(use_respawnpos) ? respawnpos : startingpos;
|
||
warp (p);
|
||
on_respawn(p);
|
||
}
|
||
|
||
void Actor::add_force (const ecl::V2 &f) {
|
||
m_actorinfo.forceacc += f;
|
||
}
|
||
|
||
void Actor::init() {
|
||
m_sprite = display::AddSprite(get_pos());
|
||
}
|
||
|
||
void Actor::on_creation(const ecl::V2 &p)
|
||
{
|
||
set_model(get_kind());
|
||
m_sprite.move (p);
|
||
move ();
|
||
}
|
||
|
||
void Actor::on_respawn (const ecl::V2 &/*pos*/) {
|
||
}
|
||
|
||
void Actor::warp(const ecl::V2 &newpos) {
|
||
m_actorinfo.pos = newpos;
|
||
m_actorinfo.vel = V2();
|
||
m_sprite.move (newpos);
|
||
move ();
|
||
}
|
||
|
||
void Actor::move ()
|
||
{
|
||
GridPos ofield = gridpos;
|
||
gridpos = GridPos (m_actorinfo.pos);
|
||
|
||
if (const Field *f = GetField (gridpos)) {
|
||
if (gridpos != ofield) {
|
||
// Actor entered a new field -> notify floor and item objects
|
||
if (Floor *fl = f->floor)
|
||
fl->actor_enter (this);
|
||
if (Item *it = f->item)
|
||
it->actor_enter (this);
|
||
|
||
if (const Field *of = GetField (ofield)) {
|
||
if (Floor *fl = of->floor)
|
||
fl->actor_leave (this);
|
||
if (Item *it = of->item)
|
||
it->actor_leave (this);
|
||
}
|
||
}
|
||
|
||
Item *it = f->item;
|
||
if (it && it->actor_hit(this))
|
||
player::PickupItem (this, gridpos);
|
||
|
||
if (Stone *st = f->stone)
|
||
st->actor_inside (this);
|
||
}
|
||
// move the actor and save the position
|
||
m_actorinfo.oldpos = m_actorinfo.pos;
|
||
}
|
||
|
||
void Actor::move_screen () {
|
||
m_sprite.move (m_actorinfo.pos);
|
||
}
|
||
|
||
void Actor::set_model(const string &name) {
|
||
m_sprite.replace_model (display::MakeModel(name));
|
||
}
|
||
|
||
void Actor::animcb () {
|
||
}
|
||
|
||
|
||
void Actor::hide() {
|
||
m_sprite.hide();
|
||
}
|
||
|
||
void Actor::show() {
|
||
m_sprite.show();
|
||
}
|
||
|
||
void Actor::set_anim (const string &modelname) {
|
||
set_model(modelname.c_str());
|
||
get_sprite().set_callback (this);
|
||
}
|
||
|
||
bool Actor::can_move() const {
|
||
if (Stone *st = GetStone (get_gridpos())) {
|
||
if (!server::NoCollisions)
|
||
return !st->is_sticky(this);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
Value Actor::message(const string &m, const Value &) {
|
||
if (m == "init") {
|
||
startingpos = get_pos();
|
||
}
|
||
return Value();
|
||
}
|
||
|
||
bool Actor::sound_event (const char *name, double vol) {
|
||
return sound::EmitSoundEvent (name, get_pos(), getVolume(name, this, vol));
|
||
}
|
||
|
||
void Actor::set_attrib(const string& key, const Value &val)
|
||
{
|
||
if (key == "controllers")
|
||
controllers = to_int (val);
|
||
else if (key == "mouseforce")
|
||
mouseforce = to_double (val);
|
||
Object::set_attrib (key, val);
|
||
}
|
||
|
||
|
||
|
||
|
||
/* -------------------- RotorBase -------------------- */
|
||
|
||
namespace
|
||
{
|
||
/*! The base class for rotors and spinning tops. */
|
||
class RotorBase : public Actor {
|
||
protected:
|
||
RotorBase(const ActorTraits &tr);
|
||
private:
|
||
// Actor interface.
|
||
void think (double dtime);
|
||
bool is_dead() const { return false; }
|
||
// bool is_flying() const { return true; }
|
||
bool is_flying() const { return false; }
|
||
// bool is_on_floor() const { return false; } we need friction!
|
||
|
||
void on_collision (Actor *a) {
|
||
SendMessage(a, "shatter");
|
||
}
|
||
|
||
bool attackCurrentOnly;
|
||
double timeKeepAttackStrategy;
|
||
};
|
||
}
|
||
|
||
RotorBase::RotorBase(const ActorTraits &tr)
|
||
: Actor(tr), attackCurrentOnly(false), timeKeepAttackStrategy(0)
|
||
{
|
||
set_attrib ("range", 5.0);
|
||
set_attrib ("force", 10.0);
|
||
set_attrib ("gohome", 1.0);
|
||
set_attrib ("attacknearest", 1.0);
|
||
set_attrib ("prefercurrent", 0.0);
|
||
}
|
||
|
||
void RotorBase::think (double dtime)
|
||
{
|
||
double range = 0, force = 0;
|
||
double_attrib("range", &range);
|
||
double_attrib("force", &force);
|
||
|
||
force /= 6;
|
||
|
||
Actor *target = 0;
|
||
V2 target_vec;
|
||
bool attack_nearest = (int_attrib ("attacknearest") != 0);
|
||
|
||
double preferCurrent;
|
||
double_attrib("prefercurrent", &preferCurrent);
|
||
timeKeepAttackStrategy -= dtime;
|
||
if (timeKeepAttackStrategy < 0) {
|
||
timeKeepAttackStrategy = enigma::DoubleRand(0.8, 1.6);
|
||
attackCurrentOnly = (enigma::DoubleRand(0.0, 1.0) < preferCurrent);
|
||
}
|
||
|
||
vector<Actor *> actors;
|
||
GetActorsInRange (get_pos(), range, actors);
|
||
for (size_t i=0; i<actors.size(); ++i) {
|
||
Actor *a = actors[i];
|
||
ActorID id = get_id (a);
|
||
if ((id == ac_whiteball || id == ac_blackball || id == ac_meditation)
|
||
&& a->is_movable() && !a->is_invisible())
|
||
{
|
||
V2 v = a->get_pos() - get_pos();
|
||
if (attack_nearest && !attackCurrentOnly ||
|
||
attackCurrentOnly && a == player::GetMainActor(
|
||
player::CurrentPlayer())) {
|
||
if (!target || (length(v) < length(target_vec))) {
|
||
target = a;
|
||
target_vec = v;
|
||
}
|
||
} else if (!attackCurrentOnly) {
|
||
target = a;
|
||
target_vec += normalize(v);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!target && int_attrib("gohome")) {
|
||
// no actors focussed -> return to start position
|
||
target_vec = get_respawnpos()-get_pos();
|
||
}
|
||
|
||
double target_dist = length(target_vec);
|
||
|
||
if (target_dist > 0.2)
|
||
target_vec.normalize();
|
||
add_force (target_vec * force);
|
||
|
||
Actor::think(dtime);
|
||
}
|
||
|
||
|
||
/* -------------------- Rotor -------------------- */
|
||
namespace
|
||
{
|
||
class Rotor : public RotorBase {
|
||
CLONEACTOR(Rotor);
|
||
DECL_TRAITS;
|
||
public:
|
||
Rotor() : RotorBase (traits)
|
||
{
|
||
}
|
||
};
|
||
}
|
||
|
||
ActorTraits Rotor::traits = {
|
||
"ac-rotor", // name
|
||
ac_rotor, // id
|
||
22.0f/64, // radius
|
||
0.8f // mass
|
||
};
|
||
|
||
|
||
/* -------------------- Spinning top -------------------- */
|
||
namespace
|
||
{
|
||
class Top : public RotorBase {
|
||
CLONEACTOR(Top);
|
||
DECL_TRAITS;
|
||
public:
|
||
Top() : RotorBase (traits)
|
||
{
|
||
}
|
||
};
|
||
}
|
||
|
||
ActorTraits Top::traits = {
|
||
"ac-top", // name
|
||
ac_top, // id
|
||
16.0f/64, // radius
|
||
0.8f // mass
|
||
};
|
||
|
||
|
||
/* -------------------- Bug -------------------- */
|
||
|
||
namespace
|
||
{
|
||
class Bug : public Actor {
|
||
CLONEACTOR(Bug);
|
||
DECL_TRAITS;
|
||
public:
|
||
Bug() : Actor(traits) {}
|
||
bool is_flying() const { return true; }
|
||
bool is_dead() const { return false; }
|
||
int get_id() const { return ac_bug; }
|
||
};
|
||
}
|
||
|
||
ActorTraits Bug::traits = {
|
||
"ac-bug", // name
|
||
ac_bug, // id
|
||
12.0f/64, // radius
|
||
0.7f // mass
|
||
};
|
||
|
||
|
||
|
||
/* -------------------- Horse -------------------- */
|
||
|
||
namespace
|
||
{
|
||
class Horse : public Actor {
|
||
CLONEACTOR(Horse);
|
||
DECL_TRAITS;
|
||
|
||
int get_id() const { return ac_horse; }
|
||
bool is_flying() const { return true; }
|
||
bool is_dead() const { return false; }
|
||
|
||
void think (double dtime);
|
||
|
||
// Private methods
|
||
void update_target ();
|
||
bool try_target (int idx);
|
||
|
||
// Variables
|
||
int m_targetidx;
|
||
V2 m_target;
|
||
public:
|
||
Horse();
|
||
};
|
||
}
|
||
|
||
ActorTraits Horse::traits = {
|
||
"ac-horse", // name
|
||
ac_horse, // id
|
||
24.0f/64, // radius
|
||
1.2f // mass
|
||
};
|
||
|
||
Horse::Horse()
|
||
: Actor(traits),
|
||
m_targetidx(-1),
|
||
m_target()
|
||
{
|
||
set_attrib("force", Value(10.0));
|
||
set_attrib("target1", Value());
|
||
set_attrib("target2", Value());
|
||
set_attrib("target3", Value());
|
||
set_attrib("target4", Value());
|
||
}
|
||
|
||
void Horse::think (double /* dtime */)
|
||
{
|
||
double force = 0;
|
||
double_attrib("force", &force);
|
||
update_target ();
|
||
if (m_targetidx != -1) {
|
||
add_force (normalize(m_target - get_pos()) * force);
|
||
}
|
||
}
|
||
|
||
bool Horse::try_target (int idx) {
|
||
if (idx <= 0 || idx > 4)
|
||
return false;
|
||
|
||
const char *attrs[] = {
|
||
"target1", "target2", "target3", "target4",
|
||
};
|
||
string targetstr;
|
||
GridLoc loc;
|
||
|
||
if (!string_attrib (attrs[idx-1], &targetstr))
|
||
return false;
|
||
|
||
to_gridloc(targetstr.c_str(), loc);
|
||
m_target = loc.pos.center();
|
||
m_targetidx = idx;
|
||
return true;
|
||
}
|
||
|
||
void Horse::update_target () {
|
||
|
||
if (m_targetidx == -1) {
|
||
// no target defined so far
|
||
try_target(1);
|
||
}
|
||
else if (length(m_target - get_pos()) < 0.2) {
|
||
// target reached or? try next one
|
||
if (!try_target (m_targetidx + 1))
|
||
m_targetidx = -1; // failed -> start anew
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
/* -------------------- CannonBall -------------------- */
|
||
|
||
namespace
|
||
{
|
||
class CannonBall : public Actor {
|
||
CLONEACTOR(CannonBall);
|
||
DECL_TRAITS;
|
||
public:
|
||
CannonBall();
|
||
bool is_flying() const { return true; }
|
||
bool is_dead() const { return false; }
|
||
bool is_on_floor() const { return false; }
|
||
void on_creation(const ecl::V2 &p);
|
||
bool can_move() const { return true; }
|
||
|
||
void animcb();
|
||
};
|
||
}
|
||
|
||
ActorTraits CannonBall::traits = {
|
||
"ac-cannonball", // name
|
||
ac_cannonball, // id
|
||
24.0/64, // radius
|
||
1.0 // mass
|
||
};
|
||
|
||
CannonBall::CannonBall()
|
||
: Actor(traits)
|
||
{
|
||
get_actorinfo()->ignore_contacts = true;
|
||
}
|
||
|
||
void CannonBall::animcb()
|
||
{
|
||
const GridPos &p = get_gridpos();
|
||
|
||
if (Stone *st = GetStone (p)) {
|
||
SendMessage (st, "spitter");
|
||
}
|
||
else if (Item *it = GetItem(p)) {
|
||
if (!has_flags(it, itf_indestructible))
|
||
SetItem (p, it_explosion3);
|
||
}
|
||
else if (Floor *fl = GetFloor(p)) {
|
||
if (fl->is_destructible())
|
||
SetItem (p, it_explosion3);
|
||
}
|
||
KillActor (this);
|
||
}
|
||
|
||
void CannonBall::on_creation(const ecl::V2 &p)
|
||
{
|
||
Actor::on_creation(p);
|
||
|
||
display::SpriteHandle &sprite = get_sprite();
|
||
sprite.kill();
|
||
sprite = display::AddEffect(p, "ac-cannonball");
|
||
sprite.set_callback(this);
|
||
}
|
||
|
||
|
||
|
||
/* -------------------- BasicBall -------------------- */
|
||
namespace
|
||
{
|
||
/*! The base class for all marbles. */
|
||
class BasicBall : public Actor {
|
||
protected:
|
||
BasicBall(const ActorTraits &tr);
|
||
|
||
enum State {
|
||
NO_STATE,
|
||
NORMAL,
|
||
SHATTERING,
|
||
DROWNING,
|
||
BUBBLING,
|
||
FALLING, // falling into abyss
|
||
JUMPING,
|
||
DEAD, // marble is dead
|
||
RESURRECTED, // has been resurrected; about to respawn
|
||
APPEARING, // appearing when level starts/after respawn
|
||
DISAPPEARING, // disappearing when level finished
|
||
FALLING_VORTEX, // falling into vortex
|
||
RISING_VORTEX, // appear in vortex
|
||
JUMP_VORTEX, // jump out of vortex (here player controls actor)
|
||
};
|
||
|
||
enum HaloState {
|
||
NOHALO, HALOBLINK, HALONORMAL
|
||
};
|
||
|
||
void sink (double dtime);
|
||
void disable_shield();
|
||
void change_state_noshield (State newstate);
|
||
void change_state(State newstate);
|
||
|
||
// Model management
|
||
void update_model();
|
||
void set_sink_model(const string &m);
|
||
void set_shine_model (bool shinep);
|
||
void update_halo();
|
||
virtual void hide();
|
||
|
||
/* ---------- Actor interface ---------- */
|
||
|
||
virtual void think (double dtime);
|
||
virtual void move_screen ();
|
||
|
||
void on_creation(const ecl::V2 &p);
|
||
void on_respawn (const ecl::V2 &/*pos*/)
|
||
{
|
||
change_state(APPEARING);
|
||
}
|
||
|
||
bool is_dead() const;
|
||
bool is_movable() const;
|
||
bool is_flying() const { return state == JUMPING; }
|
||
bool is_on_floor() const;
|
||
bool is_drunken() const { return m_drunk_rest_time>0; }
|
||
bool is_invisible() const { return m_invisible_rest_time>0; }
|
||
|
||
bool can_drop_items() const;
|
||
bool can_pickup_items() const;
|
||
bool has_shield() const;
|
||
|
||
bool can_be_warped() const { return state==NORMAL; }
|
||
|
||
// Object interface.
|
||
virtual Value message(const string &m, const Value &);
|
||
|
||
// ModelCallback interface.
|
||
void animcb();
|
||
|
||
/* ---------- Variables ---------- */
|
||
|
||
State state; // The marble's current state
|
||
|
||
static const int minSinkDepth = 0; // normal level
|
||
int maxSinkDepth; // actor dies at this depth
|
||
double sinkDepth; // how deep actor has sunk
|
||
int sinkModel; // current model
|
||
bool lastshinep;
|
||
|
||
double vortex_normal_time; // while jumping out of vortex: time at normal level
|
||
|
||
display::SpriteHandle m_halosprite;
|
||
double m_shield_rest_time;
|
||
static const double SHIELD_TIME;
|
||
HaloState m_halostate;
|
||
|
||
double m_drunk_rest_time;
|
||
double m_invisible_rest_time;
|
||
};
|
||
|
||
const double BasicBall::SHIELD_TIME = 10.0;
|
||
}
|
||
|
||
BasicBall::BasicBall(const ActorTraits &tr)
|
||
: Actor (tr),
|
||
state (NO_STATE),
|
||
maxSinkDepth (7),
|
||
sinkDepth (minSinkDepth),
|
||
sinkModel (-1),
|
||
lastshinep (false),
|
||
vortex_normal_time (0),
|
||
m_halosprite (),
|
||
m_shield_rest_time (0),
|
||
m_halostate (NOHALO),
|
||
m_drunk_rest_time (0),
|
||
m_invisible_rest_time (0)
|
||
{
|
||
}
|
||
|
||
void BasicBall::on_creation(const ecl::V2 &p)
|
||
{
|
||
Actor::on_creation(p);
|
||
if (server::CreatingPreview)
|
||
change_state(NORMAL);
|
||
else
|
||
change_state(APPEARING);
|
||
}
|
||
|
||
|
||
void BasicBall::move_screen ()
|
||
{
|
||
update_model();
|
||
update_halo();
|
||
Actor::move_screen();
|
||
}
|
||
|
||
bool BasicBall::is_movable() const
|
||
{
|
||
return (state!=DEAD && state!=RESURRECTED && state!=APPEARING && state!=DISAPPEARING);
|
||
}
|
||
|
||
bool BasicBall::is_dead() const {
|
||
return state == DEAD;
|
||
}
|
||
|
||
bool BasicBall::is_on_floor() const {
|
||
return state == NORMAL || state == JUMP_VORTEX || state==APPEARING;
|
||
}
|
||
|
||
bool BasicBall::can_drop_items() const {
|
||
return state == NORMAL || state == JUMP_VORTEX || state==JUMPING;
|
||
}
|
||
|
||
bool BasicBall::can_pickup_items() const {
|
||
return state == NORMAL || state == JUMP_VORTEX;
|
||
}
|
||
|
||
void BasicBall::change_state_noshield (State newstate)
|
||
{
|
||
if (!has_shield())
|
||
change_state(newstate);
|
||
}
|
||
|
||
Value BasicBall::message(const string &m, const Value &v)
|
||
{
|
||
switch (state) {
|
||
case NORMAL:
|
||
if (m == "shatter") change_state_noshield(SHATTERING);
|
||
else if (m == "suicide") change_state(SHATTERING);
|
||
else if (m == "laserhit") change_state_noshield(SHATTERING);
|
||
else if (m == "drown") change_state_noshield(DROWNING);
|
||
else if (m == "fall") change_state_noshield(FALLING);
|
||
else if (m == "fallvortex") change_state(FALLING_VORTEX);
|
||
else if (m == "jump") change_state(JUMPING);
|
||
else if (m == "appear") change_state(APPEARING);
|
||
else if (m == "disappear") change_state(DISAPPEARING);
|
||
break;
|
||
case JUMPING:
|
||
if (m == "shatter") change_state_noshield(SHATTERING);
|
||
else if (m == "disappear") change_state(DISAPPEARING);
|
||
break;
|
||
case DEAD:
|
||
if (m == "resurrect") change_state(RESURRECTED);
|
||
break;
|
||
case FALLING_VORTEX:
|
||
if (m == "rise") change_state(RISING_VORTEX); // vortex->vortex teleportation
|
||
else if (m == "appear") change_state(APPEARING); // vortex->non-vortex teleportation
|
||
break;
|
||
case JUMP_VORTEX:
|
||
if (m == "laserhit") change_state(SHATTERING);
|
||
break;
|
||
case APPEARING:
|
||
// ugly hack
|
||
if (m == "init")
|
||
Actor::message (m, v);
|
||
else if (m == "shatter") change_state (SHATTERING);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
|
||
// Shield, booze and invisibility can be activated in all states except DEAD
|
||
|
||
if (state != DEAD) {
|
||
if (m == "shield") {
|
||
m_shield_rest_time += SHIELD_TIME;
|
||
update_halo();
|
||
}
|
||
else if (m == "invisibility") {
|
||
m_invisible_rest_time += 8.0;
|
||
}
|
||
else if (m == "booze") {
|
||
m_drunk_rest_time += 5.0; // Drunken for 5 more seconds
|
||
}
|
||
}
|
||
return Value();
|
||
}
|
||
|
||
void BasicBall::set_sink_model(const string &m)
|
||
{
|
||
int modelnum = ecl::round_down<int>(sinkDepth);
|
||
|
||
if (!has_shield() && modelnum != sinkModel) {
|
||
ASSERT(modelnum >= minSinkDepth && modelnum < maxSinkDepth, XLevelRuntime,
|
||
"BasicBall: set_sink_model called though modelnum incorrect");
|
||
|
||
string img = m+"-sink";
|
||
img.append(1, static_cast<char>('0'+modelnum));
|
||
set_model(img);
|
||
|
||
sinkModel = modelnum;
|
||
}
|
||
}
|
||
|
||
void BasicBall::set_shine_model (bool shinep)
|
||
{
|
||
if (shinep != lastshinep) {
|
||
string modelname = get_kind();
|
||
if (shinep)
|
||
set_model (modelname + "-shine");
|
||
else
|
||
set_model (modelname);
|
||
lastshinep = shinep;
|
||
}
|
||
}
|
||
|
||
void BasicBall::update_model()
|
||
{
|
||
if (m_invisible_rest_time > 0)
|
||
get_sprite().hide();
|
||
else
|
||
get_sprite().show();
|
||
|
||
switch (state) {
|
||
case NORMAL:
|
||
if (sinkDepth > minSinkDepth && sinkDepth < maxSinkDepth) {
|
||
set_sink_model(get_kind());
|
||
}
|
||
else {
|
||
ActorInfo *ai = get_actorinfo();
|
||
int xpos = ecl::round_nearest<int> (ai->pos[0] * 32.0);
|
||
int ypos = ecl::round_nearest<int> (ai->pos[1] * 32.0);
|
||
|
||
bool shinep = ((xpos + ypos) % 2) != 0;
|
||
set_shine_model (shinep);
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void BasicBall::sink (double dtime)
|
||
{
|
||
double sink_speed = 0.0;
|
||
double raise_speed = 0.0; // at this velocity don't sink; above: raise
|
||
|
||
Item *it = GetItem(get_gridpos());
|
||
Floor *fl = GetFloor (get_gridpos());
|
||
if (!(it != NULL && it->covers_floor(get_pos())) && fl != NULL)
|
||
fl->get_sink_speed (sink_speed, raise_speed);
|
||
|
||
if (sink_speed == 0.0 || has_shield()) {
|
||
sinkDepth = minSinkDepth;
|
||
sinkModel = -1;
|
||
}
|
||
else {
|
||
ActorInfo *ai = get_actorinfo();
|
||
double sinkSpeed = sink_speed * (1 - length(ai->vel) / raise_speed);
|
||
sinkDepth += sinkSpeed*dtime;
|
||
|
||
if (sinkDepth >= maxSinkDepth) {
|
||
set_model(string(get_kind())+"-sunk");
|
||
ai->vel = V2(); // stop!
|
||
sound_event ("swamp");
|
||
change_state(BUBBLING);
|
||
}
|
||
else {
|
||
if (sinkDepth < minSinkDepth)
|
||
sinkDepth = minSinkDepth;
|
||
}
|
||
}
|
||
}
|
||
|
||
void BasicBall::think (double dtime)
|
||
{
|
||
if (m_invisible_rest_time > 0)
|
||
m_invisible_rest_time -= dtime;
|
||
|
||
// Update protection shield
|
||
if (m_shield_rest_time > 0)
|
||
m_shield_rest_time -= dtime;
|
||
|
||
switch (state) {
|
||
case NORMAL:
|
||
if (m_drunk_rest_time > 0)
|
||
m_drunk_rest_time -= dtime;
|
||
sink (dtime);
|
||
break;
|
||
case JUMP_VORTEX:
|
||
vortex_normal_time += dtime;
|
||
if (vortex_normal_time > 0.025) // same time as appear animation
|
||
if (vortex_normal_time > dtime) // ensure min. one tick in state JUMP_VORTEX!
|
||
change_state(JUMPING); // end of short control over actor
|
||
break;
|
||
default:
|
||
|
||
break;
|
||
}
|
||
|
||
Actor::think(dtime);
|
||
}
|
||
|
||
void BasicBall::animcb()
|
||
{
|
||
string kind=get_kind();
|
||
|
||
switch (state) {
|
||
case SHATTERING:
|
||
set_model(kind+"-shattered");
|
||
change_state(DEAD);
|
||
break;
|
||
case DROWNING:
|
||
case BUBBLING:
|
||
set_model("invisible");
|
||
change_state(DEAD);
|
||
break;
|
||
case FALLING:
|
||
set_model(kind+"-fallen"); // invisible
|
||
if (get_id (this) == ac_meditation)
|
||
sound_event ("shattersmall");
|
||
else
|
||
sound_event ("shatter");
|
||
change_state(DEAD);
|
||
break;
|
||
case JUMPING:
|
||
set_model(kind);
|
||
change_state(NORMAL);
|
||
break;
|
||
case APPEARING:
|
||
set_model(kind);
|
||
change_state(NORMAL);
|
||
break;
|
||
case DISAPPEARING:
|
||
set_model("ring-anim");
|
||
break;
|
||
case FALLING_VORTEX: {
|
||
set_model(kind+"-fallen"); // invisible
|
||
break;
|
||
}
|
||
case RISING_VORTEX: {
|
||
set_model(kind);
|
||
if (Item *it = GetItem(get_gridpos())) {
|
||
world::ItemID id = get_id(it);
|
||
if (id == world::it_vortex_open || id == world::it_vortex_closed)
|
||
SendMessage(it, "arrival"); // closes some vortex
|
||
}
|
||
change_state(JUMP_VORTEX);
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void BasicBall::change_state(State newstate) {
|
||
if (newstate == state)
|
||
return;
|
||
|
||
string kind = get_kind();
|
||
State oldstate = state;
|
||
|
||
state = newstate;
|
||
switch (newstate) {
|
||
case NORMAL:
|
||
if (oldstate == APPEARING) {
|
||
ActorInfo *ai = get_actorinfo();
|
||
ai->forceacc = V2();
|
||
}
|
||
world::ReleaseActor(this);
|
||
break;
|
||
|
||
case SHATTERING:
|
||
if (get_id (this) == ac_meditation)
|
||
sound_event ("shattersmall");
|
||
else
|
||
sound_event ("shatter");
|
||
world::GrabActor(this);
|
||
set_anim (kind+"-shatter");
|
||
break;
|
||
|
||
case DROWNING:
|
||
// @@@ FIXME: use same animation as SINKING ?
|
||
world::GrabActor(this);
|
||
// sound::PlaySound("drown");
|
||
sound_event("drown");
|
||
// set_anim ("ring-anim");
|
||
set_anim ("ac-drowned");
|
||
break;
|
||
case BUBBLING:
|
||
world::GrabActor(this);
|
||
// sound::PlaySound("drown");
|
||
set_anim ("ac-drowned");
|
||
break;
|
||
case FALLING:
|
||
case FALLING_VORTEX:
|
||
world::GrabActor(this);
|
||
set_anim(kind+"-fall");
|
||
break;
|
||
case DEAD:
|
||
disable_shield();
|
||
m_drunk_rest_time = 0;
|
||
m_invisible_rest_time = 0;
|
||
break;
|
||
case JUMPING:
|
||
sound_event ("jump");
|
||
set_anim(kind+"-jump");
|
||
break;
|
||
case APPEARING:
|
||
case RISING_VORTEX:
|
||
set_anim(kind+"-appear");
|
||
world::GrabActor(this);
|
||
break;
|
||
case JUMP_VORTEX:
|
||
ASSERT(oldstate == RISING_VORTEX, XLevelRuntime,
|
||
"BasicBall: change to state JUMP_VORTEX but not RISING_VORTEX");
|
||
vortex_normal_time = 0;
|
||
set_model(kind);
|
||
world::ReleaseActor(this);
|
||
break;
|
||
case DISAPPEARING:
|
||
world::GrabActor(this);
|
||
disable_shield();
|
||
set_anim(kind+"-disappear");
|
||
break;
|
||
case RESURRECTED:
|
||
disable_shield();
|
||
sinkDepth = minSinkDepth;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
void BasicBall::disable_shield() {
|
||
if (has_shield()) {
|
||
m_shield_rest_time = 0;
|
||
update_halo();
|
||
}
|
||
}
|
||
|
||
bool BasicBall::has_shield() const {
|
||
return m_shield_rest_time > 0;
|
||
}
|
||
|
||
void BasicBall::update_halo() {
|
||
HaloState newstate = m_halostate;
|
||
|
||
double radius = get_actorinfo()->radius;
|
||
string halokind;
|
||
|
||
// Determine which halomodel has to be used:
|
||
if (radius == 19.0/64) { // Halo for normal balls
|
||
halokind="halo";
|
||
}else if (radius == 13.0f/64) { // Halo for small balls
|
||
halokind="halo-small";
|
||
}
|
||
|
||
if (m_shield_rest_time <= 0)
|
||
newstate = NOHALO;
|
||
else if (m_shield_rest_time <= 3.0)
|
||
newstate = HALOBLINK;
|
||
else
|
||
newstate = HALONORMAL;
|
||
|
||
if (newstate != m_halostate) {
|
||
if (m_halostate == NOHALO){
|
||
m_halosprite = display::AddSprite (get_pos(), halokind.c_str());
|
||
}
|
||
switch (newstate) {
|
||
case NOHALO:
|
||
// remove halo
|
||
m_halosprite.kill();
|
||
m_halosprite = display::SpriteHandle();
|
||
break;
|
||
case HALOBLINK:
|
||
// blink for the last 3 seconds
|
||
m_halosprite.replace_model (display::MakeModel (halokind+"-blink"));
|
||
break;
|
||
case HALONORMAL:
|
||
m_halosprite.replace_model (display::MakeModel (halokind));
|
||
break;
|
||
}
|
||
m_halostate = newstate;
|
||
}
|
||
else if (m_halostate != NOHALO) {
|
||
m_halosprite.move (get_pos());
|
||
}
|
||
}
|
||
|
||
void BasicBall::hide() {
|
||
Actor::hide();
|
||
disable_shield();
|
||
}
|
||
|
||
//----------------------------------------
|
||
// Balls of different sorts
|
||
//----------------------------------------
|
||
|
||
namespace
|
||
{
|
||
class BlackBall : public BasicBall {
|
||
CLONEACTOR(BlackBall);
|
||
DECL_TRAITS;
|
||
public:
|
||
BlackBall() : BasicBall(traits)
|
||
{
|
||
set_attrib("mouseforce", Value(1.0));
|
||
set_attrib("color", Value(0.0));
|
||
set_attrib("blackball", Value(true));
|
||
set_attrib("player", Value(0.0));
|
||
set_attrib("controllers", Value(1.0));
|
||
}
|
||
};
|
||
|
||
class WhiteBall : public BasicBall {
|
||
CLONEACTOR(WhiteBall);
|
||
DECL_TRAITS;
|
||
public:
|
||
WhiteBall() : BasicBall(traits)
|
||
{
|
||
set_attrib("mouseforce", Value(1.0));
|
||
set_attrib("color", Value(1.0));
|
||
set_attrib("whiteball", Value(true));
|
||
set_attrib("player", Value(1.0));
|
||
set_attrib("controllers", Value(2.0));
|
||
}
|
||
};
|
||
|
||
class WhiteBall_Small : public BasicBall {
|
||
CLONEACTOR(WhiteBall_Small);
|
||
DECL_TRAITS;
|
||
public:
|
||
WhiteBall_Small() : BasicBall(traits)
|
||
{
|
||
set_attrib("mouseforce", Value(1.0));
|
||
set_attrib("color", Value(1.0));
|
||
set_attrib("whiteball", Value(true));
|
||
set_attrib("controllers", Value(3.0));
|
||
maxSinkDepth = 4;
|
||
}
|
||
};
|
||
|
||
class Killerball : public Actor {
|
||
CLONEACTOR(Killerball);
|
||
DECL_TRAITS;
|
||
public:
|
||
|
||
Killerball() : Actor (traits)
|
||
{
|
||
set_attrib("mouseforce", Value(2.0));
|
||
set_attrib("color", Value(1.0));
|
||
set_attrib("whiteball", Value(true));
|
||
set_attrib("controllers", Value(3.0));
|
||
}
|
||
bool is_dead() const { return false; }
|
||
|
||
void on_collision(Actor *a) {
|
||
SendMessage(a, "shatter");
|
||
}
|
||
};
|
||
}
|
||
|
||
ActorTraits BlackBall::traits = {
|
||
"ac-blackball", // name
|
||
ac_blackball, // id
|
||
19.0/64, // radius
|
||
1.0 // mass
|
||
};
|
||
|
||
ActorTraits WhiteBall::traits = {
|
||
"ac-whiteball", // name
|
||
ac_whiteball, // id
|
||
19.0/64, // radius
|
||
1.0 // mass
|
||
};
|
||
|
||
ActorTraits WhiteBall_Small::traits = {
|
||
"ac-whiteball-small", // name
|
||
ac_meditation, // id
|
||
13.0f/64, // radius
|
||
0.7f // mass
|
||
};
|
||
|
||
ActorTraits Killerball::traits = {
|
||
"ac-killerball", // name
|
||
ac_killerball, // id
|
||
13.0f/64, // radius
|
||
0.7f // mass
|
||
};
|
||
|
||
|
||
/* -------------------- Functions -------------------- */
|
||
|
||
void world::InitActors ()
|
||
{
|
||
RegisterActor (new Bug);
|
||
RegisterActor (new Horse);
|
||
RegisterActor (new Rotor);
|
||
RegisterActor (new Top);
|
||
RegisterActor (new BlackBall);
|
||
RegisterActor (new WhiteBall);
|
||
RegisterActor (new WhiteBall_Small);
|
||
RegisterActor (new Killerball);
|
||
RegisterActor (new CannonBall);
|
||
}
|