/* ************************************************************************
*   File: fields.c                                                        *
*                                                                         *
*  Copyright (c) 1995,2010 Dave Oldcorn                                   *
*  Targetting code in CircleMud Copyright (C) 1993, 94                    *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
************************************************************************ */

/* fields.c provides functions to supply semantic details about the type
 * of a field in a game struct and to allow those fields to be scanned and
 * modified by generic means.
 *
 * The central point of interface is arrays of the field_details struct which
 * provides the structure semantics. The key is that on either side of this
 * struct, extension should be easy; so it's easy to add new fields, or change
 * the definition of existing ones, and it's easy to write new subsystems which
 * use the semantic data.
 *
 * Currently, two subsystems are provided:
 * - a version of set implemented with the field system (which allows all
 *   writable parts of the structure to be modified)
 * - a database scanner that can supply reports about the contents of the
 *   mobile and object databases, filtered through arbitrary filters on
 *   structure members, very useful for helping to balance the world.
 *
 * Compatibility risks: although there's reinterpret casting all over the
 *   shop in here, it's probable that as long as offsetof isn't busted and
 *   only builtin types are used it should work.
 *
 * Show may well be at least slightly busted since it hasn't been fully
 * ported for bitarrays yet.
 */

#include "conf.h"
#include "sysdep.h"

#include <stddef.h> // needed for offsetof

#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "db.h"
#include "interpreter.h"
#include "handler.h"
#include "modify.h"
#include "constants.h"
#include "act.h"
#include "class.h"


/* Should be externs, but not in TBA yet */

/* Return 2x the average of a given dice roll (2x so can print 0.5 if required) */
int dice_avg2(int number, int size, int bonus)
{
    return number*(size+1) + (2*bonus);
}

/* Make a whole string lower case */
void lowercase(char *s)
{
  while(*s)
    *s++ = LOWER(*s);
}

static const char *type_str(int type, const char * const names[])
{
  int nr = 0;

  while (type && *names[nr] != '\n') {
    type--;
    nr++;
  }

  return *names[nr] != '\n' ? names[nr] : "UNDEFINED";
}


const char * const apply_types_short[] = {
  "NONE",
  "S",
  "D",
  "I",
  "W",
  "C",
  "CHA",
  "CLASS",
  "LEVEL",
  "AGE",
  "CHAR_WEIGHT",
  "CHAR_HEIGHT",
  "MP",
  "HP",
  "MVP",
  "GOLD",
  "EXP",
  "A",
  "HR",
  "DR",
  "SP",
  "SR",
  "SPET",
  "SB",
  "SS",       /* 24 */
  "\n"
};


int get_combat_rate(struct char_data *ch, int mult_attacks)
{
    return 8;   // No variable-rate combat in TBA - lock at 2s
}




// Generic structure field handling

// The various properties of objects, characters etc. fall into the following categories:
// - numeric, within some range
// - enumerated type, range defined by an array of strings that search_block and type_str can handle
// - bitvector, range defined by an array of strings that sprintbit can handle
// - string data, which may be single-line, single-line \r\n terminated or multi-line
// - 'more complicated'. Some properties may not be determined trivially (e.g. the vnum of an object
//      or mobile), some are composites of others (e.g. average damage dealt). In general these
//      will be read only but some may be more complex


typedef int (*field_read_function)(void* target);
typedef bool (*field_write_function)(char* buf, void* target, char* val_arg);
typedef void (*field_update_function)(void* target);


#define LVL_INVALID (LVL_IMPL+1)
struct bit_perms
{
    int     bit;                  // Bit to set permission for
    ubyte   min_level;            // minimum level; LVL_INVALID for not available
};

// An item of this struct defines a field. It's huge and complex, but the key values
// are actually set using much simpler macros
struct field_details
{
    const char* name;           // Name of field
    ubyte min_level;            // Min level needed to SET this field
    size_t offset;              // Offset from target start
    size_t size;                // Byte size
    bool is_signed;             // signed (determined from range
    int min;                    // minimum value if numeric
    int max;                    // maximum value if numeric
    const char* const * strings;// search_block-able field for bits and values
    field_read_function func_r; // function to call to read the field's value
    field_write_function func_w;// function to call to write the field's value. May be 0 if the value is read-only
    field_update_function func_u;// function called after write in case some other update is required
    const struct bit_perms* bit_perms; // Permissions for individual bits, if required; entries override min_level
    int parse_type;             // field type
};

#define FIELD_TYPE_NUM          0
#define FIELD_TYPE_ENUM         1
#define FIELD_TYPE_BITVECTOR    2
#define FIELD_TYPE_FUNCTION     3

#define FIELD_SIGNED(min)  ((min < 0) ? TRUE : FALSE )

#define FIELD_DETAILS_NUM(name, lvl, field, min, max, func_u) \
    { name, lvl, offsetof(struct STRUCT_NAME, field), sizeof(INSTANTIATED_STRUCT.field), FIELD_SIGNED(min), min, max, 0, 0, 0, (field_update_function)func_u, 0, FIELD_TYPE_NUM }

#define FIELD_DETAILS_ENUM(name, lvl, field, strings, func_u) \
    { name, lvl, offsetof(struct STRUCT_NAME, field), sizeof(INSTANTIATED_STRUCT.field), FALSE, 0, 0, strings, 0, 0, (field_update_function)func_u, 0, FIELD_TYPE_ENUM }

#define FIELD_DETAILS_BIT(name, lvl, field, bits, bitperms, func_u)     \
    { name, lvl, offsetof(struct STRUCT_NAME, field), sizeof(INSTANTIATED_STRUCT.field), FALSE, 0, 0, bits, 0, 0, (field_update_function)func_u, bitperms, FIELD_TYPE_BITVECTOR }

#define FIELD_DETAILS_FUNC(name, lvl, func_r, func_w, sign)    \
    { name, lvl, 0, 0, sign, 0, 0, 0, (field_read_function)func_r, (field_write_function)func_w, 0, 0, FIELD_TYPE_FUNCTION }

#define FIELD_END { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }




// Field functions
// Some of these might be more generally useful i.e. not belong here.


// general utilities


// handler.c?
int accum_affects(struct obj_data* obj, int location)
{
  int i, rv = 0;
  for (i = 0; i < MAX_OBJ_AFFECT; i++)
    if (location == obj->affected[i].location)
      rv += obj->affected[i].modifier;
  return rv;
}


// Mobiles

