dbx/cnf/
dbx/lib/
dbx/lib/misc/
dbx/lib/text/help/
dbx/lib/world/
dbx/lib/world/qst/
dbx/src/
/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

/***************************************************************************
 *  The MOBprograms have been contributed by N'Atas-ha.  Any support for   *
 *  these routines should not be expected from Merc Industries.  However,  *
 *  under no circumstances should the blame for bugs, etc be placed on     *
 *  Merc Industries.  They are not guaranteed to work on all systems due   *
 *  to their frequent use of strxxx functions.  They are also not the most *
 *  efficient way to perform their tasks, but hopefully should be in the   *
 *  easiest possible way to install and begin using. Documentation for     *
 *  such installation can be found in INSTALL.  Enjoy........    N'Atas-Ha *
 ***************************************************************************/

#include "conf.h"
#include "sysdep.h"


#include "structs.h"
#include "dg_scripts.h"
#include "db.h"
#include "utils.h"
#include "handler.h"
#include "interpreter.h"
#include "comm.h"
#include "spells.h"


extern struct descriptor_data *descriptor_list;
extern sh_int find_target_room(char_data * ch, char *rawroomstr);
extern struct index_data *mob_index;
extern struct room_data *world;
extern int dg_owner_purged;

void sub_write(char *arg, char_data *ch, byte find_invis, int targets);

/*
 * Local functions.
 */

/* attaches mob's name and vnum to msg and sends it to script_log */
void mob_log(char_data *mob, char *msg)
{
    char buf[MAX_INPUT_LENGTH + 100];

    void script_log(char *msg);

    sprintf(buf, "Mob (%s, VNum %d): %s",
	    GET_SHORT(mob), GET_MOB_VNUM(mob), msg);
    script_log(buf);
}
/*
** macro to determine if a mob is permitted to use these commands
*/
#define MOB_OR_IMPL(ch) \
  (IS_NPC(ch) && (!(ch)->desc || GET_LEVEL((ch)->desc->original)>=LVL_IMPL))



/* mob commands */

/* prints the argument to all the rooms aroud the mobile */
ACMD(do_masound)
{
    sh_int was_in_room;
    int  door;
  
    if (!MOB_OR_IMPL(ch))
    {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }

    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    if (!*argument)
    {
	mob_log(ch, "masound called with no argument");
	return;
    }

    skip_spaces(&argument);
  
    was_in_room = IN_ROOM(ch);
    for (door = 0; door < NUM_OF_DIRS; door++)
    {
	struct room_direction_data *exit;
    
	if (((exit = world[was_in_room].dir_option[door]) != NULL) &&
	    exit->to_room != NOWHERE && exit->to_room != was_in_room)
	{
	    IN_ROOM(ch) = exit->to_room;
	    sub_write(argument, ch, TRUE, TO_ROOM);
	}
    }
  
    IN_ROOM(ch) = was_in_room;
}


/* lets the mobile kill any player or mobile without murder*/
ACMD(do_mkill)
{
    char arg[MAX_INPUT_LENGTH];
    char_data *victim;
  
    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }

    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    one_argument(argument, arg);
    
    if (!*arg) {
	mob_log(ch, "mkill called with no argument");
	return;
    }

    if (*arg == UID_CHAR) {
      if (!(victim = get_char(arg))) {
        sprintf(buf, "mkill: victim (%s) not found",arg);
	mob_log(ch, buf);
	return;
      }
    } else if (!(victim = get_char_room_vis(ch, arg))) {
        sprintf(buf, "mkill: victim (%s) not found",arg);
	mob_log(ch, buf);
	return;
    }

    if (victim == ch) {
	mob_log(ch, "mkill: victim is self");
	return;
    }

    if (IS_AFFECTED(ch, AFF_MAJIN) && ch->master == victim ) {
	mob_log(ch, "mkill: MAJINed mob attacking master");
	return;
    }

    if (FIGHTING(ch)) {
	mob_log(ch, "mkill: already fighting");
	return;
    }
    
    hit(ch, victim, TYPE_UNDEFINED);
    return;
}


