/*
    Mudadmin.m  Class definition.
    Copyright (C) 1995  David Flater.

    This program 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
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "cheezmud.h"

@implementation Mudadmin

+ new
{
  self = [super new];
  capacity = 1000000;
  monitor_heartbeat = ignore_attacks = 0;
  return self;
}

- hit: fromwho: (float) damage
{
  if (dead)
    return self;
  if (!ignore_attacks)
    [enemies addIfAbsent: fromwho];
  [location emote: self: "suffer": "suffers": " no damage"];
  return self;
}

- help
{
  [self echo: "Global mud administrator commands:"];
  [self echo: "  listworld -- list all objects in the world"];
  [self echo:
    "  halt -- shut down the mud immediately, without saving players"];
  [self echo:
    "  savehalt -- save players, then halt"];
  [self echo:
    "  goto name -- magically go to a particular person, place, or thing"];
  [self echo:
    "  goto void -- go to nowhere (NULL)"];
  [self echo:
    "  create name indef/def/description -- create an inert object"];
  [self echo:
    "  heart -- toggle audible heartbeat"];
  [self echo:
    "  nice -- toggle ignore attacks"];
  [self echo:
    "  summon name -- teleport in the specified object from wherever it is"];
  [self echo:
    "  fetch name -- teleport it into your inventory, if possible"];
/*  [self echo: */
/*    "  clone name -- fetch a copy of an object"]; */
  [self echo:
    "  see name -- see something that is far away"];
  [self echo:
    "  audit name -- audit / unaudit a player (or whatever)"];
  [self echo:
    "  adduser name -- add a new player with password same as username"];
  [self echo:
    "  torch name -- delete a player"];
  [self echo:
    "  heal name -- heal a player or monster"];
  [self echo: "Localized mud administrator commands:"];
  [self echo:
    "  free name -- destroy it"];
  [self echo:
    "  stash name -- teleport it to dave_workshop"];
  [self echo:
    "  havoc -- make everybody attack everybody (for testing, of course)"];
  [super help];
  return self;
}

- (int) level
{
  return -1;
}

- (float) maxstamina
{
  return 30.0;
}

- halt
{
  cheezlog ("Mud halted by mud admin");
  exit (0);
  return self;
}

- savehalt
{
  checkpoint ();
  return [self halt];
}

- heart
{
  monitor_heartbeat = 1 - monitor_heartbeat;
  return self;
}

- nice
{
  ignore_attacks = 1 - ignore_attacks;
  return self;
}

/*  The mud admin's examine gives more information than a player's */
- examine: who: dobj
{
  char temp[160];
  [super examine: who: dobj];
  sprintf (temp, "Name:  '%s'  Class:  '%s'", [dobj mudname], [dobj name]);
  [self echo: temp];
  sprintf (temp, "Global reference:  %s %d", [dobj mudname],
    global_number ([dobj mudname], dobj));
  [self echo: temp];
  if (location) {
    if ([dobj getlocation] == location) {
      sprintf (temp, "'Get' reference:  %s %d", [dobj mudname],
        generic_number ([location contents], [dobj mudname], dobj));
      [self echo: temp];
    } else if ([dobj getlocation] == self) {
      sprintf (temp, "'Drop' reference:  %s %d", [dobj mudname],
        generic_number ([self contents], [dobj mudname], dobj));
      [self echo: temp];
    }
    sprintf (temp, "Normal reference:  %s %d", [dobj mudname],
      generic_number_cascade ([self contents], [location contents],
      [dobj mudname], dobj));
    [self echo: temp];
  } else {
    sprintf (temp, "Normal reference:  %s %d", [dobj mudname],
      generic_number ([self contents], [dobj mudname], dobj));
    [self echo: temp];
  }
  return self;
}