int get_combat_rating(struct char_data* mob)
{
  int avg_hp, avg_dmg, fight_rate, dps;
  int damroll = GET_DAMROLL(mob) + str_app[STRENGTH_APPLY_INDEX(mob)].todam;
  assert(IS_NPC(mob));
  avg_hp = dice_avg2(GET_HIT(mob), GET_MANA(mob), GET_MOVE(mob));
  avg_dmg = dice_avg2(mob->mob_specials.damnodice, mob->mob_specials.damsizedice, damroll);
  fight_rate = get_combat_rate(mob, 1);

  // sanc?
  // ac / thaco

  dps = avg_dmg*40/fight_rate / 4;  // factor 4 for the two x2s in avg_hp/dmg
  return dps*avg_hp;
}
int get_mob_vnum_field(struct char_data* mob)
{
  return GET_MOB_VNUM(mob);
}
int get_mob_zone_field(struct char_data* mob)
{
  // XXX todo - replace with a scan of the db for zone the mob is used in?
  return GET_MOB_VNUM(mob) / 100;
}




// Objects:


int get_obj_vnum_field(struct obj_data* obj)
{
  return GET_OBJ_VNUM(obj);
}
int get_obj_zone_field(struct obj_data* obj)
{
  // XXX todo - replace with a scan of the db for zone the obj is used in?
  return GET_OBJ_VNUM(obj) / 100;
}

int get_avg_dmg2(struct obj_data* obj)
{
  int dmg = 2*accum_affects(obj, APPLY_DAMROLL);
  switch(GET_OBJ_TYPE(obj))
  {
  case ITEM_WEAPON:
    dmg += dice_avg2(GET_OBJ_VAL(obj,1), GET_OBJ_VAL(obj,2), 0);
    break;
  }
  return dmg;
}
int get_avg_dmg(struct obj_data* obj)
{
    return get_avg_dmg2(obj)/2;
}
int get_ac(struct obj_data* obj)
{
  int ac = accum_affects(obj,APPLY_AC);
  if (GET_OBJ_TYPE(obj) == ITEM_ARMOR)
    ac += GET_OBJ_VAL(obj, 0);
  return ac;
}




// Object affects need a specific set
static bool set_obj_affect(char* result, struct obj_data* obj, char* val_arg)
{
  char buf[MAX_INPUT_LENGTH], buf1[MAX_INPUT_LENGTH], buf2[MAX_INPUT_LENGTH];
  int number, type, value;

  half_chop(val_arg, buf1, buf);
  half_chop(buf, buf2, buf);
  /* buf1 = number, buf2 = type, buf = value */
  if (!isdigit(*buf1)) {
    snprintf(result, MAX_STRING_LENGTH, "Format is: set <obj> affect <number> <type> <value>: %s is not a number\r\n", buf1);
    return FALSE;
  }
  number = atoi(buf1) - 1;
  if ((number < 0) || (number >= MAX_OBJ_AFFECT)) {
    snprintf(result, MAX_STRING_LENGTH, "Affect number %d is not valid\r\n", number);
    return FALSE;
  }
  type = search_block(buf2, apply_types, 0);
  if ((type == -1) || (type == 0)) {
    snprintf(result, MAX_STRING_LENGTH, "%s is not a valid affect type.\r\n", type);
    return FALSE;
  }

  if (!isdigit(*buf) && (*buf != '-')) {
    snprintf(result, MAX_STRING_LENGTH, "Format is: set <obj> affect <number> <type> <value>: value field missing\r\n");
    return FALSE;
  }
  value = atoi(buf);
  snprintf(buf, sizeof(buf), "affect %d set: %+d to %s\n", number+1, value, type_str(type, apply_types));

  obj->affected[number].location = type;
  obj->affected[number].modifier = value;

  return TRUE;
}





// Field details are searched in order, so higher priority fields (for abbreviation)
// should be above lower priority ones


// Fields for both PCs and NPCs
#define STRUCT_NAME char_data
#define INSTANTIATED_STRUCT tmp_char_data
static struct STRUCT_NAME INSTANTIATED_STRUCT;


const struct bit_perms aff_bit_perms[] = {
    { AFF_BLIND, LVL_INVALID },
    { AFF_GROUP, LVL_INVALID },
    { AFF_POISON, LVL_INVALID },
    { AFF_SLEEP, LVL_INVALID },
    { 0, 0 }
};

const struct field_details char_fields[] =
{
    FIELD_DETAILS_BIT ("aff",           LVL_IMPL,   char_specials.saved.affected_by, affected_bits, aff_bit_perms, 0),
    FIELD_DETAILS_NUM ("ac",            LVL_IMPL,   points.armor, -100, 100, affect_total),
    FIELD_DETAILS_NUM ("alignment",     LVL_IMPL,   char_specials.saved.alignment, -1000, 1000, affect_total),
    FIELD_DETAILS_NUM ("cha",           LVL_IMPL,   real_abils.cha, 3, 25, affect_total),
    FIELD_DETAILS_NUM ("coins",         LVL_IMPL,   points.gold, 0, 1000000000, 0),
    FIELD_DETAILS_NUM ("con",           LVL_IMPL,   real_abils.con, 3, 25, affect_total),
    FIELD_DETAILS_NUM ("damroll",       LVL_IMPL,   points.damroll, -128, 127, affect_total),
    FIELD_DETAILS_NUM ("dex",           LVL_IMPL,   real_abils.dex, 3, 25, affect_total),
    FIELD_DETAILS_NUM ("exp",           LVL_IMPL,   points.exp, 0, 1000000000, 0),
    FIELD_DETAILS_NUM ("gold",          LVL_IMPL,   points.gold, 0, 1000000000, 0),
    FIELD_DETAILS_NUM ("height",        LVL_GRGOD,  player.height, 1, 5000, affect_total),
    FIELD_DETAILS_NUM ("hit",           LVL_GRGOD,  points.hit, 0, 5000, affect_total),
    FIELD_DETAILS_NUM ("hitroll",       LVL_IMPL,   points.hitroll, -128, 127, affect_total),
    FIELD_DETAILS_NUM ("int",           LVL_IMPL,   real_abils.intel, 3, 25, affect_total),
    FIELD_DETAILS_NUM ("level",         LVL_IMPL,   player.level, 1, LVL_IMPL, 0),
    FIELD_DETAILS_NUM ("mana",          LVL_GRGOD,  points.mana, 0, 5000, affect_total),
    FIELD_DETAILS_NUM ("maxhit",        LVL_IMPL,   points.max_hit, 0, 5000, affect_total),
    FIELD_DETAILS_NUM ("maxmana",       LVL_IMPL,   points.max_mana, 0, 5000, affect_total),
    FIELD_DETAILS_NUM ("maxmove",       LVL_IMPL,   points.max_move, 0, 5000, affect_total),
    FIELD_DETAILS_NUM ("move",          LVL_GRGOD,  points.move, 0, 5000, affect_total),
    FIELD_DETAILS_ENUM("position",      LVL_GRGOD,  char_specials.position, position_types, 0),
    // saving throw modifiers?
    FIELD_DETAILS_ENUM("sex",           LVL_GRGOD,  player.sex, genders, 0),
    FIELD_DETAILS_NUM ("str",           LVL_IMPL,   real_abils.str, 3, 25, affect_total),
    FIELD_DETAILS_NUM ("stradd",        LVL_IMPL,   real_abils.str_add, 0, 100, affect_total),
    FIELD_DETAILS_NUM ("weight",        LVL_GRGOD,  player.weight, 1, 5000, affect_total),
    FIELD_DETAILS_NUM ("wis",           LVL_IMPL,   real_abils.wis, 3, 25, affect_total),
    // vulnerables
    FIELD_END
};
#undef STRUCT_NAME
#undef INSTANTIATED_STRUCT



