/* **********************
# Patch name: @hackcmd
# Patch version: 0.1.0
# Author's name: David Cheatham
# Author's email: david@mush.cx
# Version of PennMUSH: Tested on 1.7.5p8. Probably works on .4 also.
# Author is willing to support (yes/no): yes

Installation: copy this file into src/
You need to make two changes to cmdlocal.c
After all the #includes, put a line that says:
#include "hackcmd.c"

Then, in local_commands(), anywhere, stick a line that says
command_hack();

Note this file is *dangerous*. If you start randomly setting
permission on commands, you can crash the game. You can create
infinite loops, you can make the game unusable, you can removed all
security from your game, you can do all sort of bad things. Think of
this as @fixdb for commands. When I say I'll 'support' it, that means
I'll try to help if the code does something incorrectly, not if the code
does something correctly that's bad. This is a loaded gun, be careful.

What not to do that I know of:

Don't @hackcmd/set @trigger=!rs_args, or on any of the other commands
that comes with rs_args or ls_args set. You will crash the next time
someone uses them.

Don't @hackcmd/rename ATTRIB_SET, @wipe, @set, @*emit, or a ton of
others, as pennmush does command_check_byname() on them to see if you
can use the equivelent functions. grep for command_check_byname to see
them.

While it won't 'hurt' to @hackcmd/first <command>=<dbref other than GOD>, 
don't. Wizards can modify that. If a wizard can modify God's commands on
the fly, they can not only set other things wizard, but they can use
@hackcmd. If they can use @hackcmd, they can @hackcmd @newpassword and
@newpassword God the next time he does it. Very bad idea to let anyone
other than God or a hardcode wizard to use @hackcmd, because it's very
close to having source code access. 

You probably should treat @hackcmd *like* source code access, and run
things on a test mush first.

Parts of this used to be @command/first. But I eventually decided I
needed to set up my own command, it was just too much work playing
with @command.

If this file breaks, you can send it back to me at the address above
and download a replacement FOR FREE. And that's all the warranty you get.

Info:

@hackcmd/create <command> allows you to create a new, blank command. 
Pointless, eh? Read on.

@hackcmd/set <command>=restriction. Look in command.h for the
possible types. Just drop the CMD_T_ at the start of them to set
them. Better then @command/restrict, basically does the same thing.

@hackcmd/rename lets you rename an existing name, or alias, into 
something else. Note the name and alias are *seperate*. If you want to 
rename the 'look' command, don't forget to rename 'l', also.

@hackcmd/first lets you make 'interceptions', softcoded functions 
that evaluate first, before hardcoded commands.
You can then change all the parameters passed to said hardcoded
command, or stop it from executing at all.
The format is:
@hackcmd/first command[=#dbref[/attrib]]
  If you leave off the attribute, it defaults to the command name,
  and if you leave off the object, it defaults to GOD.

@hackcmd/first/clear <command> resets the command

Note: The 'command name' the attributes defaults to may include an @
for commands. This means the attribute has an @ *in it*. For
example, the default attribute for @pemit is set using &@pemit #1=

So, what was the point of @hackcmd/create? If you do that, you get a
command that points nowhere. But now if you @hackcmd/first it, 
instead of it being evaluated as a function, it's executed. This gives
something like @addcommand, except you don't have a $blah at the start
of it, and parameters are parsed liked hardcode and passed in as listed
below. Also note you can do @hackcmd/create/first NEW=#1/CMD_NEW, creating
a new command and intercepting it at once. You get switches in %qQ.

Now, you're wondering what you can do, and how do to it. The answer:
r-registers. The parameters being passed to the hardcode command end
up in %q*. They are:
%q0 = name: Command name. You cannot change this.
%qP = player: dbref of object executing the command
%qC = cause: dbref of the object causing the command, usually the
             same as above.
%qL = arg_left: If the command takes an =, it's the stuff to the
             left. If it doesn't take one, it should be everything.
%qR = arg_right: Stuff to the right of the =, hopefully.
%qS = sw: Known switches in the switchmask. Set known switches here.
%qQ = switches: Unknown switches in the switchmask. Must set command
                'switches' to be able to get these. While these are
                passed on, only a few commands actually look at these.
%qU = raw: unparsed arguments. Basically %c sans the command.

%qX = %c: Yes, you can change this. A good idea? Probably not, except in
		the case of softcode matching.
%qY = args_right: You need to just look at these and see where the
		the stuff you want to alter is. This is a comma or space
		delimited list, you need to keep it one.
%qZ = args_left: See args_right.

%qA = what to do: This isn't set to anything at first. Setting it to
		things controls what happens next.

You can modify these just like normal r-registers, with setq() and setr(). 
&PAGE #1=setq(R,reverse(%qR)) makes all pages backwards.
If the function evaluate to an non-empty string, it doesn't execute
the hardcode command, and instead sends notifies the player of the
results. If you're having problems, wrap it in null(). If you want to
notify the player of something and still run the command, use pemit().
&@DUMP #1=switch(%qP,#6,,You cannot use @dump.) would make @dump work
just for #6.

Obviously, it's pointless to modify these if you're getting triggered
as a command, as they don't end up anywhere.

Some actually useful examples are at the end of the file.

Oh, and, yes, none of these aliases get saved across reboots. Check
the very end for an automatic way to set them on startup.
 */