/*
 * lets the mobile destroy an object in its inventory
 * it can also destroy a worn object and it can destroy 
 * items using all.xxxxx or just plain all of them
 */
ACMD(do_mjunk)
{
    char arg[MAX_INPUT_LENGTH];
    int pos;
    obj_data *obj;
    obj_data *obj_next;
  
    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;

    one_argument(argument, arg);
  
    if (!*arg) {
	mob_log(ch, "mjunk called with no argument");
	return;
    }
  
    if (find_all_dots(arg) != FIND_INDIV) {
	if ((obj=get_object_in_equip_vis(ch,arg,ch->equipment,&pos))!= NULL) {
	    unequip_char(ch, pos);
	    extract_obj(obj);
	    return;
	}
	if ((obj = get_obj_in_list_vis(ch, arg, ch->carrying)) != NULL )
	    extract_obj(obj);
	return;
    } else {
	for (obj = ch->carrying; obj != NULL; obj = obj_next) {
	    obj_next = obj->next_content;
	    if (arg[3] == '\0' || isname(arg+4, obj->name)) {
		extract_obj(obj);
	    }
	}
	while ((obj=get_object_in_equip_vis(ch,arg,ch->equipment,&pos)))
	{
	    unequip_char(ch, pos);
	    extract_obj(obj);
	}   
    }
    return;
}