// Fields for mobiles only
#define STRUCT_NAME char_data
#define INSTANTIATED_STRUCT tmp_char_data

const struct bit_perms mob_bit_perms[] = 
{
    { MOB_ISNPC, LVL_INVALID },
    { MOB_NOTDEADYET, LVL_INVALID },
    { 0, 0}
};

const struct field_details mob_fields[] =
{
    // name
    // short
    // long
    // desc
    // title
//    FIELD_DETAILS_ENUM("attacktype",    LVL_GRGOD,  mob_specials, attack_hit_text, 0),
    FIELD_DETAILS_FUNC("combatrating",  LVL_INVALID,get_combat_rating, 0, TRUE),
    FIELD_DETAILS_NUM ("damnodice",     LVL_IMPL,   mob_specials.damnodice, 0, 20, 0),
    FIELD_DETAILS_NUM ("damsizedice",   LVL_IMPL,   mob_specials.damsizedice, 1, 100, 0),
    FIELD_DETAILS_ENUM("defaultposition", LVL_IMPL, mob_specials.default_pos, position_types, 0),
    FIELD_DETAILS_BIT ("mob",           LVL_IMPL,   char_specials.saved.act, action_bits, mob_bit_perms, 0),  // mobiles only
    // proc
    // size
    FIELD_DETAILS_FUNC("vnum",          LVL_INVALID,get_mob_vnum_field, 0, TRUE),
    FIELD_DETAILS_FUNC("zone",          LVL_INVALID,get_mob_zone_field, 0, TRUE),
    // Should add xdy+z functions for damage, hitdice
    FIELD_END
};
#undef STRUCT_NAME
#undef INSTANTIATED_STRUCT


// Fields for PCs only
#define STRUCT_NAME char_data
#define INSTANTIATED_STRUCT tmp_char_data

const struct bit_perms plr_bit_perms[] = {
    { PLR_DONTSET, LVL_INVALID },
    { PLR_WRITING, LVL_INVALID },
    { PLR_MAILING, LVL_INVALID },
    { PLR_CRASH, LVL_INVALID },
    { PLR_DELETED, LVL_INVALID },
    { PLR_NOTDEADYET, LVL_INVALID },
    { 0, 0 }
};

const struct field_details player_fields[] =
{
    // player.age, needs func
    FIELD_DETAILS_NUM ("bank",          LVL_IMPL,   points.bank_gold, 0, 1000000000, 0),
    FIELD_DETAILS_ENUM("class",         LVL_GRGOD,  player.chclass, pc_class_types, 0),
    // char_specials.saved.idnum?
    FIELD_DETAILS_BIT ("plr",           LVL_IMPL,   char_specials.saved.act, player_bits, plr_bit_perms, 0),    // postcondition function not to freeze self?
    // loadroom?
    FIELD_END
};
#undef STRUCT_NAME
#undef INSTANTIATED_STRUCT


// player_specials is accessed via a pointer, so we can't access this just via an offset
// from char_data. Instead, it becomes its own separate struct.
#define STRUCT_NAME player_special_data
#define INSTANTIATED_STRUCT tmp_player_special_data
static struct STRUCT_NAME INSTANTIATED_STRUCT;
const struct bit_perms prf_bit_perms[] = {
    { PRF_HOLYLIGHT, LVL_INVALID },
    { PRF_LOG1, LVL_INVALID },
    { PRF_LOG2, LVL_INVALID },
    { PRF_SHOWVNUMS, LVL_INVALID },
    { PRF_BUILDWALK, LVL_IMPL },
    { 0, 0 }
};
const struct field_details player_special_data_fields[] =
{
    FIELD_DETAILS_NUM ("drunk",         LVL_IMPL,   saved.conditions[0], -1, 24, 0),
    // edit perms
    FIELD_DETAILS_NUM ("hunger",        LVL_IMPL,   saved.conditions[1], -1, 24, 0),
    FIELD_DETAILS_NUM ("invislevel",    LVL_IMPL,   saved.invis_level, 0, LVL_IMPL, 0),  // Needs postcondition function to clamp to vict_lev
    // poofin/out
    FIELD_DETAILS_NUM ("practices",     LVL_IMPL,   saved.spells_to_learn, 0, 999, 0),
    FIELD_DETAILS_BIT ("prf",           LVL_GRGOD,  saved.pref, preference_bits, prf_bit_perms, 0),
    FIELD_DETAILS_NUM ("thirst",        LVL_IMPL,   saved.conditions[2], -1, 24, 0),
    FIELD_DETAILS_NUM ("wimplevel",     LVL_GRGOD,  saved.wimp_level, 0, 5000, 0),
    FIELD_END
};
#undef STRUCT_NAME
#undef INSTANTIATED_STRUCT



#define STRUCT_NAME obj_data
#define INSTANTIATED_STRUCT tmp_obj_data
static struct STRUCT_NAME INSTANTIATED_STRUCT;
const struct field_details obj_fields[] =
{
    // name
    // short
    // desc
    // exdesc
    FIELD_DETAILS_FUNC("ac",        LVL_INVALID,get_ac, 0, TRUE),
    FIELD_DETAILS_BIT ("aff",       LVL_IMPL,   obj_flags.bitvector, affected_bits, aff_bit_perms, 0),
    FIELD_DETAILS_FUNC("affect",    LVL_IMPL,   0, set_obj_affect, FALSE),
    FIELD_DETAILS_FUNC("avgdmg2",   LVL_IMPL,   get_avg_dmg, 0, TRUE),
    FIELD_DETAILS_BIT ("bits",      LVL_IMPL,   obj_flags.extra_flags, extra_bits, 0, 0),
    FIELD_DETAILS_NUM ("cost",      LVL_IMPL,   obj_flags.cost, -1, 1000000000, 0),
    // proc
    FIELD_DETAILS_NUM ("rent",      LVL_IMPL,   obj_flags.cost_per_day, -1, 1000000000, 0),
    FIELD_DETAILS_NUM ("timer",     LVL_IMPL,   obj_flags.timer, -1, 10000000, 0),
    FIELD_DETAILS_ENUM("type",      LVL_IMPL,   obj_flags.type_flag, item_types, 0),       // We should clear VAL when this is changed...
    FIELD_DETAILS_BIT ("wear",      LVL_IMPL,   obj_flags.wear_flags, wear_bits, 0, 0),
    FIELD_DETAILS_NUM ("weight",    LVL_IMPL,   obj_flags.weight, 0, 100000, 0),
    FIELD_DETAILS_NUM ("value0",    LVL_IMPL,   obj_flags.value[0], -10000, 10000, 0),     // Not a great solution!
    FIELD_DETAILS_NUM ("value1",    LVL_IMPL,   obj_flags.value[1], -10000, 10000, 0),
    FIELD_DETAILS_NUM ("value2",    LVL_IMPL,   obj_flags.value[2], -10000, 10000, 0),
    FIELD_DETAILS_NUM ("value3",    LVL_IMPL,   obj_flags.value[3], -10000, 10000, 0),
    FIELD_DETAILS_FUNC("vnum",      LVL_INVALID,get_obj_vnum_field, 0, TRUE),
    // world limit? Not actually a per-object property...
    FIELD_DETAILS_FUNC("zone",      LVL_INVALID,get_obj_zone_field, 0, TRUE),
    FIELD_END
};
#undef STRUCT_NAME
#undef INSTANTIATED_STRUCT