/*  Parse the command line. */
- (int) ohce: (char *) text
{
  /*  Special mud administrator commands. */
  if (!strncmp (text, "create", 6)) {
    char name[80], *newindef, *newdef, *newdesc;
    if (strlen (text) < 8)
      [self echo: "Create what?"];
    else {
      if ((sscanf (text+7, "%s", name) != 1) ||
          (!(newindef = strchr (text+7, ' ')))) {
        [self echo: "Malformed command."];
        return 1;
      }
      newindef++;
      if (!(newdef = strchr (newindef, '/'))) {
        [self echo: "Malformed command."];
        return 1;
      }
      *(newdef++) = '\0';
      if (!(newdesc = strchr (newdef, '/'))) {
        [self echo: "Malformed command."];
        return 1;
      }
      *(newdesc++) = '\0';
      [self create: name: newindef: newdef: newdesc];
    }
    return 1;
  }

  if (!strncmp (text, "adduser", 7)) {
    char name[80], fname[80];
    FILE *fp;
    if (sscanf (text, "adduser %s", name) != 1) {
      [self echo: "Malformed command."];
      return 1;
    }
    sprintf (fname, "db/%s", name);
    if ((fp = fopen (fname, "r"))) {
      fclose (fp);
      [self echo: "Already exists!"];
      return 1;
    }
    assert (fp = fopen (fname, "w"));
    fprintf (fp, "%s\n", name);
    fprintf (fp, "%s\n", "This is a newbie player.");
    fprintf (fp, "start\n15.0 0\n");
    fclose (fp);
    set_password (name, name);
    [self echo: "Player added."];
    return 1;
  }

  if (!strncmp (text, "torch", 5)) {
    char name[80], fname[80];
    FILE *fp;
    id sucker;
    if (sscanf (text, "torch %s", name) != 1) {
      [self echo: "Malformed command."];
      return 1;
    }
    if (!strcmp (name, mudadmin)) {
      [self echo: "Idiot!"];
      return 1;
    }
    sprintf (fname, "db/%s", name);
    if (!(fp = fopen (fname, "r"))) {
      [self echo: "Does not exist!"];
      return 1;
    }
    fclose (fp);

    [location emote: self: "weave": "weaves": " a deadly spell"];

    /*  If the player is logged in, torch the sucker */
    if ((sucker = global_find (name, 1))) {
      [[sucker getlocation] emote: sucker: "suddenly burst into flames":
        "suddenly bursts into flames": ""];
      [[sucker getlocation] emote: sucker: "are": "is":
        " consumed by hellfire"];
      [sucker logout];
    }

    unlink (fname);
    delete_user (name);
    return 1;
  }


  if ([self doadmin: text])
    return 1;

  /*  Regular player commands (inherited) */
  return [super ohce: text];
}

/* This nifty function insures that we always arrive in a room and not */
/* in somebody's saddlebag. */
- goto: dobj
{
  if (!dobj) {
    [self teleport: NULL];
    return self;
  }
  while (dobj) {
    if ([dobj isKindOf: [Room class]])
      break;
    dobj = [dobj getlocation];
  }
  [self teleport: dobj];
  return self;
}

- see: dobj
{
  [location emote: self: "seem": "seems": " to zone out for a moment"];
  while (dobj) {
    if ([dobj isKindOf: [Room class]])
      break;
    dobj = [dobj getlocation];
  }
  if (!dobj) {
    [self echo:
"This place is without form, and void.  You cannot see or hear or feel\n\
anything, and you are no longer sure if you even exist."];
    return self;
  }
  [self echo: [dobj def]];
  [self echo: [dobj longdesc]];
  [dobj listcontents: self];
  return self;
}

- summon: dobj
{
  if ([dobj isKindOf: [Room class]]) {
    [self echo: "You can't summon a room.  That would be stupid."];
    return self;
  }
  [location emote: self: "mutter": "mutters": " some mystic words"];
  [dobj teleport: location];
  return self;
}

- heal: dobj
{
  if (![dobj isKindOf: [Fighter class]]) {
    [self echo: "You can't heal that."];
    return self;
  }
  [location emote: self: "mutter": "mutters": " some mystic words"];
  [dobj heal];
  return self;
}

- stash: dobj
{
  [location emote: self: "wiggle some fingers at": "wiggles some fingers at":
    dobj: ""];
  [dobj teleport: global_find ("dave_workshop", 1)];
  return self;
}

- free: dobj
{
  [location emote: self: "cast": "casts": " a powerful spell"];
  [dobj logout];
  return self;
}

- listworld
{
  int i,n = [world size];
  for(i=0;i<n;i++) {
    id whatever = [world at:i];
    text_sock_write (tty, [whatever mudname]);
    text_sock_write (tty, "\t");
  }
  text_sock_write (tty, "\n");
  return self;
}

- create: (char *) name: (char *) newindef: (char *) newdef: (char *) newdesc
{
  [location emote: self: "wave": "waves":
      " dramatically at the ground"];
  return [[[[[[Nonroom new] setmudname: name] setindef: newindef]
    setdef: newdef] setlongdesc: newdesc] teleport: location];
}

- (void) heartbeat
{
  if (dead)
    return;
  if (monitor_heartbeat)
    [self echo: "Thump"];
  [super heartbeat];
}

- havoc
{
  id cnts;
  int i,j,n;

  if (!location)
    return self;

  [location emote: self: "cry": "cries": " havoc"];
  cnts = [location contents];
  n = [cnts size];
  for(i=0;i<n;i++) {
    id whatever = [cnts at:i];
    if ([whatever isKindOf: [Fighter class]]) {
      for(j=0;j<n;j++) {
	id w2 = [cnts at:j];
	if (whatever == w2)
	  continue;
	if ([w2 isKindOf: [Fighter class]])
	  [w2 unclue: whatever];
      }
    }
  }
  return self;
}

