/
2.0.5/doc/
2.0.5/gnu/
2.0.5/sha/
/* group.c */

#include "config.h"

/*
 *		       This file is part of TeenyMUD II.
 *	    Copyright(C) 1995 by Jason Downs.  All rights reserved.
 * 
 * TeenyMUD II 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.
 *
 * TeenyMUD II 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 (see the file 'COPYING'); if not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 *
 */

#include <stdio.h>
#include <sys/types.h>
#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif			/* HAVE_STRING_H */

#include "conf.h"
#include "teeny.h"
#include "commands.h"
#include "externs.h"

/* User-level support for ``groups''. */

/*
 * ``Groups'' are maintained seperate from the database, in memory, so
 * they don't last between boots or logins.
 */

struct glist {
  int player;		/* Current player, or leader. */
  int flags;
#define GRP_FOLLOW	0x01	/* Follow the leader. */
#define GRP_SILENT	0x02	/* Don't hear group messages. */
#define GRP_CLOSE	0x04	/* No new members. */

  /* If top of the list, use these two: */
  int count;		/* Total number of members. */
  char *str;		/* Name of group. */

  struct glist *next;	/* Next in list. NULL if bottom. */
  struct glist *prev;	/* Previous. NULL if top. */
};

#ifndef MAXPGROUPS
#define MAXPGROUPS	128		/* Should be more than enough. */
#endif			/* MAXPGROUPS */

static struct glist *garray[MAXPGROUPS];
static int gtop = 0;

static struct glist *glist_alloc _ANSI_ARGS_((void));
static void glist_free _ANSI_ARGS_((struct glist *));
static int group_find _ANSI_ARGS_((int, struct glist **, struct glist **));
static void group_zap _ANSI_ARGS_((struct glist *, int));
static void group_notify _ANSI_ARGS_((struct glist *, int, char *, int));
static void group_dump _ANSI_ARGS_((int, int, int));
static void group_sdump _ANSI_ARGS_((int, int, struct glist *));

#define can_follow(_x)	((Typeof(_x) == TYP_PLAYER) || \
			 (Typeof(_x) == TYP_THING))

/*
 * glist_alloc()
 *
 * Allocate a glist struct.
 */
static struct glist *glist_alloc()
{
  register struct glist *ret;

  ret = (struct glist *)ty_malloc(sizeof(struct glist), "glist_alloc.ret");
  bzero((VOID *)ret, sizeof(struct glist));

  return(ret);
}

/*
 * glist_free()
 *
 * Destroy a glist struct.
 */
static void glist_free(ptr)
    register struct glist *ptr;
{
  if(ptr != (struct glist *)NULL) {
    if(ptr->str != (char *)NULL) {
      /* free this, too. */
      ty_free(ptr->str);
    }
    ty_free(ptr);
  }
}

/*
 * group_find()
 *
 * Return the appropiate glist pointers.
 */
static int group_find(player, actual, leader)
    register int player;
    struct glist **actual, **leader;
{
  register struct glist *gcur;
  register int acur;

  for(acur = 0; acur < gtop; acur++) {
    if(garray[acur] != (struct glist *)NULL) {
      for(gcur = garray[acur]; gcur != (struct glist *)NULL;
	  gcur = gcur->next) {
	if(gcur->player == player) {
	  if(actual != (struct glist **)NULL)
	    *actual = gcur;
	  if(leader != (struct glist **)NULL)
	    *leader = garray[acur];

	  return(1);
	}
      }
    }
  }

  return(0);
}

/*
 * group_ismem()
 *
 * Return true if player is a member (or leader) of a group.
 */
int group_ismem(player)
    int player;
{
  return(group_find(player, (struct glist **)NULL, (struct glist **)NULL));
}

/*
 * group_isleader()
 *
 * Return true if player is a leader of a group.
 */
int group_isleader(player)
    int player;
{
  struct glist *leader;

  return(group_find(player, (struct glist **)NULL, &leader)
	 && (leader->player == player));
}

/*
 * group_remove()
 *
 * Remove a player from a group.  If the player is a leader, disolve the
 * group.
 */
void group_remove(player, cause)
    int player, cause;
{
  struct glist *actual, *leader;
  char *name, buffer[MEDBUFFSIZ];

  if(group_find(player, &actual, &leader)) {
    if(actual == leader)
      group_zap(leader, cause);
    else {
      /* prev should never be NULL. */
      (actual->prev)->next = actual->next;
      if(actual->next != (struct glist *)NULL)
	(actual->next)->prev = actual->prev;
      glist_free(actual);
 
      /* Decrement leader's count. */
      leader->count--;

      /* Notify everyone. */
      if(get_str_elt(player, NAME, &name) == -1)
	name = "???";
      snprintf(buffer, sizeof(buffer), "**] %s has left the group.", name);

      group_notify(leader, cause, buffer, 0);
    }
  }
}

