| 1 | /** |
|---|
| 2 | * \file destroy.c |
|---|
| 3 | * |
|---|
| 4 | * \brief Destroying objects and consistency checking. |
|---|
| 5 | * |
|---|
| 6 | * This file has two main parts. One part is the functions for destroying |
|---|
| 7 | * objects and getting objects off of the free list. The major public |
|---|
| 8 | * functions here are do_destroy(), free_get(), and purge(). |
|---|
| 9 | * |
|---|
| 10 | * The other part is functions for checking the consistency of the |
|---|
| 11 | * database, and repairing any inconsistencies that are found. The |
|---|
| 12 | * major function in this group is dbck(). |
|---|
| 13 | * |
|---|
| 14 | * |
|---|
| 15 | * These lengthy comments are by Ralph Melton, December 1995. |
|---|
| 16 | * |
|---|
| 17 | * First, a discourse on the theory of how we handle destruction. |
|---|
| 18 | * |
|---|
| 19 | * We want to maintain the following invariants: |
|---|
| 20 | * 1. All destroyed objects are on the free list. (linked through the next |
|---|
| 21 | * fields.) |
|---|
| 22 | * 2. All objects on the free list are destroyed objects. |
|---|
| 23 | * 3. No undestroyed object has its next, contents, location, or home |
|---|
| 24 | * field pointing to a destroyed object. |
|---|
| 25 | * 4. No object's zone or parent is a destroyed object. |
|---|
| 26 | * 5. No object's owner is a destroyed object. |
|---|
| 27 | * |
|---|
| 28 | * For the sake of efficiency, we allow indirect locks and other locks to |
|---|
| 29 | * refer to destroyed objects; boolexp.c had better be able to cope with |
|---|
| 30 | * these. |
|---|
| 31 | * |
|---|
| 32 | * |
|---|
| 33 | * There are three logically distinct parts to destroying an object: |
|---|
| 34 | * |
|---|
| 35 | * Part 1: we do all the permissions checks, check for the SAFE flag |
|---|
| 36 | * and the override switch, and decide that yes, we are going to destroy |
|---|
| 37 | * this object. |
|---|
| 38 | * |
|---|
| 39 | * Part 2: we eliminate all the links from other objects in the database |
|---|
| 40 | * to this object. This processing may depend on the object's type. |
|---|
| 41 | * |
|---|
| 42 | * Part 3: (logically concurrent with part 2, and must happen together |
|---|
| 43 | * with part 2) Remove any commands the object may have in the queue, |
|---|
| 44 | * free all the storage associated with the object, set the name to |
|---|
| 45 | * 'Garbage', and set this object to be a destroyed object, and put it |
|---|
| 46 | * on the free list. This process is independent of object type. |
|---|
| 47 | * |
|---|
| 48 | * Note that phases 2 and 3 do not have to happen immediately after Phase 1. |
|---|
| 49 | * To allow some delay, we set the object GOING, and then process it on |
|---|
| 50 | * the check that happens every ten minutes. |
|---|
| 51 | * |
|---|
| 52 | */ |
|---|
| 53 | |
|---|
| 54 | #include "config.h" |
|---|
| 55 | |
|---|
| 56 | #include <ctype.h> |
|---|
| 57 | #include <limits.h> |
|---|
| 58 | #include <stdlib.h> |
|---|
| 59 | #include <string.h> |
|---|
| 60 | |
|---|
| 61 | #include "copyrite.h" |
|---|
| 62 | #include "conf.h" |
|---|
| 63 | #include "mushdb.h" |
|---|
| 64 | #include "match.h" |
|---|
| 65 | #include "externs.h" |
|---|
| 66 | #include "log.h" |
|---|
| 67 | #include "game.h" |
|---|
| 68 | #include "extmail.h" |
|---|
| 69 | #include "malias.h" |
|---|
| 70 | #include "attrib.h" |
|---|
| 71 | #include "dbdefs.h" |
|---|
| 72 | #include "flags.h" |
|---|
| 73 | #include "lock.h" |
|---|
| 74 | #include "confmagic.h" |
|---|
| 75 | |
|---|
| 76 | |
|---|
| 77 | |
|---|
| 78 | dbref first_free = NOTHING; /**< Object at top of free list */ |
|---|
| 79 | |
|---|
| 80 | static dbref what_to_destroy(dbref player, char *name, int confirm); |
|---|
| 81 | static void pre_destroy(dbref player, dbref thing); |
|---|
| 82 | static void free_object(dbref thing); |
|---|
| 83 | static void empty_contents(dbref thing); |
|---|
| 84 | static void clear_thing(dbref thing); |
|---|
| 85 | static void clear_player(dbref thing); |
|---|
| 86 | static void clear_room(dbref thing); |
|---|
| 87 | static void clear_exit(dbref thing); |
|---|
| 88 | |
|---|
| 89 | static void check_fields(void); |
|---|
| 90 | static void check_connected_rooms(void); |
|---|
| 91 | static void mark_connected(dbref loc); |
|---|
| 92 | static void check_connected_marks(void); |
|---|
| 93 | static void mark_contents(dbref loc); |
|---|
| 94 | static void check_contents(void); |
|---|
| 95 | static void check_locations(void); |
|---|
| 96 | static void check_zones(void); |
|---|
| 97 | static int attribute_owner_helper |
|---|
| 98 | (dbref player, dbref thing, dbref parent, char const *pattern, ATTR *atr, |
|---|
| 99 | void *args); |
|---|
| 100 | |
|---|
| 101 | extern void remove_all_obj_chan(dbref thing); |
|---|
| 102 | extern void chan_chownall(dbref old, dbref new); |
|---|
| 103 | |
|---|
| 104 | extern struct db_stat_info current_state; |
|---|
| 105 | |
|---|
| 106 | /** Mark an object */ |
|---|
| 107 | #define SetMarked(x) Type(x) |= TYPE_MARKED |
|---|
| 108 | /** Unmark an object */ |
|---|
| 109 | #define ClearMarked(x) Type(x) &= ~TYPE_MARKED |
|---|
| 110 | |
|---|
| 111 | |
|---|
| 112 | /* Section I: do_destroy() and related functions. This section is where |
|---|
| 113 | * the human interface of do_destroy() should largely be determined. |
|---|
| 114 | */ |
|---|
| 115 | |
|---|
| 116 | /* Methinks it's time to consider human interfaces criteria for destruction |
|---|
| 117 | * as well as to consider the invariants that need to be maintained. |
|---|
| 118 | * |
|---|
| 119 | * My major criteria are these (with no implied ranking, since I haven't |
|---|
| 120 | * decided how they balance out): |
|---|
| 121 | * |
|---|
| 122 | * 1) It's easy to destroy things you intend to destroy. |
|---|
| 123 | * |
|---|
| 124 | * 2) It's easy to correct from destroying things that you don't intend |
|---|
| 125 | * to destroy. This includes both typos and realizing that you didn't mean |
|---|
| 126 | * to destroy that. This principle requires two sub-principles: |
|---|
| 127 | * a) The player gets notified when something 'important' gets |
|---|
| 128 | * marked to be destroyed--and gets told .what. is marked. |
|---|
| 129 | * b) The actual destruction of the important thing is delayed |
|---|
| 130 | * long enough that you can recover from it. |
|---|
| 131 | * |
|---|
| 132 | * 3) You can't destroy something you don't have the proper privileges to |
|---|
| 133 | * destroy. (Obvious, but still worth writing down.) |
|---|
| 134 | * |
|---|
| 135 | * To try to achieve a reasonable balance between items 1) and 2), we |
|---|
| 136 | * have the following design: |
|---|
| 137 | * Everything is finally destroyed on the second purge after the @destroy |
|---|
| 138 | * command is done, unless it is set !GOING in the meantime. |
|---|
| 139 | * @destroying an object while it is set GOING destroys it immediately. |
|---|
| 140 | * |
|---|
| 141 | * Let me introduce a little jargon for this discussion: |
|---|
| 142 | * pre-destroying an object == setting it GOING, running the @adestroy. |
|---|
| 143 | * (Pre-destroying corresponds to phase 1 above.) |
|---|
| 144 | * purging an object == actually irrevocably making it vanish. |
|---|
| 145 | * (This corresponds to phases 2 and 3 above.) |
|---|
| 146 | * undestroying an object == setting it !GOING, etc. |
|---|
| 147 | * |
|---|
| 148 | * We would also like to have an @adestroy attribute that contains |
|---|
| 149 | * code to be executed when the object is destroyed. This is |
|---|
| 150 | * complicated by the fact that the object is going to be |
|---|
| 151 | * destroyed. To work around this, we run the @adestroy when the |
|---|
| 152 | * object is pre-destroyed, not when it's actually purged. This |
|---|
| 153 | * introduces the possibility that the adestroy may be invoked for |
|---|
| 154 | * something that is then undestroyed. To compensate for that, we run |
|---|
| 155 | * the @startup attribute when something is undestroyed. |
|---|
| 156 | * |
|---|
| 157 | * Another issue is how to run the @adestroy for objects that are |
|---|
| 158 | * destroyed as a consequence of other objects being destroyed. For |
|---|
| 159 | * example, when rooms are destroyed, any exits leading from those |
|---|
| 160 | * rooms are also destroyed, and when a player is destroyed, !SAFE |
|---|
| 161 | * objects they own may also be destroyed. |
|---|
| 162 | * |
|---|
| 163 | * To handle this, we do the following: |
|---|
| 164 | * pre-destroying a room pre-destroys all its exits. |
|---|
| 165 | * pre-destroying a player pre-destroys all the objects that will be purged |
|---|
| 166 | * when that player is purged. |
|---|
| 167 | * |
|---|
| 168 | * This requires the following about undestroys: |
|---|
| 169 | * undestroying an exit undestroys its source room. |
|---|
| 170 | * undestroying any object requires undestroying its owner. |
|---|
| 171 | * |
|---|
| 172 | * But it also seems to require the following in order to make '@destroy |
|---|
| 173 | * foo; @undestroy foo' a no-op for all foo: |
|---|
| 174 | * undestroying a room undestroys all its exits. |
|---|
| 175 | * undestroying a player undestroys all its GOING things. |
|---|
| 176 | * |
|---|
| 177 | * Now, consider this scenario: |
|---|
| 178 | * Player A owns room #1. Player B owns exit #2, whose source is room #1. |
|---|
| 179 | * Player B owns thing #3. Player A and player B are both pre-destroyed; |
|---|
| 180 | * none of the objects are set SAFE. Thing #3 is then undestroyed. |
|---|
| 181 | * |
|---|
| 182 | * If you trace through the dependencies, you find that this involves |
|---|
| 183 | * undestroying all the objects, including both players! Is that what |
|---|
| 184 | * we want? It seems to me that it would be very surprising in practice. |
|---|
| 185 | * |
|---|
| 186 | * To reconcile this, we introduce the following compromise. |
|---|
| 187 | * undestroying a room undestroys all exits in the room that are not owned |
|---|
| 188 | * by a GOING player or set SAFE.. |
|---|
| 189 | * undestroying a player undestroys all objects he owns that are not exits |
|---|
| 190 | * in a GOING room that he does not own. |
|---|
| 191 | * |
|---|
| 192 | * In this way, the propagation of previous scenario would die out at exit |
|---|
| 193 | * #2, which would stay GOING. Metaphorically, there are two 'votes' for |
|---|
| 194 | * its destruction: the destruction of room #1, and the destruction of |
|---|
| 195 | * player B. Undestroying player B by undestroying thing #3 removes one |
|---|
| 196 | * of the 'votes' for exit #2's destruction, but there would still be |
|---|
| 197 | * the vote from room #1. |
|---|
| 198 | */ |
|---|
| 199 | |
|---|
| 200 | |
|---|
| 201 | /** Determine what object to destroy and if we're allowed. |
|---|
| 202 | * Do all matching and permissions checking. Returns the object to be |
|---|
| 203 | * destroyed if all the permissions checks are successful, otherwise |
|---|
| 204 | * return NOTHING. |
|---|
| 205 | */ |
|---|
| 206 | static dbref |
|---|
| 207 | what_to_destroy(dbref player, char *name, int confirm) |
|---|
| 208 | { |
|---|
| 209 | dbref thing; |
|---|
| 210 | |
|---|
| 211 | thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING); |
|---|
| 212 | if (thing == NOTHING) |
|---|
| 213 | return NOTHING; |
|---|
| 214 | |
|---|
| 215 | if (IsGarbage(thing)) { |
|---|
| 216 | notify(player, T("Destroying that again is hardly necessary.")); |
|---|
| 217 | return NOTHING; |
|---|
| 218 | } |
|---|
| 219 | if (God(thing)) { |
|---|
| 220 | notify(player, T("Destroying God would be blasphemous.")); |
|---|
| 221 | return NOTHING; |
|---|
| 222 | } |
|---|
| 223 | /* To destroy, you must either: |
|---|
| 224 | * 1. Control it |
|---|
| 225 | * 2. Control its source or destination if it's an exit |
|---|
| 226 | * 3. Be dealing with a dest-ok thing and pass its lock/destroy |
|---|
| 227 | */ |
|---|
| 228 | if (!controls(player, thing) && |
|---|
| 229 | !(IsExit(thing) && |
|---|
| 230 | (controls(player, Destination(thing)) || |
|---|
| 231 | controls(player, Source(thing)))) && |
|---|
| 232 | !(DestOk(thing) && eval_lock(player, thing, Destroy_Lock))) { |
|---|
| 233 | notify(player, T("Permission denied.")); |
|---|
| 234 | return NOTHING; |
|---|
| 235 | } |
|---|
| 236 | if (thing == PLAYER_START || thing == MASTER_ROOM || thing == BASE_ROOM || |
|---|
| 237 | thing == DEFAULT_HOME || God(thing)) { |
|---|
| 238 | notify(player, T("That is too special to be destroyed.")); |
|---|
| 239 | return NOTHING; |
|---|
| 240 | } |
|---|
| 241 | if (REALLY_SAFE) { |
|---|
| 242 | if (Safe(thing) && !DestOk(thing)) { |
|---|
| 243 | notify(player, |
|---|
| 244 | T |
|---|
| 245 | ("That object is set SAFE. You must set it !SAFE before destroying it.")); |
|---|
| 246 | return NOTHING; |
|---|
| 247 | } |
|---|
| 248 | } else { /* REALLY_SAFE */ |
|---|
| 249 | if (Safe(thing) && !DestOk(thing) && !confirm) { |
|---|
| 250 | notify(player, T("That object is marked SAFE. Use @nuke to destroy it.")); |
|---|
| 251 | return NOTHING; |
|---|
| 252 | } |
|---|
| 253 | } |
|---|
| 254 | /* check to make sure there's no accidental destruction */ |
|---|
| 255 | if (!confirm && !Owns(player, thing) && !DestOk(thing)) { |
|---|
| 256 | notify(player, |
|---|
| 257 | T("That object does not belong to you. Use @nuke to destroy it.")); |
|---|
| 258 | return NOTHING; |
|---|
| 259 | } |
|---|
| 260 | /* what kind of thing we are destroying? */ |
|---|
| 261 | switch (Typeof(thing)) { |
|---|
| 262 | case TYPE_PLAYER: |
|---|
| 263 | if (!IsPlayer(player)) { |
|---|
| 264 | notify(player, T("Programs don't kill people; people kill people!")); |
|---|
| 265 | return NOTHING; |
|---|
| 266 | } |
|---|
| 267 | /* The only player a player can own() is themselves... |
|---|
| 268 | * If they somehow manage to own() another player, they can't |
|---|
| 269 | * nuke that one either...which seems like a good plan, although |
|---|
| 270 | * the error message is a bit confusing. -DTC |
|---|
| 271 | */ |
|---|
| 272 | if (!Wizard(player)) { |
|---|
| 273 | notify(player, T("Sorry, no suicide allowed.")); |
|---|
| 274 | return NOTHING; |
|---|
| 275 | } |
|---|
| 276 | /* Already checked for God(thing), so use Wizard() */ |
|---|
| 277 | if (Wizard(thing) && !God(player)) { |
|---|
| 278 | notify(player, T("Even you can't do that!")); |
|---|
| 279 | return NOTHING; |
|---|
| 280 | } |
|---|
| 281 | if (Connected(thing)) { |
|---|
| 282 | notify(player, |
|---|
| 283 | T("How gruesome. You may not destroy players who are connected.")); |
|---|
| 284 | return NOTHING; |
|---|
| 285 | } |
|---|
| 286 | if (!confirm) { |
|---|
| 287 | notify(player, T("You must use @nuke to destroy a player.")); |
|---|
| 288 | return NOTHING; |
|---|
| 289 | } |
|---|
| 290 | break; |
|---|
| 291 | case TYPE_THING: |
|---|
| 292 | if (!confirm && Wizard(thing)) { |
|---|
| 293 | notify(player, |
|---|
| 294 | T("That object is set WIZARD. You must use @nuke to destroy it.")); |
|---|
| 295 | return NOTHING; |
|---|
| 296 | } |
|---|
| 297 | break; |
|---|
| 298 | case TYPE_ROOM: |
|---|
| 299 | break; |
|---|
| 300 | case TYPE_EXIT: |
|---|
| 301 | break; |
|---|
| 302 | } |
|---|
| 303 | return thing; |
|---|
| 304 | |
|---|
| 305 | } |
|---|
| 306 | |
|---|
| 307 | |
|---|
| 308 | /** User interface to destroy an object. |
|---|
| 309 | * \verbatim |
|---|
| 310 | * This is the top-level function for @destroy. |
|---|
| 311 | * \endverbatim |
|---|
| 312 | * \param player the enactor. |
|---|
| 313 | * \param name name of object to destroy. |
|---|
| 314 | * \param confirm if 1, called with /override (or nuke). |
|---|
| 315 | */ |
|---|
| 316 | void |
|---|
| 317 | do_destroy(dbref player, char *name, int confirm) |
|---|
| 318 | { |
|---|
| 319 | dbref thing; |
|---|
| 320 | thing = what_to_destroy(player, name, confirm); |
|---|
| 321 | if (!GoodObject(thing)) |
|---|
| 322 | return; |
|---|
| 323 | |
|---|
| 324 | /* If thing has already been marked for destruction, go ahead and |
|---|
| 325 | * destroy immediately. |
|---|
| 326 | */ |
|---|
| 327 | if (Going(thing)) { |
|---|
| 328 | free_object(thing); |
|---|
| 329 | notify(player, T("Destroyed.")); |
|---|
| 330 | return; |
|---|
| 331 | } |
|---|
| 332 | /* Present informative messages. */ |
|---|
| 333 | if (!REALLY_SAFE && Safe(thing)) |
|---|
| 334 | notify(player, |
|---|
| 335 | T |
|---|
| 336 | ("Warning: Target is set SAFE, but scheduling for destruction anyway.")); |
|---|
| 337 | switch (Typeof(thing)) { |
|---|
| 338 | case TYPE_ROOM: |
|---|
| 339 | /* wait until dbck */ |
|---|
| 340 | notify_except(Contents(thing), NOTHING, |
|---|
| 341 | T("The room shakes and begins to crumble."), 0); |
|---|
| 342 | if (Owns(player, thing)) |
|---|
| 343 | notify_format(player, |
|---|
| 344 | T("You will be rewarded shortly for %s."), |
|---|
| 345 | object_header(player, thing)); |
|---|
| 346 | else { |
|---|
| 347 | notify_format(player, |
|---|
| 348 | T |
|---|
| 349 | ("The wrecking ball is on its way for %s's %s and its exits."), |
|---|
| 350 | Name(Owner(thing)), object_header(player, thing)); |
|---|
| 351 | notify_format(Owner(thing), |
|---|
| 352 | T("%s has scheduled your room %s to be destroyed."), |
|---|
| 353 | Name(player), object_header(Owner(thing), thing)); |
|---|
| 354 | } |
|---|
| 355 | break; |
|---|
| 356 | case TYPE_PLAYER: |
|---|
| 357 | /* wait until dbck */ |
|---|
| 358 | notify_format(player, |
|---|
| 359 | (DESTROY_POSSESSIONS ? |
|---|
| 360 | (REALLY_SAFE ? |
|---|
| 361 | T |
|---|
| 362 | ("%s and all their (non-SAFE) objects are scheduled to be destroyed.") |
|---|
| 363 | : |
|---|
| 364 | T |
|---|
| 365 | ("%s and all their objects are scheduled to be destroyed.")) |
|---|
| 366 | : T("%s is scheduled to be destroyed.")), |
|---|
| 367 | object_header(player, thing)); |
|---|
| 368 | break; |
|---|
| 369 | case TYPE_THING: |
|---|
| 370 | if (!Owns(player, thing)) { |
|---|
| 371 | notify_format(player, T("%s's %s is scheduled to be destroyed."), |
|---|
| 372 | Name(Owner(thing)), object_header(player, thing)); |
|---|
| 373 | if (!DestOk(thing)) |
|---|
| 374 | notify_format(Owner(thing), |
|---|
| 375 | T("%s has scheduled your %s for destruction."), |
|---|
| 376 | Name(player), object_header(Owner(thing), thing)); |
|---|
| 377 | } else { |
|---|
| 378 | notify_format(player, T("%s is scheduled to be destroyed."), |
|---|
| 379 | object_header(player, thing)); |
|---|
| 380 | } |
|---|
| 381 | break; |
|---|
| 382 | case TYPE_EXIT: |
|---|
| 383 | if (!Owns(player, thing)) { |
|---|
| 384 | notify_format(Owner(thing), |
|---|
| 385 | T("%s has scheduled your %s for destruction."), |
|---|
| 386 | Name(player), object_header(Owner(thing), thing)); |
|---|
| 387 | notify_format(player, |
|---|
| 388 | T("%s's %s is scheduled to be destroyed."), |
|---|
| 389 | Name(Owner(thing)), object_header(player, thing)); |
|---|
| 390 | } else |
|---|
| 391 | notify_format(player, |
|---|
| 392 | T("%s is scheduled to be destroyed."), |
|---|
| 393 | object_header(player, thing)); |
|---|
| 394 | break; |
|---|
| 395 | default: |
|---|
| 396 | do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in do_destroy.")); |
|---|
| 397 | return; |
|---|
| 398 | } |
|---|
| 399 | |
|---|
| 400 | pre_destroy(player, thing); |
|---|
| 401 | return; |
|---|
| 402 | } |
|---|
| 403 | |
|---|
| 404 | |
|---|
| 405 | /** Spare an object from slated destruction. |
|---|
| 406 | * \verbatim |
|---|
| 407 | * This is the top-level function for @undestroy. |
|---|
| 408 | * Not undestroy, quite--it's actually 'remove it from its status as about |
|---|
| 409 | * to be destroyed.' |
|---|
| 410 | * \endverbatim |
|---|
| 411 | * \param player the enactor. |
|---|
| 412 | * \param name name of object to be spared. |
|---|
| 413 | */ |
|---|
| 414 | void |
|---|
| 415 | do_undestroy(dbref player, char *name) |
|---|
| 416 | { |
|---|
| 417 | dbref thing; |
|---|
| 418 | thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING); |
|---|
| 419 | if (!GoodObject(thing)) { |
|---|
| 420 | return; |
|---|
| 421 | } |
|---|
| 422 | if (!controls(player, thing)) { |
|---|
| 423 | notify(player, T("Alas, your efforts of mercy are in vain.")); |
|---|
| 424 | return; |
|---|
| 425 | } |
|---|
| 426 | if (undestroy(player, thing)) { |
|---|
| 427 | notify_format(Owner(thing), |
|---|
| 428 | T("Your %s has been spared from destruction."), |
|---|
| 429 | object_header(Owner(thing), thing)); |
|---|
| 430 | if (player != Owner(thing)) { |
|---|
| 431 | notify_format(player, |
|---|
| 432 | T("%s's %s has been spared from destruction."), |
|---|
| 433 | Name(Owner(thing)), object_header(player, thing)); |
|---|
| 434 | } |
|---|
| 435 | } else { |
|---|
| 436 | notify(player, T("That can't be undestroyed.")); |
|---|
| 437 | } |
|---|
| 438 | } |
|---|
| 439 | |
|---|
| 440 | |
|---|
| 441 | |
|---|
| 442 | /* Section II: Functions that manage the actual work of destroying |
|---|
| 443 | * Objects. |
|---|
| 444 | */ |
|---|
| 445 | |
|---|
| 446 | /* Schedule something to be destroyed, run @adestroy, etc. */ |
|---|
| 447 | static void |
|---|
| 448 | pre_destroy(dbref player, dbref thing) |
|---|
| 449 | { |
|---|
| 450 | dbref tmp; |
|---|
| 451 | if (Going(thing) || IsGarbage(thing)) { |
|---|
| 452 | /* we've already covered this thing. No need to do so again. */ |
|---|
| 453 | return; |
|---|
| 454 | } |
|---|
| 455 | set_flag_internal(thing, "GOING"); |
|---|
| 456 | clear_flag_internal(thing, "GOING_TWICE"); |
|---|
| 457 | |
|---|
| 458 | /* Present informative messages, and do recursive destruction. */ |
|---|
| 459 | switch (Typeof(thing)) { |
|---|
| 460 | case TYPE_ROOM: |
|---|
| 461 | DOLIST(tmp, Exits(thing)) { |
|---|
| 462 | pre_destroy(player, tmp); |
|---|
| 463 | } |
|---|
| 464 | break; |
|---|
| 465 | case TYPE_PLAYER: |
|---|
| 466 | if (DESTROY_POSSESSIONS) { |
|---|
| 467 | for (tmp = 0; tmp < db_top; tmp++) { |
|---|
| 468 | if (Owner(tmp) == thing && |
|---|
| 469 | (tmp != thing) && (!REALLY_SAFE || !Safe(thing))) { |
|---|
| 470 | pre_destroy(player, tmp); |
|---|
| 471 | } |
|---|
| 472 | } |
|---|
| 473 | } |
|---|
| 474 | break; |
|---|
| 475 | case TYPE_THING: |
|---|
| 476 | break; |
|---|
| 477 | case TYPE_EXIT: |
|---|
| 478 | /* This is the only case in which we might end up destroying something |
|---|
| 479 | * whose owner hasn't already been notified. */ |
|---|
| 480 | if ((Owner(thing) != Owner(Source(thing))) && Going(Source(thing))) { |
|---|
| 481 | if (!Owns(player, thing)) { |
|---|
| 482 | notify_format(Owner(thing), |
|---|
| 483 | T("%s has scheduled your %s for destruction."), |
|---|
| 484 | Name(player), object_header(Owner(thing), thing)); |
|---|
| 485 | } |
|---|
| 486 | } |
|---|
| 487 | break; |
|---|
| 488 | default: |
|---|
| 489 | do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in pre_destroy.")); |
|---|
| 490 | return; |
|---|
| 491 | } |
|---|
| 492 | |
|---|
| 493 | if (ADESTROY_ATTR) |
|---|
| 494 | did_it(player, thing, NULL, NULL, NULL, NULL, "ADESTROY", NOTHING); |
|---|
| 495 | |
|---|
| 496 | return; |
|---|
| 497 | |
|---|
| 498 | } |
|---|
| 499 | |
|---|
| 500 | |
|---|
| 501 | /** Spare an object from destruction. |
|---|
| 502 | * Not undestroy, quite--it's actually 'remove it from its status as about |
|---|
| 503 | * to be destroyed.' This is the internal function used in hardcode. |
|---|
| 504 | * \param player the enactor. |
|---|
| 505 | * \param thing dbref of object to be spared. |
|---|
| 506 | * \return 1 successful undestruction. |
|---|
| 507 | * \return 0 thing is not a valid object to undestroy. |
|---|
| 508 | */ |
|---|
| 509 | int |
|---|
| 510 | undestroy(dbref player, dbref thing) |
|---|
| 511 | { |
|---|
| 512 | dbref tmp; |
|---|
| 513 | if (!Going(thing) || IsGarbage(thing)) { |
|---|
| 514 | return 0; |
|---|
| 515 | } |
|---|
| 516 | clear_flag_internal(thing, "GOING"); |
|---|
| 517 | clear_flag_internal(thing, "GOING_TWICE"); |
|---|
| 518 | if (!Halted(thing)) |
|---|
| 519 | (void) queue_attribute_noparent(thing, "STARTUP", thing); |
|---|
| 520 | /* undestroy owner, if need be. */ |
|---|
| 521 | if (Going(Owner(thing))) { |
|---|
| 522 | if (Owner(thing) != player) { |
|---|
| 523 | notify_format(player, |
|---|
| 524 | T("%s has been spared from destruction."), |
|---|
| 525 | object_header(player, Owner(thing))); |
|---|
| 526 | notify_format(Owner(thing), |
|---|
| 527 | T("You have been spared from destruction by %s."), |
|---|
| 528 | Name(player)); |
|---|
| 529 | } else { |
|---|
| 530 | notify(player, T("You have been spared from destruction.")); |
|---|
| 531 | } |
|---|
| 532 | (void) undestroy(player, Owner(thing)); |
|---|
| 533 | } |
|---|
| 534 | switch (Typeof(thing)) { |
|---|
| 535 | case TYPE_PLAYER: |
|---|
| 536 | if (DESTROY_POSSESSIONS) |
|---|
| 537 | /* Undestroy all objects owned by players, except exits that are in |
|---|
| 538 | * rooms owned by other players that are set GOING, since those will |
|---|
| 539 | * be purged when the room is purged. |
|---|
| 540 | */ |
|---|
| 541 | for (tmp = 0; tmp < db_top; tmp++) { |
|---|
| 542 | if (Owns(thing, tmp) && |
|---|
| 543 | (tmp != thing) && |
|---|
| 544 | !(IsExit(tmp) && !Owns(thing, Source(tmp)) && Going(Source(tmp)))) { |
|---|
| 545 | (void) undestroy(player, tmp); |
|---|
| 546 | } |
|---|
| 547 | } |
|---|
| 548 | break; |
|---|
| 549 | case TYPE_THING: |
|---|
| 550 | break; |
|---|
| 551 | case TYPE_EXIT: |
|---|
| 552 | /* undestroy containing room. */ |
|---|
| 553 | if (Going(Source(thing))) { |
|---|
| 554 | (void) undestroy(player, Source(thing)); |
|---|
| 555 | notify_format(player, |
|---|
| 556 | T("The room %s has been spared from destruction."), |
|---|
| 557 | object_header(player, Source(thing))); |
|---|
| 558 | if (Owner(Source(thing)) != player) { |
|---|
| 559 | notify_format(Owner(Source(thing)), |
|---|
| 560 | T("The room %s has been spared from destruction by %s."), |
|---|
| 561 | object_header(Owner(Source(thing)), Source(thing)), |
|---|
| 562 | Name(player)); |
|---|
| 563 | } |
|---|
| 564 | } |
|---|
| 565 | break; |
|---|
| 566 | case TYPE_ROOM: |
|---|
| 567 | /* undestroy exits in this room, except exits that are going to be |
|---|
| 568 | * destroyed anyway due to a GOING player. |
|---|
| 569 | */ |
|---|
| 570 | DOLIST(tmp, Exits(thing)) { |
|---|
| 571 | if (DESTROY_POSSESSIONS ? (!Going(Owner(tmp)) || Safe(tmp)) : 1) { |
|---|
| 572 | (void) undestroy(player, tmp); |
|---|
| 573 | } |
|---|
| 574 | } |
|---|
| 575 | break; |
|---|
| 576 | default: |
|---|
| 577 | do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in un_destroy.")); |
|---|
| 578 | return 0; |
|---|
| 579 | } |
|---|
| 580 | return 1; |
|---|
| 581 | } |
|---|
| 582 | |
|---|
| 583 | |
|---|
| 584 | /* Does the real work of freeing all the memory and unlinking an object. |
|---|
| 585 | * This is going to have to be very tightly coupled with the implementation; |
|---|
| 586 | * if the database format changes, this will likely have to change too. |
|---|
| 587 | */ |
|---|
| 588 | static void |
|---|
| 589 | free_object(dbref thing) |
|---|
| 590 | { |
|---|
| 591 | dbref i, loc; |
|---|
| 592 | if (!GoodObject(thing)) |
|---|
| 593 | return; |
|---|
| 594 | local_data_free(thing); |
|---|
| 595 | switch (Typeof(thing)) { |
|---|
| 596 | case TYPE_THING: |
|---|
| 597 | clear_thing(thing); |
|---|
| 598 | break; |
|---|
| 599 | case TYPE_PLAYER: |
|---|
| 600 | clear_player(thing); |
|---|
| 601 | break; |
|---|
| 602 | case TYPE_EXIT: |
|---|
| 603 | clear_exit(thing); |
|---|
| 604 | break; |
|---|
| 605 | case TYPE_ROOM: |
|---|
| 606 | clear_room(thing); |
|---|
| 607 | break; |
|---|
| 608 | default: |
|---|
| 609 | do_log(LT_ERR, NOTHING, NOTHING, T("Unknown type on #%d in free_object."), |
|---|
| 610 | thing); |
|---|
| 611 | return; |
|---|
| 612 | } |
|---|
| 613 | change_quota(Owner(thing), QUOTA_COST); |
|---|
| 614 | do_halt(thing, "", thing); |
|---|
| 615 | /* The equivalent of an @drain/any/all: */ |
|---|
| 616 | dequeue_semaphores(thing, NULL, INT_MAX, 1, 1); |
|---|
| 617 | |
|---|
| 618 | /* if something is zoned or parented or linked or chained or located |
|---|
| 619 | * to/in destroyed object, undo */ |
|---|
| 620 | for (i = 0; i < db_top; i++) { |
|---|
| 621 | if (Zone(i) == thing) { |
|---|
| 622 | Zone(i) = NOTHING; |
|---|
| 623 | } |
|---|
| 624 | if (Parent(i) == thing) { |
|---|
| 625 | Parent(i) = NOTHING; |
|---|
| 626 | } |
|---|
| 627 | if (Home(i) == thing) { |
|---|
| 628 | switch (Typeof(i)) { |
|---|
| 629 | case TYPE_PLAYER: |
|---|
| 630 | case TYPE_THING: |
|---|
| 631 | Home(i) = DEFAULT_HOME; |
|---|
| 632 | break; |
|---|
| 633 | case TYPE_EXIT: |
|---|
| 634 | /* Huh. An exit that claims to be from here, but wasn't linked |
|---|
| 635 | * in properly. */ |
|---|
| 636 | do_rawlog(LT_ERR, |
|---|
| 637 | T("ERROR: Exit %s leading from invalid room #%d destroyed."), |
|---|
| 638 | unparse_object(GOD, i), thing); |
|---|
| 639 | free_object(i); |
|---|
| 640 | break; |
|---|
| 641 | case TYPE_ROOM: |
|---|
| 642 | /* Hrm. It claims we're an exit from it, but we didn't agree. |
|---|
| 643 | * Clean it up anyway. */ |
|---|
| 644 | do_log(LT_ERR, NOTHING, NOTHING, |
|---|
| 645 | T("Found a destroyed exit #%d in room #%d"), thing, i); |
|---|
| 646 | break; |
|---|
| 647 | } |
|---|
| 648 | } |
|---|
| 649 | /* The location check MUST be done AFTER the home check. */ |
|---|
| 650 | if (Location(i) == thing) { |
|---|
| 651 | switch (Typeof(i)) { |
|---|
| 652 | case TYPE_PLAYER: |
|---|
| 653 | case TYPE_THING: |
|---|
| 654 | /* Huh. It thought it was here, but we didn't agree. */ |
|---|
| 655 | enter_room(i, Home(i), 0); |
|---|
| 656 | break; |
|---|
| 657 | case TYPE_EXIT: |
|---|
| 658 | /* If our destination is destroyed, then we relink to the |
|---|
| 659 | * source room (so that the exit can't be stolen). Yes, it's |
|---|
| 660 | * inconsistent with the treatment of exits leading from |
|---|
| 661 | * destroyed rooms, but it's a lot better than turning exits |
|---|
| 662 | * into nasty limbo exits. |
|---|
| 663 | */ |
|---|
| 664 | Destination(i) = Source(i); |
|---|
| 665 | break; |
|---|
| 666 | case TYPE_ROOM: |
|---|
| 667 | /* Just remove a dropto. */ |
|---|
| 668 | Location(i) = NOTHING; |
|---|
| 669 | break; |
|---|
| 670 | } |
|---|
| 671 | } |
|---|
| 672 | if (Next(i) == thing) { |
|---|
| 673 | Next(i) = NOTHING; |
|---|
| 674 | } |
|---|
| 675 | } |
|---|
| 676 | |
|---|
| 677 | /* chomp chomp */ |
|---|
| 678 | atr_free_all(thing); |
|---|
| 679 | List(thing) = NULL; |
|---|
| 680 | /* don't eat name otherwise examine will crash */ |
|---|
| 681 | |
|---|
| 682 | free_locks(Locks(thing)); |
|---|
| 683 | Locks(thing) = NULL; |
|---|
| 684 | |
|---|
| 685 | s_Pennies(thing, 0); |
|---|
| 686 | Owner(thing) = GOD; |
|---|
| 687 | Parent(thing) = NOTHING; |
|---|
| 688 | Zone(thing) = NOTHING; |
|---|
| 689 | remove_all_obj_chan(thing); |
|---|
| 690 | |
|---|
| 691 | switch (Typeof(thing)) { |
|---|
| 692 | /* Make absolutely sure we are removed from Location's content or |
|---|
| 693 | exit list. If we are in a room we own and destroy_possessions |
|---|
| 694 | is yes, this can happen, causing much ickyness: All garbage |
|---|
| 695 | items would be in DEFAULT_HOME. */ |
|---|
| 696 | case TYPE_PLAYER: |
|---|
| 697 | case TYPE_THING: |
|---|
| 698 | loc = Location(thing); |
|---|
| 699 | if (GoodObject(loc)) |
|---|
| 700 | Contents(loc) = remove_first(Contents(loc), thing); |
|---|
| 701 | if (Typeof(thing) == TYPE_THING) |
|---|
| 702 | current_state.things--; |
|---|
| 703 | else |
|---|
| 704 | current_state.players--; |
|---|
| 705 | break; |
|---|
| 706 | case TYPE_EXIT: /* This probably won't be needed, but lets make sure */ |
|---|
| 707 | loc = Source(thing); |
|---|
| 708 | if (GoodObject(loc)) |
|---|
| 709 | Exits(loc) = remove_first(Exits(loc), thing); |
|---|
| 710 | current_state.exits--; |
|---|
| 711 | break; |
|---|
| 712 | case TYPE_ROOM: |
|---|
| 713 | current_state.rooms--; |
|---|
| 714 | break; |
|---|
| 715 | default: |
|---|
| 716 | /* Do nothing for rooms. */ |
|---|
| 717 | break; |
|---|
| 718 | } |
|---|
| 719 | |
|---|
| 720 | Type(thing) = TYPE_GARBAGE; |
|---|
| 721 | destroy_flag_bitmask(Flags(thing)); |
|---|
| 722 | Flags(thing) = NULL; |
|---|
| 723 | destroy_flag_bitmask(Powers(thing)); |
|---|
| 724 | Powers(thing) = NULL; |
|---|
| 725 | Location(thing) = NOTHING; |
|---|
| 726 | set_name(thing, "Garbage"); |
|---|
| 727 | Exits(thing) = NOTHING; |
|---|
| 728 | Home(thing) = NOTHING; |
|---|
| 729 | CreTime(thing) = 0; /* Prevents it from matching objids */ |
|---|
| 730 | |
|---|
| 731 | clear_objdata(thing); |
|---|
| 732 | |
|---|
| 733 | Next(thing) = first_free; |
|---|
| 734 | first_free = thing; |
|---|
| 735 | |
|---|
| 736 | current_state.garbage++; |
|---|
| 737 | |
|---|
| 738 | } |
|---|
| 739 | |
|---|
| 740 | static void |
|---|
| 741 | empty_contents(dbref thing) |
|---|
| 742 | { |
|---|
| 743 | /* Destroy any exits they may be carrying, send everything else home. */ |
|---|
| 744 | dbref first; |
|---|
| 745 | dbref rest; |
|---|
| 746 | dbref target; |
|---|
| 747 | notify_except(Contents(thing), NOTHING, |
|---|
| 748 | T |
|---|
| 749 | ("The floor disappears under your feet, you fall through NOTHINGness and then:"), |
|---|
| 750 | 0); |
|---|
| 751 | first = Contents(thing); |
|---|
| 752 | Contents(thing) = NOTHING; |
|---|
| 753 | /* send all objects to nowhere */ |
|---|
| 754 | DOLIST(rest, first) { |
|---|
| 755 | Location(rest) = NOTHING; |
|---|
| 756 | } |
|---|
| 757 | /* now send them home */ |
|---|
| 758 | while (first != NOTHING) { |
|---|
| 759 | rest = Next(first); |
|---|
| 760 | /* if home is in thing set it to limbo */ |
|---|
| 761 | switch (Typeof(first)) { |
|---|
| 762 | case TYPE_EXIT: /* if holding exits, destroy it */ |
|---|
| 763 | free_object(first); |
|---|
| 764 | break; |
|---|
| 765 | case TYPE_THING: /* move to home */ |
|---|
| 766 | case TYPE_PLAYER: |
|---|
| 767 | /* Make sure the home is a reasonable object. */ |
|---|
| 768 | if (!GoodObject(Home(first)) || IsExit(Home(first)) || |
|---|
| 769 | Home(first) == thing) |
|---|
| 770 | Home(first) = DEFAULT_HOME; |
|---|
| 771 | target = Home(first); |
|---|
| 772 | /* If home isn't a good place to send it, send it to DEFAULT_HOME. */ |
|---|
| 773 | if (!GoodObject(target) || recursive_member(target, first, 0)) |
|---|
| 774 | target = DEFAULT_HOME; |
|---|
| 775 | if (target != NOTHING) { |
|---|
| 776 | /* Use enter_room() on everything so that AENTER and such |
|---|
| 777 | * are all triggered properly. */ |
|---|
| 778 | enter_room(first, target, 0); |
|---|
| 779 | } |
|---|
| 780 | break; |
|---|
| 781 | } |
|---|
| 782 | first = rest; |
|---|
| 783 | } |
|---|
| 784 | } |
|---|
| 785 | |
|---|
| 786 | static void |
|---|
| 787 | clear_thing(dbref thing) |
|---|
| 788 | { |
|---|
| 789 | dbref loc; |
|---|
| 790 | int a; |
|---|
| 791 | /* Remove object from room's contents */ |
|---|
| 792 | loc = Location(thing); |
|---|
| 793 | if (loc != NOTHING) { |
|---|
| 794 | Contents(loc) = remove_first(Contents(loc), thing); |
|---|
| 795 | } |
|---|
| 796 | /* Remove object from any following chains */ |
|---|
| 797 | clear_followers(thing, 0); |
|---|
| 798 | clear_following(thing, 0); |
|---|
| 799 | /* give player money back */ |
|---|
| 800 | giveto(Owner(thing), (a = Pennies(thing))); |
|---|
| 801 | empty_contents(thing); |
|---|
| 802 | clear_flag_internal(thing, "PUPPET"); |
|---|
| 803 | if (!Quiet(thing) && !Quiet(Owner(thing))) |
|---|
| 804 | notify_format(Owner(thing), |
|---|
| 805 | T("You get your %d %s deposit back for %s."), |
|---|
| 806 | a, ((a == 1) ? MONEY : MONIES), |
|---|
| 807 | object_header(Owner(thing), thing)); |
|---|
| 808 | } |
|---|
| 809 | |
|---|
| 810 | static void |
|---|
| 811 | clear_player(dbref thing) |
|---|
| 812 | { |
|---|
| 813 | dbref i; |
|---|
| 814 | ATTR *atemp; |
|---|
| 815 | char alias[BUFFER_LEN + 1]; |
|---|
| 816 | |
|---|
| 817 | /* Clear out mail. */ |
|---|
| 818 | do_mail_clear(thing, NULL); |
|---|
| 819 | do_mail_purge(thing); |
|---|
| 820 | malias_cleanup(thing); |
|---|
| 821 | |
|---|
| 822 | /* Chown any chat channels they own to God */ |
|---|
| 823 | chan_chownall(thing, GOD); |
|---|
| 824 | |
|---|
| 825 | /* Clear out names from the player list */ |
|---|
| 826 | delete_player(thing, NULL); |
|---|
| 827 | if ((atemp = atr_get_noparent(thing, "ALIAS")) != NULL) { |
|---|
| 828 | strcpy(alias, atr_value(atemp)); |
|---|
| 829 | delete_player(thing, alias); |
|---|
| 830 | } |
|---|
| 831 | /* Do all the thing-esque manipulations. */ |
|---|
| 832 | clear_thing(thing); |
|---|
| 833 | |
|---|
| 834 | /* Deal with objects owned by the player. */ |
|---|
| 835 | for (i = 0; i < db_top; i++) { |
|---|
| 836 | if (Owner(i) == thing && i != thing) { |
|---|
| 837 | if (DESTROY_POSSESSIONS ? (REALLY_SAFE ? Safe(i) : 0) : 1) { |
|---|
| 838 |
|---|