#include "ptab.h"
#include "match.h"
#include "flags.h"
#include "attrib.h"
#include "dbdefs.h"

void command_hack(void);
void command_rename(char const *, char const *);
int command_set(char const *, char const *);

/* external stuff */
int list_check(dbref, dbref, char, char, char *, int);
//extern char *wmxt[10], *rxnt[NUMJ];
struct command_perms_t {
  const char *name;
  unsigned int type;
};

struct command_perms_t command_perms[] = {
  {"player", CMD_T_PLAYER},
  {"thing", CMD_T_THING},
  {"exit", CMD_T_EXIT},
  {"room", CMD_T_ROOM},
  {"any", CMD_T_ANY},
  {"god", CMD_T_GOD},
  {"switches", CMD_T_SWITCHES},
  {"disabled", CMD_T_DISABLED},
  {"nogagged", CMD_T_NOGAGGED},
  {"noguest", CMD_T_NOGUEST},
  {"nofixed", CMD_T_NOFIXED},
  {"listed", CMD_T_LISTED},
  {"internal", CMD_T_INTERNAL},
  {"eqsplit", CMD_T_EQSPLIT},
  {"ls_args", CMD_T_LS_ARGS},
  {"ls_space", CMD_T_LS_SPACE},
  {"ls_noparse", CMD_T_LS_NOPARSE},
  {"rs_args", CMD_T_RS_ARGS},
  {"rs_space", CMD_T_RS_SPACE},
  {"rs_noparse", CMD_T_RS_NOPARSE},
  {NULL, 0}
};

PTAB ptab_intercept;
PTAB ptab_command_perms;
extern PTAB ptab_command;
extern SWITCH_VALUE switch_list[];
extern char ccom[];

struct command_intercept {
  command_func func;
  char check_name[ATTRIBUTE_NAME_LIMIT];
  dbref obj;
  char name[COMMAND_NAME_LIMIT];
};

