/*
 * gBeing.js
 *
 * Copyright (c) 2008 Tyler de Witt (tyler-dewitt.com)
 * Unauthorized reproduction or use of this code is not permitted.
 */

function gBeing() {
  this.image='man.bmp';   //default image for character
  this.walkable=false;
  this.zIndex=90;
  this.object_type="gBeing";
  this.strength=20;
  this.speed=16;
  this.dexterity=15;
  this.wisdom=15;
  this.charisma=15;
  this.constitution=15;
  this.state=new Object();
  this.speed_counter=1;
  this.height=10;
  this.gender="male";
  this.talk_strings=null;
  this.talk_string_index=0;
  this.pack=new Array();
  this.pack_ids=new Array();
  this.events=new Array();
  this.events_ids=new Array();
  this.max_hp=12;
  this.hp=this.max_hp;
  this.max_mp=7;
  this.hp_regen_rate=14;
  this.mp_regen_rate=14;
  this.mp=this.max_mp;
  this.proper_name=null;
  this.hostile=true;
  this.worn=new Object();
  this.ac=0;
  this.dc="1d2";
  this.mod=new Object();

  this.xp=0;
  this.xp_level=0;
  this.next_level_xp=7;

  this.__proto__=gBeing.prototype;

  this.before_unregister=function() {
    for(i in this.pack) { g.unregister_object(this.pack[i]); }
    for(i in this.pack_ids) { g.unregister_object(g.oi(this.pack_ids[i])); }

    for(i in this.events) { g.unregister_object(this.events[i]); }
    for(i in this.events_ids) { g.unregister_object(g.oi(this.events_ids[i])); }
  }

  this.destroy=function() {
    for(i in this.pack) { g.destroy_object(this.pack[i]); }
    for(i in this.pack_ids) { g.destroy_object(g.oi(this.pack_ids[i])); }

    for(i in this.events) { g.destroy_object(this.events[i]); }
    for(i in this.events_ids) { g.destroy_object(g.oi(this.events_ids[i])); }
  }

  this.after_register=function() { for(i in this.pack) { g.register_object(this.pack[i]); }
				   for(i in this.events) { g.register_object(this.events[i]); }
  }

  this.elapse=function(ticks) {
    //do nothing
  }

  this.clean_events=function() {
    //remove finished events
    for(var i=0; i< this.events.length; i++) {
      if (!g.is_registered(this.events[i].id)) {
	this.events.splice(i,1);
	i--;
      }
    }
  }

  this.act=function(elapsed) {
    if (this.hp <=0) {
      //this being is dead!
      this.die();
      return;
    }

    this.speed_counter += elapsed;

    while ((this.speed_counter - this.speed) >= 0) {
      this.speed_counter -= this.speed;
      if (this.asleep) continue;

      if (this.hostile==false) {
	//just do random stuff
	this.move(rand(3)-1,rand(3)-1);
      } else {
	//hostile, seek the hero
	var dx=g.me.x-this.x;
	var dy=g.me.y-this.y;
	if (Math.abs(dx) <= 1 && Math.abs(dy) <=1) {
	  this.do_attack(g.me);
	} else {
	  if (dx != 0) { dx=dx/Math.abs(dx); }
	  if (dy != 0) { dy=dy/Math.abs(dy); }
	  this.slant_move({x: dx,y: dy});
	}
      }
      this.heal();
      if (this.hp <=0) {
	//this being is dead!
	this.die();
	return;
      }
    }
    

  }
	
  this.slant_move=function(delta) {
    var delta_list=new Array(delta);
    if (delta.x != 0 && delta.y != 0) {
      delta_list.push({x: delta.x, y: 0});
      delta_list.push({x: 0, y: delta.y});
    }

    for (var k in delta_list) {
      var d=delta_list[k];
      var ok=true;
      var o_list=g.gm.tilesAt(this.x+d.x,this.y+d.y);
      for(var i in o_list) {
	var o=o_list[i];
	if (!o.test_walk || !o.walkable) {
	  ok=false;
	  break;
	}
      }
      
      if (ok==true) {
	this.move(d.x,d.y);
	return;
      }
    }
    this.move(delta.x,delta.y);
  }

  this.heal=function() {
    //heal hp
    //TODO make this function depend on stats
    if (rand(400) <= this.hp_regen_rate) this.hp +=1;
    if (rand(400) <= this.hp_regen_rate) this.hp +=1;
    this.hp=(this.hp > this.max_hp ? this.max_hp : this.hp);

    if (rand(400) <= this.mp_regen_rate) this.mp +=1;
    if (rand(400) <= this.mp_regen_rate) this.mp +=1;
    this.mp=(this.mp > this.max_mp ? this.max_mp : this.mp);
    if (this.poisoned && roll("1d7") >= 2) this.hp--;
  }


  /* override to save pack and armour */
  this.save=function() {
    var o=this.save_default("gBeing");
    var pids=new Array();
    for(i in this.pack) { pids.push(this.pack[i].id); }
    o.push(pids);

    var wids=new Object();
    for(i in this.worn) { 
      wids[i]=this.worn[i].id;
    }
    o.push(wids);

    this.clean_events();
    var eids=new Array();
    for(i in this.events) { 
      eids.push(this.events[i].id);
    }
    o.push(eids);
    return o;
  }

  /* override to load pack  and armour*/
  this.load=function(o) {
    this.load_default(o,"gBeing");
    this.pack_ids=o.shift();
    this.worn_ids=o.shift();
    this.events_ids=o.shift();
    if (this.pack_ids==null) this.pack_ids=new Array();
    if (this.worn_ids==null) this.worn_ids=new Object();
    if (this.events_ids==null) this.events_ids=new Array();
  }

  //reassociate the characters pack (pack_ids to objects)
  this.reassociate=function() {
    //this beings pack
    this.pack=new Array();
    for(i in this.pack_ids) {
      var o=g.oi(this.pack_ids[i]);
      if (o != null) this.pack.push(o);
    }
    this.pack_ids=new Array();

    //worn objects
    this.worn=new Object();
    for (i in this.worn_ids) {
      var o=g.oi(this.worn_ids[i]);
      if (o != null) { this.worn[i]=o; o.worn=i; }
    }
    this.worn_ids=new Object();

    //events for this being
    this.events=new Array();
    for (i in this.events_ids) {
      var o=g.oi(this.events_ids[i]);
      if (o != null) { this.events.push(o); }
    }
    this.events_ids=new Array();
  }

  this.move=function(xoffset,yoffset) {
    if(!this.onMap) return false;
    
    var new_x=this.x + parseInt(xoffset);
    var new_y=this.y + parseInt(yoffset);

    if (new_x >= g.gm.ar_width || new_x < 0) return false;
    if (new_y >= g.gm.ar_height || new_y < 0) return false;

    var o_list=g.gm.tilesAt(this.x+parseInt(xoffset), this.y+parseInt(yoffset));
    for (var i in o_list) {
      var o=o_list[i];
      if (!o_list[i].walk(this,xoffset,yoffset))
	return false;
    }

    return g.gm.moveTile(this,xoffset,yoffset);
  }

  this.do_pickup=function(o) {
    if (this.state.levitate) {
      msg("You can't reach the ground to pick up the " + o.title + ".");
      return;
    }
    if (o.has_func("pickup")) { 
      if (o.pickup(this)) {
	g.gm.removeTile(o);
	this.pack.push(o);
	//TODO conjugate who picked up this item
	msg("You pick up a " + o.title + ".");
	this.elapse(this.speed);
      }
    } 
  }

  this.equip=function(o,where) {
    if (this.worn[where]) {
      return false;
    }
    this.worn[where]=o;
    o.worn=where;
    if (o.onequip) o.onequip(this);
    return true;
  }

  this.unequip=function(o) {
    var where=o.worn;
    if (this.worn[where]) {
      delete this.worn[where];
      delete o.worn;
      if (o.onunequip) o.onunequip(this);
      return true;
    }
    return false;
  }

  this.do_wear=function(o,where) {
    if (this.worn[where]) {
      var x=this.worn[where];
      if (this.unequip(x)) {
	msg(conjugate(this,"unequip",x));
      } else {
	msg("You couldn't unequip the " + x.title);
	return false;
      }
    }

    if (this.equip(o,where)) {
      var verb="wear";
      if (o.object_type=="gWeapon") verb="wield";

      msg(conjugate(this,verb,o));
      return true;
    } 
    msg("You couldn't equip the " + o.title);
    return false;
  }

  this.do_unwear=function(o) {
    this.unequip(o);
    msg("You remove the " + o.title);
  }

  this.do_drink=function(o) { msg(conjugate(this,"drink",o)); o.drink(this); this.use_up(o); this.elapse(this.speed);}
  this.do_read=function(o) { msg(conjugate(this,"read",o)); o.read(this); this.use_up(o); this.elapse(this.speed);}
  
  this.use_up=function(o) {
    this.remove_from_pack(o);
    g.gm.removeTile(o);
    g.gm.dirtyList.push(o);
    g.unregister_object(o);
  }

  this.remove_from_pack=function(o) {
    //search for the object in this list and remove it
    var found=false;
    for (var i=0;i <this.pack.length; i++) {
      if (this.pack[i]==o) {
	this.pack.splice(i,1);
	var found=true;
	if (o.worn) {
	  //remove it if it's being worn
	  delete this.worn[o.worn];
	  delete o.worn;  
	}
	break;
      }
    }
    return found;
  }

  this.do_drop=function(o) {
    if (!this.remove_from_pack(o)) return false;

    g.gm.placeTile(o,this.x,this.y);
    msg(conjugate(this,"drop",o));
    this.elapse(this.speed);
  }

  this.do_enter=function(o,dir) {
    var o_list=g.gm.tilesAt(this.x,this.y);
    
    var o=null;
    for (var i in o_list) {
      var o=o_list[i];
      if (o_list[i].has_func("enter")) { o=o_list[i]; break; }
    }
    if (o==null) {
      msg("There's no door here.");
      return false;
    }

    if (o.has_func("enter")) { 
      if (o.enter(this)==true) {
	msg(conjugate(this,"enter",o));
      }
    }

    return true;
  }

  this.do_attack=function(o,dir) {
    if (o==null) {
      if (dir==null) return;
      //find the object to attack
      if (Math.abs(dir.delta.x) > 1 || Math.abs(dir.delta.y) > 1) {
	msg("You can't reach that far.");
	return false;
      }
      
      var o_list=g.gm.tilesAt(dir.x,dir.y);
      
      for (var i in o_list) {
	if (o_list[i].has_func("attack")) {
	  o=o_list[i];
	  break;
	}
      }
    }

    if (o==null) {
      msg("You swing at thin air.");
      return true;
    }

    if (!o.has_func("attack")) {
      msg("You can't attack that");
      return false;
    }

    this.elapse(this.speed);
    var m=calc_melee(this,o);
    if (m.miss) {
      //misses
      msg(conjugate(this,"miss",o));
      return true;
    } 

    //TODO make a noise, animation
    hit_animation(o.id);
    
    //calculate damage
    if (m.damage==0) {
      msg(conjugate(this,"hit",o) + ", but " + (this==g.me ? "do" : "does") + " no damage.");
      return true;
    }

    o.hp -= m.damage;
    if (o.hp > 0) {
      msg(conjugate(this,"hit",o));
      return true;
    }
    
    //the being was killed!
    msg(conjugate(this,"kill",o));
    o.die();
    return "kill";
  }

  this.die=function() {
    g.gm.removeTile(this);
    g.gm.dirtyList.push(this);
    g.unregister_object(this);
  }

  this.do_throw=function(o,dir) {
    if (!this.remove_from_pack(o)) return false;

    if (dir.delta.x > 0) dir.delta.x=1;
    if (dir.delta.x < 0) dir.delta.x=-1;
    if (dir.delta.y > 0) dir.delta.y=1;
    if (dir.delta.y < 0) dir.delta.y=-1;

    var x=this.x + dir.delta.x;
    var y=this.y + dir.delta.y;
    
    if (!g.gm.placeTile(o,x,y)) {
      if (!g.gm.placeTile(o,this.x,this.y)) {
	//we couldn't throw it, leave in the pack
	this.pack.push(o);
	msg(conjugate(this,"couldn't throw",o));
	return false;
      } 
    } 

    msg(conjugate(this,"throw",o));
    this.elapse(this.speed);
  }
  this.do_look=function(o,dir) {
    if (o == null) {
      if (Math.abs(dir.delta.x) > 1 || Math.abs(dir.delta.y) > 1) {
	msg("That's too far away.");
	return false;
      }
      
      var o_list=g.gm.tilesAt(dir.x,dir.y);
      
      var o=null;
      for (var i in o_list) {
	o=o_list[i];
	if (o_list[i].has_func("look") || (o_list[i].look_str && o_list[i].look_str != "")) {
	  o=o_list[i]; break;
	}
      }
    }

    if (o==null) {
      msg("You see nothing special.");
      return false;
    }

    show_look(o);
    this.elapse(this.speed);
  }

  this.do_talk=function(o,dir) {
    if (Math.abs(dir.delta.x) > 1 || Math.abs(dir.delta.y) > 1) {
      msg("That's too far away.");
      return false;
    }

    var o_list=g.gm.tilesAt(dir.x,dir.y);
    
    var o=null;
    for (var i in o_list) {
      o=o_list[i];
      if (o_list[i].has_func("talk")) {
	o=o_list[i]; break;
      }
    }

    if (o==null) {
      msg("There's no answer");
      return false;
    }

    var s="nothing";
    if (!o.has_func("talk") || ((s=o.talk()) == "")) {
      s="nothing";
    }
    show_talk(o,s);
    this.elapse(this.speed);
  }

  this.attack=function() {
    //handled by do_attack
  }

  this.talk=function() {
    if (typeof this.talk_strings == "string")
      return this.talk_strings;

    if (typeof this.talk_strings == "object" && this.talk_strings != null) {
      this.talk_string_index = (this.talk_string_index + 1) % this.talk_strings.length;
      return(this.talk_strings[this.talk_string_index]);
    }
    return "";
  }
}