#define STRUCT_NAME obj_data
#define INSTANTIATED_STRUCT tmp_obj_data
const struct field_details weapon_fields[] =
{
    FIELD_DETAILS_NUM ("hitroll",       LVL_IMPL,   obj_flags.value[0], -20, 20, 0),
    FIELD_DETAILS_NUM ("damnodice",     LVL_IMPL,   obj_flags.value[1], 0, 20, 0),
    FIELD_DETAILS_NUM ("damsizedice",   LVL_IMPL,   obj_flags.value[2], 1, 100, 0),
//    FIELD_DETAILS_ENUM("attacktype",    LVL_GRGOD,  obj_flags.value[3], attack_hit_text, 0),
    // should add xdy function parser for damage
    FIELD_END
};
#undef STRUCT_NAME
#undef INSTANTIATED_STRUCT

#define STRUCT_NAME obj_data
#define INSTANTIATED_STRUCT tmp_obj_data
const struct field_details armor_fields[] =
{
    FIELD_DETAILS_NUM ("armor",         LVL_IMPL,   obj_flags.value[0], -50, 50, 0),
    FIELD_DETAILS_NUM ("armour",        LVL_IMPL,   obj_flags.value[0], -50, 50, 0),
    FIELD_END
};
#undef STRUCT_NAME
#undef INSTANTIATED_STRUCT

#define STRUCT_NAME obj_data
#define INSTANTIATED_STRUCT tmp_obj_data
const struct field_details wandstaff_fields[] =
{
    FIELD_DETAILS_NUM ("level",         LVL_IMPL,   obj_flags.value[0], -20, 20, 0),
    FIELD_DETAILS_NUM ("maxcharges",    LVL_IMPL,   obj_flags.value[1], 0, 20, 0),
    FIELD_DETAILS_NUM ("charges",       LVL_IMPL,   obj_flags.value[2], 1, 100, 0),
//    FIELD_DETAILS_ENUM("spell",         LVL_IMPL,   obj_flags.value[3], spells, 0),
    FIELD_END
};
#undef STRUCT_NAME
#undef INSTANTIATED_STRUCT

#define STRUCT_NAME obj_data
#define INSTANTIATED_STRUCT tmp_obj_data
const struct field_details scrollpotion_fields[] =
{
    FIELD_DETAILS_NUM ("level",         LVL_IMPL,   obj_flags.value[0], -20, 20, 0),
//    FIELD_DETAILS_ENUM("spell1",        LVL_IMPL,   obj_flags.value[1], spells, 0),
//    FIELD_DETAILS_ENUM("spell2",        LVL_IMPL,   obj_flags.value[2], spells, 0),
//    FIELD_DETAILS_ENUM("spell3",        LVL_IMPL,   obj_flags.value[3], spells, 0),
    FIELD_END
};
#undef STRUCT_NAME
#undef INSTANTIATED_STRUCT



const struct {
    int type;
    const struct field_details *fields;
} item_type_fieldlists[] =
{
    { ITEM_WEAPON, weapon_fields },
    { ITEM_ARMOR,  armor_fields },
    { ITEM_SCROLL, scrollpotion_fields },
    { ITEM_POTION, scrollpotion_fields },
    { ITEM_WAND, wandstaff_fields },
    { ITEM_STAFF, wandstaff_fields },
    { 0, 0 }
};



// General field utilities
const struct field_details *match_field(const char *fieldname, const struct field_details *list)
{
  while(list->name)
  {
    if (is_abbrev(fieldname, list->name))
      return list;
    list++;
  }

  return NULL;
}


int min_level_bit_field(const struct field_details* list, int bit)
{
  const struct bit_perms *perms = list->bit_perms;
  for(; perms && perms->bit; perms++)
    if (perms->bit == bit)
      return perms->min_level;

  return list->min_level;
}


void strcat_enums(char *buf, const char * const *list)
{
  int col = 0;
  while(**list != '\n')
  {
    if (**list != '!')
    {
      snprintf(buf, MAX_STRING_LENGTH, "%s%-15.15s ", buf, *list);
      if (++col == 5)
      {
          col = 0;
          snprintf(buf, MAX_STRING_LENGTH, "%s\r\n", buf);
      }
    }
    list++;
  }
}

void strcat_bits(struct char_data* ch, char *buf, const struct field_details *list, int *col)
{
  char buf1[MAX_INPUT_LENGTH];
  const char * const *strings = list->strings;
  int bit = 0;
  while(**strings != '\n')
  {
    int min_level = min_level_bit_field(list, bit);

    if ((**strings != '!') && GET_LEVEL(ch) >= min_level)
    {
      snprintf(buf1, sizeof(buf1), "%s.%s", list->name, *strings);
      lowercase(buf1);
      snprintf(buf, MAX_STRING_LENGTH, "%s%-15.15s%s", buf, buf1, ++(*col) == 5 ? "\r\n" : " ");
    }
    strings++;
    bit++;
  }
}

void list_fields(struct char_data *ch, const struct field_details *list, int *col, bool is_set)
{
  char buf[MAX_STRING_LENGTH];
  while(list->name)
  {
    if ((list->parse_type == FIELD_TYPE_BITVECTOR) && is_set)
    {
      *buf = 0;
      strcat_bits(ch, buf, list, col);
      send_to_char(ch, "%s", buf);
    }
    else if ((is_set && (list->parse_type != FIELD_TYPE_FUNCTION || list->func_w) && (GET_LEVEL(ch) >= list->min_level)) ||
             (!is_set && (list->parse_type != FIELD_TYPE_FUNCTION || list->func_r) ) )
      send_to_char(ch, "%-15.15s%s", list->name, ++(*col) == 5 ? "\r\n" : " ");

    list++;
  }
}