/* prints the message to everyone in the room other than the mob and victim */
ACMD(do_mechoaround)
{
    char arg[MAX_INPUT_LENGTH];
    char_data *victim;
    char *p;

    if (!MOB_OR_IMPL(ch)) {
	send_to_char( "Huh?!?\r\n", ch );
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    p = one_argument(argument, arg);
    skip_spaces(&p);
    
    if (!*arg) {
	mob_log(ch, "mechoaround called with no argument");
	return;
    }
  
    if (*arg == UID_CHAR) {
      if (!(victim = get_char(arg))) {
        sprintf(buf, "mechoaround: victim (%s) does not exist",arg);
	mob_log(ch, buf);
	return;
      }
    } else if (!(victim = get_char_room_vis(ch, arg))) {
        sprintf(buf, "mechoaround: victim (%s) does not exist",arg);
	mob_log(ch, buf);
	return;
    }

    sub_write(p, victim, TRUE, TO_ROOM);
}


/* sends the message to only the victim */
ACMD(do_msend)
{
    char arg[MAX_INPUT_LENGTH];
    char_data *victim;
    char *p;

    if (!MOB_OR_IMPL(ch)) {
	send_to_char( "Huh?!?\r\n", ch );
	return;
    }

    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    p = one_argument(argument, arg);
    skip_spaces(&p);
  
    if (!*arg) {
	mob_log(ch, "msend called with no argument");
	return;
    }

    if (*arg == UID_CHAR) {
      if (!(victim = get_char(arg))) {
        sprintf(buf, "msend: victim (%s) does not exist",arg);
	mob_log(ch, buf);
	return;
      }
    } else if (!(victim = get_char_room_vis(ch, arg))) {
        sprintf(buf, "msend: victim (%s) does not exist",arg);
	mob_log(ch, buf);
	return;
    }
  
    sub_write(p, victim, TRUE, TO_CHAR);
}


/* prints the message to the room at large */
ACMD(do_mecho)
{
    char *p;
  
    if (!MOB_OR_IMPL(ch)) {
	send_to_char( "Huh?!?\r\n", ch );
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    if (!*argument) {
	mob_log(ch, "mecho called with no arguments");
	return;
    }
    p = argument;
    skip_spaces(&p);
  
    sub_write(p, ch, TRUE, TO_ROOM);
}


/*
 * lets the mobile load an item or mobile.  All items
 * are loaded into inventory, unless it is NO-TAKE. 
 */
ACMD(do_mload)
{
    char arg1[MAX_INPUT_LENGTH], arg2[MAX_INPUT_LENGTH];
    int number = 0;
    char_data *mob;
    obj_data *object;
  
    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }

    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;

    if( ch->desc && GET_LEVEL(ch->desc->original) < LVL_IMPL)
	return;
  
    two_arguments(argument, arg1, arg2);
  
    if (!*arg1 || !*arg2 || !is_number(arg2) || ((number = atoi(arg2)) < 0)) {
	mob_log(ch, "mload: bad syntax");
	return;
    }

    if (is_abbrev(arg1, "mob")) {
	if ((mob = read_mobile(number, VIRTUAL)) == NULL) {
	    mob_log(ch, "mload: bad mob vnum");
	    return;
	}
	char_to_room(mob, IN_ROOM(ch));
        load_mtrigger(mob);
    }
  
    else if (is_abbrev(arg1, "obj")) {
	if ((object = read_object(number, VIRTUAL)) == NULL) {
	    mob_log(ch, "mload: bad object vnum");
	    return;
	}
	if (CAN_WEAR(object, ITEM_WEAR_TAKE)) {
	    obj_to_char(object, ch);
	} else {
	    obj_to_room(object, IN_ROOM(ch));
	}
        load_otrigger(object);
    }

    else
	mob_log(ch, "mload: bad type");
}


/*
 * lets the mobile purge all objects and other npcs in the room,
 * or purge a specified object or mob in the room.  It can purge
 *  itself, but this will be the last command it does.
 */
ACMD(do_mpurge)
{
    char arg[MAX_INPUT_LENGTH];
    char_data *victim;
    obj_data  *obj;
  
    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;

    if (ch->desc && (GET_LEVEL(ch->desc->original) < LVL_IMPL))
	return;
  
    one_argument(argument, arg);
  
    if (!*arg) {
	/* 'purge' */
	char_data *vnext;
	obj_data  *obj_next;
    
	for (victim = world[IN_ROOM(ch)].people; victim; victim = vnext) {
	    vnext = victim->next_in_room;
	    if (IS_NPC(victim) && victim != ch)
		extract_char(victim);
	}
    
	for (obj = world[IN_ROOM(ch)].contents; obj; obj = obj_next) {
	    obj_next = obj->next_content;
	    extract_obj(obj);
	}
    
	return;
    }
  
    if (*arg == UID_CHAR)
      victim = get_char(arg);
    else victim = get_char_room_vis(ch, arg);

    if (victim == NULL) {
	if ((obj = get_obj_vis(ch, arg))) {
	    extract_obj(obj);
	} else 
	    mob_log(ch, "mpurge: bad argument");
	
	return;
    }
    
    if (!IS_NPC(victim)) {
	mob_log(ch, "mpurge: purging a PC");
	return;
    }

    if (victim==ch) dg_owner_purged = 1;
    
    extract_char(victim);
}


/* lets the mobile goto any location it wishes that is not private */
ACMD(do_mgoto)
{
    char arg[MAX_INPUT_LENGTH];
    sh_int location;
  
    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }

    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    one_argument(argument, arg);

    if (!*arg) {
	mob_log(ch, "mgoto called with no argument");
	return;
    }
  
    if ((location = find_target_room(ch, arg)) == NOWHERE) {
	mob_log(ch, "mgoto: invalid location");
	return;
    }
  
    if (FIGHTING(ch))
	stop_fighting(ch);
    
    char_from_room(ch);
    char_to_room(ch, location);
}


/* lets the mobile do a command at another location. Very useful */
ACMD(do_mat)
{
    char arg[MAX_INPUT_LENGTH];
    sh_int location;
    sh_int original;
  
    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    argument = one_argument( argument, arg );
  
    if (!*arg || !*argument) {
	mob_log(ch, "mat: bad argument");
	return;
    }
  
    if ((location = find_target_room(ch, arg)) == NOWHERE) {
	mob_log(ch, "mat: invalid location");
	return;
    }
  
    original = IN_ROOM(ch);
    char_from_room(ch);
    char_to_room(ch, location);
    command_interpreter(ch, argument);
    
    /*
     * See if 'ch' still exists before continuing!
     * Handles 'at XXXX quit' case.
     */
    if (IN_ROOM(ch) == location) {
	char_from_room(ch);
	char_to_room(ch, original);
    }
}


