root/1.8.3/tags/p6/src/destroy.c

Revision 1032, 44.0 KB (checked in by shawnw, 18 months ago)

Merged 1.8.3p4 into trunk

Line 
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
78dbref first_free = NOTHING;   /**< Object at top of free list */
79
80static dbref what_to_destroy(dbref player, char *name, int confirm);
81static void pre_destroy(dbref player, dbref thing);
82static void free_object(dbref thing);
83static void empty_contents(dbref thing);
84static void clear_thing(dbref thing);
85static void clear_player(dbref thing);
86static void clear_room(dbref thing);
87static void clear_exit(dbref thing);
88
89static void check_fields(void);
90static void check_connected_rooms(void);
91static void mark_connected(dbref loc);
92static void check_connected_marks(void);
93static void mark_contents(dbref loc);
94static void check_contents(void);
95static void check_locations(void);
96static void check_zones(void);
97static int attribute_owner_helper
98  (dbref player, dbref thing, dbref parent, char const *pattern, ATTR *atr,
99   void *args);
100
101extern void remove_all_obj_chan(dbref thing);
102extern void chan_chownall(dbref old, dbref new);
103
104extern 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 */
206static dbref
207what_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 */
316void
317do_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 */
414void
415do_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. */
447static void
448pre_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 */
509int
510undestroy(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 */
588static void
589free_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
740static void
741empty_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
786static void
787clear_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
810static void
811clear_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