/*
 * group_create()
 *
 * Create a new group and make player the leader.
 */
void group_create(player, cause, switches, str)
    int player, cause, switches;
    char *str;
{
  if(group_ismem(player)) {
    if(!(switches & CMD_QUIET))
      notify_player(player, cause, player, "You're already in a group!",
      		    NOT_QUIET);
  } else {
    if(gtop < MAXPGROUPS) {
      struct glist *newgrp;

      newgrp = glist_alloc();
      garray[gtop++] = newgrp;

      newgrp->player = player;

      /* Set the desc. */
      if(str != (char *)NULL)
	newgrp->str = ty_strdup(str, "group_create.str");

      if(!(switches & CMD_QUIET))
        notify_player(player, cause, player, "New group created.", NOT_QUIET);
    } else {
      if(!(switches & CMD_QUIET))
        notify_player(player, cause, player,
		      "Sorry, there are too many groups.", NOT_QUIET);
    }
  }
}

/*
 * group_join()
 *
 * Add player to the other's group.
 */
void group_join(player, cause, switches, friend)
    int player, cause, switches, friend;
{
  struct glist *actual, *leader;

  if(group_ismem(player)) {
    if(!(switches & CMD_QUIET))
      notify_player(player, cause, player,
		    "You can only be in one group at a time.", NOT_QUIET);
    return;
  }

  if(!group_find(friend, &actual, &leader)) {
    if(!(switches & CMD_QUIET))
      notify_player(player, cause, player, "But they aren't in a group!",
      		    NOT_QUIET);
  } else {
    struct glist *newmem;
    char *name, buf[MEDBUFFSIZ];

    if(leader->flags & GRP_CLOSE) {
      if(!(switches & CMD_QUIET))
        notify_player(player, cause, player, "Sorry, that group is closed.",
		      NOT_QUIET);
      return;
    }

    newmem = glist_alloc();
    newmem->player = player;

    /* Insert them forward of leader. */
    newmem->prev = leader;
    if(leader->next != (struct glist *)NULL)
      (leader->next)->prev = newmem;
    newmem->next = leader->next;
    leader->next = newmem;

    /* Increment leader's count. */
    leader->count++;

    /* Notify everyone. */
    if(get_str_elt(player, NAME, &name) == -1)
      name = "???";
    snprintf(buf, sizeof(buf), "**] %s has joined the group.", name);
    group_notify(leader, cause, buf, 0);
  }
}

/*
 * group_zap()
 *
 * Lower-level group killer.
 */
static void group_zap(grptr, cause)
    struct glist *grptr;
    int cause;
{
  register struct glist *curr, *next;
  register int idx;

  /* Inline notify_group(). */
  curr = grptr;
  while(curr != (struct glist *)NULL) {
    next = curr->next;		/* Save next. */

    if(!(curr->flags & GRP_SILENT))
      notify_player(curr->player, cause, curr->player,
		    "**] Your group has been dissolved.", NOT_QUIET);
    glist_free(curr);

    curr = next;
  }

  /* grptr is no longer valid, but we need it to fix garray. */
  for(idx = 0; idx < gtop; idx++) {
    if(garray[idx] == grptr) {
      /* Found it.  Sure hope your bcopy() likes to overlap! */
      bcopy((VOID *)&garray[idx+1], (VOID *)&garray[idx], gtop - (idx + 1));
      gtop--;	/* Important! */
      break;
    }
  }
}

/*
 * group_notify()
 *
 * Send a message to everyone in a group. Low level.
 */
static void group_notify(grptr, cause, str, flags)
    struct glist *grptr;
    int cause;
    char *str;
    int flags;
{
  register struct glist *curr;

  for(curr = grptr; curr != (struct glist *)NULL; curr = curr->next) {
    if(!(curr->flags & GRP_SILENT))
      notify_player(curr->player, cause, curr->player, str, flags);
  }
}

/*
 * notify_group()
 *
 * Send a message to everyone in a group. High level.
 */
void notify_group(player, cause, sender, str, flags)
    int player, cause, sender;
    char *str;
    int flags;
{
  struct glist *actual, *leader;
  register struct glist *curr;

  if(group_find(player, &actual, &leader)) {
    for(curr = leader; curr != (struct glist *)NULL; curr = curr->next) {
      notify_player(curr->player, cause, sender, str, flags);
    }
  }
}