- fetch: dobj
{
  if ([dobj isKindOf: [Room class]]) {
    [self echo: "You can't fetch a room.  That would be stupid."];
    return self;
  }
  if ([dobj isKindOf: [Fighter class]]) {
    [self echo: "You can't fetch that.  Use summon instead."];
    return self;
  }
  [location emote: self: "mutter": "mutters": " some mystic words"];
  [[dobj getlocation] emote: dobj: "vanish": "vanishes":
      " into a cloud of magic smoke"];
  [dobj setlocation: self];
  [location emote: self: "yank": "yanks": dobj:
    " out of a cloud of magic smoke"];
  return self;
}

- audit: dobj
{
  if (dobj == self) {
    [self echo: "Idiot!"];
    return self;
  }
  [dobj toggle_audit];
  [self echo: "OK"];
  return self;
}

/*  Cloning has been disabled because it is more of a maintenance hassle */
/*  to make sure that cloning works right for every class of object than */
/*  it is worth. */
#if 0
- clone: dobj
{
  if ([dobj isKindOf: [Room class]]) {
    [self echo: "You can't clone a room.  That would be stupid."];
    return self;
  }
  if ([dobj isKindOf: [Player class]]) {
    [self echo: "You can't clone a player.  That would be stupid."];
    return self;
  }
  [location emote: self: "cast": "casts": " a powerful spell"];
  [[dobj clone] setlocation: self];
  [location emote: self: "yank": "yanks": dobj:
    " out of a cloud of magic smoke"];
  return self;
}
#endif

/*  This is similar to resolve_action except that it simply says yes or no */
/*  to whether the action is a valid mud admin action. */
/*      0   Not supported. */
/*      1   Supported, global. */
/*      2   Supported, local. */
- (int) check_admin_action: (char *) action: (int) numargs
{
  if (numargs == 1) {
    if (!strcmp (action, "listworld"))
      return 1;
    if (!strcmp (action, "halt"))
      return 1;
    if (!strcmp (action, "savehalt"))
      return 1;
    if (!strcmp (action, "heart"))
      return 1;
    if (!strcmp (action, "nice"))
      return 1;
    if (!strcmp (action, "havoc"))
      return 1;
  }
  if (numargs == 2) {
    if (!strcmp (action, "summon"))
      return 1;
    if (!strcmp (action, "heal"))
      return 1;
    if (!strcmp (action, "audit"))
      return 1;
    if (!strcmp (action, "goto"))
      return 1;
    if (!strcmp (action, "see"))
      return 1;
    if (!strcmp (action, "clone"))
      return 1;
    if (!strcmp (action, "fetch"))
      return 1;
    if (!strcmp (action, "free"))
      return 2;
    if (!strcmp (action, "stash"))
      return 2;
  }
  return 0;
}

/*  Do a special mud administrator action.  Compare and contrast with do */
/*  in Nonroom.m.  This one does not allow actions to be overridden and */
/*  resolves object references against the whole world instead of just the */
/*  room and inventory.  Note also that mud admin actions implicitly take */
/*  self as who and have one less argument than is claimed.  (Sorry....) */
- (int) doadmin: (char *) someaction
{
  id dobj = NULL;  /* Gets rid of stupid compiler warning */
  SEL a;
  char verb[80], directobject[80];
  int numargs, number;
  numargs = sscanf (someaction, "%s %s %d", verb, directobject, &number);
  switch (numargs) {
  case 1:
    number = 1;
    break;
  case 2:
    number = 1;
    /*  Fall through */
  case 3:
    numargs = 2;

    switch ([self check_admin_action: verb: numargs]) {
    case 1: /* global */
      /*  Goto void is special. */
      if ((!strcmp (verb, "goto")) && (!strcmp (directobject, "void")))
        dobj = NULL;
      else {
        dobj = global_find (directobject, number);
        if (!dobj) {
          [self echo: "Not found."];
          return 1;
        }
      }
      break;

    case 2: /* local */
      if (!location)
        dobj = [self find: directobject: number];
      else
        dobj = generic_find_cascade ([self contents], [location contents],
        directobject, number);
      if (!dobj) {
        [self echo: "Not found."];
        return 1;
      }
    }

    break;
  default:
    return 0;
  }

  /*  It is important to verify that the action is one of the accepted ones */
  /*  rather than simply to check if the message could be received; otherwise */
  /*  none of the regular actions that we _want_ to be overridden will work. */

  if ([self check_admin_action: verb: numargs]) {
    char temp[80];
    strcpy (temp, verb);
    if (numargs > 1)
      strcat (temp, ":");
    a = [Object findSel:temp];
    if (numargs == 1)
      [self perform: a];
    else
      [self perform: a with: dobj];
    return 1;
  }

  /*  Try to print a helpful message for missing args. */
  if (numargs == 1) {
    if ([self check_admin_action: verb: 2]) {
      char temp[80];
      sprintf (temp, "%s what?", capitalize (verb));
      [self echo: temp];
      return 1;
    }
  }
  if (numargs == 2) {
    if ([self check_admin_action: verb: 1]) {
      [self echo: "That action does not get a direct object."];
      return 1;
    }
  }

  return 0;
}

@end