COMMAND (cmd_intercepted) {
  struct command_intercept *intercept;
  SWITCH_VALUE *sw_val;
  ATTR *attrib;
  char const *attrib_con;
  static char buff[BUFFER_LEN] = "\0";
  char *bp;
  char *tp;
  char *saver[NUMQ];
  switch_mask new_sw;
  int i;
  char w;

  intercept = ptab_find(&ptab_intercept, cmd->name);
  if (!intercept || !(attrib = atr_get_noparent(intercept->obj,
						intercept->check_name))) {
    notify(player, "Error in intercept table.");
    return;
  }

  save_global_regs("cmd_intercepted_regs", saver);
  *renv[28] = '\0';
  if (sw) {
    for (sw_val = switch_list; sw_val->name; sw_val++)
      if (SW_ISSET(sw, sw_val->value))
	strcat(renv[28], sw_val->name);	/* %qS */
  }
  if (switches && *switches)
    strcpy(renv[26], switches);	/* %qQ */

  strcpy(renv[0], cmd->name);	/* %q0 */

  strcpy(renv[33], ccom);	/* %qX */
  sprintf(renv[25], "#%i", player);	/* %qP */
  sprintf(renv[12], "#%i", cause);	/* %qC */
  strcpy(renv[27], arg_right);	/* %qR */
  strcpy(renv[21], arg_left);	/* %qL */
  strcpy(renv[30], args_raw);	/* %qU */

  *renv[34] = '\0';		/* %qY */
/* There don't appear to actually *be* any of this type, but whatever. */
  if ((cmd->type & CMD_T_LS_ARGS) && args_left)
    for (i = 1; i < 11; i++) {
      if (args_left[i])
	strcat(renv[34], args_left[i]);
      strcat(renv[34], ((cmd->type & CMD_T_LS_SPACE) ? " " : ","));
    }

  *renv[35] = '\0';		/* %qZ */
  if ((cmd->type & CMD_T_RS_ARGS) && args_right)
    for (i = 1; i < 11; i++) {
      if (args_right[i])
	strcat(renv[35], args_right[i]);
      strcat(renv[35], ((cmd->type & CMD_T_RS_SPACE) ? " " : ","));
    }

  /* If there isn't a hardcoded function to go along with this, 
     @trig the command. */
  if (!intercept->func) {
    /* Don't know exactly why I have to do this, but whatever */
    for (i = 0; i < NUMQ; i++)
      rnxt[i] = renv[i];

    queue_attribute(intercept->obj, intercept->check_name, player);
    restore_global_regs("cmd_intercepted_reqs", saver);
    return;
  }

  attrib_con = safe_uncompress(attrib->value);

  bp = buff;
  process_expression(buff, &bp, &attrib_con, intercept->obj, player,
		     cause, PE_DEFAULT, PT_DEFAULT, NULL);

  *bp = '\0';
  if (buff && *buff) {
    notify(player, buff);
    restore_global_regs("cmd_intercepted_regs", saver);
    return;
  }

  SW_ZERO(new_sw);
  if (*renv[28]) {
    memcpy(new_sw, switchmask(renv[28]), sizeof(switch_mask));
    strcpy(switches, renv[28]);
  }

  strcpy(ccom, renv[33]);
  strcpy(arg_right, renv[27]);
  strcpy(arg_left, renv[21]);
  strcpy(args_raw, renv[30]);
  player = parse_dbref(renv[25]);
  cause = parse_dbref(renv[12]);

  if (*renv[34] != '\0') {
    bp = renv[34];
    for (i = 1; i < 11; i++) {
      if ((tp = strchr(renv[34], (cmd->type & CMD_T_LS_SPACE) ? ' ' : ','))) {
	*tp++ = '\0';
	if (*bp)
	  strcpy(args_left[i], bp);
	bp = tp;
      }
    }
  }

  if (*renv[35] != '\0') {
    bp = renv[35];
    for (i = 1; i < 11; i++) {
      if ((tp = strchr(renv[35], (cmd->type & CMD_T_RS_SPACE) ? ' ' : ','))) {
	*tp++ = '\0';
	if (*bp)
	  strcpy(args_right[i], bp);
	bp = tp;
      }
    }
  }

  w = *renv[10];
  i = 0;
  restore_global_regs("cmd_intercepted_reqs", saver);

  if (DO_GLOBALS && (w == '+')) {
    if (Zone(Location(player)) != NOTHING) {
      if (IsRoom(Zone(Location(player))))
	i = atr_comm_match(Contents(Zone(Location(player))), player, 
		'$', ':', ccom, 0, NULL, NULL);
      else
	i = list_check(Zone(Location(player)), player, '$', ':', ccom, 0);
    }
    if (!i) 
      i += list_check(Contents(MASTER_ROOM), player, '$', ':', ccom, 0);
    if (!i)
      notify(player, T("Huh? (Type \"help\" for help.)"));
    return;
  }
  intercept->func(cmd, player, cause, new_sw, raw, switches, args_raw,
		  arg_left, args_left, arg_right, args_right);
}


