summaryrefslogtreecommitdiff
path: root/src/game-server/mapcomposite.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/game-server/mapcomposite.cpp')
-rw-r--r--src/game-server/mapcomposite.cpp470
1 files changed, 470 insertions, 0 deletions
diff --git a/src/game-server/mapcomposite.cpp b/src/game-server/mapcomposite.cpp
new file mode 100644
index 00000000..1591ad9f
--- /dev/null
+++ b/src/game-server/mapcomposite.cpp
@@ -0,0 +1,470 @@
+/*
+ * The Mana World Server
+ * Copyright 2006 The Mana World Development Team
+ *
+ * This file is part of The Mana World.
+ *
+ * The Mana World 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
+ * any later version.
+ *
+ * The Mana World 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 The Mana World; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * $Id$
+ */
+
+#include <algorithm>
+#include <cassert>
+
+#include "map.h"
+#include "game-server/mapcomposite.hpp"
+
+/* TODO: Implement overlapping map zones instead of strict partitioning.
+ Purpose: to decrease the number of zone changes, as overlapping allows for
+ hysteresis effect and prevents an object from changing zone each server
+ tick. It requires storing the zone in the object since it will not be
+ uniquely defined any longer. */
+
+/* Pixel-based width and height of the squares used in partitioning the map.
+ Squares should be big enough so that a moving object cannot cross several
+ ones in one world tick.
+ TODO: Tune for decreasing server load. The higher the value, the closer we
+ regress to quadratic behavior; the lower the value, the more we waste time
+ in dealing with zone changes. */
+static int const zoneDiam = 256;
+
+void MapZone::insert(Object *obj)
+{
+ int type = obj->getType();
+ switch (type)
+ {
+ case OBJECT_PLAYER:
+ {
+ if (nbPlayers != nbMovingObjects)
+ {
+ if (nbMovingObjects != objects.size())
+ {
+ objects.push_back(objects[nbMovingObjects]);
+ objects[nbMovingObjects] = objects[nbPlayers];
+ }
+ else
+ {
+ objects.push_back(objects[nbPlayers]);
+ }
+ objects[nbPlayers] = obj;
+ ++nbPlayers;
+ ++nbMovingObjects;
+ break;
+ }
+ ++nbPlayers;
+ } // no break!
+ case OBJECT_MONSTER:
+ case OBJECT_NPC:
+ {
+ if (nbMovingObjects != objects.size())
+ {
+ objects.push_back(objects[nbMovingObjects]);
+ objects[nbMovingObjects] = obj;
+ ++nbMovingObjects;
+ break;
+ }
+ ++nbMovingObjects;
+ } // no break!
+ default:
+ {
+ objects.push_back(obj);
+ }
+ }
+}
+
+void MapZone::remove(Object *obj)
+{
+ std::vector< Object * >::iterator i_beg = objects.begin(), i, i_end;
+ int type = obj->getType();
+ switch (type)
+ {
+ case OBJECT_PLAYER:
+ {
+ i = i_beg;
+ i_end = objects.begin() + nbPlayers;
+ } break;
+ case OBJECT_MONSTER:
+ case OBJECT_NPC:
+ {
+ i = objects.begin() + nbPlayers;
+ i_end = objects.begin() + nbMovingObjects;
+ } break;
+ default:
+ {
+ i = objects.begin() + nbMovingObjects;
+ i_end = objects.end();
+ }
+ }
+ i = std::find(i, i_end, obj);
+ assert(i != i_end);
+ unsigned pos = i - i_beg;
+ if (pos < nbPlayers)
+ {
+ objects[pos] = objects[nbPlayers - 1];
+ pos = nbPlayers - 1;
+ --nbPlayers;
+ }
+ if (pos < nbMovingObjects)
+ {
+ objects[pos] = objects[nbMovingObjects - 1];
+ pos = nbMovingObjects - 1;
+ --nbMovingObjects;
+ }
+ objects[pos] = objects[objects.size() - 1];
+ objects.pop_back();
+}
+
+static void addZone(MapRegion &r, unsigned z)
+{
+ MapRegion::iterator i_end = r.end(),
+ i = std::lower_bound(r.begin(), i_end, z);
+ if (i == i_end || *i != z)
+ {
+ r.insert(i, z);
+ }
+}
+
+ZoneIterator::ZoneIterator(MapRegion const &r, MapComposite const *m)
+ : region(r), pos(0), map(m)
+{
+ current = &map->zones[r.empty() ? 0 : r[0]];
+}
+
+void ZoneIterator::operator++()
+{
+ current = NULL;
+ if (!region.empty())
+ {
+ if (++pos != region.size())
+ {
+ current = &map->zones[region[pos]];
+ }
+ }
+ else
+ {
+ if (++pos != (unsigned)map->mapWidth * map->mapHeight)
+ {
+ current = &map->zones[pos];
+ }
+ }
+}
+
+PlayerIterator::PlayerIterator(ZoneIterator const &it)
+ : iterator(it), pos(0)
+{
+ while (iterator && (*iterator)->nbPlayers == 0) ++iterator;
+ if (iterator)
+ {
+ current = static_cast< Player * >((*iterator)->objects[pos]);
+ }
+}
+
+void PlayerIterator::operator++()
+{
+ if (++pos == (*iterator)->nbPlayers)
+ {
+ do ++iterator; while (iterator && (*iterator)->nbPlayers == 0);
+ pos = 0;
+ }
+ if (iterator)
+ {
+ current = static_cast< Player * >((*iterator)->objects[pos]);
+ }
+}
+
+MovingObjectIterator::MovingObjectIterator(ZoneIterator const &it)
+ : iterator(it), pos(0)
+{
+ while (iterator && (*iterator)->nbMovingObjects == 0) ++iterator;
+ if (iterator)
+ {
+ current = static_cast< MovingObject * >((*iterator)->objects[pos]);
+ }
+}
+
+void MovingObjectIterator::operator++()
+{
+ if (++pos == (*iterator)->nbMovingObjects)
+ {
+ do ++iterator; while (iterator && (*iterator)->nbMovingObjects == 0);
+ pos = 0;
+ }
+ if (iterator)
+ {
+ current = static_cast< MovingObject * >((*iterator)->objects[pos]);
+ }
+}
+
+ObjectIterator::ObjectIterator(ZoneIterator const &it)
+ : iterator(it), pos(0)
+{
+ while (iterator && (*iterator)->objects.empty()) ++iterator;
+ if (iterator)
+ {
+ current = (*iterator)->objects[pos];
+ }
+}
+
+void ObjectIterator::operator++()
+{
+ if (++pos == (*iterator)->objects.size())
+ {
+ do ++iterator; while (iterator && (*iterator)->objects.empty());
+ pos = 0;
+ }
+ if (iterator)
+ {
+ current = (*iterator)->objects[pos];
+ }
+}
+
+ObjectBucket::ObjectBucket()
+ : free(256), next_object(0)
+{
+ for (unsigned i = 0; i < 256 / int_bitsize; ++i)
+ {
+ bitmap[i] = ~0;
+ }
+}
+
+int ObjectBucket::allocate()
+{
+ if (!free)
+ {
+ return -1;
+ }
+
+ if (bitmap[next_object / int_bitsize] & (1 << (next_object % int_bitsize)))
+ {
+ bitmap[next_object / int_bitsize] &= ~(1 << (next_object % int_bitsize));
+ int i = next_object;
+ next_object = (i + 1) & 255;
+ --free;
+ return i;
+ }
+
+ for (unsigned i = 0; i < 256 / int_bitsize; ++i)
+ {
+ int k = (i + next_object / int_bitsize) & 255;
+ if (unsigned b = bitmap[k])
+ {
+ int j = 0;
+ while (!(b & 1))
+ {
+ b >>= 1;
+ ++j;
+ }
+ bitmap[k] &= ~(1 << j);
+ j += k * int_bitsize;
+ next_object = (j + 1) & 255;
+ --free;
+ return j;
+ }
+ }
+ return -1;
+}
+
+void ObjectBucket::deallocate(int i)
+{
+ assert(!(bitmap[i / int_bitsize] & (1 << (i % int_bitsize))));
+ bitmap[i / int_bitsize] |= 1 << (i % int_bitsize);
+ ++free;
+}
+
+MapComposite::MapComposite(Map *m)
+ : map(m), last_bucket(0)
+{
+ buckets[0] = new ObjectBucket;
+ for (int i = 1; i < 256; ++i)
+ {
+ buckets[i] = NULL;
+ }
+ mapWidth = (map->getWidth() * 32 + zoneDiam - 1) / zoneDiam;
+ mapHeight = (map->getHeight() * 32 + zoneDiam - 1) / zoneDiam;
+ zones = new MapZone[mapWidth * mapHeight];
+}
+
+MapComposite::~MapComposite()
+{
+ for (int i = 0; i < 256; ++i)
+ {
+ delete buckets[i];
+ }
+ delete[] zones;
+ delete map;
+}
+
+bool MapComposite::allocate(MovingObject *obj)
+{
+ // First, try allocating from the last used bucket.
+ ObjectBucket *b = buckets[last_bucket];
+ int i = b->allocate();
+ if (i >= 0)
+ {
+ b->objects[i] = obj;
+ obj->setPublicID(last_bucket * 256 + i);
+ return true;
+ }
+
+ /* If the last used bucket is already full, scan all the buckets for an
+ empty place. If none is available, create a new bucket. */
+ for (i = 0; i < 256; ++i)
+ {
+ b = buckets[i];
+ if (!b)
+ {
+ b = new ObjectBucket;
+ buckets[i] = b;
+ }
+ int j = b->allocate();
+ if (j >= 0)
+ {
+ last_bucket = i;
+ b->objects[j] = obj;
+ obj->setPublicID(last_bucket * 256 + j);
+ return true;
+ }
+ }
+
+ // All the IDs are currently used, fail.
+ return false;
+}
+
+void MapComposite::deallocate(MovingObject *obj)
+{
+ unsigned short id = obj->getPublicID();
+ buckets[id / 256]->deallocate(id % 256);
+}
+
+void MapComposite::fillRegion(MapRegion &r, Point const &p, int radius) const
+{
+ int ax = p.x > radius ? (p.x - radius) / zoneDiam : 0,
+ ay = p.y > radius ? (p.y - radius) / zoneDiam : 0,
+ bx = std::max((p.x + radius) / zoneDiam, mapWidth - 1),
+ by = std::max((p.y + radius) / zoneDiam, mapHeight - 1);
+ for (int y = ay; y <= by; ++y)
+ {
+ for (int x = ax; x <= bx; ++x)
+ {
+ addZone(r, x + y * mapWidth);
+ }
+ }
+}
+
+ZoneIterator MapComposite::getAroundObjectIterator(Object *obj, int radius) const
+{
+ MapRegion r;
+ fillRegion(r, obj->getPosition(), radius);
+ return ZoneIterator(r, this);
+}
+
+ZoneIterator MapComposite::getAroundPlayerIterator(Player *obj, int radius) const
+{
+ MapRegion r1;
+ fillRegion(r1, obj->getOldPosition(), radius);
+ MapRegion r2 = r1;
+ for (MapRegion::iterator i = r1.begin(), i_end = r1.end(); i != i_end; ++i)
+ {
+ /* Fills region with destinations taken around the old position.
+ This is necessary to detect two moving objects changing zones at the
+ same time and at the border, and going in opposite directions (or
+ more simply to detect teleportations, if any). */
+ MapRegion &r4 = zones[*i].destinations;
+ if (!r4.empty())
+ {
+ MapRegion r3;
+ r3.reserve(r2.size() + r4.size());
+ std::set_union(r2.begin(), r2.end(), r4.begin(), r4.end(),
+ std::back_insert_iterator< MapRegion >(r3));
+ r2.swap(r3);
+ }
+ }
+ fillRegion(r2, obj->getPosition(), radius);
+ return ZoneIterator(r2, this);
+}
+
+bool MapComposite::insert(ObjectPtr obj)
+{
+ Point const &pos = obj->getPosition();
+ zones[(pos.x / zoneDiam) + (pos.y / zoneDiam) * mapWidth].insert(obj.get());
+
+ int type = obj->getType();
+ if (type == OBJECT_MONSTER || type == OBJECT_PLAYER || type == OBJECT_NPC)
+ {
+ if (!allocate(static_cast< MovingObject * >(obj.get())))
+ {
+ return false;
+ }
+ }
+
+ objects.push_back(obj);
+ return true;
+}
+
+void MapComposite::remove(ObjectPtr obj)
+{
+ Point const &pos = obj->getPosition();
+ zones[(pos.x / zoneDiam) + (pos.y / zoneDiam) * mapWidth].remove(obj.get());
+
+ int type = obj->getType();
+ if (type == OBJECT_MONSTER || type == OBJECT_PLAYER || type == OBJECT_NPC)
+ {
+ deallocate(static_cast< MovingObject * >(obj.get()));
+ }
+
+ for (Objects::iterator o = objects.begin(),
+ o_end = objects.end(); o != o_end; ++o)
+ {
+ if (o->get() == obj.get())
+ {
+ *o = *(o_end - 1);
+ objects.pop_back();
+ return;
+ }
+ }
+ assert(false);
+}
+
+void MapComposite::update()
+{
+ for (int i = 0; i < mapHeight * mapWidth; ++i)
+ {
+ zones[i].destinations.clear();
+ }
+
+ // Cannot use a WholeMap iterator as objects will change zones under its feet.
+ for (Objects::iterator o = objects.begin(),
+ o_end = objects.end(); o != o_end; ++o)
+ {
+ int type = (*o)->getType();
+ if (type != OBJECT_PLAYER && type != OBJECT_MONSTER && type != OBJECT_NPC)
+ {
+ continue;
+ }
+ MovingObject *obj = static_cast< MovingObject * >(o->get());
+
+ Point const &pos1 = obj->getOldPosition(),
+ &pos2 = obj->getPosition();
+ int src = (pos1.x / zoneDiam) + (pos1.y / zoneDiam) * mapWidth,
+ dst = (pos2.x / zoneDiam) + (pos2.y / zoneDiam) * mapWidth;
+ if (src != dst)
+ {
+ addZone(zones[src].destinations, dst);
+ zones[src].remove(obj);
+ zones[dst].insert(obj);
+ }
+ }
+}