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

2115 lines
56 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,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 "laser.hh"
#include "player.hh"
#include "sound.hh"
#include "options.hh"
#include "server.hh"
#include "lua.hh"
#include "client.hh"
#include "main.hh"
#include "stones_internal.hh"
#include <iostream>
#include <algorithm>
#include <functional>
#include <map>
#include <numeric>
// remove comment from define below to switch on verbose messaging
// note: VERBOSE_MESSAGES is defined in multiple source files!
// #define VERBOSE_MESSAGES
using namespace std;
using namespace world;
using namespace ecl;
#include "world_internal.hh"
// Size of one time step -- do not change!
const double ActorTimeStep = 0.0025;
/* -------------------- Auxiliary functions -------------------- */
namespace {
/*! Enigma's stones have rounded corners; this leads to realistic
behaviour when a marble rebounds from the corner of a single
stone. But it's annoying when there are two adjacent stones and
a marble rebounds from any one (or even both) corners, because
it changes the direction of the marble quite unpredictably.
This function modifies the contact information for two adjacent
stones in such a way that the two collisions are treated as a
single collision with a flat surface. */
void maybe_join_contacts (StoneContact &a, StoneContact &b)
{
// double maxd = 4.0/32; // Max distance between joinable collisions
if (a.is_contact && b.is_contact
&& a.is_collision && b.is_collision
&& a.response==STONE_REBOUND && b.response==STONE_REBOUND)
// && length(a.contact_point - b.contact_point) <= maxd)
{
b.ignore = true; // Don't rebound from `b'
DirectionBits fa = contact_faces(a);
DirectionBits fb = contact_faces(b);
switch (fa & fb) {
case NORTHBIT: a.normal = V2(0,-1); break;
case EASTBIT: a.normal = V2(1,0); break;
case SOUTHBIT: a.normal = V2(0,1); break;
case WESTBIT: a.normal = V2(-1,0); break;
case NODIRBIT:
//fprintf(stderr, "Strange: contacts have no direction in common\n");
break;
default:
//fprintf(stderr, "Strange: contacts have multiple directions in common\n");
break;
}
}
}
/*! Find an already existing contact point in the ContactList that is
similar to the second argument. */
bool has_nearby_contact (const ContactList &cl, const Contact &c)
{
double posdelta = 0.2;
double normaldelta = 0.1;
for (size_t i=0; i<cl.size(); ++i) {
if (length (cl[i].pos - c.pos) < posdelta
&& length (cl[i].normal - c.normal) < normaldelta)
return true;
}
return false;
}
}
/* -------------------- ActorInfo -------------------- */
ActorInfo::ActorInfo()
: pos(), vel(), forceacc(),
charge(0), mass(1), radius(1),
grabbed(false), ignore_contacts (false),
last_pos(), oldpos(), force(),
contacts(), new_contacts()
{}
/* -------------------- Messages -------------------- */
Message::Message ()
{
}
Message::Message (const std::string &message_,
const enigma::Value &value_,
GridPos from_)
: message (message_),
value (value_),
gridpos (from_)
{
}
/* -------------------- Signals -------------------- */
namespace
{
void emit_signal (const Signal *s, int value)
{
Object *src = s->source;
Object *dst = GetObject(s->destloc);
#if defined(VERBOSE_MESSAGES)
src->warning("emit_from: msg='%s'", // dest=%i/%i obj=%p",
s->message.c_str()
// destloc.pos.x, destloc.pos.y,
// dst);
);
#endif
if (GridObject *go = dynamic_cast<GridObject*>(src))
SendMessage (dst, Message (s->message, value, go->get_pos()));
else
SendMessage (dst, Message (s->message, value));
}
void emit_from (const SignalList &sl, Object *source, int value)
{
size_t size = sl.size();
for (unsigned i=0; i<size; ++i) {
if (sl[i].source == source)
emit_signal (&sl[i], value);
}
// // signals may have side effects. To minimize them
// // 1. collect all targets and then
// // 2. emit signals to targets
// vector<Object*> targets;
// for (unsigned i=0; i<size; ++i) {
// if (m_signals[i].get_source() == source)
// target.push_back (m_signals[i]);
// for (unsigned i=0; i<size; ++i)
// if (GetObject(m_signals[i].get_target_loc()) == targets[i])
// m_signals[i].emit_from (source, value);
// }
}
bool emit_by_index (const SignalList &sl, Object *source, int signalidx, int value)
{
size_t size = sl.size();
int signalcnt = 0;
for (unsigned i=0; i<size; ++i) {
if (sl[i].source == source) {
if (signalcnt == signalidx) {
emit_signal (&sl[i], value);
return true;
}
signalcnt += 1;
}
}
return false;
}
Object *find_single_destination (const SignalList &sl, Object *src)
{
Object *found = 0;
size_t size = sl.size();
for (unsigned i = 0; i<size; ++i) {
if (sl[i].source == src) {
if (Object *candidate = GetObject(sl[i].destloc)) {
if (!found)
found = candidate;
else if (candidate != found)
return 0; // multiple targets
}
}
}
return found;
}
}
/* -------------------- RubberBandData -------------------- */
RubberBandData::RubberBandData () {
strength = 1;
length = 2;
minlength = 0;
}
RubberBandData::RubberBandData (const RubberBandData &x) {
strength = x.strength;
length = x.length;
minlength = x.minlength;
}
/* -------------------- RubberBand -------------------- */
RubberBand::RubberBand (Actor *a1, Actor *a2, const RubberBandData &d)
: actor(a1), actor2(a2), stone(0),
model(display::AddRubber(get_p1(),get_p2())),
data (d)
{
ASSERT(actor, XLevelRuntime, "RubberBand: no actor defined");
ASSERT(d.length >= 0, XLevelRuntime, "RubberBand: length negative");
}
RubberBand::RubberBand (Actor *a1, Stone *st, const RubberBandData &d)
: actor(a1), actor2(0), stone(st), model(0),
data (d)
{
ASSERT(actor, XLevelRuntime, "RubberBand: no actor defined");
ASSERT(d.length >= 0, XLevelRuntime, "RubberBand: length negative");
model = display::AddRubber(get_p1(), get_p2());
}
RubberBand::~RubberBand() {
model.kill();
}
void RubberBand::apply_forces ()
{
V2 v = get_p2()-get_p1();
double vv = ecl::length(v);
if (vv > data.length) {
V2 force = v * data.strength*(vv-data.length)/vv;
force /= 6;
actor->add_force(force);
if (actor2)
actor2->add_force(-force);
}
else if (vv < data.minlength) {
V2 force = v * data.strength * (vv-data.minlength) / vv;
force /= 6;
actor->add_force(force);
if (actor2)
actor2->add_force(-force);
}
}
V2 RubberBand::get_p1() const
{
return V2(actor->get_pos()[0], actor->get_pos()[1]);
}
V2 RubberBand::get_p2() const
{
if (!stone)
return V2(actor2->get_pos()[0], actor2->get_pos()[1]);
else
return stone->get_pos().center();
}
void RubberBand::tick (double /* dtime */)
{
model.update_first (get_p1());
model.update_second (get_p2());
}
/* -------------------- Field -------------------- */
Field::Field()
{
floor=0;
item=0;
stone=0;
}
Field::~Field()
{
DisposeObject(floor);
DisposeObject(item);
DisposeObject(stone);
}
/* -------------------- StoneContact -------------------- */
StoneContact::StoneContact(Actor *a, GridPos p,
const V2 &cp, const V2 &n)
: actor(a), stonepos(p),
response(STONE_PASS),
contact_point(cp),
normal(n),
is_collision(false),
ignore (false),
new_collision(false),
is_contact(true)
{}
StoneContact::StoneContact()
: is_collision(false),
ignore (false),
new_collision(false),
is_contact(false)
{}
DirectionBits
world::contact_faces(const StoneContact &sc)
{
using namespace enigma;
int dirs = NODIRBIT;
if (sc.normal[0] < 0)
dirs |= WESTBIT;
else if (sc.normal[0] > 0)
dirs |= EASTBIT;
if (sc.normal[1] < 0)
dirs |= NORTHBIT;
else if (sc.normal[1] > 0)
dirs |= SOUTHBIT;
return DirectionBits(dirs);
}
Direction
world::contact_face(const StoneContact &sc)
{
using namespace enigma;
if (sc.normal == V2(-1,0))
return WEST;
else if (sc.normal == V2(1,0))
return EAST;
else if (sc.normal == V2(0,-1))
return NORTH;
else if (sc.normal == V2(0,1))
return SOUTH;
else
return NODIR;
}
/* -------------------- Global variables -------------------- */
namespace
{
auto_ptr<World> level;
}
enigma::Timer world::GameTimer;
bool world::TrackMessages;
Actor *world::CurrentCollisionActor = 0;
/* -------------------- Layer implementation -------------------- */
template <class T>
T *Layer<T>::get(GridPos p) {
if (Field *f=level->get_field(p))
return raw_get(*f);
else
return defaultval;
}
template <class T>
T *Layer<T>::yield(GridPos p) {
if (Field *f=level->get_field(p)) {
T *x = raw_get(*f);
if (x) {
raw_set(*f, 0);
x->removal(p);
}
return x;
} else
return defaultval;
}
template <class T>
void Layer<T>::set(GridPos p, T *x) {
if (x) {
if (Field *f=level->get_field(p)) {
if (T *old = raw_get (*f)) {
old->removal(p);
dispose (old);
}
raw_set(*f, x);
x->creation(p);
}
else
dispose(x);
}
}
/* -------------------- World -------------------- */
World::World(int ww, int hh)
: fields(ww,hh),
preparing_level(true)
{
w = ww;
h = hh;
scrambleIntensity = server::GetDifficulty() == DIFFICULTY_EASY ? 3 : 10;
}
World::~World()
{
fields = FieldArray(0,0);
for_each(actorlist.begin(), actorlist.end(), mem_fun(&Actor::dispose));
delete_sequence (m_rubberbands.begin(), m_rubberbands.end());
}
bool World::is_border (GridPos p) {
return(p.x==0 || p.y==0 || p.x==w-1 || p.y==h-1);
}
void World::remove (ForceField *ff)
{
ForceList::iterator i=find (forces.begin(), forces.end(), ff);
if (i != forces.end())
forces.erase(i);
}
Object *World::get_named (const string &name)
{
ecl::Dict<Object*>::iterator found = m_objnames.find(name);
if (found != m_objnames.end())
return found->second;
Log << "Did not find named object: " << name << '\n';
return 0;
}
void World::name_object (Object *obj, const std::string &name)
{
m_objnames.insert(name, obj); // [name] = obj;
obj->set_attrib("name", name);
}
void World::unname (Object *obj)
{
ASSERT(obj, XLevelRuntime, "unname: no object given");
string name;
if (obj->string_attrib("name", &name)) {
m_objnames.remove(name);
obj->set_attrib("name", "");
}
}
void World::add_actor (Actor *a)
{
add_actor (a, a->get_pos());
}
void World::add_actor (Actor *a, const V2 &pos)
{
actorlist.push_back(a);
a->get_actorinfo()->pos = pos;
if (!preparing_level) {
// if game is already running, call on_creation() from here
a->on_creation(pos);
}
}
void World::tick (double dtime)
{
// dtime is always 0.01 (cf. server.cc)
move_actors (dtime);
handle_delayed_impulses (dtime);
tick_sound_dampings();
// Tell floors and items about new stones.
for (unsigned i=0; i<changed_stones.size(); ++i)
stone_change(changed_stones[i]);
changed_stones.clear();
m_mouseforce.tick (dtime);
for_each (forces.begin(), forces.end(),
bind2nd(mem_fun(&ForceField::tick), dtime));
GameTimer.tick(dtime);
lasers::RecalcLightNow(); // recalculate laser beams if necessary
}
/* ---------- Puzzle scrambling -------------------- */
void World::add_scramble(GridPos p, Direction dir)
{
scrambles.push_back(Scramble(p, dir, scrambleIntensity));
}
void World::scramble_puzzles()
{
while (!scrambles.empty()) {
list<Scramble>::iterator i = scrambles.begin();
list<Scramble>::iterator e = scrambles.end();
for (; i != e; ++i) {
Stone *puzz = GetStone(i->pos);
if (puzz && i->intensity) {
SendMessage(puzz, "scramble", Value(double(i->dir)));
--i->intensity;
}
else {
fprintf(stderr, "no stone found for scramble at %i/%i\n", i->pos.x, i->pos.y);
i->intensity = 0;
}
}
scrambles.remove_if(mem_fun_ref(&Scramble::expired));
}
}
//----------------------------------------------------------------------
// Physics simulation
//----------------------------------------------------------------------
/* -------------------- Force calculation -------------------- */
#ifndef M_PI
#define M_PI 3.1415926535
#endif
void World::add_mouseforce (Actor *a, Floor *floor, V2 &mforce)
{
if (a->get_controllers() != 0) {
V2 f = floor->process_mouseforce(a, m_mouseforce.get_force(a));
if (a->is_drunken()) {
// rotate mouse force by random angle
double maxangle = M_PI * 0.7;
double angle = DoubleRand (-maxangle, maxangle);
f = V2(f[0]*cos(angle) - f[1]*sin(angle),
f[0]*sin(angle) + f[1]*cos(angle));
}
mforce += f;
}
}
/*! Calculate the total force on an actor A at time TIME. The
actor's current position X and velocity V are also passed. [Note
that the position and velocity entries in ActorInfo will be updated
only after a *successful* time step, so they cannot be used
here.] */
V2 World::get_local_force (Actor *a)
{
V2 f;
if (a->is_on_floor()) {
const Field *field = GetField (a->get_gridpos());
if (Floor *floor = field->floor) {
// Constant force
m_flatforce.add_force(a, f);
// Mouse force
add_mouseforce (a, floor, f);
// Friction
double friction = floor->friction();
if (a->has_spikes())
friction += 7.0;
V2 v = a->get_vel();
double vv=length(v);
if (vv > 0) {
V2 frictionf = v * (server::FrictionFactor*friction);
frictionf /= vv;
frictionf *= pow(vv, 0.8);
f -= frictionf;
}
floor->add_force(a, f);
}
if (Item *item = field->item)
item->add_force(a, f);
}
return f;
}
/* Global forces are calculated less often than local ones, namely
only once every four time steps, cf. move_actors(). They are used
for forces that are more time consuming to calculate, i.e.,
actor-actor interactions and external force fields. */
V2 World::get_global_force (Actor *a)
{
V2 f;
// Electrostatic forces between actors.
if (double q = get_charge(a)) {
for (ActorList::iterator i=actorlist.begin();
i != actorlist.end(); ++i)
{
Actor *a2 = *i;
if (a2 == a) continue;
if (double q2 = get_charge(a2)) {
V2 distv = a->get_pos() - a2->get_pos();
if (double dist = distv.normalize())
f += server::ElectricForce * q * q2 / (dist) * distv;
}
}
}
// All other force fields.
for (ForceList::iterator i=forces.begin(); i != forces.end(); ++i)
(*i)->add_force(a, f);
return f;
}
/* -------------------- Collision handling -------------------- */
struct Ball {
ecl::V2 c; // center
double r; // radius
};
struct Oblong {
ecl::V2 c; // center
double w; // width
double h; // height
double erad; // edge radius
};
// static void contact_ball_oblong ()
// {
// }
/* Determine whether an actor is in contact with a stone at position
`p'. The result is returned in `c'. Three situations can occur:
1) There is no stone at `p'. In this case, `is_contact' is set to
false and nothing else is done.
2) The stone and the actor are in contact. In this case, `c' is
filled with the contact information and `is_contact'p is set to
true.
3) The stone and the actor are _not_ in contact. In this case, `c'
is filled is filled with information about the closest feature
on the stone and `is_contact' is set to false.
*/
void World::find_contact_with_stone (Actor *a, GridPos p, StoneContact &c)
{
c.is_contact = false;
const ActorInfo &ai = *a->get_actorinfo();
double r = ai.radius;
// Ball b (a->get_pos(), ai.radius);
// Oblong o (p.center(), 1, 1, 2.0 / 32);
V2 centerdist = a->get_pos() - p.center();
if (square (centerdist) > 1.0)
return;
Stone *stone = world::GetStone(p);
if (!stone)
return;
int x = p.x, y = p.y;
double ax = ai.pos[0];
double ay = ai.pos[1];
const double contact_e = 0.02;
const double erad = 2.0/32; // edge radius
// Closest feature == north or south face of the stone?
if (ax>x+erad && ax<x+1-erad) {
double dist = r+5;
// south
if (ay>y+1) {
c.contact_point = V2(ax, y+1);
c.normal = V2(0,+1);
dist = ay-(y+1);
}
// north
else if (ay<y) {
c.contact_point = V2(ax, y);
c.normal = V2(0,-1);
dist = y-ay;
}
c.is_contact = (dist-r < contact_e);
}
// Closest feature == west or east face of the stone?
else if (ay>y+erad && ay<y+1-erad) {
double dist=r+5;
if (ax>x+1) { // east
c.contact_point = V2(x+1,ay);
c.normal = V2(+1,0);
dist = ax-(x+1);
}
else if (ax<x) { // west
c.contact_point = V2(x,ay);
c.normal = V2(-1,0);
dist = x-ax;
}
c.is_contact = (dist-r < contact_e);
}
// Closest feature == any of the four corners
else {
int xcorner=(ax >= x+1-erad);
int ycorner=(ay >= y+1-erad);
double cx[2] = {erad, -erad};
V2 corner(x+xcorner+cx[xcorner], y+ycorner+cx[ycorner]);
V2 b=V2(ax,ay) - corner;
c.is_contact = (length(b)-r-erad < contact_e);
c.normal = normalize(b);
c.contact_point = corner + c.normal*erad;
}
if (c.is_contact) {
// treat this as a collision only if actor not inside the stone
// and velocity towards stone
if (ax >= x && ax < x+1 && ay >= y && ay < y+1)
c.is_collision = false;
else
c.is_collision = c.normal*ai.vel < 0;
c.ignore = false;
c.actor = a;
c.stonepos = p;
c.stoneid = stone->get_traits().id;
c.response = stone->collision_response(c);
c.sound = stone->collision_sound();
}
}
void World::find_stone_contacts (Actor *a, StoneContactList &cl)
{
ActorInfo &ai = *a->get_actorinfo();
ecl::Rect r(round_down<int>(ai.pos[0]-0.5), round_down<int>(ai.pos[1]-0.5), 1, 1);
static StoneContact contacts[2][2];
// Test for collisions with the nearby stones
int ncontacts = 0;
for (int i=r.w; i>=0; --i) {
for (int j=r.h; j>=0; --j) {
GridPos p (r.x + i, r.y + j);
find_contact_with_stone(a, p, contacts[i][j]);
ncontacts += contacts[i][j].is_contact;
}
}
if (ncontacts > 0) {
maybe_join_contacts (contacts[0][0], contacts[1][0]);
maybe_join_contacts (contacts[0][0], contacts[0][1]);
maybe_join_contacts (contacts[1][0], contacts[1][1]);
maybe_join_contacts (contacts[0][1], contacts[1][1]);
for (int i=0; i<=r.w; i++)
for (int j=0; j<=r.h; j++)
if (contacts[i][j].is_contact)
cl.push_back(contacts[i][j]);
}
}
/*! This function is called for every contact between an actor and a
stone. It handles: (1) informing stones about contacts, (2)
collision response, (3) updating the actor's contact list. Note
that a stone may kill itself during a call to actor_hit() or
actor_contact() so we must not refer to it after having called these
functions. (Incidentally, this is why StoneContact only contains a
'stonepos' and no 'stone' entry.) */
void World::handle_stone_contact (StoneContact &sc)
{
Actor *a = sc.actor;
ActorInfo &ai = *a->get_actorinfo();
double restitution = 1.0; //0.85;
if (server::NoCollisions && (sc.stoneid != st_borderstone))
return;
Contact contact (sc.contact_point, sc.normal);
if (sc.is_contact && sc.response == STONE_REBOUND) {
ai.new_contacts.push_back (contact);
}
if (sc.is_collision) {
if (!sc.ignore && sc.response == STONE_REBOUND) {
bool slow_collision = length (ai.vel) < 0.3;
if (!has_nearby_contact(ai.contacts, contact)) {
if (Stone *stone = world::GetStone(sc.stonepos)) {
CurrentCollisionActor = a;
if (slow_collision) stone->actor_touch(sc);
else stone->actor_hit(sc);
CurrentCollisionActor = 0;
if (!slow_collision) {
client::Msg_Sparkle (sc.contact_point);
double volume = std::max (0.25, length(ai.vel)/8);
volume = std::min (1.0, volume);
volume = getVolume(sc.sound.c_str(), a, volume);
sound::EmitSoundEvent (sc.sound.c_str(), sc.contact_point, volume);
}
}
}
double dt = ActorTimeStep;
ai.collforce -= (1 + restitution)*(ai.vel*sc.normal)*sc.normal / dt * ai.mass;
}
}
else if (sc.is_contact) {
if (Stone *stone = world::GetStone(sc.stonepos))
stone->actor_contact(sc.actor);
}
}
namespace {
struct ActorEntry {
double pos;
size_t idx;
ActorEntry () { pos = 0; idx = 0; }
ActorEntry (double pos_, size_t idx_) { pos = pos_; idx = idx_; }
bool operator < (const ActorEntry &x) const {
return pos < x.pos;
}
};
};
void World::handle_actor_contacts () {
size_t nactors = actorlist.size();
vector<ActorEntry> xlist (nactors);
for (size_t i=0; i<nactors; ++i) {
ActorInfo *ai = actorlist[i]->get_actorinfo();
xlist[i] = ActorEntry (ai->pos[0], i);
}
sort (xlist.begin(), xlist.end());
for (size_t i=0; i<nactors; ++i) {
size_t a1 = xlist[i].idx;
ActorInfo &ai1 = *actorlist[a1]->get_actorinfo();
double r1 = ai1.radius;
double x1 = ai1.pos[0];
for (size_t j=i+1; j<nactors; ++j) {
size_t a2 = xlist[j].idx;
if (xlist[j].pos - x1 < r1 + get_radius(actorlist[a2]))
handle_actor_contact (a1, a2);
else
break;
}
}
}
void World::handle_actor_contact (size_t i, size_t j)
{
Actor *actor1 = actorlist[i];
ActorInfo &a1 = *actor1->get_actorinfo();
Actor *actor2 = actorlist[j];
ActorInfo &a2 = *actor2->get_actorinfo();
if (a1.ignore_contacts || a2.ignore_contacts)
return;
V2 n = a1.pos - a2.pos; // normal to contact surface
double dist = n.normalize();
double overlap = a1.radius + a2.radius - dist;
if (overlap > 0 && !a2.grabbed) {
double relspeed = (a2.vel-a1.vel)*n;
if (relspeed < 0) // not moving towards each other
return;
actor1->on_collision (actor2);
actor2->on_collision (actor1);
bool reboundp = (actor1->is_movable() && actor2->is_movable() &&
(actor1->is_on_floor() == actor2->is_on_floor()));
if (reboundp) {
Contact contact (a2.pos + n*a2.radius, -n);
a2.new_contacts.push_back (contact);
contact.normal = n;
a1.new_contacts.push_back (contact);
double restitution = 1.0; //0.95;
double mu = a1.mass*a2.mass / (a1.mass + a2.mass); // reduced mass
V2 force = (restitution * 2 * mu * relspeed / ActorTimeStep) * n;
a1.collforce += force;
a2.collforce -= force;
if (!has_nearby_contact (a1.contacts, contact)) {
double volume = length (force) * ActorTimeStep;
volume = std::min(1.0, volume);
if (volume > 0.4) {
volume = getVolume("ballcollision", NULL, volume);
sound::EmitSoundEvent ("ballcollision", contact.pos, volume);
}
}
}
}
}
void World::handle_contacts (unsigned actoridx)
{
Actor *actor1 = actorlist[actoridx];
ActorInfo &a1 = *actor1->get_actorinfo();
if (a1.ignore_contacts) // used by the cannonball for example
return;
// Handle contacts with stones
StoneContactList cl;
find_stone_contacts(actor1, cl);
for (StoneContactList::iterator i=cl.begin(); i != cl.end(); ++i)
handle_stone_contact (*i);
}
/* -------------------- Actor Motion -------------------- */
void World::move_actors (double dtime)
{
const double dt = ActorTimeStep;
static double rest_time = 0;
rest_time += dtime;
size_t nactors = actorlist.size();
vector<V2> global_forces (nactors);
for (unsigned i=0; i<nactors; ++i) {
Actor *a = actorlist[i];
global_forces[i] = get_global_force (a);
}
while (rest_time > 0) {
for (unsigned i=0; i<nactors; ++i) {
Actor *a = actorlist[i];
ActorInfo &ai = *a->get_actorinfo();
// the "6" is a historical accident, don't change it!
ai.force = ai.forceacc * 6;
ai.force += global_forces[i];
ai.force += get_local_force (a);
ai.forceacc = V2();
ai.collforce = V2();
ai.last_pos = ai.pos;
ai.new_contacts.clear();
}
handle_actor_contacts();
for (unsigned i=0; i<nactors; ++i)
handle_contacts (i);
for (unsigned i=0; i<nactors; ++i) {
Actor *a = actorlist[i];
ActorInfo &ai = * a->get_actorinfo();
swap (ai.contacts, ai.new_contacts);
if (!a->can_move()) {
if (length(ai.force) > 30)
client::Msg_Sparkle (ai.pos);
ai.vel = V2();
}
else if (!a->is_dead() && a->is_movable() && !ai.grabbed) {
advance_actor(a, dt);
}
a->move (); // 'move' nevertheless, to pick up items etc
a->think (dt);
}
for_each (m_rubberbands.begin(), m_rubberbands.end(),
mem_fun(&RubberBand::apply_forces));
rest_time -= dt;
}
}
/* This function performs one step in the numerical integration of an
actor's equation of motion. TIME ist the current absolute time and
H the size of the integration step. */
void World::advance_actor (Actor *a, double dtime)
{
const double MAXVEL = 70;
ActorInfo &ai = *a->get_actorinfo();
V2 force = ai.force;
// If the actor is currently in contact with other objects, remove
// the force components in the direction of these objects.
for (unsigned i=0; i<ai.contacts.size(); ++i)
{
const V2 &normal = ai.contacts[i].normal;
double normal_component = normal * force;
if (normal_component < 0) {
force -= normal_component * normal;
}
}
force += ai.collforce;
ai.vel += dtime * force / ai.mass;
ai.pos += dtime * ai.vel;
// Limit maximum velocity
double q = length(ai.vel) / MAXVEL;
if (q > 1)
ai.vel /= q;
}
void World::handle_delayed_impulses (double dtime)
{
// Handle delayed impulses
ImpulseList::iterator i = delayed_impulses.begin(),
end = delayed_impulses.end();
while (i != end) {
// shall the impulse take effect now ?
if (i->tick(dtime)) {
i->mark_referenced(true);
if (Stone *st = GetStone(i->destination()))
i->send_impulse(st); // may delete stones and revoke delayed impuleses!
i = delayed_impulses.erase(i);
}
else
++i;
}
}
void World::revoke_delayed_impulses(const Stone *target) {
// Revokes delayed impulses to and from target
ImpulseList::iterator i = delayed_impulses.begin(),
end = delayed_impulses.end();
while (i != end) {
if (i->is_receiver(target) || i->is_sender(target)) {
if (i->is_referenced()) {
i->mark_obsolete();
++i;
} else {
i = delayed_impulses.erase(i);
}
} else {
++i;
}
}
}
void World::tick_sound_dampings ()
{
// See sound.hh and sound.cc for details.
static int counter = 0;
++counter;
if (counter > 9) {
counter = 0;
SoundDampingList::iterator i = level->sound_dampings.begin(),
end = level->sound_dampings.end();
int count = 0;
while (i != end) {
if(i->tick()) // return true if damping entry should be deleted
i = level->sound_dampings.erase(i);
else
++i;
}
}
}
void World::stone_change(GridPos p)
{
if (const Field *f = GetField (p)) {
Stone *st = f->stone;
if (st)
st->on_floor_change();
if (Item *it = f->item)
it->stone_change(st);
if (Floor *fl = f->floor)
fl->stone_change(st);
lasers::MaybeRecalcLight(p);
}
}
/* -------------------- Functions -------------------- */
void world::Resize (int w, int h)
{
level.reset (new World(w,h));
display::NewWorld(w, h);
}
void world::PrepareLevel ()
{
GameTimer.clear();
CurrentCollisionActor = 0;
Resize (20, 13);
}
bool world::InitWorld()
{
level->scramble_puzzles();
lasers::RecalcLight();
lasers::RecalcLightNow(); // recalculate laser beams if necessary
bool seen_player0 = false;
for (ActorList::iterator i=level->actorlist.begin();
i != level->actorlist.end(); ++i)
{
Actor *a = *i;
a->on_creation(a->get_actorinfo()->pos);
a->message ("init", Value());
int iplayer;
if (a->int_attrib("player", &iplayer)) {
player::AddActor(iplayer,a);
if (iplayer == 0) seen_player0 = true;
} else {
player::AddUnassignedActor(a);
}
}
level->changed_stones.clear();
if (!seen_player0)
throw XLevelLoading("Error: No player 0 defined!");
world::BroadcastMessage("init", Value(),
GridLayerBits(GRID_ITEMS_BIT | GRID_STONES_BIT | GRID_FLOOR_BIT));
server::InitMoveCounter();
STATUSBAR->show_move_counter (server::ShowMoves);
display::FocusReferencePoint();
level->preparing_level = false;
return true;
}
void world::SetMouseForce(V2 f)
{
level->m_mouseforce.add_force(f);
}
void world::NameObject(Object *obj, const std::string &name)
{
string old_name;
if (obj->string_attrib("name", &old_name)) {
obj->warning("name '%s' overwritten by '%s'",
old_name.c_str(), name.c_str());
UnnameObject(obj);
}
level->name_object (obj, name);
}
void world::UnnameObject(Object *obj)
{
level->unname(obj);
}
void world::TransferObjectName (Object *source, Object *target)
{
string name;
if (source->string_attrib("name", &name)) {
UnnameObject(source);
string targetName;
if (target->string_attrib("name", &targetName)) {
target->warning("name '%s' overwritten by '%s'",
targetName.c_str(), name.c_str());
UnnameObject(target);
}
NameObject(target, name);
}
}
Object * world::GetNamedObject (const std::string &name)
{
return level->get_named (name);
}
bool world::IsLevelBorder(GridPos p)
{
return level->is_border(p);
}
bool world::IsInsideLevel(GridPos p)
{
return level->contains(p);
}
/* -------------------- Force fields -------------------- */
void world::AddForceField(ForceField *ff)
{
level->forces.push_back(ff);
}
void world::RemoveForceField(ForceField *ff) {
level->remove (ff);
}
void world::SetConstantForce (V2 force) {
level->m_flatforce.set_force(force);
}
/* -------------------- Rubber bands -------------------- */
void world::AddRubberBand (Actor *a, Stone *st, const RubberBandData &d)
{
level->m_rubberbands.push_back(new RubberBand (a, st, d));
}
void world::AddRubberBand (Actor *a, Actor *a2, const RubberBandData &d)
{
RubberBandData rbd (d);
rbd.length = ecl::Max (d.length, get_radius(a) + get_radius(a2));
level->m_rubberbands.push_back(new RubberBand (a, a2, rbd));
}
bool world::KillRubberBands (Actor *a)
{
bool didKill = false;
for (unsigned i=0; i<level->m_rubberbands.size(); ) {
RubberBand &r = *level->m_rubberbands[i];
if (r.get_actor() == a || r.get_actor2() == a) {
delete &r;
level->m_rubberbands.erase(level->m_rubberbands.begin()+i);
didKill = true;
continue; // don't increment i
}
++i;
}
return didKill;
}
void world::KillRubberBand (Actor *a, Stone *st)
{
ASSERT(a, XLevelRuntime, "KillRubberBand: no actor attached");
for (unsigned i=0; i<level->m_rubberbands.size(); ) {
RubberBand &r = *level->m_rubberbands[i];
if (r.get_actor() == a && r.get_stone() != 0)
if (r.get_stone()==st || st==0) {
delete &r;
level->m_rubberbands.erase(level->m_rubberbands.begin()+i);
continue; // don't increment i
}
++i;
}
}
void world::KillRubberBand (Actor *a, Actor *a2)
{
ASSERT(a, XLevelRuntime, "KillRubberBand: no actor attached");
for (unsigned i=0; i<level->m_rubberbands.size(); ) {
RubberBand &r = *level->m_rubberbands[i];
if (r.get_actor() == a && r.get_actor2() != 0)
if (r.get_actor2()==a2 || a2==0) {
delete &r;
level->m_rubberbands.erase(level->m_rubberbands.begin()+i);
continue; // don't increment i
}
++i;
}
}
void world::KillRubberBands (Stone *st)
{
for (unsigned i=0; i<level->m_rubberbands.size(); ) {
RubberBand &r = *level->m_rubberbands[i];
if (r.get_stone() != 0 && r.get_stone()==st) {
delete &r;
level->m_rubberbands.erase(level->m_rubberbands.begin()+i);
continue; // don't increment i
}
++i;
}
}
void world::GiveRubberBands (Stone *st, vector<Rubber_Band_Info> &rubs) {
for (unsigned i=0; i<level->m_rubberbands.size(); ) {
RubberBand &r = *level->m_rubberbands[i];
if (r.get_stone() == st) {
Rubber_Band_Info rbi;
rbi.act = r.get_actor();
rbi.data = r.get_data();
rubs.push_back(rbi);
}
++i;
}
}
bool world::HasRubberBand (Actor *a, Stone *st)
{
for (unsigned i=0; i<level->m_rubberbands.size(); ++i) {
RubberBand &r = *level->m_rubberbands[i];
if (r.get_actor() == a && r.get_stone() == st)
return true;
}
return false;
}
/* -------------------- Signals -------------------- */
void world::AddSignal (const GridLoc &srcloc,
const GridLoc &dstloc,
const string &msg)
{
#if defined(VERBOSE_MESSAGES)
fprintf(stderr, "AddSignal src=%i/%i dest=%i/%i msg='%s'\n",
srcloc.pos.x, srcloc.pos.y, dstloc.pos.x, dstloc.pos.y, msg.c_str());
#endif // VERBOSE_MESSAGES
if (Object *src = GetObject(srcloc)) {
src->set_attrib("action", "signal");
level->m_signals.push_back (Signal (src, dstloc, msg));
}
else {
Log << "AddSignal: Invalid signal source\n";
}
}
bool world::HaveSignals (Object *src)
{
SignalList::const_iterator i=level->m_signals.begin(),
end = level->m_signals.end();
for (; i != end; ++i)
if (i->source == src)
return true;
return false;
}
bool world::EmitSignalByIndex (Object *src, int signalidx, int value)
{
return emit_by_index (level->m_signals, src, signalidx, value);
}
bool world::GetSignalTargetPos (Object *src, GridPos &pos, int signalidx)
{
SignalList::const_iterator i = level->m_signals.begin(),
end = level->m_signals.end();
int idx = 0;
for (; i != end; ++i) {
if (i->source == src) {
if (idx == signalidx) {
pos = i->destloc.pos;
return true;
}
idx += 1;
}
}
return false;
}
Value world::SendMessage(Object *o, const std::string &msg)
{
return SendMessage (o, Message (msg, Value()));
}
Value world::SendMessage(Object *o, const std::string &msg, const Value& value)
{
return SendMessage (o, Message (msg, value));
}
Value world::SendMessage (Object *o, const Message &m)
{
if (o) {
if (TrackMessages)
o->warning("will be sent message '%s' (with Value)", m.message.c_str());
return o->on_message(m);
}
else if (TrackMessages) {
fprintf(stderr, "Sending message '%s' to NULL-object\n", m.message.c_str());
return Value();
}
return Value();
}
void world::BroadcastMessage (const std::string& msg,
const Value& value,
GridLayerBits grids)
{
int width = level->w;
int height = level->h;
bool to_floors = (grids & GRID_FLOOR_BIT) != 0;
bool to_items = (grids & GRID_ITEMS_BIT) != 0;
bool to_stones = (grids & GRID_STONES_BIT) != 0;
for (int y = 0; y<height; ++y) {
for (int x = 0; x<width; ++x) {
GridPos p(x, y);
Field *f = level->get_field(p);
if (to_floors && f->floor) SendMessage (f->floor, msg, value);
if (to_items && f->item) SendMessage (f->item, msg, value);
if (to_stones && f->stone) SendMessage (f->stone, msg, value);
}
}
}
void world::PerformAction (Object *o, bool onoff)
{
string action = "idle";
string target;
o->string_attrib("action", &action);
o->string_attrib("target", &target);
#if defined(VERBOSE_MESSAGES)
o->warning("PerformAction action=%s target=%s", action.c_str(), target.c_str());
#endif // VERBOSE_MESSAGES
if (action == "callback") {
if (lua::CallFunc(lua::LevelState(), target.c_str(), Value(onoff), o) != 0) {
throw XLevelRuntime(string("callback '")+target+"' failed:\n"+lua::LastError(lua::LevelState()));
}
}
else if (action == "signal") {
emit_from (level->m_signals, o, onoff);
}
else if (Object *t = GetNamedObject(target)) {
if (GridObject *go = dynamic_cast<GridObject*>(o))
SendMessage (t, Message (action, Value(onoff), go->get_pos()));
else
SendMessage (t, Message (action, Value(onoff)));
}
else if (action != "idle") {
fprintf (stderr, "Unknown target '%s' for action '%s'\n",
target.c_str(), action.c_str());
}
}
namespace
{
void explosion (GridPos p, ItemID explosion_item)
{
if (Stone *stone = GetStone(p))
SendMessage(stone, "expl");
if (Item *item = GetItem(p)) {
if (has_flags(item, itf_indestructible))
SendMessage(item, "expl");
else
SetItem(p, explosion_item);
}
else
SetItem(p, explosion_item);
if (Floor *floor = GetFloor(p))
SendMessage(floor, "expl");
}
}
void world::SendExplosionEffect(GridPos center, ExplosionType type)
{
const int AFFECTED_FIELDS = 8;
for (int a = 0; a<AFFECTED_FIELDS; ++a) {
GridPos dest = get_neighbour (center, a+1);
Item *item = GetItem (dest);
Stone *stone = GetStone (dest);
Floor *floor = GetFloor (dest);
bool direct_neighbor = a<4;
switch (type) {
case EXPLOSION_DYNAMITE:
if (stone) SendMessage(stone, "ignite");
if (item) SendMessage(item, "ignite");
if (floor) SendMessage(floor, "ignite");
break;
case EXPLOSION_BLACKBOMB:
if (direct_neighbor) {
explosion (dest, it_explosion1);
} else {
// Note: should not ignite in non-enigma-mode!
if (stone) SendMessage(stone, "ignite");
if (item) SendMessage(item, "ignite");
if (floor) SendMessage(floor, "ignite");
}
break;
case EXPLOSION_WHITEBOMB:
// Note: at least in oxyd1 only direct neighbors
// explode, and the others not even ignite
explosion (dest, it_explosion3);
break;
case EXPLOSION_BOMBSTONE:
if (direct_neighbor) {
if (stone) SendMessage(stone, "bombstone");
if (item) SendMessage(item, "bombstone");
if (floor) SendMessage(floor, "bombstone");
}
break;
case EXPLOSION_SPITTER:
// TODO: spitter explosions
break;
}
}
}
Object *world::GetObject (const GridLoc &l)
{
switch (l.layer) {
case GRID_FLOOR: return GetFloor(l.pos);
case GRID_ITEMS: return GetItem(l.pos);
case GRID_STONES: return GetStone(l.pos);
default: return 0;
}
}
const Field *world::GetField (GridPos p)
{
return level->get_field(p);
}
/* -------------------- Floor manipulation -------------------- */
void world::KillFloor(GridPos p)
{
level->fl_layer.kill(p);
}
Floor *world::GetFloor(GridPos p)
{
return level->fl_layer.get(p);
}
void world::SetFloor(GridPos p, Floor* fl)
{
level->fl_layer.set(p,fl);
if (!level->preparing_level)
if (Stone *st = GetStone(p))
st->on_floor_change();
}
/* -------------------- Stone manipulation -------------------- */
Stone * world::GetStone(GridPos p) {
if (Field *f = level->get_field (p))
return f->stone;
else
return level->st_layer.get(p);
}
void world::KillStone(GridPos p) {
level->st_layer.kill(p);
level->changed_stones.push_back(p);
}
Stone * world::YieldStone(GridPos p) {
Stone *st = level->st_layer.yield(p);
level->changed_stones.push_back(p);
return st;
}
void world::SetStone(GridPos p, Stone* st) {
level->st_layer.set(p,st);
level->changed_stones.push_back(p);
}
void world::ReplaceStone (GridPos p, Stone* st) {
Stone *old = level->st_layer.get(p);
if (old) {
TransferObjectName(old, st);
level->st_layer.kill(p);
}
SetStone(p, st);
}
void world::MoveStone (GridPos oldPos, GridPos newPos) {
SetStone(newPos, YieldStone(oldPos));
}
void world::SetScrambleIntensity (int intensity) {
level->scrambleIntensity = intensity;
}
void world::AddScramble(GridPos p, Direction d) {
level->add_scramble(p, d);
}
/* -------------------- Item manipulation -------------------- */
void world::KillItem(GridPos p)
{
lasers::MaybeRecalcLight(p);
level->it_layer.kill(p);
}
Item *world::GetItem(GridPos p) {
return level->it_layer.get(p);
}
Item *world::YieldItem(GridPos p) {
lasers::MaybeRecalcLight(p);
return level->it_layer.yield(p);
}
void world::SetItem (GridPos p, Item* it)
{
lasers::MaybeRecalcLight(p);
level->it_layer.set(p,it);
}
void world::SetItem (GridPos p, ItemID id)
{
SetItem (p, MakeItem (id));
}
/* -------------------- Actor manipulation -------------------- */
void world::AddActor(double x, double y, Actor* a)
{
level->add_actor (a, V2(x, y));
}
void world::AddActor (Actor *a)
{
level->add_actor (a);
}
Actor * world::YieldActor(Actor *a)
{
ActorList::iterator i=find(level->actorlist.begin(), level->actorlist.end(), a);
if (i != level->actorlist.end()) {
level->actorlist.erase(i);
GrabActor(a);
return a;
}
return 0;
}
void world::KillActor (Actor *a) {
delete YieldActor (a);
}
void world::WarpActor(Actor *a, double newx, double newy, bool keep_velocity)
{
V2 newpos = V2(newx, newy);
ASSERT(IsInsideLevel(GridPos(newpos)), XLevelRuntime,
"WarpActor: Tried to warp actor out of level grid. (And hyperspace travel is not implemented yet, sorry.)");
if (!keep_velocity)
a->get_actorinfo()->vel = V2();
a->warp(newpos);
}
void world::FastRespawnActor(Actor *a, bool keep_velocity) {
a->find_respawnpos();
const V2& p = a->get_respawnpos();
WarpActor(a, p[0], p[1], keep_velocity);
}
void world::RespawnActor(Actor *a) {
a->find_respawnpos();
a->respawn();
}
Actor *FindActorByID (ActorID id)
{
ActorList::iterator i = level->actorlist.begin(),
end = level->actorlist.end();
for (; i != end; ++i) {
Actor *a = *i;
if (get_id(a) == id)
return a;
}
return 0;
}
unsigned world::CountActorsOfKind (ActorID id)
{
unsigned count = 0;
ActorList::iterator i = level->actorlist.begin(),
end = level->actorlist.end();
for (; i != end; ++i) {
Actor *a = *i;
if (get_id(a) == id)
++count;
}
return count;
}
Actor *world::FindOtherMarble(Actor *thisMarble)
{
if (!thisMarble)
return 0;
switch (get_id(thisMarble)) {
case ac_blackball: return FindActorByID (ac_whiteball);
case ac_whiteball: return FindActorByID (ac_blackball);
default:
return 0;
}
}
bool world::ExchangeMarbles(Actor *marble1) {
Actor *marble2 = FindOtherMarble(marble1);
if (marble2) {
ActorInfo *info1 = marble1->get_actorinfo();
ActorInfo *info2 = marble2->get_actorinfo();
swap(info1->pos, info2->pos);
swap(info1->oldpos, info2->oldpos);
return true;
}
return false;
}
void world::GrabActor(Actor *a)
{
a->get_actorinfo()->grabbed = true;
}
void world::ReleaseActor(Actor *a)
{
a->get_actorinfo()->grabbed = false;
}
bool world::GetActorsInRange (ecl::V2 center, double range,
vector<Actor*>& actors)
{
ActorList &al = level->actorlist;
for (ActorList::iterator i=al.begin(); i!=al.end(); ++i) {
Actor *a = *i;
if (length(a->get_pos() - center) < range)
actors.push_back(a);
}
return !actors.empty();
}
bool world::GetActorsInsideField (const GridPos& pos, vector<Actor*>& actors)
{
ActorList &al = level->actorlist;
for (ActorList::iterator i=al.begin(); i!=al.end(); ++i) {
Actor *a = *i;
if (a->get_gridpos() == pos)
actors.push_back(a);
}
return !actors.empty();
}
void world::ShatterActorsInsideField (const GridPos &p)
{
vector<Actor *> actors;
GetActorsInsideField (p, actors);
vector<Actor *>::iterator i=actors.begin(),
end = actors.end();
for (; i != end; ++i)
SendMessage(*i, "shatter");
}
/* -------------------- Functions -------------------- */
void world::addDelayedImpulse (const Impulse& impulse, double delay,
const Stone *estimated_receiver)
{
// @@@ FIXME: is a special handling necessary if several impulses hit same destination ?
level->delayed_impulses.push_back(DelayedImpulse(impulse, delay, estimated_receiver));
}
void world::revokeDelayedImpulses(const Stone *target) {
// Any stone may call this function on deletion.
// When the repository shuts down no world is existing thus check
// world first.
if (level.get() != NULL)
level->revoke_delayed_impulses(target);
}
float world::getVolume(const char *name, Object *obj, float def_volume)
{
// See sound.hh and sound.cc for details.
SoundDampingList::iterator i = level->sound_dampings.begin(),
end = level->sound_dampings.end();
while (i != end) {
if (i->is_equal(name, obj))
return i->get_volume(def_volume);
++i;
}
// No entry found for this object. Create a new one.
level->sound_dampings.push_back(sound::SoundDamping(name, obj));
return def_volume;
}
void world::Tick(double dtime) {
level->tick (dtime);
}
void world::TickFinished () {
for (unsigned i=0; i<level->actorlist.size(); ++i) {
level->actorlist[i]->move_screen();
}
//
for (unsigned i=0; i<level->m_rubberbands.size();++i)
level->m_rubberbands[i]->tick (0.0);
}
void world::Init()
{
InitActors();
lasers::Init();
InitItems();
stones::Init();
InitFloors();
}
void world::Shutdown()
{
level.reset();
Repos_Shutdown();
}
/* -------------------- Object repository -------------------- */
namespace
{
class ObjectRepos : public ecl::Nocopy {
public:
ObjectRepos();
~ObjectRepos();
void add_templ(Object *o);
void add_templ (const string &name, Object *o);
bool has_templ(const string &name);
Object *make(const string &name);
Object *get_template(const string &name);
void dump_info();
private:
typedef std::map<string, Object*> ObjectMap;
ObjectMap objmap; // repository of object templates
int stonecount, floorcount, itemcount;
};
}
ObjectRepos::ObjectRepos() {
stonecount = floorcount = itemcount = 0;
}
ObjectRepos::~ObjectRepos()
{
ecl::delete_map(objmap.begin(), objmap.end());
}
void ObjectRepos::add_templ (const string &kind, Object *o)
{
if (has_templ(kind))
enigma::Log << "add_templ: redefinition of object `" <<kind<< "'.\n";
else
objmap[kind] = o;
}
void ObjectRepos::add_templ (Object *o) {
string kind = o->get_kind();
if (has_templ(kind))
enigma::Log << "add_templ: redefinition of object `" <<kind<< "'.\n";
else
objmap[kind] = o;
}
bool ObjectRepos::has_templ(const string &name) {
return objmap.find(name) != objmap.end();
}
Object * ObjectRepos::make(const string &name) {
ObjectMap::iterator i=objmap.find(name);
if (i==objmap.end())
return 0;
else
return i->second->clone();
}
Object * ObjectRepos::get_template(const string &name) {
if (objmap.find (name) != objmap.end())
return objmap[name];
else
return 0;
}
/* Generate a list of all available objects and their attributes. */
void ObjectRepos::dump_info()
{
ObjectMap::iterator iter = objmap.begin();
for (; iter != objmap.end(); ++iter) {
cout << iter->first << "( ";
Object *obj = iter->second;
const Object::AttribMap &a = obj->get_attribs();
for (Object::AttribMap::const_iterator j=a.begin(); j!=a.end(); ++j)
{
if (j->first != "kind")
cout << j->first << " ";
}
cout << ")\n";
}
}
namespace
{
ObjectRepos *repos;
vector<Actor *> actor_repos(ac_COUNT);
vector<Stone *> stone_repos(st_COUNT);
vector<Item *> item_repos(it_COUNT);
}
void world::Register (const string &kind, Object *obj) {
if (!repos)
repos = new ObjectRepos;
if (kind.empty())
repos->add_templ(obj->get_kind(), obj);
else
repos->add_templ(kind, obj);
}
void world::Register (Object *obj) {
Register (obj->get_kind(), obj);
}
void world::Register (const string &kind, Floor *obj)
{
Object *o = obj;
Register(kind, o);
}
void world::Register (const string &kind, Stone *obj) {
Object *o = obj;
Register(kind, o);
}
void world::RegisterStone (Stone *stone)
{
Register(static_cast<Object*>(stone));
StoneID id = get_id(stone);
ASSERT (id != st_INVALID, XLevelRuntime,
"RegisterStone: trying to register with invalid ID");
stone_repos[id] = stone;
}
void world::RegisterActor (Actor *actor)
{
Register(static_cast<Object*>(actor));
ActorID id = get_id(actor);
ASSERT (id != ac_INVALID, XLevelRuntime,
"RegisterActor: trying to register with invalid ID");
actor_repos[id] = actor;
}
void world::Repos_Shutdown() {
delete repos;
}
Object * world::MakeObject(const char *kind) {
static Object *last_templ = 0;
static string last_kind;
if (last_kind != kind) {
last_kind = kind;
last_templ = repos->get_template(kind);
}
Object *o = 0;
if (last_templ)
o=last_templ->clone();
if (!o)
fprintf(stderr, "MakeObject: unknown object name `%s'\n",kind);
return o;
}
Object * world::GetObjectTemplate(const std::string &kind) {
if (!repos->has_templ(kind)) {
cerr << "GetObjectTemplate: unknown object name `" <<kind<< "'.\n";
return 0;
} else
return repos->get_template(kind);
}
Floor* world::MakeFloor(const char *kind) {
return dynamic_cast<Floor*>(MakeObject(kind));
}
Stone * world::MakeStone (const char *kind) {
return dynamic_cast<Stone*>(MakeObject(kind));
}
Actor * world::MakeActor (const char *kind) {
return dynamic_cast<Actor*>(MakeObject(kind));
}
Actor *world::MakeActor (ActorID id)
{
if (Actor *ac = actor_repos[id])
return ac->clone();
else
ASSERT(0, XLevelRuntime, "MakeActor: no actor for ID defined");
return 0;
}
Stone *world::MakeStone (StoneID id)
{
if (Stone *st = stone_repos[id])
return st->clone();
else
ASSERT(0, XLevelRuntime, "MakeStone: no stone for ID defined");
return 0;
}
void world::DisposeObject(Object *o) {
if (o != 0) {
UnnameObject(o);
o->dispose();
}
}
void world::DefineSimpleFloor(const std::string &kind, double friction,
double mousefactor, bool burnable,
const std::string &firetransform)
{
Register(new Floor(kind.c_str(), friction, mousefactor,
flf_default, burnable ? flft_burnable : flft_default,
firetransform.c_str(), ""));
}
void world::DumpObjectInfo() {
repos->dump_info();
}
/* ------------------- Item repository ------------------- */
void world::Register (const string &kind, Item *obj)
{
Object *o = obj;
world::Register(kind, o);
}
void world::RegisterItem (Item *item)
{
Register(static_cast<Object*>(item));
ItemID id = get_id(item);
ASSERT(id != it_INVALID, XLevelRuntime,
"RegisterItem: trying to register with invalid ID");
item_repos[id] = item;
}
Item *world::MakeItem (ItemID id)
{
if (Item *it = item_repos[id])
return it->clone();
else
ASSERT(0, XLevelRuntime, "MakeItem: no item for ID defined");
return 0;
}
Item * world::MakeItem(const char *kind) {
return dynamic_cast<Item*>(MakeObject(kind));
}