--- deliantra/server/common/object.C 2010/04/28 11:19:09 1.336
+++ deliantra/server/common/object.C 2018/11/17 23:33:17 1.365
@@ -1,24 +1,24 @@
/*
* This file is part of Deliantra, the Roguelike Realtime MMORPG.
- *
- * Copyright (©) 2005,2006,2007,2008,2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
+ *
+ * Copyright (©) 2005,2006,2007,2008,2009,2010,2011,2012,2013,2014,2015,2016 Marc Alexander Lehmann / Robin Redeker / the Deliantra team
* Copyright (©) 2001 Mark Wedel & Crossfire Development Team
* Copyright (©) 1992 Frank Tore Johansen
- *
+ *
* Deliantra is free software: you can redistribute it and/or modify it under
* the terms of the Affero GNU General Public License as published by the
* Free Software Foundation, either version 3 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 Affero GNU General Public License
* and the GNU General Public License along with this program. If not, see
* .
- *
+ *
* The authors can be reached via e-mail to
*/
@@ -47,13 +47,13 @@
//+GPL
-short freearr_x[SIZEOFFREE] = {
+int freearr_x[SIZEOFFREE] = {
0,
0, 1, 1, 1, 0, -1, -1, -1,
0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2, -2, -2, -2, -2, -1,
0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3, -3, -3, -3, -3, -2, -1
};
-short freearr_y[SIZEOFFREE] = {
+int freearr_y[SIZEOFFREE] = {
0,
-1, -1, 0, 1, 1, 1, 0, -1,
-2, -2, -2, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2, -2,
@@ -73,6 +73,25 @@
49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49
};
+const char *wall_suffix[16] = {
+ "0",
+ "1_3",
+ "1_4",
+ "2_1_2",
+ "1_2",
+ "2_2_4",
+ "2_2_1",
+ "3_1",
+ "1_1",
+ "2_2_3",
+ "2_2_2",
+ "3_3",
+ "2_1_1",
+ "3_4",
+ "3_2",
+ "4"
+};
+
static void
write_uuid (uval64 skip, bool sync)
{
@@ -216,14 +235,14 @@
static bool
compare_ob_value_lists_one (const object *wants, const object *has)
{
- /* n-squared behaviour (see kv_get), but I'm hoping both
+ /* n-squared behaviour (see kv.get), but I'm hoping both
* objects with lists are rare, and lists stay short. If not, use a
- * different structure or at least keep the lists sorted...
+ * different structure or at least keep the lists sorted...
*/
/* For each field in wants, */
- for (key_value *kv = wants->key_values; kv; kv = kv->next)
- if (has->kv_get (kv->key) != kv->value)
+ for (key_value *kv = wants->kv.first; kv; kv = kv->next)
+ if (has->kv.get (kv->key) != kv->value)
return false;
/* If we get here, every field in wants has a matching field in has. */
@@ -257,9 +276,10 @@
{
/* A couple quick sanity checks */
if (ob1 == ob2
- || ob1->type != ob2->type
- || ob1->value != ob2->value
- || ob1->name != ob2->name
+ || ob1->type != ob2->type
+ || ob1->value != ob2->value
+ || ob1->name != ob2->name
+ || ob1->custom_name != ob2->custom_name
|| fabs (ob1->speed - ob2->speed) >= MIN_ACTIVE_SPEED)
return 0;
@@ -333,15 +353,15 @@
*/
}
- /* Don't merge objects that are applied. With the new 'body' code,
+ /* Don't merge objects that are applied. With the new 'body' code,
* it is possible for most any character to have more than one of
* some items equipped, and we don't want those to merge.
*/
if (ob1->flag [FLAG_APPLIED] || ob2->flag [FLAG_APPLIED])
return 0;
- /* Note sure why the following is the case - either the object has to
- * be animated or have a very low speed. Is this an attempted monster
+ /* Not sure why the following is the case - either the object has to
+ * be animated or have a very low speed. Is this an attempted monster
* check?
*/
if (!ob1->flag [FLAG_ANIMATE] && ob1->has_active_speed ())
@@ -355,10 +375,10 @@
break;
}
- if (ob1->key_values || ob2->key_values)
+ if (!ob1->kv.empty () || !ob2->kv.empty ())
{
/* At least one of these has key_values. */
- if ((!ob1->key_values) != (!ob2->key_values))
+ if (ob1->kv.empty () != ob2->kv.empty ())
return 0; /* One has fields, but the other one doesn't. */
if (!compare_ob_value_lists (ob1, ob2))
@@ -428,32 +448,31 @@
}
// adjust weight per container type ("of holding")
-static sint32
-weight_adjust_for (object *op, sint32 weight)
+static weight_t
+weight_adjust_for (object *op, weight_t weight)
{
- return op->type == CONTAINER
- ? lerp (weight, 0, 100, 0, 100 - op->stats.Str)
- : weight;
+ if (op->type == CONTAINER)
+ weight -= weight * op->stats.Str / 100;
+
+ return weight;
}
/*
- * adjust_weight(object, weight) adds the specified weight to an object,
+ * subtracts, then adds, the specified weight to an object,
* and also updates how much the environment(s) is/are carrying.
*/
static void
-adjust_weight (object *op, sint32 weight)
+adjust_weight (object *op, weight_t sub, weight_t add)
{
while (op)
{
- // adjust by actual difference to account for rounding errors
- // i.e. (w2 - w1) / f != w2 / f - w1 / f and the latter is correct
- weight = weight_adjust_for (op, op->carrying + weight)
- - weight_adjust_for (op, op->carrying);
-
- if (!weight)
- return;
-
- op->carrying += weight;
+ weight_t carrying = (weight_t)op->carrying
+ - weight_adjust_for (op, sub)
+ + weight_adjust_for (op, add);
+
+ sub = op->carrying;
+ op->carrying = carrying;
+ add = op->carrying;
if (object *pl = op->visible_to ())
if (pl != op) // player is handled lazily
@@ -471,21 +490,18 @@
void
object::update_weight ()
{
- sint32 sum = 0;
+ weight_t sum = 0;
for (object *op = inv; op; op = op->below)
{
- if (op->inv)
- op->update_weight ();
+ op->update_weight ();
- sum += op->total_weight ();
+ sum += weight_adjust_for (this, op->total_weight ());
}
- sum = weight_adjust_for (this, sum);
-
if (sum != carrying)
{
- if (carrying != sum)//D
+ if (carrying != sum && carrying)//D
LOG (llevDebug, "updating carrying got %ld, expected %ld (%s)\n",
(long long)sum, (long long)carrying, debug_desc ());
@@ -586,23 +602,6 @@
this->owner = owner;
}
-/* Zero the key_values on op, decrementing the shared-string
- * refcounts and freeing the links.
- */
-static void
-free_key_values (object *op)
-{
- for (key_value *i = op->key_values; i; )
- {
- key_value *next = i->next;
- delete i;
-
- i = next;
- }
-
- op->key_values = 0;
-}
-
/*
* copy_to first frees everything allocated by the dst object,
* and then copies the contents of itself into the second
@@ -616,36 +615,11 @@
{
dst->remove ();
*(object_copy *)dst = *this;
- dst->flag [FLAG_REMOVED] = true;
-
- /* Copy over key_values, if any. */
- if (key_values)
- {
- key_value *tail = 0;
- dst->key_values = 0;
-
- for (key_value *i = key_values; i; i = i->next)
- {
- key_value *new_link = new key_value;
- new_link->next = 0;
- new_link->key = i->key;
- new_link->value = i->value;
-
- /* Try and be clever here, too. */
- if (!dst->key_values)
- {
- dst->key_values = new_link;
- tail = new_link;
- }
- else
- {
- tail->next = new_link;
- tail = new_link;
- }
- }
- }
+ // maybe move to object_copy?
+ dst->kv = kv;
+ dst->flag [FLAG_REMOVED] = true;
dst->activate ();
}
@@ -655,7 +629,7 @@
if (!uuid.seq) // HACK
uuid = UUID::gen ();
- // TODO: unclean state changes, should nt be done in copy_to AND instantiate
+ // TODO: unclean state changes, should not be done in copy_to AND instantiate
if (flag [FLAG_RANDOM_SPEED] && speed)
speed_left = - speed - rndm (); // TODO animation
else
@@ -664,10 +638,10 @@
/* copy the body_info to the body_used - this is only really
* need for monsters, but doesn't hurt to do it for everything.
* by doing so, when a monster is created, it has good starting
- * values for the body_used info, so when items are created
+ * values for the body_used info, so when items are created
* for it, they can be properly equipped.
*/
- for (int i = NUM_BODY_LOCATIONS; i--; )
+ for (int i = 0; i < NUM_BODY_LOCATIONS; ++i)
slot[i].used = slot[i].info;
attachable::instantiate ();
@@ -766,7 +740,7 @@
mapspace &m = op->ms ();
if (!(m.flags_ & P_UPTODATE))
- /* nop */;
+ m.update_up (); // nothing to do except copy up
else if (action == UP_OBJ_INSERT)
{
#if 0
@@ -781,7 +755,7 @@
|| (m.move_off | op->move_off ) != m.move_off
|| (m.move_slow | op->move_slow) != m.move_slow
/* This isn't perfect, but I don't expect a lot of objects to
- * have move_allow right now.
+ * have move_allow right now.
*/
|| ((m.move_block | op->move_block) & ~op->move_allow) != m.move_block
m.invalidate ();
@@ -797,7 +771,7 @@
else if (action == UP_OBJ_CHANGE || action == UP_OBJ_REMOVE)
m.invalidate ();
else if (action == UP_OBJ_FACE)
- /* Nothing to do for that case */ ;
+ m.update_up (); // nothing to do for that case, except copy up
else
LOG (llevError, "update_object called with invalid action: %d\n", action);
@@ -818,7 +792,7 @@
{
unlink ();
- free_key_values (this);
+ kv.clear ();
}
void object::link ()
@@ -871,7 +845,7 @@
/* This function removes object 'op' from the list of active
* objects.
- * This should only be used for style maps or other such
+ * This should only be used for style maps or other such
* reference maps where you don't want an object that isn't
* in play chewing up cpu time getting processed.
* The reverse of this is to call update_ob_speed, which
@@ -927,7 +901,7 @@
*/
if (!drop_to_ground
|| !map
- || map->in_memory != MAP_ACTIVE
+ || !map->linkable ()
|| map->no_drop
|| ms ().move_block == MOVE_ALL)
{
@@ -1034,25 +1008,6 @@
++free_count;
}
-static struct freed_map : maptile
-{
- freed_map ()
- : maptile (3, 3)
- {
- path = "";
- name = "/internal/freed_objects_map";
- no_drop = 1;
- no_reset = 1;
-
- in_memory = MAP_ACTIVE;
- }
-
- ~freed_map ()
- {
- destroy ();
- }
-} freed_map; // freed objects are moved here to avoid crashes
-
void
object::do_destroy ()
{
@@ -1135,7 +1090,7 @@
if (more)
more->remove ();
- /*
+ /*
* In this case, the object to be removed is in someones
* inventory.
*/
@@ -1146,7 +1101,7 @@
esrv_del_item (pl->contr, count);
flag [FLAG_REMOVED] = true; // hack around the issue of visible_to checking flag_removed
- adjust_weight (env, -total_weight ());
+ adjust_weight (env, total_weight (), 0);
object *pl = in_player ();
@@ -1170,14 +1125,14 @@
{
if (expect_false (pl->contr->combat_ob == this))
{
- pl->apply (pl->contr->combat_ob, AP_UNAPPLY);
+ pl->apply (pl->contr->combat_ob, AP_UNAPPLY | AP_IGNORE_CURSE);
pl->contr->combat_ob = 0;
if (pl->contr->ranged_ob) pl->apply (pl->contr->ranged_ob);
}
if (expect_false (pl->contr->ranged_ob == this))
{
- pl->apply (pl->contr->ranged_ob, AP_UNAPPLY);
+ pl->apply (pl->contr->ranged_ob, AP_UNAPPLY | AP_IGNORE_CURSE);
pl->contr->ranged_ob = 0;
if (pl->contr->combat_ob) pl->apply (pl->contr->combat_ob);
}
@@ -1228,9 +1183,6 @@
ms.invalidate ();
- if (map->in_memory == MAP_SAVING)
- return;
-
int check_walk_off = !flag [FLAG_NO_APPLY];
if (object *pl = ms.player ())
@@ -1374,7 +1326,7 @@
if (m == &freed_map)//D TODO: remove soon
{//D
- LOG (llevError | logBacktrace, "tries to insret object on freed objects map: %s", op->debug_desc ());//D
+ LOG (llevError | logBacktrace, "tries to insert object on freed objects map: %s", op->debug_desc ());//D
}//D
/* Ideally, the caller figures this out. However, it complicates a lot
@@ -1453,7 +1405,7 @@
* flag contains INS_ABOVE_FLOOR_ONLY, once we find the last
* floor, we want to insert above that and no further.
* Also, if there are spell objects on this space, we stop processing
- * once we get to them. This reduces the need to traverse over all of
+ * once we get to them. This reduces the need to traverse over all of
* them when adding another one - this saves quite a bit of cpu time
* when lots of spells are cast in one area. Currently, it is presumed
* that flying non pickable objects are spell objects.
@@ -1589,7 +1541,7 @@
}
/* this function inserts an object in the map, but if it
- * finds an object of its own type, it'll remove that one first.
+ * finds an object of its own type, it'll remove that one first.
* op is the object to insert it under: supplies x and the map.
*/
void
@@ -1631,9 +1583,9 @@
&& ms.volume () < m->max_volume))
return true;
- if (originator && originator->is_player ())
- originator->contr->failmsgf (
- "No matter how hard you try, you just cannot put the %s here H",
+ if (originator)
+ originator->failmsgf (
+ "No matter how hard you try, you just cannot put the %s here! H",
query_name ()
);
@@ -1657,12 +1609,15 @@
if (nrof > nr)
{
+ weight_t oweight = total_weight ();
+
nrof -= nr;
- adjust_weight (env, -weight * max (1, nr)); // carrying == 0
if (object *pl = visible_to ())
esrv_update_item (UPD_NROF, pl, this);
+ adjust_weight (env, oweight, total_weight ());
+
return true;
}
else
@@ -1725,7 +1680,7 @@
* This function inserts the object op in the linked list
* inside the object environment.
*
- * The function returns now pointer to inserted item, and return value can
+ * The function returns now pointer to inserted item, and return value can
* be != op, if items are merged. -Tero
*/
object *
@@ -1746,13 +1701,17 @@
if (object::can_merge (tmp, op))
{
/* return the original object and remove inserted object
- (client needs the original object) */
+ (client prefers the original object) */
+
+ // carrying must be 0 for mergable objects
+ weight_t oweight = weight_t (tmp->weight) * tmp->nrof;
+
tmp->nrof += op->nrof;
if (object *pl = tmp->visible_to ())
esrv_update_item (UPD_NROF, pl, tmp);
- adjust_weight (this, op->total_weight ());
+ adjust_weight (this, oweight, weight_t (tmp->weight) * tmp->nrof);
op->destroy ();
op = tmp;
@@ -1778,7 +1737,7 @@
if (object *pl = op->visible_to ())
esrv_send_item (pl, op);
- adjust_weight (this, op->total_weight ());
+ adjust_weight (this, 0, op->total_weight ());
inserted:
/* reset the light list and los of the players on the map */
@@ -1851,7 +1810,7 @@
return 0;
/* The objects have to be checked from top to bottom.
- * Hence, we first go to the top:
+ * Hence, we first go to the top:
*/
for (object *next, *tmp = ms.top; tmp; tmp = next)
{
@@ -2045,14 +2004,14 @@
* Note - updated to take an object instead of archetype - this is necessary
* because arch_blocked (now ob_blocked) needs to know the movement type
* to know if the space in question will block the object. We can't use
- * the archetype because that isn't correct if the monster has been
+ * the archetype because that isn't correct if the monster has been
* customized, changed states, etc.
*/
int
find_free_spot (const object *ob, maptile *m, int x, int y, int start, int stop)
{
int altern[SIZEOFFREE];
- int index = 0, flag;
+ int index = 0;
for (int i = start; i < stop; i++)
{
@@ -2078,7 +2037,7 @@
/* Basically, if we find a wall on a space, we cut down the search size.
* In this way, we won't return spaces that are on another side of a wall.
- * This mostly work, but it cuts down the search size in all directions -
+ * This mostly work, but it cuts down the search size in all directions -
* if the space being examined only has a wall to the north and empty
* spaces in all the other directions, this will reduce the search space
* to only the spaces immediately surrounding the target area, and
@@ -2118,7 +2077,7 @@
find_first_free_spot (const object *ob, maptile *m, int x, int y)
{
for (int i = 0; i < SIZEOFFREE; i++)
- if (!ob->blocked (m, x + freearr_x[i], y + freearr_y[i]))
+ if (!ob->blocked (m, x + DIRX (i), y + DIRY (i)))
return i;
return -1;
@@ -2139,7 +2098,7 @@
swap (arr [end], arr [rndm (end + 1)]);
}
-/* new function to make monster searching more efficient, and effective!
+/* new function to make monster searching more efficient, and effective!
* This basically returns a randomized array (in the passed pointer) of
* the spaces to find monsters. In this way, it won't always look for
* monsters to the north first. However, the size of the array passed
@@ -2174,7 +2133,7 @@
int
find_dir (maptile *m, int x, int y, object *exclude)
{
- int max = SIZEOFFREE, mflags;
+ int max = SIZEOFFREE;
MoveType move_type;
if (exclude && exclude->head_ () != exclude)
@@ -2393,8 +2352,8 @@
if (dir < 0)
return 0; /* exit condition: invalid direction */
- dx = x + freearr_x[dir];
- dy = y + freearr_y[dir];
+ dx = x + DIRX (dir);
+ dy = y + DIRY (dir);
mflags = get_map_flags (m, &m, dx, dy, &dx, &dy);
@@ -2435,6 +2394,8 @@
!item->flag [FLAG_ALIVE] && !item->invisible && (who->is_player () || item->weight < who->weight / 3));
}
+//-GPL
+
/*
* create clone from object to another
*/
@@ -2474,10 +2435,26 @@
return 0;
}
+/* Zero the key_values on op, decrementing the shared-string
+ * refcounts and freeing the links.
+ */
+void
+key_values::clear ()
+{
+ for (key_value *kvp = first; kvp; )
+ {
+ key_value *next = kvp->next;
+ delete kvp;
+ kvp = next;
+ }
+
+ first = 0;
+}
+
shstr_tmp
-object::kv_get (shstr_tmp key) const
+key_values::get (shstr_tmp key) const
{
- for (key_value *kv = key_values; kv; kv = kv->next)
+ for (key_value *kv = first; kv; kv = kv->next)
if (kv->key == key)
return kv->value;
@@ -2485,28 +2462,34 @@
}
void
-object::kv_set (shstr_tmp key, shstr_tmp value)
+key_values::add (shstr_tmp key, shstr_tmp value)
+{
+ key_value *kv = new key_value;
+
+ kv->next = first;
+ kv->key = key;
+ kv->value = value;
+
+ first = kv;
+}
+
+void
+key_values::set (shstr_tmp key, shstr_tmp value)
{
- for (key_value *kv = key_values; kv; kv = kv->next)
+ for (key_value *kv = first; kv; kv = kv->next)
if (kv->key == key)
{
kv->value = value;
return;
}
- key_value *kv = new key_value;
-
- kv->next = key_values;
- kv->key = key;
- kv->value = value;
-
- key_values = kv;
+ add (key, value);
}
void
-object::kv_del (shstr_tmp key)
+key_values::del (shstr_tmp key)
{
- for (key_value **kvp = &key_values; *kvp; kvp = &(*kvp)->next)
+ for (key_value **kvp = &first; *kvp; kvp = &(*kvp)->next)
if ((*kvp)->key == key)
{
key_value *kv = *kvp;
@@ -2516,6 +2499,34 @@
}
}
+void
+key_values::reverse ()
+{
+ key_value *prev = 0;
+ key_value *head = first;
+
+ while (head)
+ {
+ key_value *node = head;
+ head = head->next;
+ node->next = prev;
+ prev = node;
+ }
+
+ first = prev;
+}
+
+key_values &
+key_values::operator =(const key_values &kv)
+{
+ clear ();
+
+ for (key_value *kvp = kv.first; kvp; kvp = kvp->next)
+ add (kvp->key, kvp->value);
+
+ reverse ();
+}
+
object::depth_iterator::depth_iterator (object *container)
: iterator_base (container)
{
@@ -2607,6 +2618,8 @@
: region::default_region ();
}
+//+GPL
+
void
object::open_container (object *new_container)
{
@@ -2673,11 +2686,43 @@
// contr->ns->floorbox_reset ();
}
+//-GPL
+
+// prefetch some flat area around the player
+static void
+prefetch_surrounding_area (object *op, maptile *map, int range)
+{
+ for (maprect *rect = map->split_to_tiles (mapwalk_buf,
+ op->x - range , op->y - range ,
+ op->x + range + 1, op->y + range + 1);
+ rect->m;
+ ++rect)
+ {
+ rect->m->touch ();
+ rect->m->activate ();
+ }
+}
+
+// prefetch a generous area around the player, also up and down
+void
+object::prefetch_surrounding_maps ()
+{
+ prefetch_surrounding_area (this, map, 40);
+
+ if (maptile *m = map->tile_available (TILE_DOWN))
+ prefetch_surrounding_area (this, m, 20);
+
+ if (maptile *m = map->tile_available (TILE_UP))
+ prefetch_surrounding_area (this, m, 20);
+}
+
+//+GPL
+
object *
object::force_find (shstr_tmp name)
{
- /* cycle through his inventory to look for the MARK we want to
- * place
+ /* cycle through his inventory to look for the MARK we want to
+ * place
*/
for (object *tmp = inv; tmp; tmp = tmp->below)
if (tmp->type == FORCE && tmp->slaying == name)
@@ -2686,8 +2731,6 @@
return 0;
}
-//-GPL
-
void
object::force_set_timer (int duration)
{
@@ -2703,7 +2746,7 @@
if (object *force = force_find (name))
force->destroy ();
- object *force = get_archetype (FORCE_NAME);
+ object *force = archetype::get (FORCE_NAME);
force->slaying = name;
force->force_set_timer (duration);
@@ -2797,3 +2840,13 @@
return 0;
}
+// put marked object first in the inventory
+// this is used by identify-like spells so players can influence
+// the order a bit.
+void
+object::splay_marked ()
+{
+ if (object *marked = mark ())
+ splay (marked);
+}
+