/*
 * lets the mobile transfer people.  the all argument transfers
 * everyone in the current room to the specified location
 */
ACMD(do_mteleport)
{
    char arg1[MAX_INPUT_LENGTH], arg2[MAX_INPUT_LENGTH];
    sh_int target;
    char_data *vict, *next_ch;
  
    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;

    argument = two_arguments(argument, arg1, arg2);
    
    if (!*arg1 || !*arg2) {
	mob_log(ch, "mteleport: bad syntax");
	return;
    }

    target = find_target_room(ch, arg2);

    if (target == NOWHERE)
	mob_log(ch, "mteleport target is an invalid room");

    else if (!str_cmp(arg1, "all")) {
	if (target == IN_ROOM(ch)) {
	    mob_log(ch, "mteleport all target is itself");
	    return;
	}

	for (vict = world[IN_ROOM(ch)].people; vict; vict = next_ch) {
	    next_ch = vict->next_in_room;

	    if (GET_LEVEL(vict)<LVL_IMMORT) {
		char_from_room(vict);
		char_to_room(vict, target);
	    }
	}
    }

    else {
      if (*arg1 == UID_CHAR) {
        if (!(vict = get_char(arg1))) {
          sprintf(buf, "mteleport: victim (%s) does not exist",arg1);
          mob_log(ch, buf);
          return;
        }
      } else if (!(vict = get_char_vis(ch, arg1))) {
        sprintf(buf, "mteleport: victim (%s) does not exist",arg1);
	mob_log(ch, buf);
	return;
      }

      if (GET_LEVEL(vict)<LVL_IMMORT) {
	char_from_room(vict);
	char_to_room(vict, target);
      }
    }
}


/*
 * lets the mobile force someone to do something.  must be mortal level
 * and the all argument only affects those in the room with the mobile
 */
ACMD(do_mforce)
{
    char arg[MAX_INPUT_LENGTH];

    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    if (ch->desc && (GET_LEVEL(ch->desc->original) < LVL_IMPL))
	return;
  
    argument = one_argument(argument, arg);
  
    if (!*arg || !*argument) {
	mob_log(ch, "mforce: bad syntax");
	return;
    }
  
    if (!str_cmp(arg, "all")) {
	struct descriptor_data *i;
	char_data *vch;
    
	for (i = descriptor_list; i ; i = i->next) {
	    if ((i->character != ch) && !i->connected &&
		(IN_ROOM(i->character) == IN_ROOM(ch))) {
		vch = i->character;
		if (GET_LEVEL(vch) < GET_LEVEL(ch) && CAN_SEE(ch, vch) &&
		    GET_LEVEL(vch)<LVL_IMMORT) {
		    command_interpreter(vch, argument);
		}
	    }
	}
    } else {
	char_data *victim;
	
        if (*arg == UID_CHAR) {
          if (!(victim = get_char(arg))) {
            sprintf(buf, "mforce: victim (%s) does not exist",arg);
	    mob_log(ch, buf);
	    return;
          }
	} else if ((victim = get_char_room_vis(ch, arg)) == NULL) {
	    mob_log(ch, "mforce: no such victim");
	    return;
	}
    
	if (victim == ch) {
	    mob_log(ch, "mforce: forcing self");
	    return;
	}
    
	if (GET_LEVEL(victim)<LVL_IMMORT)
	    command_interpreter(victim, argument);
    }
}


