Pendant

#include "../shifter.h"
#include <classes.h>
#define SIZES ({ \
              ({"medium"}), \
              ({"small", "big"}), \
              ({"small","medium", "big"}), \
              ({"small", "medium", "big", "biggest"}), \
              ({"smallest", "small", "medium", "big", "biggest"}) \
              })
#define ORDER ({"primary","second","third","fourth","fifth","sixth"}) 


inherit "/obj/treasure";
inherit PATH+"help";

#define SAVE(X)         save_forms(X);
#define RESTORE      restore_forms();

void stop_casting(object ob);
int check_previous();
void save_forms(string who);
void restore_forms(string who);

void save_form_settings(object ob);
void set_form_settings(object ob);

int query_time(string shape);
int query_size(string shape);
int clear_info();
int query_maxtime_modifier(string form,int maxmod);
int query_clan_index(string clan);
int query_modifier(string clan);
void add_kills(string form);

static string current_form;
static string final_form;
string pendant_shape;
static int busy;
static int new_size;
mapping info;
mapping learning;
mapping base_duration;
mapping base_xp;
mapping kills;
mapping form_exp;
mapping misc;
static int restored; // Static so we dont load it up again
int feeling_toggle;
mapping form_settings;

create() {
    ::create();

    if (!is_clone()) return;
    set_name("pendant");
    set_short("a small pendant");
    set_alias(({"shapeshifters pendant"}));
    set_long("The pendant is made of a small block of wood. "+
      " It seems to be waiting to be carved into a shape.");
    current_form = "basic";
    busy = 0;
    learning = ([ ]);
    base_duration = ([ ]);
    base_xp = ([ ]);
    kills = ([ ]);
    form_exp = ([ ]);
    misc = ([ ]);
    feeling_toggle = 1;
    form_settings = ([]);
    call_out("restore_forms",0);
}

short() {
    mixed clan;
    string first_clan;
    if (!pendant_shape && environment()) {
        clan = this_object()->query_clan_order();
        if (clan != -1 && pointerp(clan) && sizeof(clan) > 0 ) {
            first_clan = clan[0];
            switch(first_clan) {
            case "reptile" : pendant_shape = "a reptile";break;
            case "forest"  : pendant_shape = "a tree";break;
            case "feline"  : pendant_shape = "a cat";break;
            case "earth"   : pendant_shape = "a pebble";break;
            }
            return "a small pendant in the shape of "+pendant_shape;
        }
        return "a small pendant";
    }
    return "a small pendant in the shape of "+pendant_shape;
}

long() {
    string long_string;
    mixed clan;
    string first_clan;
    long_string = "It symbolises that you are a member of the shapeshifters "+
    "enclave. Its shape represents your primary clan.\n "+
    "see 'help clan' and 'help change'.";
    if (!pendant_shape && environment()) {
        clan = this_object()->query_clan_order();
        if (clan != -1 && pointerp(clan) && sizeof(clan) > 0 ) {
            first_clan = clan[0];
            switch(first_clan) {
            case "reptile" : pendant_shape = "a small reptile";break;
            case "forest"  : pendant_shape = "a small tree";break;
            case "feline"  : pendant_shape = "a small cat";break;
            case "earth"   : pendant_shape = "a small pebble";break;
            }
            writef("This is a small wooden pendant, that has been carved into "+
              "the shape of "+pendant_shape+". "+long_string);
            return;
        }
        writef("This is a small block of wood waiting to be carved into a "+
          "clan symbol. "+long_string);
        return;
    }
    writef("This is a small wooden pendant, that has been carved into the "+
      "shape of "+pendant_shape+". "+long_string);
}


int check_kill(int arg) {
  object env;
  object att;
  env = environment(this_object());
  if (env && att = env->query_attack()) {
    if (environment(env) == environment(att) &&
        current_form != "basic") {
       this_object()->add_kills(current_form);
    }
  }
  return 0;
}

void set_kill_hook(object ob) {
  // use align_bonus because its the only hook guaranteed to be called.
  // owner_killer() is a present so it will be first ob only!
  ob->remove_hook("align_bonus");
  ob->add_hook("align_bonus", ({ "check_kill" }) );
}

init() {
    ::init();
    if (environment(this_object()) != TP) return;
    help::init();
    add_action("change","change");
    add_action("do_form", "form");
    add_action("do_shapes", "shapes");
    add_action("ctitle","ctitle");
    add_action("do_shift","shift");
    set_kill_hook(TP);
    // incase of LD
    stop_casting(TP);
    call_out("check_earth",0);

}

// Incase players have rock and no earth clan
// Add it as the next clan
check_earth() {
  while (remove_call_out("check_earth")!=-1);
  // Check for info since it may not have restored yet
  if (!info) {
    call_out("check_earth",2);
  }
  if (this_object()->query_size("rock") &&
      member_array("earth",this_object()->query_clan_order())==-1)
     this_object()->add_clan("earth");
}