gBeing.prototype=new gTile;
gBeing.prototype.savedVars = ["strength","hp","max_hp","mp","max_mp","state","xp_level","xp","next_level_xp"];

gBeing.prototype.gain_level=function() {
  while (this.xp >= this.next_level_xp) {
    this.xp_level++;	

    //BALANCE: exponential parameter 2.6
    this.next_level_xp=calc_next_level_xp(this.xp_level+1);

    msg("Welcome to Experience Level " + this.xp_level + ".");
    //gain hit points
    var n=roll("2d3+2");
    this.hp = Math.ceil(this.hp * (1 + (n/this.max_hp)));
    this.max_hp += n;

    var n=roll("1d3+2");
    this.mp = Math.ceil(this.mp * (1 + (n/this.max_mp)));
    this.max_mp += n;

    if (roll("1d3" <= 1)) this.strength +=1;
    if (roll("1d3" <= 1)) this.dexterity +=1;

    //increase melee damage
    if (roll("1d3" <= 1)) {
      if (typeof this.mod.plus_melee =="undefined") 
	this.mod.plus_melee=0;
	
      this.mod.plus_melee+=1;
    }


  }
}

gBeing.prototype.construct=function() {
  //called when we create a monster
  this.hp=this.max_hp;
  this.mp=this.max_mp;

  this.next_level_xp=calc_next_level_xp(this.xp_level+1);

  //TODO: vary some of the creatures stats
  //turn '2d6+3' into attack_vars, etc
  //vary max hp
}