/*
 * group_follow()
 *
 * For everyone in player's group (if they lead) who is following, and are
 * in ``oldloc'', teleport to ``newloc''.  This is different from standard
 * teleport: it doesn't trigger teleport actions.
 */
void group_follow(leader, cause, oldloc, newloc)
    int leader, cause, oldloc, newloc;
{
  struct glist *actual, *rleader;
  register struct glist *curr;

  if(group_find(leader, &actual, &rleader) && (actual == rleader)) {
    for(curr = rleader->next; curr != (struct glist *)NULL; curr = curr->next) {
      if((curr->flags & GRP_FOLLOW) && can_follow(curr->player)) {
	int loc;

	if(get_int_elt(curr->player, LOC, &loc) == -1) {
	  logfile(LOG_ERROR, "group_follow: couldn't get location of #%d\n",
	          curr->player);
	  continue;
	}
	if((loc != oldloc) || !legal_thingloc_check(curr->player, newloc)) {
	  /* They aren't with the group. */
	  continue;
	}

        move_player_leave(curr->player, cause, oldloc, 0,
    		          "You feel a wrenching sensation...");
        move_player_arrive(curr->player, cause, newloc, 0);
        look_location(curr->player, cause, 0);
	if(has_html(curr->player))
	  html_anchor_location(curr->player, cause);

      }
    }
  }
}

/*
 * group_dump()
 *
 * Produce a pretty-formatted group listing.  High level.
 */
static void group_dump(player, cause, switches)
    int player, cause, switches;
{
  struct glist *actual, *leader;
  register int idx;
  
  if(switches & GROUP_ALL) {
    for(idx = 0; idx < gtop; idx++) {
      if(garray[idx] != (struct glist *)NULL)
	group_sdump(player, cause, garray[idx]);
    }
    notify_player(player, cause, player, "***End of list***", 0);
  } else {
    if(group_find(player, &actual, &leader)) {
      group_sdump(player, cause, leader);
      notify_player(player, cause, player, "***End of list***", 0);
    } else {
      if(!(switches & CMD_QUIET))
        notify_player(player, cause, player, "But you're not in a group!",
		      NOT_QUIET);
    }
  }
}

/*
 * group_sdump()
 *
 * Produce a pretty-formatted group listing.  Low level.
 */
static void group_sdump(player, cause, grptr)
    int player, cause;
    struct glist *grptr;
{
  char buf[MEDBUFFSIZ], *name;
  register struct glist *curr;

  snprintf(buf, sizeof(buf), "Group: %s",
	  ((grptr->str != (char *)NULL) ? grptr->str : "*NO NAME*"));
  notify_player(player, cause, player, buf, 0);

  strcpy(buf, "Members:");
  for(curr = grptr; curr != (struct glist *)NULL; curr = curr->next) {
    if(get_str_elt(curr->player, NAME, &name) == -1)
      name = "???";

    if((sizeof(buf) - strlen(buf)) < strlen(name)) {	/* Overflow */
      notify_player(player, cause, player, buf, 0);
      sprintf(buf, "Members: %s", name);
    } else
      sprintf(&buf[strlen(buf)], " %s", name);
  }
  notify_player(player, cause, player, buf, 0);
}

/* User commands. */