do_shapes(str) {
    mapping data;
    string *form, *form2, *clans;
    int size, maxsize;
    int i, c;

    if (str) {
        notify_fail("Usage: shapes\n");
        return 0;
    }
    data = info;
    if (!data) {
        write("You do not know any forms!\n");
        return 1;
    }
    form = m_indices(data);
    form = filter_array(form, "query_form", FORMS);
    clans = this_object()->query_clan_order();
    form2 = filter_array(form, 
      lambda(({'form}),
        ({#'<=, ({ #'sizeof, ({ #'call_other, FORMS, "query_form", 
              ({ #'call_other, FORMS, "query_form", 'form }) 
            })
          }), 2}) ) );
    // should get non-clan forms
    if (!pointerp(form2) || !sizeof(form2)) {
        write("You do not know any forms.\n");
        return 1;
    }
    write("Basic forms:\n");
    form2 -= ({"opossum"});
    size = this_object()->query_size("opossum");
    write("  "+capitalize(FORMS->query_form_shape("opossum", size))+
      " ("+SIZES[4][size-1]+" opossum)\n");
    for (i=0;i<sizeof(form2);i++) {
        size = this_object()->query_size(form2[i]);
        maxsize = sizeof(FORMS->query_form_shapes()[form2[i]]);
        write("  "+capitalize(FORMS->query_form_shape(form2[i], size))+
          " ("+SIZES[maxsize-1][size-1]+" "+form2[i]+")\n");
    }
    if (pointerp(clans)) {
      for (i=0;i<sizeof(clans);i++) {
        write(capitalize(clans[i])+" forms ("+ORDER[i]+" clan):\n");
        form2 = FORMS->query_form(clans[i]);
        for (c=1;c<sizeof(form2);c++) {
          if (size = this_object()->query_size(form2[c])) {
            maxsize = sizeof(FORMS->query_form_shapes()[form2[c]]);
            write("  "+capitalize(FORMS->query_form_shape(form2[c], size))+
                  " ("+SIZES[maxsize-1][size-1]+" "+form2[c]+")\n");
          }
        }
      }
    }
    return 1;
}

do_form(str) {
  object form_shape;
  string form;
  string shape;
  int size;
  if (str) { // no arg wanted... 
    notify_fail("Usage: form (no argument)\n");
    return 0;
  }
  if (final_form) {
    if (final_form == "basic") {
      write("You are currently changing into "+(TP->query_real_race()?TP->query_real_race():TP->query_race())+" form.\n");
    } else {
      write("You are currently changing into "+final_form+" form.\n");
    }
    return 1;
  }
  form = query_current_form();
  if (form == "basic") {
    write("You are currently "+a_an_string((TP->query_real_race()?TP->query_real_race():TP->query_race()))+".\n");
  } else {
    form_shape = present(form+"_shape",TP);
    if (form_shape)
      size = form_shape->query_size();
    if (!size) size=1;
    shape = FORMS->query_form_shape(form,size);
    write("You are currently "+a_an_string(shape)+" in "+form+" form.\n");
  }
  return 1;
}