COMMAND (cmd_hackcmd) {
  COMMAND_INFO *command = NULL;
  struct command_intercept *intercept;
  char *attrib = NULL;
  dbref where;

  if (!arg_left || !arg_left[0]) {
    notify(player, "Hacked list:");
    intercept = ptab_firstentry(&ptab_intercept);
    while (intercept) {

      if (intercept->func)
	notify_format(player, "%s intercepted by '#%i/%s'.", intercept->name,
		      intercept->obj, intercept->check_name);
      else
	notify_format(player, "%s triggers '#%i/%s'.", intercept->name,
		      intercept->obj, intercept->check_name);
      intercept = ptab_nextentry(&ptab_intercept);
    }
    return;
  }

  command = command_find_exact(arg_left);

  if (SW_ISSET(sw, SWITCH_CREATE)) {
    if (command) {
      notify_format(player, "Delete or rename %s first.", command->name);
      return;
    }
    if (!*arg_left) {
      notify(player, "What do you want to name it?");
      return;
    }
    attrib = mush_malloc(COMMAND_NAME_LIMIT, "string");
    strcpy(attrib, strupper(arg_left));
    command = command_add(attrib, CMD_T_ANY | CMD_T_SWITCHES, 0, 0, 0, 0, NULL);
    notify_format(player, "%s created.", attrib);
  }

  if (!command) {
    notify(player, "No such command.");
    return;
  }

  if (SW_ISSET(sw, SWITCH_RENAME)) {
    if (!*arg_right)
      notify(player, "What do you want to name it?");
    else {
      command_rename(arg_left, strupper(arg_right));
      notify_format(player, "%s renamed to '%s'.", arg_left,
		    strupper(arg_right));

    }
    return;
  } else if (SW_ISSET(sw, SWITCH_SET)) {
    if (*arg_right)
      command_set(arg_left, arg_right);
    return;
  }


  /* Well, we're about out of things to do here */
  if (!SW_ISSET(sw, SWITCH_FIRST)) {
    notify(player, "Sorry, I don't know what to do.");
    return;
  }

  /* We need the real command, not the alias. */
  if (command != command_find_exact(command->name)) {
    command = command_find_exact(command->name);
    if (!command) {
      notify(player, "No such command. Broken command table.");
      return;
    }

    notify_format(player, "Using '%s' instead of '%s'.",
		  command->name, strupper(arg_left));
  }
  intercept = ptab_find(&ptab_intercept, command->name);
  if (intercept) {
    command->func = intercept->func;
    ptab_delete(&ptab_intercept, command->name);
    mush_free(intercept, "command_first");
    intercept = NULL;
  }

  if (!SW_ISSET(sw, SWITCH_CLEAR)) {
    if (arg_right && *arg_right) {
      if ((attrib = strchr(arg_right, '/')))
	*attrib++ = '\0';
      where =
	match_result(player, arg_right, NOTYPE, MAT_ABSOLUTE | MAT_CONTROL);
      if (where < 0)
	where = GOD;
      if (!good_atr_name(strupper(attrib)) ||
	  !atr_get_noparent(where, strupper(attrib)))
	notify_format(player,
		      "Warning, attribute #%i/%s does not exist",
		      where, strupper(attrib));
    } else {
      where = GOD;
      if (!good_atr_name(command->name) ||
	  !atr_get_noparent(where, command->name))
	notify_format(player,
		      "Warning, attribute #%i/%s does not exist",
		      where, command->name);
    }
    intercept = mush_malloc(sizeof(struct command_intercept), "command_first");
    intercept->func = command->func;
    strncpy(intercept->check_name,
	    (attrib ? strupper(attrib) : command->name), COMMAND_NAME_LIMIT);
    strncpy(intercept->name, arg_left, COMMAND_NAME_LIMIT);
    intercept->obj = where;
    ptab_start_inserts(&ptab_intercept);
    ptab_insert(&ptab_intercept, arg_left, intercept);
    ptab_end_inserts(&ptab_intercept);
    command->func = cmd_intercepted;
  }

}

