/* * 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 #include #include #include #include // 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; isource; 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(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 targets; // for (unsigned i=0; i= 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 level; } enigma::Timer world::GameTimer; bool world::TrackMessages; Actor *world::CurrentCollisionActor = 0; /* -------------------- Layer implementation -------------------- */ template T *Layer::get(GridPos p) { if (Field *f=level->get_field(p)) return raw_get(*f); else return defaultval; } template T *Layer::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 void Layer::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::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::iterator i = scrambles.begin(); list::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 && axy+1) { c.contact_point = V2(ax, y+1); c.normal = V2(0,+1); dist = ay-(y+1); } // north else if (ayy+erad && ayx+1) { // east c.contact_point = V2(x+1,ay); c.normal = V2(+1,0); dist = ax-(x+1); } else if (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(ai.pos[0]-0.5), round_down(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 xlist (nactors); for (size_t i=0; iget_actorinfo(); xlist[i] = ActorEntry (ai->pos[0], i); } sort (xlist.begin(), xlist.end()); for (size_t i=0; iget_actorinfo(); double r1 = ai1.radius; double x1 = ai1.pos[0]; for (size_t j=i+1; jget_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 global_forces (nactors); for (unsigned i=0; i 0) { for (unsigned i=0; iget_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; iget_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 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; im_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; im_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; im_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; im_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 &rubs) { for (unsigned i=0; im_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; im_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; yget_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(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; aget_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& 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& 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 actors; GetActorsInsideField (p, actors); vector::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; iactorlist.size(); ++i) { level->actorlist[i]->move_screen(); } // for (unsigned i=0; im_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 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 `" <get_kind(); if (has_templ(kind)) enigma::Log << "add_templ: redefinition of object `" <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_repos(ac_COUNT); vector stone_repos(st_COUNT); vector 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(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(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 `" <get_template(kind); } Floor* world::MakeFloor(const char *kind) { return dynamic_cast(MakeObject(kind)); } Stone * world::MakeStone (const char *kind) { return dynamic_cast(MakeObject(kind)); } Actor * world::MakeActor (const char *kind) { return dynamic_cast(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(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(MakeObject(kind)); }