bool read_field(void *vtarget, const struct field_details *field, int *value)
{
  // Read up the result. Must promote to int correctly for signed / unsigned
  byte* target = (byte*)vtarget + field->offset;
  switch(field->size)
  {
  case 0: assert(field->parse_type == FIELD_TYPE_FUNCTION); *value = (field->func_r)(vtarget); break;
  case 1: *value = field->is_signed ? ((int)*((sbyte*)target)) : ((int)*((ubyte*)target)); break;
  case 2: *value = field->is_signed ? ((int)*((sh_int*)target)) : ((int)*((ush_int*)target)); break;
  case 4: *value = *(int*)target; break;    // no promotion; uint to int converts rather than clamping
  default: return FALSE;
  }
  return TRUE;
}

bool read_field_bitarray(void *vtarget, const struct field_details *field, int *value, int array_max)
{
  int *target = (int*)((byte*)vtarget + field->offset);
  int array_size = field->size / 4;
  if ((field->size % 4) || (array_size > array_max))
      return FALSE;

  for(; array_size; array_size--)
    *value++ = *target++;

  return TRUE;
}

bool write_field_bitarray(void *vtarget, const struct field_details *field, int *value, int array_max)
{
  int *target = (int*)((byte*)vtarget + field->offset);
  int array_size = field->size / 4;
  if ((field->size % 4) || (array_size > array_max))
      return FALSE;

  for(; array_size; array_size--)
    *target++ = *value++;

  return TRUE;
}

int match_field_string(char* buf, const struct field_details *field, char *name)
{
  int value = search_block(name, field->strings, FALSE);
  if (value == -1)
  {
    if (buf)
    {
      snprintf(buf, MAX_STRING_LENGTH, "Couldn't find %s. Must be one of:\r\n", name);
      strcat_enums(buf, field->strings);
    }
  }
  return value;
}


// Set the value of (field) to (val_arg)
// returns TRUE if successful
bool field_set(struct char_data *ch, char* buf, void* vtarget, const struct field_details* field, char *val_arg, bool is_bitfield)
{
  char bitname[MAX_INPUT_LENGTH];
  byte* target = (byte*)vtarget + field->offset;
  int value = 0;
  int bitarray[4];
  int bitindex;
  int min_level = field->min_level;

  if (is_bitfield && (field->parse_type != FIELD_TYPE_BITVECTOR))
  {
    snprintf(buf, MAX_STRING_LENGTH, "%s is not a bitfield.\r\n", field->name);
    return FALSE;
  }

  switch(field->parse_type)
  {
  case FIELD_TYPE_FUNCTION:
    // Hand straight off to the write function
    return (field->func_w)(buf, vtarget, val_arg);

  case FIELD_TYPE_NUM:
    if (!is_number(val_arg))
    {
      snprintf(buf, MAX_STRING_LENGTH, "%s is not a number.\r\n", val_arg);
      return FALSE;
    }
    value = atoi(val_arg);
    value = MIN(field->max, MAX(value, field->min));
    snprintf(buf, MAX_STRING_LENGTH, "%s set to %d", field->name, value);
    break;

  case FIELD_TYPE_ENUM:
    if ((value = match_field_string(buf, field, val_arg)) == -1)
      return FALSE;
    snprintf(buf, MAX_STRING_LENGTH, "%s set to %s", field->name, field->strings[value]);
    break;

  case FIELD_TYPE_BITVECTOR:
    if (!is_bitfield)
    {
      snprintf(buf, MAX_STRING_LENGTH, "Format for bit fields is set <target> <field>.<bit> <on/yes/off/no>.\r\n");
      return FALSE;
    }
    half_chop(val_arg, bitname, val_arg);
    if ((bitindex = match_field_string(buf, field, bitname)) == -1)
      return FALSE;
    if (!read_field_bitarray(vtarget, field, bitarray, sizeof(bitarray)/4))
    {
      snprintf(buf, MAX_STRING_LENGTH, "Error reading field %s (to set %s)\r\n", field->name, bitname);
      return FALSE;
    }

    min_level = min_level_bit_field(field, bitindex);
    if (min_level == LVL_INVALID)
    {
      int col = 0;
      snprintf(buf, MAX_STRING_LENGTH, "Can't set that. Valid bits:\r\n");
      strcat_bits(ch, buf, field, &col);
      return FALSE;
    }

    if (!strcmp(val_arg, "on") || !strcmp(val_arg, "yes"))
      SET_BIT_AR(bitarray, bitindex);
    else if (!strcmp(val_arg, "off") || !strcmp(val_arg, "no"))
      REMOVE_BIT_AR(bitarray, bitindex);
    else
    {
      snprintf(buf, MAX_STRING_LENGTH, "Value must be on or off.\r\n");
      return FALSE;
    }

    snprintf(buf, MAX_STRING_LENGTH, "%s.%s set to %s", field->name, field->strings[bitindex], val_arg);

    return write_field_bitarray(vtarget, field, bitarray, sizeof(bitarray)/4);
  }

  if (GET_LEVEL(ch) < min_level)
  {
      
  }

  // Write the result. Assumes result fits into the target size
  switch(field->size)
  {
  case 1: *((sbyte*)target) = value; break;
  case 2: *((sh_int*)target) = value; break;
  case 4: *((int*)target) = value; break;
  default: assert(0); return FALSE;
  }
  if (field->func_u)
    (field->func_u)(vtarget);

  return TRUE;
}