/* A better version of restrict_command() */
int
command_set(char const *name, char const *restriction)
{
  COMMAND_INFO *command;
  struct command_perms_t *c;
  int clear = 0;

  if (!name || !*name || !restriction || !*restriction)
    return 0;
  command = command_find_exact(name);
  if (!command)
    return 0;
  if (*restriction == '!') {
    restriction++;
    clear = 1;
  }

  c = ptab_find(&ptab_command_perms, restriction);
  if (!c)
    return 0;
  if (clear)
    command->type &= ~c->type;
  else
    command->type |= c->type;

  return 1;
}


/* Can't do this with command_modify() */
void
command_rename(char const *old_name, char const *new_name)
{
  COMMAND_INFO *command;
  struct command_intercept *intercept;
  command = command_find_exact(old_name);
  if (command) {
    ptab_delete(&ptab_command, old_name);
    /* If these don't match, it's an alias. */
    if (!strcmp(old_name, command->name))
      command->name = new_name;
    intercept = ptab_find(&ptab_intercept, old_name);
    if (intercept) {
      ptab_delete(&ptab_intercept, old_name);
      ptab_start_inserts(&ptab_intercept);
      ptab_insert(&ptab_intercept, new_name, intercept);
      ptab_end_inserts(&ptab_intercept);
      strncpy(intercept->name, new_name, COMMAND_NAME_LIMIT);
    }

    ptab_start_inserts(&ptab_command);
    ptab_insert(&ptab_command, new_name, command);
    ptab_end_inserts(&ptab_command);
  }

}

void
command_hack()
{
  struct command_perms_t *c;
  ptab_init(&ptab_intercept);
  command_add("@HACKCMD", CMD_T_PLAYER | CMD_T_EQSPLIT | CMD_T_GOD,
	      0, 0, 0, switchmask("FIRST CLEAR CREATE RENAME SET"),
	      cmd_hackcmd);

  ptab_init(&ptab_command_perms);
  ptab_start_inserts(&ptab_command_perms);
  for (c = command_perms; c->name; c++)
    ptab_insert(&ptab_command_perms, c->name, c);
  ptab_end_inserts(&ptab_command_perms);
}