/* increases the target's exp */
ACMD(do_mexp)
{
    char_data *victim;
    char name[MAX_INPUT_LENGTH], amount[MAX_INPUT_LENGTH];

    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    if (ch->desc && (GET_LEVEL(ch->desc->original) < LVL_IMPL))
	return;  

    two_arguments(argument, name, amount);

    if (!*name || !*amount) {
	mob_log(ch, "mexp: too few arguments");
	return;
    }

    if (*name == UID_CHAR) {
      if (!(victim = get_char(name))) {
        sprintf(buf, "mexp: victim (%s) does not exist",name);
	mob_log(ch, buf);
	return;
      }
    } else if (!(victim = get_char_vis(ch, name))) {
        sprintf(buf, "mexp: victim (%s) does not exist",name);
	mob_log(ch, buf);
	return;
    }
  
    gain_exp(victim, atoi(amount));      
}


/* increases the target's gold */
ACMD(do_mgold)
{
    char_data *victim;
    char name[MAX_INPUT_LENGTH], amount[MAX_INPUT_LENGTH];

    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    if (ch->desc && (GET_LEVEL(ch->desc->original) < LVL_IMPL))
	return;  

    two_arguments(argument, name, amount);

    if (!*name || !*amount) {
	mob_log(ch, "mgold: too few arguments");
	return;
    }

    if (*name == UID_CHAR) {
      if (!(victim = get_char(name))) {
        sprintf(buf, "mgold: victim (%s) does not exist",name);
	mob_log(ch, buf);
	return;
      }
    } else if (!(victim = get_char_vis(ch, name))) {
        sprintf(buf, "mgold: victim (%s) does not exist",name);
	mob_log(ch, buf);
	return;
    }
  
    if ((GET_GOLD(victim) += atoi(amount)) < 0) {
	mob_log(ch, "mgold subtracting more gold than character has");
	GET_GOLD(victim) = 0;
    }
}


/* hunt for someone */
ACMD(do_mhunt)
{
    char_data *victim;
    char arg[MAX_INPUT_LENGTH];

    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    if (ch->desc && (GET_LEVEL(ch->desc->original) < LVL_IMPL))
	return;
  
    one_argument(argument, arg);

    if (!*arg) {
	mob_log(ch, "mhunt called with no argument");
	return;
    }
  

    if (FIGHTING(ch)) return;

    if (*arg == UID_CHAR) {
      if (!(victim = get_char(arg))) {
        sprintf(buf, "mhunt: victim (%s) does not exist", arg);
	mob_log(ch, buf);
	return;
      }
    } else if (!(victim = get_char_vis(ch, arg))) {
        sprintf(buf, "mhunt: victim (%s) does not exist", arg);
	mob_log(ch, buf);
	return;
    }
    HUNTING(ch) = victim;
  

}


/* place someone into the mob's memory list */
ACMD(do_mremember)
{
    char_data *victim;
    struct script_memory *mem;
    char arg[MAX_INPUT_LENGTH];

    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    if (ch->desc && (GET_LEVEL(ch->desc->original) < LVL_IMPL))
	return;
  
    argument = one_argument(argument, arg);
  
    if (!*arg) {
	mob_log(ch, "mremember: bad syntax");
	return;
    }

    if (*arg == UID_CHAR) {
      if (!(victim = get_char(arg))) {
        sprintf(buf, "mremember: victim (%s) does not exist", arg);
	mob_log(ch, buf);
	return;
      }
    } else if (!(victim = get_char_vis(ch, arg))) {
        sprintf(buf, "mremember: victim (%s) does not exist", arg);
	mob_log(ch, buf);
	return;
    }

    /* create a structure and add it to the list */
    CREATE(mem, struct script_memory, 1);
    if (!SCRIPT_MEM(ch)) SCRIPT_MEM(ch) = mem;
    else {
      struct script_memory *tmpmem = SCRIPT_MEM(ch);
      while (tmpmem->next) tmpmem = tmpmem->next;
      tmpmem->next = mem;
    }

    /* fill in the structure */
    mem->id = GET_ID(victim);
    if (argument && *argument) {
      mem->cmd = strdup(argument);
    }
}