find_cursed_items(ob) {
    closure mine;

    mine = lambda(({'x}),
   ({ #'&&,  ({ #'&&, ({ #'||, ({ #'&&, ({ #'call_other, 'x, "query_is_weapon" }),
                                 ({ #'call_other, 'x, "query_wielded" })
                      }),
                      ({ #'&&, ({ #'call_other, 'x, "query_is_armour" }),
                               ({ #'call_other, 'x, "query_worn" })
                      })
             }),
             ({ #'||, ({ #'call_other, 'x, "query_cursed"}),
                      ({ #'call_other, 'x, "drop", 1 })
             })
             }),
             ({ #'!, ({ #'call_other, 'x, "query_is_shifter_item"})
             })
    })
    );
    return sizeof(filter_array(all_inventory(ob), mine));
}

change(string str) {
    int x;
    if (!str) {
        notify_fail("Change [into] <what>?\n");
        return 0;
    }
    if (TP->query_ghost()) {
      notify_fail(get_f_string("You cannot change your physical form when "+
                  "you do not have one?\n"));
      return 0;
    }
    if (busy) {
        notify_fail("You are in the middle of a lesson! You cannot change now!.\n");
        return 0;
    }
    if (query_current_form() == "basic" && find_cursed_items(TP)) {
        write("Your equipment impedes your transformation.\n");
        return 1;
    }
    sscanf(str,"into %s",str);
    if ((sscanf(str,"%s %d",str,x) == 2) && x > 0)
        new_size = x;
    else
        new_size = 0;
    if ((stringp(FORMS->query_form(str)) &&
        this_object()->query_size(str)) ||
      str == "back")
        return change_form(str);
    write("Change [into] what?\n");
    write("Choices :"+list_choices()+"\n");
    return 1;
}

do_shift(string str) {
    object who;
    string *opt;
    string *cur_opt;
    string fm;
    int x;

    who = this_player();
    // No message until you have all forms
    if (! (MAX_FOREST && MAX_FELINE && MAX_REPTILE &&
          MAX_ROCK && MAX_FORM("frog") &&
          MAX_FORM("opossum")) ) {
        return 0;
    }

    if (!str) {
        notify_fail("Shift [into] <what>?\n");
        return 0;
    }
    if (TP->query_ghost()) {
      notify_fail(get_f_string("You cannot shift your physical form when "+
                  "you do not have one?\n"));
      return 0;
    }
    if (busy) {
        notify_fail("You are in the middle of a lesson! You cannot change now!.\n");
        return 0;
    }
    if (current_form=="basic") {
      notify_fail("You can only shift between max forms in primary clans.\n");
      return 0;
    }
    sscanf(str,"into %s",str);
    if ((sscanf(str,"%s %d",str,x) == 2) && x > 0)
        new_size = x;
    else
        new_size = 0;
    if ( (fm = FORMS->query_form(str)) && query_size(str)) {
      opt = FORMS->query_form(fm);
      if (sizeof(opt)==4 && opt[3]==str) {
        fm = FORMS->query_form(current_form);
        cur_opt = FORMS->query_form(fm);
        if (sizeof(cur_opt)==4 && cur_opt[3]==current_form) {
          return shift_form(str);
        } else {
          notify_fail("You are not in a final primary form to shift.\n");
          return 0;
        }
      } else {
          notify_fail("You can only shift to a final primary form.\n");
          return 0;
      }
    }
    notify_fail("Shift into what?\n");
    return 0;
}

stop_change(val) {
    busy = val;
}

string list_choices() {
    string *forms;
    string list;
    int i;
    list ="";
    forms = m_indices(info);
    forms = filter_array(forms, "query_form", FORMS);
    for(i=0;i<sizeof(forms);i++) {
        if (stringp(FORMS->query_forms()[forms[i]]) &&
          CLASSOB(TP)->query_current_form() != forms[i] ){
            list += " "+forms[i];
            list += ((i==sizeof(forms)-1) && 
              CLASSOB(TP)->query_current_form() == "basic") ? "." : ",";
        }
    }
    if (CLASSOB(TP)->query_current_form() != "basic") list += " back.";
    return get_f_string(list, 67, 10);
}

change_form(string form) {
    object ob;
    object env;
    string next_form,form_type,start_msg;
    string *all_forms;
    string *dramas;
    int delay,pos1,pos2,i;
    int prevdelay,tmpdelay, modifier;
    env = environment(this_object());
    if (form == "back") form = "basic";
    if (current_form == form) {
        if (form == "basic") 
            write("You are already "+a_an_string((env->query_real_race()?env->query_real_race():env->query_race()))+".\n");
        else
            write("You are already "+a_an_string(form)+"!\n");
        return 1;
    }
    if (final_form) {
        if (final_form == "basic") 
            write("You are already changing into "+a_an_string((env->query_real_race()?env->query_real_race():env->query_race()))+"!\n");
        else
            write("You are already changing into "+a_an_string(final_form)+"!\n");
        return 1;
    }
    form_type = FORMS->query_forms()[form];
    if (!form_type)
        form_type = FORMS->query_forms()[current_form];
    if (!form_type) {
        write("You are already "+a_an_string((env->query_real_race()?env->query_real_race():env->query_race()))+", you do not need to change!\n");
        return 1;
    }
    if (form_type != FORMS->query_forms()[current_form] &&
      FORMS->query_forms()[current_form]) {
        write("You need to change back to "+(env->query_real_race()?env->query_real_race():env->query_race())+" before changing types of form.\n");
        return 1;
    }
// DEBUG("ChangeForm Form "+form+" Cur: "+current_form+" Type: "+form_type+"\n");
    start_casting(env);
    final_form = form;
    all_forms = FORMS->query_forms()[form_type];
    pos1 = member_array(form,all_forms);
    pos2 = member_array(current_form,all_forms);
    modifier = query_modifier(form_type);

    delay = 0;
    prevdelay = 0;
    if (pos1 > pos2) {
        next_form = all_forms[pos2 + 1];
        if (call_other((OBJ+next_form),"query_cost") > env->query_sp()) {
            write("You do not have the power to change!\n");
            stop_casting(env);
            return 1;
        }
        if (call_other((OBJ+next_form),"query_fail_change_requirement",env)) {
            stop_casting(env);
            return 1;
        }
        start_msg = call_other((OBJ+next_form),"query_start_change_message");
        for(i=1;i<(pos1-pos2+1);i++) {
            tmpdelay = check_delay(modifier,all_forms[(pos2+i)],form_type);
//          tmpdelay = (modifier * call_other((OBJ+all_forms[(pos2+i-1)]),"query_delay"))/2;
            delay += tmpdelay;
            dramas = call_other((OBJ+all_forms[(pos2+i)]),"query_change_drama");
            call_out("change_into",delay,all_forms[(pos2+i)]);
            call_out("display_change_drama",prevdelay,dramas,tmpdelay);
            prevdelay = delay;
        }
    } else {
        next_form = all_forms[pos2 - 1];
        if (call_other((OBJ+next_form),"query_cost") > environment(this_object())->query_sp()) {
            write("You do not have the power to change!\n");
            stop_casting(environment(this_object()));
            return 1;
        }
        if (call_other((OBJ+next_form),"query_fail_change_requirement",environment(this_object()))) {
            stop_casting(environment(this_object()));
            return 1;
        }
        start_msg = call_other((OBJ+next_form),"query_change_back_message");
        for(i=1;i<(pos2-pos1+1);i++) {
            tmpdelay = check_delay(modifier,all_forms[(pos2-i+1)],form_type);
//          tmpdelay = modifier * call_other((OBJ+all_forms[(pos2-i+1)]),"query_back_delay")/2;
            delay += tmpdelay;
            dramas = call_other((OBJ+all_forms[(pos2-i+1)]),"query_change_back_drama");
            call_out("change_into",delay,all_forms[(pos2-i)]);
            call_out("display_change_drama",prevdelay,dramas,tmpdelay);
            prevdelay = delay;
        }
    }
    if (next_form == current_form) next_form = form;
    if (start_msg) {
        write(start_msg[0]+"\n");
        say(start_msg[1]+"\n");
    } else {
        if (form != "basic") 
            write("You start to change into "+a_an_string(next_form)+".\n");
        else
            write("You start to change back to normal.\n");
    }

    return 1;
}

shift_form(string form) {
    object ob;
    object env;
    string next_form,form_type,start_msg;
    string *all_forms;
    string *dramas;
    int delay,pos1,pos2,i;
    int prevdelay,tmpdelay, modifier;
    env = environment(this_object());
    if (current_form == form) {
      write("You are already "+a_an_string(form)+"!\n");
      return 1;
    }
    if (final_form) {
      write("You are already changing into "+a_an_string(final_form)+"!\n");
      return 1;
    }

    form_type = FORMS->query_forms()[form];
    if (!form_type)
        form_type = FORMS->query_forms()[current_form];
    start_casting(env);
    final_form = form;

    all_forms = FORMS->query_forms()[form_type];
    modifier = query_modifier(form_type);

    delay = 0;
    prevdelay = 0;


    if (call_other((OBJ+form),"query_cost") > env->query_sp()) {
      write("You do not have the power to change!\n");
      stop_casting(env);
      return 1;
    }
    if (call_other((OBJ+form),"query_fail_change_requirement",env)) {
      stop_casting(env);
      return 1;
    }

    // Out of change_form. First Change 'back' then change forward

    start_msg = call_other((OBJ+current_form),"query_shift_back_message");
    tmpdelay = check_delay(modifier,current_form,form_type);
    delay += tmpdelay;
    dramas = call_other((OBJ+current_form),"query_shift_back_drama");
    call_out("display_change_drama",prevdelay,dramas,tmpdelay);
    prevdelay = delay;

    if (start_msg) {
        writef(start_msg[0]);
        sayf(start_msg[1]);
    }

    tmpdelay = 2*modifier-query_maxtime_modifier(form,10);
    if (tmpdelay < 2) tmpdelay=1+delay%2;
    delay += tmpdelay/2;

    start_msg = call_other((OBJ+form),"query_start_shift_message");
    if (start_msg) {
      call_out("delay_message",delay,start_msg[0],start_msg[1]);
    }
    delay += (tmpdelay -tmpdelay/2);

    tmpdelay = check_delay(modifier,form,form_type);
    delay += tmpdelay;
    dramas = call_other((OBJ+form),"query_shift_drama");
    call_out("change_into",delay,form,1);
    call_out("display_change_drama",prevdelay,dramas,tmpdelay);
    prevdelay = delay;

    return 1;
}

int delay_message(string msg1, string msg2) {
  writef(msg1);
  sayf(msg2);
  return 1;
}

int check_delay(int modifier, string form, string form_type) {
  int tmpdelay;
  int max_time;
  int delay;
  int form_delay_mod;
  int min_mod;
  int i;
  string *all_forms;
  int maxsize;

  all_forms = FORMS->query_forms()[form_type];
  all_forms -= ({ "basic" });
  min_mod = 5;
  // for each form in the list, check modifier
  // allow higher forms to be sped  up, but min at
  // current rate. So no doubling effect on
  // single forms or primary clan.
  // Take off time to get the form, so this is bonus time only
  for(i=0;i<sizeof(all_forms);i++) {
    form_delay_mod = query_maxtime_modifier(all_forms[i],10)/2;
    if (form_delay_mod < min_mod) min_mod = form_delay_mod;
//DEBUG("Max ("+all_forms[i]+") "+max_time+" "+form_delay_mod+" "+
//       query_time(all_forms[i])+" "+maxsize);
  }
  form_delay_mod = query_maxtime_modifier(form,10)/2;
  modifier -= min_mod;
  if (modifier < 2) modifier = 2;

  delay = call_other((OBJ+form),"query_delay") - form_delay_mod;
  tmpdelay = (modifier * delay)/2;
//DEBUG("Delay: "+form+" "+tmpdelay+" "+modifier+" "+delay+" "+form_delay_mod+" "+
//       min_mod+" "+max_time+" "+query_time(form));
  return tmpdelay;
}

int query_maxtime_modifier(string form,int maxmod) {
  string form_type;
  int maxsize;
  int max_time;
  int mod;
  int modifier;

  form_type = FORMS->query_form(form);
  if (!form_type) return 0;
  modifier = query_modifier(form_type);

  maxsize = sizeof(FORMS->query_form_shapes()[form]);
  max_time = (modifier*(int)FORMS->query_duration(form,0,maxsize-2,
                                     environment()->query_real_name()))/2;

  return MOD_D->query_maxtime_modifier(form,max_time,query_time(form),maxmod);
}

display_change_drama(string *dramas,int tottime) {
    int spacing1,spacing2;
    if (!dramas || !pointerp(dramas)) return 0;
    spacing1 = tottime/4;
    spacing2 = 3*tottime/4;
    call_out("do_message",spacing1,dramas[0],dramas[1]);
    call_out("do_message",spacing2,dramas[2],dramas[3]);
}

do_message(string str1,string str2) {
    tell_object(environment(),get_f_string(str1));
    tell_room(environment(environment()),get_f_string(str2),({environment() }) );
}

change_into(string form,int shift) {
    object env;
    object ob;
    string new_type;
    int cost;
    string *group, *order;
    int p,q;
    env = environment(this_object());
    if (current_form!="basic" && !shift) {
      group = FORMS->query_form(current_form);  // the clan of current form
      order = FORMS->query_form(group);         // get the list of forms
      p = member_array(current_form, order);
      q = member_array(form,order);
      if (p == -1 || q == -1) {
        stop_casting(env);
        raise_error("Missing "+current_form+" or "+form+" shape in form daemon\n");
      }
    }
    if (p <= q) {
      cost = (OBJ+form)->query_cost();
    } else {
      cost = 0;
    }
    if (env->query_sp() < cost) {
        tell_object(env,"You do not have the power to change to "+a_an_string(form)+".\n");
        stop_casting(env);
        return 1;
    }
    if (WIZSPCHECK(env,50))
      env->reduce_spell_points(cost);
    if (current_form != "basic") {
        ob = present(current_form+"_shape",env);
        if (ob) {
            save_form_settings(ob);
            destruct(ob);
        }
    }
    current_form = form;
    if (form != "basic") {
        ob = clone_object(OBJ+form);
        ob->initialise(environment(),new_size);
        set_form_settings(ob);
        ob->move(env);
        new_type = ob->query_type();
        tell_object(env,"You change into "+a_an_string(new_type)+".\n");
        say(env->query_name()+" changes into "+a_an_string(new_type)+".\n",env);
    } else {
        tell_object(env,"You change back to normal.\n");
        say(env->query_name()+" changes into "+a_an_string((env->query_real_race()?env->query_real_race():env->query_race()))+"!\n",env);
    }
    if (final_form == form) 
        stop_casting(env);
    SAVE(0);
    return 1;
}

start_casting(object ob) {
    ob->remove_hook("move_player");
    ob->add_hook("move_player", ({ "check_movement" }) );
}

stop_casting(object ob) {
    while(remove_call_out("change_into") != -1);
    while(remove_call_out("do_message") != -1);
    while(remove_call_out("display_change_drama") != -1);
    ob->remove_hook("move_player");
    final_form = 0;
}

owner_died() {
  stop_casting(previous_object());
  set_current_form("basic");
}

check_movement(dir,dest) {
    int i;
    i = remove_call_out("change_into");
    if (i != -1) {
        write("The movement disrupts your transformation.\n");
    }
    stop_casting(TP);
}

lose_form() {
    string *order;
    string group;
    int p;
    object nwob, form;

    if (current_form == "basic") return;
    group = FORMS->query_form(current_form);  // the clan of current form
    order = FORMS->query_form(group);         // get the list of forms
    p = member_array(current_form, order);
    if (p==-1) {
        raise_error("Missing "+current_form+" shape in form daemon\n");
        return;
    }
    if (order[p-1] == "basic") {
        set_current_form("basic");
        tell_object(environment(), "You change into "+
          a_an_string((environment()->query_real_race()?environment()->query_real_race():environment()->query_race()))+".\n");
        return;
    }
    if (nwob = clone_object(OBJ+order[p-1])) {
        if (form = present(current_form+"_shape", environment()))
            destruct(form);
        set_current_form(order[p-1]);
        nwob->initialise(environment());
        nwob->move(environment());
        tell_object(environment(), "You change into "+
          a_an_string(nwob->query_type())+".\n");
    }
    else {
        raise_error("Missing shape file "+OBJ+order[p-1]+".c\n");
    }
}

a_an_string(string word) {
    string letter;
    if (!word) return word;
    letter = extract(word,0,0);
    if (letter == "a" || letter == "e" || 
      letter == "i" || letter == "o" ||
      letter == "u")
        return "an "+word;
    return "a "+word;
}
set_current_form(string form) {
current_form = form;
}
query_current_form() { return current_form; }
drop() { return 1; }

ctitle(str) {
 string cform;
 int csize;
    int plevel, pgender, forms;
    mapping pdata;
    string *clans,form_list;
    string pclan,form_type;
    string ntitle;
    object who;

    who = this_player();
    clans = this_object()->query_clan_order();
    if (clans != ({ }) && clans != -1)
        pclan = clans[0];
    else
        pclan = "";
    pdata = query_info();
    forms = sizeof(m_indices(pdata));

    pgender = (int)who->query_gender();

    // Dont have all forms. Dont get the command
    if (forms != (TOT_FORMS+1)) {
      return 0;
    }

    // Havent maxed them all
    if (! (MAX_FOREST && MAX_FELINE && MAX_REPTILE &&
          MAX_ROCK && MAX_FORM("frog") &&
          MAX_FORM("opossum")) ) {
        return 0;
    }
#ifndef TESTMUD

    if (this_player()->query_guild()) {
      notify_fail("You cannot use this command while in a guild.\n");
      return 0;
    }
#endif

    if (!str) {
      ntitle = (TP->query_class())->query_class_title(TP);
       TP->set_title(ntitle);
       write("You become: "+TP->query_name()+" "+ntitle+"\n");
       return 1;
    }

    if (sscanf(str,"%s %d",cform,csize) != 2) {
      notify_fail("Usage: ctitle <form> <size>\n");
      return 0;
    }

    if (csize < 1) {
      write("You do not get that small in "+cform+".\n");
      return 1;
    }
    if (csize > 5) {
      write("You do not get that large in "+cform+".\n");
      return 1;
    }

    form_type = FORMS->query_forms()[cform];
    if (!form_type || !stringp(form_type)) {
       notify_fail("You do not know the form "+cform+".\n");
       return 0;
    }

    form_list = FORMS->query_forms()[form_type];
    if (sizeof(form_list) > 2 &&
        form_type != pclan) {
        notify_fail("You can only change your title to a basic form "+
            "or one in your primary clan.\n");
        return 0;
    }

    ntitle = SHIFTER->query_form_title(pgender,cform,csize);
//    ntitle = (this_player()->query_class())->query_form_title(pgender,cform,csize);
    if (!ntitle) {
      write("Something errored in "+cform+". Notify Paldin\n");
      return 1;
    }
   write("You become: "+this_player()->query_name()+" "+ntitle+"\n");
   this_player()->set_title(ntitle);
  return 1;
}

/* ######################################### */
// Query Functions for single save

void restore_forms(string who) {
  mapping tmpinfo;
  object form_ob;
  string savefile;

  if (!check_previous()) return;
  if (!who) {
    // If we dont have an environment and no one
    // specific .. then drop out
    if (!environment(this_object())) return;
    who = environment(this_object())->query_real_name();
    if (!who) return;
  }
  info = 0;
  savefile = SAVE_PATH+"/"+who[0..0]+"/"+who;
  if (file_size(savefile+".o") > 0) {
    restore_object(savefile);
  }
  // If were missing the info get it from the old save daemon
  if (!info) {
    tmpinfo = OLD_INFO->query_info()[who];
    if (tmpinfo && sizeof(m_indices(tmpinfo))) {
      info = tmpinfo;
    }
  }
  if (!info)
    this_object()->set_start_xp_info(SHIFTER->total_xp(environment(this_object())) );

  if (environment(this_object())) {
    form_ob = present(current_form+"_shape", environment(this_object()));
    if (form_ob)
      set_form_settings(form_ob);
  }
  restored = 1;
}

varargs void save_forms(string who) {
  object form_ob;
  if (!check_previous()) return;
  if (!who)
    if (environment())
      who = environment(this_object())->query_real_name();
  if (!who || who!=environment(this_object())->query_real_name() ) return;
  if (!restored) return;
  form_ob = present(current_form+"_shape", environment());
  if (form_ob)
    save_form_settings(form_ob);
  save_object(SAVE_PATH+"/"+who[0..0]+"/"+who);
}

mapping query_info() {
if (!check_previous()) return 0;
  return info;
}

query_form_info(string shape) {
if (!check_previous()) return 0;
 if (info && info[shape])
   return info[shape];
 return 0;
}
int query_start_xp() {
 if (!check_previous()) return 0;
 if (info && info["info"])
  return info["info"][0][0];
 return -1;
}

int query_xp_used() {
 if (!check_previous()) return 0;
 if (info && info["info"] ) 
   return info["info"][0][1];
 return -1;
}

mixed query_clan_order() {
 if (!check_previous()) return 0;
 if (info && sizeof(info["info"]) > 1) {
  return info["info"][1];
 }
 return -1;
}

int query_clan_index(string clan) {
int val;
 if (!check_previous()) return 0;
 if (info && sizeof(info["info"]) > 1) {
  val = member_array(clan,info["info"][1]);
  if (val == -1) {
    if (pointerp(FORMS->query_form(clan)) && sizeof(FORMS->query_form(clan)) > 2)
      return sizeof(info["info"][1]);
    else
      return 0;
  }
  return val;
 }
 return -2;
}

int query_modifier(string clan) {
  mixed ret;
  int mod;
  ret = FORMS->query_form(clan);
  if (pointerp(ret))
    mod = query_clan_index(clan)+2;
  else
    mod = query_clan_index(ret)+2;
  return mod;
}

int query_size(string shape) {
 if (!check_previous()) return 0;
 if (info) 
  if (info[shape]) 
   return info[shape][1];
}

int query_time(string shape) {
 if (!check_previous()) return 0;
 if (info) 
  if (info[shape]) 
   return info[shape][0];
}

void add_shape(string shape) {
 if (!check_previous()) return;
 if (!info) {
  info = ([ shape : ({ 0, 1 }) ]);
 } else {
  if (!info[shape]) 
    info += ([ shape : ({ 0, 1 }) ]);
 }
 SAVE(0);
}

void delete_shape(string shape) {
 if (!check_previous()) return;
 if (!info) return;
 m_delete(info,shape);
 SAVE(0);
}

void add_time(string shape, int amt) {
 if (!check_previous()) return;
 if (amt > 5400) {
   log_file(LOG+"time.log", environment()->query_name()+" ("+TP->query_real_name()+") spent "+amt+
            " beats in "+shape+" form.\n"); 
   }
 if (!info) {
  info = ([ shape : ({ amt, 1 }) ]);
 } else {
  if (!info[shape]) {
    info += ([ shape : ({ amt, 1 }) ]);
  } else {
    info[shape][0] += amt;
  }
 }
 SAVE(0);
}

void add_size(string shape, int amt) {
 if (!check_previous()) return;
 if (!info) {
  info = ([ shape : ({ 0, amt }) ]);
 } else {
  if (!info[shape]) {
    info += ([ shape : ({ 0, amt }) ]);
  } else {
    info[shape][1] += amt;
  }
 }
 SAVE(0);
}

void set_start_xp_info(int xp) {
 if (!check_previous()) return;
 if (!info) {
  info = ([ "info" : ({ ({ xp,0 }), ({ }) })]);
 } else {
  clear_info();
  if (info["info"]) 
   info["info"][0][0] = xp;
  else
   info["info"] =  ({ ({ xp,0 }), ({ }) });
 }
 SAVE(0);
}

void add_xp_used(int xp) {
 if (!check_previous()) return;
if (!info) { /* Should never be called */
  info = ([ "info" : ({ ({ 0,xp }) ,({ }) })]);
 } else {
  if (info["info"]) /* Only one that *should* be called */
   info["info"][0][1] += xp;
  else
   info["info"] = ({ ({ 0,xp }), ({ }) });
 }
 SAVE(0);
}

void set_clan_order(string *order) {
 if (!check_previous()) return;
 if (!info) { /* shouldnt happen */
  info = ([ "info" : ({ ({ 0, 0 }) , order }) ]);
 } else {
  if (!info["info"]) { /* shouldnt happen either */
    info += ([ "info" : ({ ({ 0, 0 }) , order }) ]);
  } else {
    info["info"][1] = order;
  }
 }
 SAVE(0);
}

void add_clan(string clan) {
  if (!check_previous()) return;
  if (!info) { /* shouldnt happen */
    info = ([ "info" : ({ ({ 0, 0 }) , ({ clan }) }) ]);
  } else {
    if (!info["info"]) { /* shouldnt happen either */
      info += ([ "info" : ({ ({ 0, 0 }) , ({ clan }) }) ]);
    } else if (member_array(clan, info["info"][1]) == -1) {
      info["info"][1] += ({ clan });
    }
  }
  SAVE(0);
}

int clear_info() {
  if (!check_previous()) return 0;
  info = ([ ]);
 SAVE(0);
 return 1;
}

void add_learning(string str) {
  if (!check_previous()) return 0;
  if (!learning) {
    learning = ([ str : 1 ]);
  } else {
      if (!learning[str])
        learning += ([ str : 1 ]);
      else
        learning[str]++;
  }
  SAVE(0);
}

mapping query_kills() {
  if (!check_previous()) return 0;
  return kills;
}

void add_kills(string form) {
  if (!check_previous()) return 0;
  if (!kills) {
    kills = ([ form : 1 ]);
  } else {
      if (!kills[form])
        kills += ([ form : 1 ]);
      else
        kills[form]++;
  }
  SAVE(0);
}

int query_learning_modifier(string form, string ability, int max) {
  int mod, mod2;
  if (!check_previous()) return 0;
  mod2 = query_modifier(form);
  mod = MOD_D->query_learning_mod(ability, mod2, this_object()->query_learning(ability));
  if (mod > max) mod = max;
  return mod;
}

int query_learning(string str) {
  if (!check_previous()) return 0;
  if (!learning) return 0;
  return learning[str];
}

mapping query_all_learning() {
  if (!check_previous()) return 0;
  if (!learning) return 0;
  return learning;
}

int query_duration_modifier(string who, string type, int dur) {
  if (!check_previous()) return 0;
  if (!environment(this_object()) || !environment(this_object())->query_is_player()) restore_forms(who);
  if (!base_duration) {
    base_duration = ([ type : random(20)+1 ]);
    SAVE(0);
  } else {
    if (!base_duration[type]) {
      base_duration += ([ type : random(20)+1 ]);
      SAVE(0);
    }
  }
  dur += MOD_D->duration_mod(type,dur,base_duration[type],learning,kills);
  return dur;
}

mapping query_form_exp() {
  if (!check_previous()) return 0;
  return form_exp;
}

void add_exp(string form,int amt) {
  if (!check_previous()) return 0;
  if (!form_exp) {
    form_exp = ([ form : amt ]);
  } else {
      if (!form_exp[form])
        form_exp += ([ form : amt ]);
      else
        form_exp[form]+=amt;
  }
  SAVE(0);
}

int query_exp_modifier(string who, string type, int xp) {
  if (!check_previous()) return 0;
  if (!environment(this_object()) || !environment(this_object())->query_is_player()) restore_forms(who);
  if (!base_xp) {
    base_xp = ([ type : random(20)+1 ]);
    SAVE(0);
  } else {
    if (!base_xp[type]) {
      base_xp += ([ type : random(20)+1 ]);
      SAVE(0);
    }
  }
  xp += MOD_D->exp_mod(type,xp,base_xp[type],learning,kills);
  return xp;
}

int set_base_xp(string type, int xp) {
  if (!check_previous()) return 0;
  base_xp[type] = xp;
  SAVE(0);
}
int set_base_duration(string type, int dur) {
  if (!check_previous()) return 0;
  base_duration[type] = dur;
  SAVE(0);
}

void add_misc(string ref, int val) {
  if (!check_previous()) return 0;
  if (!misc) misc = ([]);
  if (!misc[ref])
    misc += ([ref : val]);
  else
    misc[ref] += val;
}

int query_misc(string ref) {
  if (!check_previous()) return 0;
  if (!misc) return 0;
  return misc[ref];
}

// Make it !feeling_toggle to default to on
int query_toggle() {
  if (!restored) return 0; // If it hasnt loaded up turn them off by default
  return feeling_toggle;
}
int set_toggle(int s) { feeling_toggle = s; SAVE(0); }

int check_previous() {
 if (geteuid(previous_object()) != "Shifters" &&
     geteuid(previous_object()) != "paldin" &&
     geteuid(previous_object()) != "serf") return 0;
 return 1;
}

// query_auto_load so when saving to quit force
// the pendant to save
query_auto_load() { SAVE(0); }

void save_form_settings(object ob) {
  mapping FS;
  string *ind;
  string fname;
  int i;

  if (!form_settings) form_settings = ([]);
  if (!ob) return;
  fname = ob->query_form();
  FS = FORM_SETTINGS[fname];
  if (!FS) return;
  ind = m_indices(FS);
  for(i=0;i<sizeof(ind);i++) {
    if (!member(form_settings, ind[i])) {
      form_settings += ([ ind[i] : call_other(ob, FS[ind[i], 0]) ]);
    } else {
      form_settings[ind[i]] = call_other(ob, FS[ind[i], 0]);
    }
  }
}

void set_form_settings(object ob) {
  mapping FS;
  string *ind;
  string fname;
  int i;

  if (!form_settings) form_settings = ([]);
  if (!ob) return;
  fname = ob->query_form();
  FS = FORM_SETTINGS[fname];
  if (!FS) return;
  ind = m_indices(FS);
  for(i=0;i<sizeof(ind);i++) {
    call_other(ob, FS[ind[i], 1], form_settings[ind[i]]);
  }
}

mapping query_form_settings() { return copy_mapping(form_settings); }