ACMD(do_fieldset)
{
  char fieldname[MAX_INPUT_LENGTH], name[MAX_INPUT_LENGTH], val_arg[MAX_INPUT_LENGTH];
  char buf[MAX_STRING_LENGTH], *p;
  bool is_mob = 0, is_player = 0, is_player_special = 0, is_obj = 0, is_char = 0;
  bool is_bitfield = FALSE;
  struct char_data *vict = 0;
  struct obj_data *obj = 0;
  const char* target_name;
  const struct field_details *field = NULL, *type_fields = NULL;
  bool result = FALSE;

  if (IS_NPC(ch))
    return;

  half_chop(argument, name, buf);
  if (!str_cmp(name, "player")) {
    is_player = 1;
    half_chop(buf, name, buf);
  } else if (!str_cmp(name, "mob")) {
    is_mob = 1;
    half_chop(buf, name, buf);
  } else if (!str_cmp(name, "obj")) {
    is_obj = 1;
    half_chop(buf, name, buf);
  }

  half_chop(buf, fieldname, val_arg);
  if ((p = strchr(fieldname, '.')) != NULL)
  {
    // Bit field; rechop the buf
    *strchr(buf, '.') = ' ';
    half_chop(buf, fieldname, val_arg);
    is_bitfield = TRUE;
  }

  if (!*name) {
    send_to_char(ch, "Usage: set [<file/obj/char/room/exit/zone/zcmd>] [<name>] <field> <value>\r\n");
    return;
  }

  {
    /* Not file: check for correct type */
    if (is_player) {
      is_char = 1;
      if (!(vict = get_player_vis(ch, name, NULL, FIND_CHAR_ROOM))) {
        send_to_char(ch, "There is no such player.\r\n");
        return;
      }
    } else if (is_obj) {
      if (!(obj = get_obj_vis(ch, name, NULL))) {
        send_to_char(ch, "There is no such object.\r\n");
        return;
      }
    } else {
      /* mob or unspecified */
      vict = get_char_vis(ch, name, NULL, FIND_CHAR_ROOM);
      if (!vict && !is_mob)
        obj = get_obj_vis(ch, name, NULL);
      if (!vict && !obj)
        vict = get_char_vis(ch, name, NULL, FIND_CHAR_WORLD);

      if (!vict && !obj) {
        send_to_char(ch, "No target found.\r\n");
        return;
      }
      
      if (obj)
        is_obj = 1;
      else
      {
        is_char = 1;
        if (IS_NPC(vict))
          is_mob = 1;
        else
          is_player = 1;
      }
    }
  }

  target_name = is_char ? GET_NAME(ch) :
                is_obj  ? obj->short_description :
                          "(unknown)";

  /* Can only affect chars of lower level or npc if not imp */
  if (is_char && (GET_LEVEL(ch) < LVL_IMPL)) {
    if (!IS_NPC(vict) && (GET_LEVEL(ch) <= GET_LEVEL(vict)) && (vict != ch)) {
      send_to_char(ch, "You haven't enough priority to set that person.\r\n");
      return;
    }
  }

  if (is_player)
  {
    field = match_field(fieldname, player_fields);

    // player_specials is accessed via a pointer, so we can't access this just via an offset
    // from char_data. Instead, this becomes a particular special case, a different object
    // type, but by simply checking the fields along with the others, the user can't see the
    // difference.
    if (!field)
    {
      field = match_field(fieldname, player_special_data_fields);
      if (field)
        is_player_special = TRUE;
    }
  }
  if (!field && is_mob)
    field = match_field(fieldname, mob_fields);
  if (!field && is_char)
    field = match_field(fieldname, char_fields);
  if (!field && is_obj)
  {
    field = match_field(fieldname, obj_fields);
    if (!field)
    {
      int i;
      for(i=0; item_type_fieldlists[i].fields; i++)
        if (item_type_fieldlists[i].type == GET_OBJ_TYPE(obj))
          type_fields = item_type_fieldlists[i].fields;

      if (type_fields)
        field = match_field(fieldname, type_fields);
    }
  }

  if (!field)
  {
    int col = 0;
    send_to_char(ch, "Field %s not found. Options are:\r\n", fieldname);
    if (is_player)
    {
        list_fields(ch, player_fields, &col, TRUE);
        list_fields(ch, player_special_data_fields, &col, TRUE);
    }
    if (is_mob) list_fields(ch, mob_fields, &col, TRUE);
    if (is_char) list_fields(ch, char_fields, &col, TRUE);
    if (is_obj) list_fields(ch, obj_fields, &col, TRUE);
    if (type_fields) list_fields(ch, type_fields, &col, TRUE);
    send_to_char(ch, "\r\n");

    return;
  }

  if (is_player_special)
    result = field_set(ch, buf, vict->player_specials, field, val_arg, is_bitfield);
  else if (is_char)
    result = field_set(ch, buf, vict, field, val_arg, is_bitfield);
  else if (is_obj)
    result = field_set(ch, buf, obj, field, val_arg, is_bitfield);

  if (result)
  {
    send_to_char(ch, "%s: %s.\r\n", target_name, buf);
    mudlog((!is_char || IS_NPC(vict)) ? CMP : BRF, GET_LEVEL(ch), TRUE, "(GC) %s: %s %s", GET_NAME(ch), target_name, buf);
  }
  else
    send_to_char(ch, "%s\r\n", buf);

}














// base_type -> derived from sizeof
// signed == (min < 0)
// unsigned == (max > max_int_type)

// A parsed filter
struct filter_details
{
    const struct field_details* field;
    bool invert;
    int min;    // min (value or enum)
    int max;    // max (value if no min/max range specified)
    int bitarray[4];    // Bitvetctor to match
};

// A filter is a single arg of the form:
// <field>[!]=<value/enum>
// <field>[!]=<bit>[|<bit>...]
// <field>[!]=<value>-<value>
bool parse_filter(struct char_data* ch, const char* arg,
                  const struct field_details* const * fieldset, struct filter_details* filter)
{
  const struct field_details* field = NULL;
  char fieldname[MAX_INPUT_LENGTH];
  char *p, *p2;

  strlcpy(fieldname, arg, sizeof(fieldname));
  p = strchr(fieldname, '=');
  if (!p)
  {
    send_to_char(ch, "Missing = or != in filter %s format is field=value<-value (numeric fields)> <|value (bitvectorfields)>\r\n", fieldname);
    return FALSE;
  }

  filter->invert = FALSE;
  if (p != fieldname && p[-1] == '!')
  {
      p[-1] = 0;
      filter->invert = TRUE;
  }
  *p++ = 0;

  for(; *fieldset && !field; fieldset++)
    field = match_field(fieldname, *fieldset);
  if (!field)
  {
    send_to_char(ch, "Unknown field %s in filter\r\n", fieldname);
    return FALSE;
  }


  filter->field = field;
  switch(field->parse_type)
  {
  case FIELD_TYPE_NUM:
  case FIELD_TYPE_FUNCTION:
    filter->min = filter->max = strtol(p, &p, 0);
    if (*p == '-')
      filter->max = strtol(p+1, &p, 0);
    return TRUE;

  case FIELD_TYPE_ENUM:
    filter->min = filter->max = search_block(p, field->strings, 1);
    if (filter->min == -1)
    {
      send_to_char(ch, "Field %s, no value %s found\r\n", field->name, p);
      return FALSE;
    }
    return TRUE;

  case FIELD_TYPE_BITVECTOR:
    filter->min = 0;
    for(;;) {
      int bv;
      p2 = strchr(p, '|');
      if (p2)
        *p2++ = 0;
      bv = search_block(p, field->strings, 1);
      if (bv == -1) {
        send_to_char(ch, "Field %s, unknown bit %s\r\n", field->name, p);
        return FALSE;
      }
      SET_BIT_AR(filter->bitarray, bv);
      if (p2)
        p = p2;
      else
        break;
    }
    filter->max = filter->min;
    return TRUE;

  default:
    return FALSE;
  }
}