/* remove someone from the list */
ACMD(do_mforget)
{
    char_data *victim;
    struct script_memory *mem, *prev;
    char arg[MAX_INPUT_LENGTH];

    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    if (ch->desc && (GET_LEVEL(ch->desc->original) < LVL_IMPL))
	return;
  
    one_argument(argument, arg);
  
    if (!*arg) {
	mob_log(ch, "mforget: bad syntax");
	return;
    }

    if (*arg == UID_CHAR) {
      if (!(victim = get_char(arg))) {
        sprintf(buf, "mforget: victim (%s) does not exist", arg);
	mob_log(ch, buf);
	return;
      }
    } else if (!(victim = get_char_vis(ch, arg))) {
        sprintf(buf, "mforget: victim (%s) does not exist", arg);
	mob_log(ch, buf);
	return;
    }

    mem = SCRIPT_MEM(ch);
    prev = NULL;
    while (mem) {
      if (mem->id == GET_ID(victim)) {
        if (mem->cmd) free(mem->cmd);
        if (prev==NULL) {
          SCRIPT_MEM(ch) = mem->next;
          free(mem);
          mem = SCRIPT_MEM(ch);
        } else {
          prev->next = mem->next;
          free(mem);
          mem = prev->next;
        }
      } else {
        prev = mem;
        mem = mem->next;
      }
   }
}


/* transform into a different mobile */
ACMD(do_mtransform)
{
  char arg[MAX_INPUT_LENGTH];
  char_data *m, tmpmob;
  obj_data *obj[NUM_WEARS];
  int pos;

    if (!MOB_OR_IMPL(ch)) {
	send_to_char("Huh?!?\r\n", ch);
	return;
    }
  
    if (AFF_FLAGGED(ch, AFF_MAJIN))
	return;
  
    if (ch->desc) {
      send_to_char("You've got no VNUM to return to, dummy! try 'switch'\r\n",
        ch);
      return;
    }
  
  one_argument(argument, arg);

  if (!*arg)
    mob_log(ch, "mtransform: missing argument");
  else if (!isdigit(*arg)) 
    mob_log(ch, "mtransform: bad argument");
  else {
    m = read_mobile(atoi(arg), VIRTUAL);
    if (m==NULL) {
      mob_log(ch, "mtransform: bad mobile vnum");
      return;
    }

    /* move new obj info over to old object and delete new obj */

    for (pos = 0; pos < NUM_WEARS; pos++) {
      if (GET_EQ(ch, pos))
        obj[pos] = unequip_char(ch, pos);
      else
        obj[pos] = NULL;
    }

    /* put the mob in the same room as ch so extract will work */
    char_to_room(m, IN_ROOM(ch));

    memcpy(&tmpmob, m, sizeof(*m));
    tmpmob.id = ch->id;
    tmpmob.affected = ch->affected;
    tmpmob.carrying = ch->carrying;
    tmpmob.proto_script = ch->proto_script;
    tmpmob.script = ch->script;
    tmpmob.memory = ch->memory;
    tmpmob.next_in_room = ch->next_in_room;
    tmpmob.next = ch->next;
    tmpmob.next_fighting = ch->next_fighting;
    tmpmob.followers = ch->followers;
    tmpmob.master = ch->master;

    GET_WAS_IN(&tmpmob) = GET_WAS_IN(ch);
    GET_HIT(&tmpmob) = GET_HIT(ch);
    GET_MAX_HIT(&tmpmob) = GET_MAX_HIT(ch);
    GET_EXP(&tmpmob) = GET_EXP(ch);
    GET_GOLD(&tmpmob) = GET_GOLD(ch);
    GET_POS(&tmpmob) = GET_POS(ch);
    IS_CARRYING_W(&tmpmob) = IS_CARRYING_W(ch);
    IS_CARRYING_N(&tmpmob) = IS_CARRYING_N(ch);
    FIGHTING(&tmpmob) = FIGHTING(ch);
    HUNTING(&tmpmob) = HUNTING(ch);
    memcpy(ch, &tmpmob, sizeof(*ch));

    for (pos = 0; pos < NUM_WEARS; pos++) {
      if (obj[pos])
        equip_char(ch, obj[pos], pos);
    }

    extract_char(m);
  }
}