gBeing_protos = {
  male: {  image: 'man.jpg', strength: 16, talk_strings: ['Hi there', 'Nice weather, huh?'] },
  spider: {  descrip: 'an itsy bitsy spider', image: 'spider.jpg', strength: 2, speed: 16, dc: "1d1", ac: 2, max_hp: 4, xp: 2},
  snake: {  descrip: 'a common garden snake', image: 'snake.jpg', strength: 5, speed: 16, dc: "1d2", dexterity: 21, ac: 4, max_hp: 5, talk_strings: "Ssss!", xp: 2 },
  killer_crab: {  title: "killer crab", image: 'crab.jpg', strength: 4, speed: 16, dc: "1d2+1", ac: 6, max_hp: 6, talk_strings: "Clak clak!", xp: 3 },
  spectre: {  image: 'spectre.jpg', strength: 4, speed: 16, dc: "1d1", ac: 22,  max_hp: 4, talk_strings: "Wooooo....", xp: 5 },
  giant_centipede: {  title: "giant centipede", image: 'centipede.jpg', strength: 9, speed: 16, dc: "1d3+1", ac: 7, max_hp: 12, xp: 6},
  zombie: {  image: 'zombie.jpg', strength: 19, speed: 48, dc: "1d5", ac: 9, max_hp: 25, talk_strings: "Brains...", xp: 7 },
  giant_lizard: {  title: "giant lizard", image: 'giantlizard.jpg', strength: 21, speed: 16, dc: "1d4+1", ac: 9, max_hp: 18, xp: 10},
  goblin: {  image: 'goblin.jpg', strength: 19, speed: 16, dc: "1d4+2", ac: 11, max_hp: 20, xp: 15},
  hag: {  image: 'hag.jpg', strength: 4, speed: 16, dc: "2d3+2", ac: 15, max_hp: 23, talk_strings: "Hah..hahaha!!", xp: 20},
  bugzilla: {  image: 'bugzilla.jpg', strength: 30, speed: 32, dc: "2d4+3", ac: 25, max_hp: 39, xp: 25},

  goat: {  image: 't_12_22.jpg',  strength: 7, speed: 16, talk_strings: ['Baaaa...', 'Ba?'], hostile: false },
  chicken: {  image: 'chicken.jpg', strength: 1, speed: 16, talk_strings: ['Buk buk...'], hostile: false },
  bunny: {  image: 'bunny.jpg', strength: 1, descrip: 'a cute bunny', speed: 16, hostile: false },
  boy: {  image: 'boy.jpg', strength: 1, descrip: 'a young boy', speed: 16, talk_strings: ["You seen my mommy?"], hostile: false },
  girl: {  image: 'girl.jpg', strength: 1, descrip: 'a young girl', speed: 16, talk_strings: ["I'm picking flowers."], hostile: false },
  maiden: {  image: 'maiden.jpg', strength: 1, descrip: 'a woman', speed: 16, talk_strings: ['Welcome to our town..'], hostile: false },
  deer: { title: 'deer', descrip: 'a gentle doe', image: 'deer.jpg', strength: 7, speed: 16, talk_strings: ['Mooo?', 'What are you looking at?'], hostile: false }
};