bool test_filter(void* vtarget, struct filter_details* filter)
{
  bool result;
  int value;
  int bitarray[4];
  int i;

  // Do the comparison. Again, jump through some signed/unsigned hoops
  switch(filter->field->parse_type)
  {
  default:
      result = read_field(vtarget, filter->field, &value);
      if (!result)
        return FALSE;

      result = filter->field->is_signed ? (value >= filter->min) && (value <= filter->max) :
                                          ((unsigned)value >= (unsigned)filter->min) && ((unsigned)value <= (unsigned)filter->max);
      break;
  case FIELD_TYPE_BITVECTOR:
      result = read_field_bitarray(vtarget, filter->field, &value, 4);
      if (!result)
        return FALSE;
      result = FALSE;
      for(i=0; i<4; i++)
        if ((bitarray[i] & filter->bitarray[i]) != 0)
          result = TRUE;
      break;
  }

  return result ^ filter->invert;
}


typedef void (*report_function)(char* buf, size_t bufleft, void* target);

struct report_functions
{
  const char* name;
  const char* header;
  report_function report;
};

// show_general: taking a structure and a description of its fields, parse filters from
// the supplied argument and generate one of several supplied reports on all the items
// matching the filter.
void show_general(struct char_data* ch, char* argument, const char* argres,
                  void* target_list, size_t target_size, int target_count,
                  const struct field_details* const* fieldset, const struct report_functions* reports)
{
#define MAX_FILTERS 20
  char arg[MAX_INPUT_LENGTH], buf[MAX_STRING_LENGTH], *bufptr = buf;
  size_t bufsize = sizeof(buf);
  int filtercount, i, j;
  struct filter_details filters[MAX_FILTERS];
  const struct report_functions* report;
  void* result_array[MAX_STRING_LENGTH/40];     // Assuming about 1 line per item, so this is about the most we could need
  int result_count = 0;

  // Allow ? (or -anything) to list the field and report options
  if ((*argres == '?') || (*argres == '-')) {
    int col = 0;
    send_to_char(ch, "Reports available:\r\n");
    for(report=reports; report->name; report++)
      send_to_char(ch, "  %s\r\n", report->name);
    send_to_char(ch, "Fields available: \r\n");
    for(; *fieldset; fieldset++)
      list_fields(ch, *fieldset, &col, FALSE);
    return;
  }

  // argres is the result type
  for(report=reports; report->name; report++)
    if (is_abbrev(argres, report->name))
      break;
  if (!report->name) {
    send_to_char(ch, "Unknown report type %s\r\nOptions are:\r\n", argres);
    for(report=reports; report->name; report++)
      send_to_char(ch, "  %s\r\n", report->name);
    return;
  }

  // Run through the remaining arguments and generate a list of filters
  for(filtercount=0; filtercount<MAX_FILTERS; filtercount++) {
    argument = one_argument(argument, arg);
    if (!*arg)
      break;
    if (!parse_filter(ch, arg, fieldset, &filters[filtercount]))
      break;
  }

  // Run through each of the targets checking it matches each filter and
  // adding to the array of valid results
  for(i=0; i<target_count; i++) {
    bool passed = TRUE;
    for(j=0; j<filtercount; j++)
      passed &= test_filter(target_list, filters + j);
    if (passed && result_count < (sizeof(result_array)/sizeof(void*)))
      result_array[result_count++] = target_list;
    target_list = (byte*)target_list + target_size;
  }

  if (!result_count) {
    send_to_char(ch, "No matches found\r\n");
    return;
  }

  // Sort the results - on what criteria?




  // Average the results? Needs cooperation from the report to do that... can
  // we enforce that report columns must come from fields? Field functions might
  // give us enough flexibility there...




  // Generate the results
  for(i=0; i<result_count; i++) {
    int bufused;
//    bufused = snprintf(bufptr, bufsize, "%3d. ", ++found);
//    bufsize -= bufused;
//    bufptr += bufused;
    (report->report)(bufptr, bufsize, result_array[i]);
    bufused = strlen(bufptr);
    bufptr = bufptr + bufused;
    bufsize -= bufused;
    if (bufsize < 80) {
      strcat(bufptr, "*** TRUNCATED ***\r\n");
      break;
    }
  }

  send_to_char(ch, "%s\r\n", report->header);
  page_string(ch->desc, buf, 1);
}



void mob_report_name(char* buf, size_t bufleft, struct char_data* mob)
{
  snprintf(buf, bufleft, "[%5d] %s (%s)\r\n",
           GET_MOB_VNUM(mob), mob->player.short_descr, mob->player.name);
}
void mob_report_danger(char* buf, size_t bufleft, struct char_data* mob)
{
  int avg_hp2, avg_dmg2, fight_rate, dps;
  char buf2[20];
  int damroll = GET_DAMROLL(mob) + str_app[STRENGTH_APPLY_INDEX(mob)].todam;
  int cr = get_combat_rating(mob);

  snprintf(buf2, sizeof(buf2), "%dd%d+%d", mob->mob_specials.damnodice, mob->mob_specials.damsizedice, GET_DAMROLL(mob));
  assert(IS_NPC(mob));
  avg_hp2 = dice_avg2(GET_HIT(mob), GET_MANA(mob), GET_MOVE(mob));
  avg_dmg2 = dice_avg2(mob->mob_specials.damnodice, mob->mob_specials.damsizedice, damroll)/2;
  fight_rate = get_combat_rate(mob, 1);
  dps = avg_dmg2*40/fight_rate / 4;  // factor 4 for the two x2s in avg_hp/dmg

  snprintf(buf, bufleft, "%5d %-20.20s "
                         "%2d %5d %4d %-8s %d.%2.2d %2d.%d %4d%s %s%s%s%s\r\n",
           GET_MOB_VNUM(mob), mob->player.short_descr,
           GET_LEVEL(mob), avg_hp2/2, GET_AC(mob),
           buf2, fight_rate/4, (fight_rate%4)*25, dps/10, dps%10,
           cr < 10000 ? cr : cr/1000, cr < 10000 ? " " : "k",
           MOB_FLAGGED(mob, MOB_AGGRESSIVE|MOB_AGGR_GOOD|MOB_AGGR_NEUTRAL|MOB_AGGR_EVIL) ? "Ag " : "",
           AFF_FLAGGED(mob, AFF_SANCTUARY) ? "Sc ": "",
           AFF_FLAGGED(mob, AFF_PROTECT_EVIL) ? "Pe ": "",
           AFF_FLAGGED(mob, AFF_PROTECT_GOOD) ? "Pg ": ""
           );
}

int objcmpfunc(const void* a, const void* b)
{
  struct obj_data *obja=obj_proto + *(int*)a;
  struct obj_data *objb=obj_proto + *(int*)b;
  return GET_OBJ_COST(obja) > GET_OBJ_COST(objb);
}

