--- deliantra/server/common/object.C 2007/02/05 01:24:45 1.128 +++ deliantra/server/common/object.C 2008/04/11 13:59:05 1.203 @@ -1,25 +1,24 @@ /* - * CrossFire, A Multiplayer game for X-windows + * This file is part of Deliantra, the Roguelike Realtime MMORPG. * - * Copyright (C) 2005, 2006, 2007 Marc Lehmann & Crossfire+ Development Team - * Copyright (C) 2001 Mark Wedel & Crossfire Development Team - * Copyright (C) 1992 Frank Tore Johansen + * Copyright (©) 2005,2006,2007 Marc Alexander Lehmann / Robin Redeker / the Deliantra team + * Copyright (©) 2001,2007 Mark Wedel & Crossfire Development Team + * Copyright (©) 1992,2007 Frank Tore Johansen * - * This program is free software; you can redistribute it and/or modify + * Deliantra 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 + * 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 + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * The authors can be reached via e-mail at + * along with this program. If not, see . + * + * The authors can be reached via e-mail to */ /* Eneq(@csd.uu.se): Added weight-modifiers in environment of objects. @@ -31,50 +30,50 @@ #include #include #include +#include #include #include -int nrofallocobjects = 0; -static UUID uuid; -const uint64 UUID_SKIP = 1<<19; +UUID UUID::cur; +static const uint64 UUID_SKIP = 1<<19; objectvec objects; activevec actives; -short 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_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] = { 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, - -3, -3, -3, -3, -2, -1, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3 +short 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, + -3, -3, -3, -3, -2, -1, 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0, -1, -2, -3, -3, -3 }; -int maxfree[SIZEOFFREE] = { 0, 9, 10, 13, 14, 17, 18, 21, 22, 25, 26, 27, 30, 31, 32, 33, 36, 37, 39, 39, 42, 43, 44, 45, - 48, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49 +int maxfree[SIZEOFFREE] = { + 0, + 9, 10, 13, 14, 17, 18, 21, 22, + 25, 26, 27, 30, 31, 32, 33, 36, 37, 39, 39, 42, 43, 44, 45, 48, + 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49, 49 }; int freedir[SIZEOFFREE] = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 2, 2, 3, 4, 4, 4, 5, 6, 6, 6, 7, 8, 8, 8, - 1, 2, 2, 2, 2, 2, 3, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 7, 8, 8, 8, 8, 8 + 0, + 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 2, 2, 3, 4, 4, 4, 5, 6, 6, 6, 7, 8, 8, 8, + 1, 2, 2, 2, 2, 2, 3, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 6, 7, 8, 8, 8, 8, 8 }; static void -write_uuid (void) +write_uuid (uval64 skip, bool sync) { - char filename1[MAX_BUF], filename2[MAX_BUF]; - - sprintf (filename1, "%s/uuid", settings.localdir); - sprintf (filename2, "%s/uuid~", settings.localdir); - - FILE *fp; - - if (!(fp = fopen (filename2, "w"))) - { - LOG (llevError, "ERROR: cannot open %s for writing, unable to write UUID!\n", filename2); - return; - } - - fprintf (fp, "<1,%llx>\n", (unsigned long long)uuid.seq + UUID_SKIP * 2); - fclose (fp); - rename (filename2, filename1); + CALL_BEGIN (2); + CALL_ARG_SV (newSVval64 (skip)); + CALL_ARG_SV (boolSV (sync)); + CALL_CALL ("cf::write_uuid", G_DISCARD); + CALL_END; } static void @@ -91,8 +90,8 @@ if (errno == ENOENT) { LOG (llevInfo, "RESET uid to 1\n"); - uuid.seq = 0; - write_uuid (); + UUID::cur.seq = 0; + write_uuid (UUID_SKIP, true); return; } @@ -100,35 +99,37 @@ _exit (1); } - int version; - unsigned long long uid; - if (2 != fscanf (fp, "<%d,%llx>\n", &version, &uid) || version != 1) + UUID::BUF buf; + buf[0] = 0; + fgets (buf, sizeof (buf), fp); + + if (!UUID::cur.parse (buf)) { - LOG (llevError, "FATAL: error reading uid from %s!\n", filename); + LOG (llevError, "FATAL: error reading uid from %s (%s)!\n", filename, buf); _exit (1); } - uuid.seq = uid; - write_uuid (); - LOG (llevDebug, "read UID: %" PRId64 "\n", uid); + LOG (llevDebug, "read UUID: %s\n", UUID::cur.c_str ()); + + write_uuid (UUID_SKIP, true); fclose (fp); } UUID -gen_uuid () +UUID::gen () { UUID uid; - uid.seq = ++uuid.seq; + uid.seq = ++cur.seq; - if (!(uuid.seq & (UUID_SKIP - 1))) - write_uuid (); + if (expect_false (!(cur.seq & (UUID_SKIP - 1)))) + write_uuid (UUID_SKIP, false); return uid; } void -init_uuid () +UUID::init () { read_uuid (); } @@ -256,13 +257,14 @@ */ if (ob1->inv || ob2->inv) { - /* if one object has inventory but the other doesn't, not equiv */ - if ((ob1->inv && !ob2->inv) || (ob2->inv && !ob1->inv)) - return 0; + if (!(ob1->inv && ob2->inv)) + return 0; /* inventories differ in length */ + + if (ob1->inv->below || ob2->inv->below) + return 0; /* more than one object in inv */ - /* Now check to see if the two inventory objects could merge */ if (!object::can_merge (ob1->inv, ob2->inv)) - return 0; + return 0; /* inventory objexts differ */ /* inventory ok - still need to check rest of this object to see * if it is valid. @@ -301,14 +303,23 @@ return 0; } - //TODO: generate an event or call into perl for additional checks if (ob1->self || ob2->self) { ob1->optimise (); ob2->optimise (); if (ob1->self || ob2->self) - return 0; + { + int k1 = ob1->self ? HvTOTALKEYS (ob1->self) : 0; + int k2 = ob2->self ? HvTOTALKEYS (ob2->self) : 0; + + if (k1 != k2) + return 0; + else if (k1 == 0) + return 1; + else if (!cfperl_can_merge (ob1, ob2)) + return 0; + } } /* Everything passes, must be OK. */ @@ -326,10 +337,11 @@ long sum; object *inv; - for (sum = 0, inv = op->inv; inv != NULL; inv = inv->below) + for (sum = 0, inv = op->inv; inv; inv = inv->below) { if (inv->inv) sum_weight (inv); + sum += inv->carrying + inv->weight * (inv->nrof ? inv->nrof : 1); } @@ -359,7 +371,6 @@ * Some error messages. * The result of the dump is stored in the static global errmsg array. */ - char * dump_object (object *op) { @@ -367,7 +378,7 @@ return strdup ("[NULLOBJ]"); object_freezer freezer; - save_object (freezer, op, 1); + op->write (freezer); return freezer.as_string (); } @@ -376,7 +387,6 @@ * multi-object 1 which is closest to the second object. * If it's not a multi-object, it is returned. */ - object * get_nearest_part (object *op, const object *pl) { @@ -422,42 +432,101 @@ return op; } -void -free_all_object_data () -{ - LOG (llevDebug, "%d allocated objects\n", nrofallocobjects); -} - /* * Sets the owner and sets the skill and exp pointers to owner's current * skill and experience objects. + * ACTUALLY NO! investigate! TODO */ void object::set_owner (object *owner) { - if (!owner) - return; + // allow objects which own objects + if (owner) + while (owner->owner) + owner = owner->owner; - /* next line added to allow objects which own objects */ - /* Add a check for ownercounts in here, as I got into an endless loop - * with the fireball owning a poison cloud which then owned the - * fireball. I believe that was caused by one of the objects getting - * freed and then another object replacing it. Since the ownercounts - * didn't match, this check is valid and I believe that cause is valid. - */ - while (owner->owner) - owner = owner->owner; + if (flag [FLAG_FREED]) + { + LOG (llevError | logBacktrace, "tried to set owner of %s to %s\n", debug_desc (), owner->debug_desc ()); + return; + } this->owner = owner; } +int +object::slottype () const +{ + if (type == SKILL) + { + if (IS_COMBAT_SKILL (subtype)) return slot_combat; + if (IS_RANGED_SKILL (subtype)) return slot_ranged; + } + else + { + if (slot [body_combat].info) return slot_combat; + if (slot [body_range ].info) return slot_ranged; + } + + return slot_none; +} + +bool +object::change_weapon (object *ob) +{ + if (current_weapon == ob) + return true; + + if (chosen_skill) + chosen_skill->flag [FLAG_APPLIED] = false; + + current_weapon = ob; + chosen_skill = !ob || ob->type == SKILL ? ob : find_skill_by_name (this, ob->skill); + + if (chosen_skill) + chosen_skill->flag [FLAG_APPLIED] = true; + + update_stats (); + + if (ob) + { + // now check wether any body locations became invalid, in which case + // we cannot apply the weapon at the moment. + for (int i = 0; i < NUM_BODY_LOCATIONS; ++i) + if (slot[i].used < 0) + { + current_weapon = chosen_skill = 0; + update_stats (); + + new_draw_info_format (NDI_UNIQUE, 0, this, + "You try to balance all your items at once, " + "but the %s is just too much for your body. " + "[You need to unapply some items first.]", &ob->name); + return false; + } + + //new_draw_info_format (NDI_UNIQUE, 0, this, "You switch to your %s.", &ob->name); + } + else + ;//new_draw_info_format (NDI_UNIQUE, 0, this, "You unwield your weapons."); + + if (ob && !ob->flag [FLAG_APPLIED] && ob->type != SPELL) + { + LOG (llevError | logBacktrace, "%s changed to unapplied weapon %s", + &name, ob->debug_desc ()); + return false; + } + + return true; +} + /* 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 != 0;) + for (key_value *i = op->key_values; i; ) { key_value *next = i->next; delete i; @@ -468,40 +537,24 @@ op->key_values = 0; } -/* - * copy_to first frees everything allocated by the dst object, - * and then copies the contents of itself into the second - * object, allocating what needs to be allocated. Basically, any - * data that is malloc'd needs to be re-malloc/copied. Otherwise, - * if the first object is freed, the pointers in the new object - * will point at garbage. - */ -void -object::copy_to (object *dst) +object & +object::operator =(const object &src) { - bool is_freed = QUERY_FLAG (dst, FLAG_FREED); - bool is_removed = QUERY_FLAG (dst, FLAG_REMOVED); - - *(object_copy *)dst = *this; - - if (is_freed) - SET_FLAG (dst, FLAG_FREED); + bool is_freed = flag [FLAG_FREED]; + bool is_removed = flag [FLAG_REMOVED]; - if (is_removed) - SET_FLAG (dst, FLAG_REMOVED); + *(object_copy *)this = src; - if (speed < 0) - dst->speed_left = speed_left - rndm (); + flag [FLAG_FREED] = is_freed; + flag [FLAG_REMOVED] = is_removed; /* Copy over key_values, if any. */ - if (key_values) + if (src.key_values) { key_value *tail = 0; - key_value *i; - - dst->key_values = 0; + key_values = 0; - for (i = key_values; i; i = i->next) + for (key_value *i = src.key_values; i; i = i->next) { key_value *new_link = new key_value; @@ -510,9 +563,9 @@ new_link->value = i->value; /* Try and be clever here, too. */ - if (!dst->key_values) + if (!key_values) { - dst->key_values = new_link; + key_values = new_link; tail = new_link; } else @@ -522,10 +575,46 @@ } } } +} + +/* + * copy_to first frees everything allocated by the dst object, + * and then copies the contents of itself into the second + * object, allocating what needs to be allocated. Basically, any + * data that is malloc'd needs to be re-malloc/copied. Otherwise, + * if the first object is freed, the pointers in the new object + * will point at garbage. + */ +void +object::copy_to (object *dst) +{ + *dst = *this; + + if (speed < 0) + dst->speed_left -= rndm (); dst->set_speed (dst->speed); } +void +object::instantiate () +{ + if (!uuid.seq) // HACK + uuid = UUID::gen (); + + speed_left = -0.1f; + /* 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 + * for it, they can be properly equipped. + */ + for (int i = NUM_BODY_LOCATIONS; i--; ) + slot[i].used = slot[i].info; + + attachable::instantiate (); +} + object * object::clone () { @@ -591,8 +680,6 @@ void update_object (object *op, int action) { - MoveType move_on, move_off, move_block, move_slow; - if (op == NULL) { /* this should never happen */ @@ -682,7 +769,7 @@ void object::link () { assert (!index);//D - uuid = gen_uuid (); + uuid = UUID::gen (); count = ++object_count; refcnt_inc (); @@ -821,15 +908,7 @@ remove_button_link (this); if (flag [FLAG_FRIENDLY]) - { - remove_friendly_object (this); - - if (type == GOLEM - && owner - && owner->type == PLAYER - && owner->contr->ranges[range_golem] == this) - owner->contr->ranges[range_golem] = 0; - } + remove_friendly_object (this); if (!flag [FLAG_REMOVED]) remove (); @@ -849,9 +928,11 @@ { freed_map = new maptile; + freed_map->path = ""; freed_map->name = "/internal/freed_objects_map"; freed_map->width = 3; freed_map->height = 3; + freed_map->nodrop = 1; freed_map->alloc (); freed_map->in_memory = MAP_IN_MEMORY; @@ -862,18 +943,19 @@ y = 1; } - head = 0; - if (more) { more->destroy (); more = 0; } - // clear those pointers that likely might have circular references to us - owner = 0; - enemy = 0; - attacked_by = 0; + head = 0; + + // clear those pointers that likely might cause circular references + owner = 0; + enemy = 0; + attacked_by = 0; + current_weapon = 0; } void @@ -885,6 +967,12 @@ if (destroy_inventory) destroy_inv (false); + if (is_head ()) + if (sound_destroy) + play_sound (sound_destroy); + else if (flag [FLAG_MONSTER]) + play_sound (sound_find ("monster_destroy")); // quick hack, too lazy to create a generic mechanism + attachable::destroy (); } @@ -966,6 +1054,12 @@ { if (type == PLAYER) { + // leaving a spot always closes any open container on the ground + if (container && !container->env) + // this causes spurious floorbox updates, but it ensures + // that the CLOSE event is being sent. + close_container (); + --map->players; map->touch (); } @@ -1002,28 +1096,24 @@ int check_walk_off = !flag [FLAG_NO_APPLY]; + if (object *pl = ms.player ()) + { + if (pl->container == this) + /* If a container that the player is currently using somehow gets + * removed (most likely destroyed), update the player view + * appropriately. + */ + pl->close_container (); + + pl->contr->ns->floorbox_update (); + } + for (tmp = ms.bot; tmp; tmp = tmp->above) { /* No point updating the players look faces if he is the object * being removed. */ - if (tmp->type == PLAYER && tmp != this) - { - /* If a container that the player is currently using somehow gets - * removed (most likely destroyed), update the player view - * appropriately. - */ - if (tmp->container == this) - { - flag [FLAG_APPLIED] = 0; - tmp->container = 0; - } - - if (tmp->contr->ns) - tmp->contr->ns->floorbox_update (); - } - /* See if object moving off should effect something */ if (check_walk_off && ((move_type & tmp->move_off) @@ -1035,11 +1125,6 @@ LOG (llevError, "BUG: remove_ob(): name %s, destroyed leaving object\n", tmp->debug_desc ()); } - /* Eneq(@csd.uu.se): Fixed this to skip tmp->above=tmp */ - //TODO: why is this horrible hacka fix? get rid of this code=bug! (schmorp) - if (tmp->above == tmp) - tmp->above = 0; - last = tmp; } @@ -1069,7 +1154,7 @@ if (!op->nrof) return 0; - if (top) + if (!top) for (top = op; top && top->above; top = top->above) ; @@ -1092,6 +1177,29 @@ return 0; } +void +object::expand_tail () +{ + if (more) + return; + + object *prev = this; + + for (archetype *at = (archetype *)arch->more; at; at = (archetype *)at->more) + { + object *op = arch_to_object (at); + + op->name = name; + op->name_pl = name_pl; + op->title = title; + + op->head = this; + prev->more = op; + + prev = op; + } +} + /* * same as insert_ob_in_map except it handles separate coordinates and does a clean * job preparing multi-part monsters. @@ -1101,8 +1209,8 @@ { for (object *tmp = op->head_ (); tmp; tmp = tmp->more) { - tmp->x = x + tmp->arch->clone.x; - tmp->y = y + tmp->arch->clone.y; + tmp->x = x + tmp->arch->x; + tmp->y = y + tmp->arch->y; } return insert_ob_in_map (op, m, originator, flag); @@ -1131,69 +1239,35 @@ object * insert_ob_in_map (object *op, maptile *m, object *originator, int flag) { - object *tmp, *top, *floor = NULL; + assert (!op->flag [FLAG_FREED]); - if (QUERY_FLAG (op, FLAG_FREED)) - { - LOG (llevError, "Trying to insert freed object!\n"); - return NULL; - } - - if (!QUERY_FLAG (op, FLAG_REMOVED)) - LOG (llevError, "Trying to insert already inserted object %s\n", op->debug_desc ()); + object *top, *floor = NULL; op->remove (); - if (!m) - { - char *dump = dump_object (op); - LOG (llevError, "Trying to insert in null-map!\n%s\n", dump); - free (dump); - return op; - } - - if (out_of_map (m, op->x, op->y)) + /* Ideally, the caller figures this out. However, it complicates a lot + * of areas of callers (eg, anything that uses find_free_spot would now + * need extra work + */ + if (!xy_normalise (m, op->x, op->y)) { - char *dump = dump_object (op); - LOG (llevError, "Trying to insert object outside the map.\n%s\n", dump); -#ifdef MANY_CORES - /* Better to catch this here, as otherwise the next use of this object - * is likely to cause a crash. Better to find out where it is getting - * improperly inserted. - */ - abort (); -#endif - free (dump); - return op; + op->destroy (); + return 0; } if (object *more = op->more) - { - if (!insert_ob_in_map (more, m, originator, flag)) - { - if (!op->head) - LOG (llevError, "BUG: insert_ob_in_map(): inserting op->more killed op\n"); - - return 0; - } - } + if (!insert_ob_in_map (more, m, originator, flag)) + return 0; CLEAR_FLAG (op, FLAG_REMOVED); - /* Ideally, the caller figures this out. However, it complicates a lot - * of areas of callers (eg, anything that uses find_free_spot would now - * need extra work - */ - if (!xy_normalise (m, op->x, op->y)) - return 0; - op->map = m; mapspace &ms = op->ms (); /* this has to be done after we translate the coordinates. */ if (op->nrof && !(flag & INS_NO_MERGE)) - for (tmp = ms.bot; tmp; tmp = tmp->above) + for (object *tmp = ms.bot; tmp; tmp = tmp->above) if (object::can_merge (op, tmp)) { op->nrof += tmp->nrof; @@ -1230,7 +1304,7 @@ top = ms.bot; /* If there are other objects, then */ - if ((!(flag & INS_MAP_LOAD)) && top) + if (top) { object *last = 0; @@ -1270,14 +1344,14 @@ */ /* Have object 'fall below' other objects that block view. - * Unless those objects are exits, type 66 + * Unless those objects are exits. * If INS_ON_TOP is used, don't do this processing * Need to find the object that in fact blocks view, otherwise * stacking is a bit odd. */ if (!(flag & INS_ON_TOP) && ms.flags () & P_BLOCKSVIEW - && (op->face && !op->face->visibility)) + && (op->face && !faces [op->face].visibility)) { for (last = top; last != floor; last = last->below) if (QUERY_FLAG (last, FLAG_BLOCKSVIEW) && (last->type != EXIT)) @@ -1293,9 +1367,6 @@ } } /* If objects on this space */ - if (flag & INS_MAP_LOAD) - top = ms.top; - if (flag & INS_ABOVE_FLOOR_ONLY) top = floor; @@ -1337,13 +1408,8 @@ op->map->dirty = true; - /* If we have a floor, we know the player, if any, will be above - * it, so save a few ticks and start from there. - */ - if (!(flag & INS_MAP_LOAD)) - if (object *pl = ms.player ()) - if (pl->contr->ns) - pl->contr->ns->floorbox_update (); + if (object *pl = ms.player ()) + pl->contr->ns->floorbox_update (); /* If this object glows, it may affect lighting conditions that are * visible to others on this map. But update_all_los is really @@ -1372,7 +1438,7 @@ */ /* if this is not the head or flag has been passed, don't check walk on status */ - if (!(flag & INS_NO_WALK_ON) && !op->head) + if (!(flag & INS_NO_WALK_ON) && op->head_ () == op) { if (check_move_on (op, originator)) return 0; @@ -1380,7 +1446,7 @@ /* If we are a multi part object, lets work our way through the check * walk on's. */ - for (tmp = op->more; tmp != NULL; tmp = tmp->more) + for (object *tmp = op->more; tmp; tmp = tmp->more) if (check_move_on (tmp, originator)) return 0; } @@ -1400,7 +1466,7 @@ /* first search for itself and remove any old instances */ for (tmp = op->ms ().bot; tmp; tmp = tmp->above) - if (!strcmp (tmp->arch->name, arch_string)) /* same archetype */ + if (!strcmp (tmp->arch->archname, arch_string)) /* same archetype */ tmp->destroy (); tmp1 = arch_to_object (archetype::find (arch_string)); @@ -1413,7 +1479,7 @@ object * object::insert_at (object *where, object *originator, int flags) { - where->map->insert (this, where->x, where->y, originator, flags); + return where->map->insert (this, where->x, where->y, originator, flags); } /* @@ -1463,7 +1529,6 @@ * * Return value: 'op' if something is left, NULL if the amount reached 0 */ - object * decrease_ob_nr (object *op, uint32 i) { @@ -1548,7 +1613,6 @@ * add_weight(object, weight) adds the specified weight to an object, * and also updates how much the environment(s) is/are carrying. */ - void add_weight (object *op, signed long weight) { @@ -1573,9 +1637,9 @@ return op; } - if (where->head) + if (where->head_ () != where) { - LOG (llevDebug, "Warning: Tried to insert object into wrong part of multipart object.\n"); + LOG (llevError | logBacktrace, "Warning: Tried to insert object into wrong part of multipart object.\n"); where = where->head; } @@ -1590,12 +1654,9 @@ * The function returns now pointer to inserted item, and return value can * be != op, if items are merged. -Tero */ - object * object::insert (object *op) { - object *tmp, *otmp; - if (!QUERY_FLAG (op, FLAG_REMOVED)) op->remove (); @@ -1607,9 +1668,10 @@ CLEAR_FLAG (op, FLAG_OBJ_ORIGINAL); CLEAR_FLAG (op, FLAG_REMOVED); + if (op->nrof) { - for (tmp = inv; tmp != NULL; tmp = tmp->below) + for (object *tmp = inv; tmp; tmp = tmp->below) if (object::can_merge (tmp, op)) { /* return the original object and remove inserted object @@ -1638,19 +1700,19 @@ else add_weight (this, (op->weight + op->carrying)); - otmp = this->in_player (); - if (otmp && otmp->contr) - if (!QUERY_FLAG (otmp, FLAG_NO_FIX_PLAYER)) + if (object *otmp = this->in_player ()) + if (otmp->contr && !QUERY_FLAG (otmp, FLAG_NO_FIX_PLAYER)) otmp->update_stats (); - op->map = 0; - op->env = this; + op->owner = 0; // its his/hers now. period. + op->map = 0; + op->env = this; op->above = 0; op->below = 0; - op->x = 0, op->y = 0; + op->x = op->y = 0; /* reset the light list and los of the players on the map */ - if ((op->glow_radius != 0) && map) + if (op->glow_radius && map) { #ifdef DEBUG_LIGHTS LOG (llevDebug, " insert_ob_in_ob(): got %s to insert in map/op\n", op->name); @@ -1894,12 +1956,11 @@ void flag_inv (object *op, int flag) { - if (op->inv) - for (object *tmp = op->inv; tmp != NULL; tmp = tmp->below) - { - SET_FLAG (tmp, flag); - flag_inv (tmp, flag); - } + for (object *tmp = op->inv; tmp; tmp = tmp->below) + { + SET_FLAG (tmp, flag); + flag_inv (tmp, flag); + } } /* @@ -1908,25 +1969,11 @@ void unflag_inv (object *op, int flag) { - if (op->inv) - for (object *tmp = op->inv; tmp != NULL; tmp = tmp->below) - { - CLEAR_FLAG (tmp, flag); - unflag_inv (tmp, flag); - } -} - -/* - * set_cheat(object) sets the cheat flag (WAS_WIZ) in the object and in - * all it's inventory (recursively). - * If checksums are used, a player will get set_cheat called for - * him/her-self and all object carried by a call to this function. - */ -void -set_cheat (object *op) -{ - SET_FLAG (op, FLAG_WAS_WIZ); - flag_inv (op, FLAG_WAS_WIZ); + for (object *tmp = op->inv; tmp; tmp = tmp->below) + { + CLEAR_FLAG (tmp, flag); + unflag_inv (tmp, flag); + } } /* @@ -1938,10 +1985,7 @@ * start and stop are where to start relative to the free_arr array (1,9 * does all 4 immediate directions). This returns the index into the * array of the free spot, -1 if no spot available (dir 0 = x,y) - * Note - this only checks to see if there is space for the head of the - * object - if it is a multispace object, this should be called for all - * pieces. - * Note2: This function does correctly handle tiled maps, but does not + * Note: This function does correctly handle tiled maps, but does not * inform the caller. However, insert_ob_in_map will update as * necessary, so the caller shouldn't need to do any special work. * Note - updated to take an object instead of archetype - this is necessary @@ -1953,14 +1997,30 @@ int find_free_spot (const object *ob, maptile *m, int x, int y, int start, int stop) { - int index = 0, flag; int altern[SIZEOFFREE]; + int index = 0, flag; for (int i = start; i < stop; i++) { - flag = ob_blocked (ob, m, x + freearr_x[i], y + freearr_y[i]); - if (!flag) - altern [index++] = i; + mapxy pos (m, x, y); pos.move (i); + + if (!pos.normalise ()) + continue; + + mapspace &ms = *pos; + + if (ms.flags () & P_IS_ALIVE) + continue; + + /* However, often + * ob doesn't have any move type (when used to place exits) + * so the AND operation in OB_TYPE_MOVE_BLOCK doesn't work. + */ + if (ob && ob->move_type == 0 && ms.move_block != MOVE_ALL) + { + altern [index++] = i; + continue; + } /* 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. @@ -1970,8 +2030,22 @@ * to only the spaces immediately surrounding the target area, and * won't look 2 spaces south of the target space. */ - else if ((flag & P_NO_PASS) && maxfree[i] < stop) - stop = maxfree[i]; + if (ms.move_block == MOVE_ALL && maxfree[i] < stop) + { + stop = maxfree[i]; + continue; + } + + /* Note it is intentional that we check ob - the movement type of the + * head of the object should correspond for the entire object. + */ + if (OB_TYPE_MOVE_BLOCK (ob, ms.move_block)) + continue; + + if (ob->blocked (m, pos.x, pos.y)) + continue; + + altern [index++] = i; } if (!index) @@ -1990,7 +2064,7 @@ find_first_free_spot (const object *ob, maptile *m, int x, int y) { for (int i = 0; i < SIZEOFFREE; i++) - if (!ob_blocked (ob, m, x + freearr_x[i], y + freearr_y[i])) + if (!ob->blocked (m, x + freearr_x[i], y + freearr_y[i])) return i; return -1; @@ -2054,7 +2128,7 @@ MoveType blocked, move_type; - if (exclude && exclude->head) + if (exclude && exclude->head_ () != exclude) { exclude = exclude->head; move_type = exclude->move_type; @@ -2087,7 +2161,7 @@ { for (tmp = ms.bot; tmp; tmp = tmp->above) if ((tmp->flag [FLAG_MONSTER] || tmp->type == PLAYER) - && (tmp != exclude || (tmp->head && tmp->head != exclude))) + && (tmp != exclude || (tmp->head_ () != tmp && tmp->head_ () != exclude))) break; if (tmp) @@ -2277,7 +2351,6 @@ * * Add a check so we can't pick up invisible objects (0.93.8) */ - int can_pick (const object *who, const object *item) { @@ -2292,17 +2365,15 @@ object * object_create_clone (object *asrc) { - object *dst = 0, *tmp, *src, *part, *prev, *item; + object *dst = 0, *tmp, *src, *prev, *item; if (!asrc) return 0; - src = asrc; - if (src->head) - src = src->head; + src = asrc->head_ (); prev = 0; - for (part = src; part; part = part->more) + for (object *part = src; part; part = part->more) { tmp = part->clone (); tmp->x -= src->x; @@ -2330,44 +2401,6 @@ return dst; } -/* GROS - Creates an object using a string representing its content. */ -/* Basically, we save the content of the string to a temp file, then call */ -/* load_object on it. I admit it is a highly inefficient way to make things, */ -/* but it was simple to make and allows reusing the load_object function. */ -/* Remember not to use load_object_str in a time-critical situation. */ -/* Also remember that multiparts objects are not supported for now. */ -object * -load_object_str (const char *obstr) -{ - object *op; - char filename[MAX_BUF]; - - sprintf (filename, "%s/cfloadobstr2044", settings.tmpdir); - - FILE *tempfile = fopen (filename, "w"); - - if (tempfile == NULL) - { - LOG (llevError, "Error - Unable to access load object temp file\n"); - return NULL; - } - - fprintf (tempfile, obstr); - fclose (tempfile); - - op = object::create (); - - object_thawer thawer (filename); - - if (thawer) - load_object (thawer, op, 0); - - LOG (llevDebug, " load str completed, object=%s\n", &op->name); - CLEAR_FLAG (op, FLAG_REMOVED); - - return op; -} - /* This returns the first object in who's inventory that * has the same type and subtype match. * returns NULL if no match. @@ -2431,7 +2464,6 @@ return 0; } - /* * Updates the canonical_key in op to value. * @@ -2464,7 +2496,7 @@ * it, we save the empty value so that when we load, * we get this value back again. */ - if (get_ob_key_link (&op->arch->clone, canonical_key)) + if (get_ob_key_link (op->arch, canonical_key)) field->value = 0; else { @@ -2543,7 +2575,6 @@ item = item->env; } - const char * object::flag_desc (char *desc, int len) const { @@ -2580,14 +2611,15 @@ char info2[256 * 4]; char *p = info; - p += snprintf (p, 512, "{cnt:%d,uuid:<1.%" PRIx64 ">,name:\"%s\"%s%s,flags:[%s],type:%d}", - count, uuid.seq, + p += snprintf (p, 512, "{cnt:%d,uuid:%s,name:\"%s\"%s%s,flags:[%s],type:%d}", + count, + uuid.c_str (), &name, title ? "\",title:\"" : "", title ? (const char *)title : "", flag_desc (flagdesc, 512), type); - if (env) + if (!this->flag[FLAG_REMOVED] && env) p += snprintf (p, 256, "(in %s)", env->debug_desc (info2)); if (map) @@ -2599,15 +2631,10 @@ const char * object::debug_desc () const { - static char info[256 * 4]; - return debug_desc (info); -} + static char info[3][256 * 4]; + static int info_idx; -const char * -object::debug_desc2 () const -{ - static char info[256 * 4]; - return debug_desc (info); + return debug_desc (info [++info_idx % 3]); } struct region * @@ -2617,3 +2644,115 @@ : region::default_region (); } +const materialtype_t * +object::dominant_material () const +{ + if (materialtype_t *mt = name_to_material (materialname)) + return mt; + + return name_to_material (shstr_unknown); +} + +void +object::open_container (object *new_container) +{ + if (container == new_container) + return; + + if (object *old_container = container) + { + if (INVOKE_OBJECT (CLOSE, old_container, ARG_OBJECT (this))) + return; + +#if 0 + // remove the "Close old_container" object. + if (object *closer = old_container->inv) + if (closer->type == CLOSE_CON) + closer->destroy (); +#endif + + old_container->flag [FLAG_APPLIED] = 0; + container = 0; + + esrv_update_item (UPD_FLAGS, this, old_container); + new_draw_info_format (NDI_UNIQUE, 0, this, "You close %s.", query_name (old_container)); + play_sound (sound_find ("chest_close")); + } + + if (new_container) + { + if (INVOKE_OBJECT (OPEN, new_container, ARG_OBJECT (this))) + return; + + // TODO: this does not seem to serve any purpose anymore? +#if 0 + // insert the "Close Container" object. + if (archetype *closer = new_container->other_arch) + { + object *closer = arch_to_object (new_container->other_arch); + closer->flag [FLAG_NO_MAP_SAVE] = 1; + new_container->insert (closer); + } +#endif + + new_draw_info_format (NDI_UNIQUE, 0, this, "You open %s.", query_name (new_container)); + + new_container->flag [FLAG_APPLIED] = 1; + container = new_container; + + esrv_update_item (UPD_FLAGS, this, new_container); + esrv_send_inventory (this, new_container); + play_sound (sound_find ("chest_open")); + } +} + +object * +object::force_find (const shstr name) +{ + /* 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) + return splay (tmp); + + return 0; +} + +void +object::force_add (const shstr name, int duration) +{ + if (object *force = force_find (name)) + force->destroy (); + + object *force = get_archetype (FORCE_NAME); + + force->slaying = name; + force->stats.food = 1; + force->speed_left = -1.f; + + force->set_speed (duration ? 1.f / duration : 0.f); + force->flag [FLAG_IS_USED_UP] = true; + force->flag [FLAG_APPLIED] = true; + + insert (force); +} + +void +object::play_sound (faceidx sound) const +{ + if (!sound) + return; + + if (flag [FLAG_REMOVED]) + return; + + if (env) + { + if (object *pl = in_player ()) + pl->contr->play_sound (sound); + } + else + map->play_sound (sound, x, y); +} +