VOID do_group(player, cause, switches, argone)
    int player, cause, switches;
    char *argone;
{
  if(mudconf.enable_groups) {
    if(switches & GROUP_DISOLVE) {
      struct glist *actual, *leader;

      if(!group_find(player, &actual, &leader)) {
        if(!(switches & CMD_QUIET))
	  notify_player(player, cause, player, "You're not even in a group!",
	  		NOT_QUIET);
	return;
      }
      if((leader->player != player) && !isWIZARD(player)) {
        if(!(switches & CMD_QUIET))
	  notify_player(player, cause, player, "Permission denied.", NOT_QUIET);
	return;
      }
      group_zap(leader, cause);
    } else if((switches & GROUP_CLOSE) || (switches & GROUP_OPEN)
	      || (switches & GROUP_SILENCE) || (switches & GROUP_NOISY)) {
      struct glist *actual, *leader;
      char *msg;

      if(!group_find(player, &actual, &leader)) {
        if(!(switches & CMD_QUIET))
	  notify_player(player, cause, player, "You're not even in a group!",
	  		NOT_QUIET);
	return;
      }
      if((leader->player != player) && !isWIZARD(player)) {
        if(!(switches & CMD_QUIET))
	  notify_player(player, cause, player, "Permission denied.", NOT_QUIET);
	return;
      }

      if(switches & GROUP_CLOSE) {
	leader->flags |= GRP_CLOSE;
	msg = "Group closed.";
      } else if(switches & GROUP_OPEN) {
	leader->flags &= ~GRP_CLOSE;
	msg = "Group opened.";
      } else if(switches & GROUP_SILENCE) {
	actual->flags |= GRP_SILENT;
	msg = "Group silenced.";
      } else {
	actual->flags &= ~GRP_SILENT;
	msg = "Group unsilenced.";
      }
      if(!(switches & CMD_QUIET))
        notify_player(player, cause, player, msg, NOT_QUIET);
    } else if(switches & GROUP_BOOT) {
      struct glist *actual, *leader;
      int victim;
	
      if((argone == (char *)NULL) || (argone[0] == '\0')) {
        if(!(switches & CMD_QUIET))
	  notify_player(player, cause, player, "You must specify a player.", 
	  		NOT_QUIET);
	return;
      }
      victim = resolve_player(player, cause, argone,
      			      (!(switches & CMD_QUIET) ? RSLV_NOISY|RSLV_ALLOK
						       : RSLV_ALLOK));
      if(victim == -1)
	return;

      if(!group_find(victim, &actual, &leader)) {
        if(!(switches & CMD_QUIET))
	  notify_player(player, cause, player,
		        "They're not even in a group!", NOT_QUIET);
	return;
      }
      if((leader->player != player) && !isWIZARD(player)) {
        if(!(switches & CMD_QUIET))
	  notify_player(player, cause, player, "Permission denied.", NOT_QUIET);
	return;
      }
      group_remove(victim, cause);
    } else if((switches & GROUP_SAY) || (switches & GROUP_EMOTE)) {
      char *name, buf[MEDBUFFSIZ];

      if(get_str_elt(player, NAME, &name) == -1) {
	notify_bad(player);
	return;
      }
      if(switches & GROUP_SAY) {
	snprintf(buf, sizeof(buf), "**] %s says, \"%s\"", name, 
		((argone != (char *)NULL) ? argone : ""));
      } else {
	if((argone != (char *)NULL) &&
	   ((argone[0] == ',') || (argone[0] == '\''))) {
	  snprintf(buf, sizeof(buf), "**] %s%s", name, argone);
	} else
	  snprintf(buf, sizeof(buf), "**] %s %s", name,
		  ((argone != (char *)NULL) ? argone : ""));
      }

      notify_group(player, cause, player, buf, 0);
    } else if(switches & GROUP_DISPLAY) {
      group_dump(player, cause, switches);
    } else if(switches & GROUP_JOIN) {
      int friend;

      if((argone == (char *)NULL) || (argone[0] == '\0')) {
        if(!(switches & CMD_QUIET))
	  notify_player(player, cause, player,
		        "You must specify a player to join.", NOT_QUIET);
	return;
      }
      friend = resolve_player(player, cause, argone,
      			      (!(switches & CMD_QUIET) ? RSLV_NOISY|RSLV_ALLOK
						       : RSLV_ALLOK));
      if(friend == -1)
	return;
      group_join(player, cause, switches, friend);
    } else if(switches & GROUP_FOLLOW) {
      struct glist *actual, *leader;

      if(!group_find(player, &actual, &leader)) {
        if(!(switches & CMD_QUIET))
          notify_player(player, cause, player,
		        "You're not a member of a group!", NOT_QUIET);
      } else {
        if(actual == leader) {
	  if(!(switches & CMD_QUIET))
	    notify_player(player, cause, player,
		          "You wish to follow yourself?", NOT_QUIET);
	  return;
        }
        actual->flags |= GRP_FOLLOW;

	if(!(switches & CMD_QUIET))
          notify_player(player, cause, player, "Following activated.",
	  		NOT_QUIET);

        if(!(leader->flags & GRP_SILENT)) {
	  char *name, buf[MEDBUFFSIZ];

          if(get_str_elt(player, NAME, &name) == -1)
	    name = "???";
          snprintf(buf, sizeof(buf), "%s is now following you.", name);
          notify_player(leader->player, cause, player, buf, 0);
        }
      }
    } else {
      if(isGUEST(player)) {		/* XXX */
        if(!(switches & CMD_QUIET))
	  notify_player(player, cause, player, "Permission denied.", NOT_QUIET);
	return;
      }
      group_create(player, cause, switches, argone);
    }
  } else {
    if(!(switches & CMD_QUIET))
      notify_player(player, cause, player, "Sorry, groups are not enabled.",
      		    NOT_QUIET);
  }
}