void mob_report_reward(char* buf, size_t bufleft, struct char_data* mob)
{
  int equivalent_level = MIN(GET_LEVEL(mob), LVL_IMMORT-1);
  int forlevel = level_exp(CLASS_WARRIOR, equivalent_level + 1) - level_exp(CLASS_WARRIOR, equivalent_level);
  int level_percent = ((GET_EXP(mob)/3) * 10000) / forlevel;
  int cr = get_combat_rating(mob);
  int eq_cost = 0, eq_rent = 0;

  assert(IS_NPC(mob));

  {
    // Want to display EQ too, but that's not in the mob file, it's in the zone...
    int zone;
//    obj_rnum obj_rnums[MAX_OBJS_TRACK], objindex = 0;
    for(zone=0; zone<=top_of_zone_table; zone++) {
      int cmd_no = 0;
      bool this_mob = FALSE;
      if (!zone_table[zone].cmd)
        continue;
      while(zone_table[zone].cmd[cmd_no].command != 'S')
      {
        switch(zone_table[zone].cmd[cmd_no].command)
        {
        case 'M':
          this_mob = zone_table[zone].cmd[cmd_no].arg1 == mob->nr;
          break;
        case 'O':
        case 'E':
          if (this_mob) {
            eq_cost += obj_proto[zone_table[zone].cmd[cmd_no].arg1].obj_flags.cost;
            eq_rent += obj_proto[zone_table[zone].cmd[cmd_no].arg1].obj_flags.cost_per_day;
          }
//          if (this_mob && objindex < MAX_OBJS_TRACK)
//            obj_rnums[objindex++] = zone_table[zone].cmd[cmd_no].arg1;
          break;
        default:
         break;
        }
        cmd_no++;
      }
    }

#if 0
    // Sort and display objs. How? Weapon, armour, affects are the three key things people want.
    // Sort by value for now; it's the job of the object balancers to ensure that cost is
    //   reasonably proportional to wantedness
    qsort(obj_rnums, objindex, sizeof(int), objcmpfunc);
#endif

  }

  snprintf(buf, bufleft, "%5d %-20.20s "
                         "%2d %5d%s %5dG %6dXP (%2d.%2.2d%%) %5dG %5dG\r\n",
           GET_MOB_VNUM(mob), mob->player.short_descr,
           GET_LEVEL(mob), 
           cr < 10000 ? cr : cr/1000, cr < 10000 ? " " : "k",
           GET_GOLD(mob), GET_EXP(mob)/3, level_percent/100, level_percent%100,
           eq_cost, eq_rent
           );

}

void obj_affects_short(char* buf, size_t* bufleft, struct obj_data *obj, int skip)
{
  int i;

  for (i = 0; i < MAX_OBJ_AFFECT; i++)
    if (obj->affected[i].modifier) {
      int loc = obj->affected[i].location;
      int bit = 1<<loc;
      if ((loc < NUM_APPLIES) && !(bit&skip)) {
        int used;
        used = snprintf(buf, *bufleft, "%s%+d ", type_str(loc, apply_types_short), obj->affected[i].modifier);
        *bufleft -= used;
        buf += used;
      }
    }
}



void obj_report_name(char* buf, size_t bufleft, struct obj_data* obj)
{
  snprintf(buf, bufleft, "%5d %s (%s)\r\n",
          GET_OBJ_VNUM(obj), obj->short_description, obj->name);
}
void obj_report_combat(char* buf, size_t bufleft, struct obj_data* obj)
{
  char buf2[80];
  int hitroll = accum_affects(obj, APPLY_HITROLL);
  int ac = accum_affects(obj,APPLY_AC);
  int avg_dmg2 = get_avg_dmg2(obj);
  buf2[0] = 0;
  switch(GET_OBJ_TYPE(obj))
  {
  case ITEM_WEAPON:
    snprintf(buf2, sizeof(buf2), "(%d.%1.1d) %dd%d", avg_dmg2/2, (avg_dmg2%2)*5, GET_OBJ_VAL(obj,1), GET_OBJ_VAL(obj,2));
    hitroll += GET_OBJ_VAL(obj,0);
    break;
  case ITEM_ARMOR:
    ac += GET_OBJ_VAL(obj, 0);
    break;
  }
  snprintf(buf, bufleft, "%5d %-20.20s %+2d %2d %11s%+2d %+2d %+2d %+3d\r\n",
          GET_OBJ_VNUM(obj), obj->short_description,
          hitroll,
          ac, buf2, accum_affects(obj, APPLY_DAMROLL),
          accum_affects(obj, APPLY_STR), accum_affects(obj, APPLY_DEX), accum_affects(obj, APPLY_HIT));
}
void obj_report_value(char* buf, size_t bufleft, struct obj_data* obj)
{
  char buf2[80];
  int avg_dmg2 = get_avg_dmg2(obj);
  int skip = 1<<APPLY_DAMROLL;
  int used = 0;
  buf2[0] = 0;
  if (GET_OBJ_TYPE(obj) == ITEM_ARMOR)
  {
    snprintf(buf2, sizeof(buf2), "%d", GET_OBJ_VAL(obj, 0)+accum_affects(obj,APPLY_AC));
    skip |= 1<<APPLY_AC;
  }
  used += snprintf(buf, bufleft, "%5d %-20.20s %5d %5d %2d.%1.1d %3s ",
          GET_OBJ_VNUM(obj), obj->short_description,
          GET_OBJ_COST(obj), GET_OBJ_RENT(obj),
          avg_dmg2/2, (avg_dmg2%2)*5, buf2);
  bufleft -= used;
  obj_affects_short(buf+used, &bufleft, obj, skip);
  strcat(buf, "\r\n");
}

const struct report_functions mob_reports[] =
{
    { "name",   " vnum name", (report_function)mob_report_name },
    { "combat", " vnum name                 Lv   ahp   AC Damage   rate  dps Rating bits", (report_function)mob_report_danger },
    { "reward", " vnum name                 Lv Rating   gold       XP %lev(ELW) EQ (cost/rent)", (report_function)mob_report_reward },
    { 0, 0 }
};
const struct report_functions obj_reports[] =
{
    { "name",     "vnum   name", (report_function)obj_report_name },
    { "combat",   "vnum   name                +Ht AC       Dm +Dm ST DX +hp", (report_function)obj_report_combat },
    { "value",    "vnum   name                 cost  rent AvDm  AC aff", (report_function)obj_report_value },
    { 0, 0 }
};



void show_mobiles(struct char_data *ch, char *arg, char *value)
{
  const struct field_details *mob_fieldset[] = { mob_fields, char_fields, NULL };
  show_general(ch, arg, value,
               mob_proto, sizeof(struct char_data), top_of_mobt+1,
               mob_fieldset, mob_reports);
}

void show_objects(struct char_data *ch, char *arg, char *value)
{
  const struct field_details *obj_fieldset[] = { obj_fields, NULL };
  show_general(ch, arg, value,
               obj_proto, sizeof(struct obj_data), top_of_objt+1,
               obj_fieldset, obj_reports);
}