/*
Code examples. These code examples are just idea that you can use on
your mush. They're all completely public domain, use them how you
will, you don't even have to credit people. And they, like this file, 
has no useful warranty. They assume you're using the default 
dbref/attrib for things.

This makes whisper act something like page, storing the last person
whispered to. Silly looking with multiple people, and doesn't remove
bad people from the list. Notice the returned text stops do_whisper for 
blank whispers:
&WHISPER #1=switchall(t(%qR)[t(%qL)]
,00,You last whispered to [xget(%qP,LASTWHISPERED)].
,01,setq(R,%qL)[setq(L,xget(%qP,LASTWHISPERED))]
,*1,[set(%qP,LASTWHISPERED:%qL)])

This gets rid of the ability to do page/blind or page/list, without
editing hardcode. The setting of blind_page will control the only type
of pages people can do:
&PAGE #1=setq(S,setdiff(%qS,BLIND LIST))

Here's a useful one for @cemit:
&@CEMIT #1=ifelse(hasflag(%qP, WIZARD),,setq(S,setunion(%qS,NOISY))

Don't think that you can't make two commands use the same interception
attribute. This adds /paranoid to @dump and @shutdown:
@hackcmd/first @dump=#1/ALWAYS_PARANOID
@hackcmd/first @shutdown=#1/ALWAYS_PARANOID
&ALWAYS_PARANOID #1=setq(S,setunion(%qS,PARANOID))

Unevaluated speech. Note this works for the tokens also. (You could add 
/noeval to the switches, but tokens don't get switches no matter what.)
If you want to do this for page, note that the message belongs in in %qR n
ot %qL, except when there is no %qR. Lots of fun.
@hackcmd/first say=#1/NOEVAL_SPEECH
@hackcmd/first pose=#1/NOEVAL_SPEECH
@hackcmd/first semipose=#1/NOEVAL_SPEECH
@hackcmd/first @emit=#1/NOEVAL_SPEECH
&NOEVAL_SPEECH #1=setq(L,%qU)

If you can't figure something out, try this:
&DEBUG #1=Player: %qP%rCause: %qC%r%%c: %qX%rRaw: %qU%r
Arg_left: %qL%r: Args_left %qZ%rArg_right: %qR%r: Args_right %qY

----

How to set things up on startup, automatically. Pick one.
This doesn't use the default of #1/<command>:
@startup #1=@dolist lattr(#1/INTER-*)=@hackcmd/first rest(##,-)=#1/##

This keeps the default, but makes you keep another list:
@startup #1=@dolist v(INTERCEPTIONS)=@hackcmd/first ##

----

Here's a neat trick. You like @icmd on Rhost and MUX? Here you go.
Quote this in as #1. You need this next line to have a list of the
commands you want to be able to @icmd, so it's not as automatic as a
real @icmd, but it still is useful.
&ICMDS me=PAGE @PEMIT LOOK @EMIT

&@ICMD me=th setq(Q,match(ON OFF CLEAR CROOM IGNORE IROOM DISABLE DROOM, 
first(%qQ)*))[setq(R,ucstr(%qR))][pemit(%#,switch(%qq[u(FUN_ICMD_FIND)],
*0, I don't see that here.,11, You don't need to do that., 
<50, wipe(%#/ICMD_LIST)Icmd: Done., <70, u(FUN_ICMD_MODIFY, I)Icmd: Set., 
<90, u(FUN_ICMD_MODIFY, D)Icmd: Set., Not implimented.))]

@set me/@ICMD=no_command

&FUN_ICMD_FIND me=t(setr(L, locate(%#, %qL,PR*)))

&FUN_ICMD_MODIFY me=iter(%qR,u(FUN_ICMD_MODIFY_ONE,%0,last(##,!),left(##,1))

&FUN_ICMD_MODIFY_ONE me=set(%qL,ICMD_LIST:[switch(%2,!,setdiff(xget(%qL,
ICMD_LIST), I_%1 D_%1),setunion(xget(%qL,ICMD_LIST),%0_%1))]

@set me/FUN_ICMD_*=no_command

&CHECK_ICMD me=if(cor(hasattrval(%#,ICMD_LIST),hasattrval(%L,ICMD_LIST)),
switch(grab(xget(%#, ICMD_LIST) [xget(%l, ICMD_LIST)], ?_%q0), D_*, 
Permission denied, setq(A,+)))

@set me/CHECK_ICMD=no_command

&SETUP_ICMD me=@attrib/access ICMD_LIST=no_command no_clone no_inherit 
mortal_dark wizard;@hackcmd/create/first @ICMD;@hackcmd/set @ICMD=eqsplit
;@dolist v(ICMDS)=@hackcmd/first ##=#1/CHECK_ICMD

@set me/SETUP_ICMD=no_command

@STARTUP me=@trig me/SETUP_ICMD

*/

