/*
 * game.js
 *
 * Copyright (c) 2008 Tyler de Witt (tyler-dewitt.com)
 * Unauthorized reproduction or use of this code is not permitted.
 */


function gState() {
  //for keeping track of game state
  this.object_type="gState";
  this.displayTile=false;
  this.vars=new Object();
  this.vars.level=0;
  this.act=function(elapsed) { return; }
  //TODO add act function for the gState

  this.save=function() {
    return this.save_default(this.object_type);
  }


  this.load=function(o) {
    this.load_default(o,this.object_type);
  }

}

gState.prototype=new gTile;
gState.prototype.savedVars = ["vars"];
gState_protos = {
  dungeon: { vars: {level: 0} }
}

/*------------------------------

gGame is the top level object.
Encapsulates gMap data structure
Object creation, registration

-------------------------------*/

function gDisplay() {
  this.x_offset=0;
  this.y_offset=0;
  this.f_x_min=-1;
  this.f_x_max=-1;
  this.f_y_min=-1;
  this.f_y_max=-1;
  this.images=new Object();
  this.fov_disp=new Array();

  //initialize fov_disp
  for (var i=0;i<9;i++) {
    this.fov_disp[i]=new Array();
    for (var j=0;j<9;j++) {
      this.fov_disp[i][j]=0;
    }
  }

  this.clearDisplayCache=function() {
    this.f_x_min=-1;
    this.f_x_max=-1;
    this.f_y_min=-1;
    this.f_y_max=-1;
    this.background_image=null;
    di.fov=new Object();
  }

  this.in_fov=function(x,y) {
    if (this.fov) {
      if (this.fov["z" + x + "_" + y]) {
	return true;
      }
      return false;
    }
    return true;
  }

  this.getImage=function(s) {
    if (typeof this.images[s] == "undefined" || this.images[s]==null) {
      this.images[s]=new Image();
      this.images[s].isloaded=false;

      this.images[s].onload=function() {
	this.isloaded=true;
      }
      this.images[s].src=s;
      return null;
    } else {
      if (this.images[s].isloaded==false) return null;
      return this.images[s];
    }
  }
}


function gGame() {
  this.gm=null;
  this.me=null;
  this.gs=null; //the game state;
  this.gen_info=new Object();
  this.elapsed_time=0;

  this.object_list=new Array();
  this.actor_list=new Array();



  /* Lookup an object by ID 
     id: integer, object's id 
     return: object if it exists, null on failure   */
  this.oi=function(id) {
    if (typeof this.object_list['z' + id] == "undefined" || this.object_list['z' + id]==null)
    return null;

    return this.object_list['z' + id];
  }

  this.is_registered=function(id) { return !(id < 0 || typeof this.object_list['z' + id] == "undefined" || this.object_list['z' + id]==null); }

  /* Register a game object 
     o: object, the game object 
     return: bool, true on success, false otherwise */

  this.register_object=function(o) { 
    var result=false;
    if (typeof this.object_list['z' + o.id] == "undefined") {
      this.object_list['z' + o.id]=o;
      result=true;
//    if (o.has_func("after_register")) o.after_register();
      if (o.after_register != null) o.after_register();
    } else {
      //already registered!
      result=false;
    }

    /* Call the after_register hook. This is usually so 
       container items can register the objects they contain */

    return result;
  } 

  this.destroy_object=function(o) {
    if (o==null || typeof o=="undefined") return false;

    if (this.gm != null) this.gm.removeTile(o); 
    this.unregister_object(o);
    if (o.has_func("destroy")) o.destroy();
    o.id=-1;
  }

  /* Unregister a game object
     o: object, the object to unregister */
  this.unregister_object=function(o) {
    if (o==null || typeof o=="undefined") return false;

    if (typeof this.object_list['z' + o.id] == "undefined") {
      return false;
    }

    /* Call the before_unregister hook. This is usually so 
       container items can unregister the objects they contain */

    if (o.has_func("before_unregister")) o.before_unregister();

    delete this.object_list['z' + o.id];
    return true;
  }

  /* Create new object without assigning id or putting on object_list 
     This should not be called directly, used only when loading maps 
     function(object_type, sub_type)
     object_type: string, the name of the class to instantiate
     sub_type: string, passed to object's 'load_proto' method 
     return: object, the created object */
  this.create_object_raw=function() {
    var o = eval('new ' + arguments[0] + '()');
    if (arguments.length > 1) {
      o.load_proto(arguments[1]);
    }
    return o;
  }

  /* Create a new object with unique identifier and register it.
     function(object_type, args)
     object_type: string, the name of the class to instantiate
     sub_type: string, passed to object's 'load_proto' method 
     return: object, the created object
  */
  this.create_object=function() {
    var o;
    if (arguments.length==1) { o=this.create_object_raw(arguments[0]); }
    if (arguments.length==2) { o=this.create_object_raw(arguments[0],arguments[1]); }
    o.id=gGame.prototype.getNextID();
    this.object_list['z' + o.id]=o;
    return o;
  }

  /* Clone an object. Calls create_object_raw, then copies all properties,
     assigns and ID and registers object
     c: object, the object to clone 
     return: object, the cloned object
  */
  this.clone_object=function(c) {
    var o=this.create_object_raw(c.object_type,c.sub_type);
    for (i in c) o[i] = c[i];
    o.id=gGame.prototype.getNextID();
    this.object_list['z' + o.id]=o;
    return o;
  }

  /* Serializes to a JSON representation
     return: object, array of strings in JSON representation */
  this.save_game=function() {
    var o=new Object();
    //save the nextID auto_increment counter
    o.maxID=gGame.prototype.nextID;
    o.gm=this.gm.save();
    
    //save me and gs separately, so unregister them first
    if (this.me) { this.me.save_x=this.me.x; this.me.save_y=this.me.y; o.me=this.me.save(); }
    if (this.gs && typeof this.gs.save == "function") { o.gs=this.gs.save(); }

    o.object_list=new Array();
    for(i in this.object_list) {
      var id=this.object_list[i].id;
      if (id == -1)  continue;
      //skip this.me and this.gs, they get saved separately;
      if (this.me && id == this.me.id) continue;
      if (this.gs && id == this.gs.id) continue;

      o.object_list.push(this.object_list[i].save());

    }
    return o;
  }

  /* completely abandon this game */
  this.clear=function() {
    this.gm=new gMap();
    this.gs=new Object();
    this.me=null;
    this.object_list=new Array();  
    this.actor_list=new Array();  
    if (typeof $ != "undefined") { clear_display(); }
  }  

  /* Loads game state from an object.
     Completely abandon's previous game state.
     s: object, the JSON serialized data parsed to an object
  */

  this.load_game=function(data,load_me) {
    this.gm=null;
    this.gs=null;
    this.me=null;
    this.object_list=new Array();
    this.actor_list=new Array();
    if (typeof $ != "undefined") { clear_display(); }    
    
    //update max ID
    if (data.maxID) {
      gGame.prototype.nextID=(data.maxID >= gGame.prototype.nextID ? data.maxID : gGame.prototype.nextID);
    }

    //load character
    if (load_me=="none") {
      if (data.me) {
	this.me = this.create_object_raw(data.me[0]);
	this.me.load(data.me);
      } else {
	this.me=null;
      }
    } else if (typeof load_me == "undefined" || load_me==null) {
      if (data.me) {
	this.me = this.create_object_raw(data.me[0]);
	this.me.load(data.me);
      } else {
	//last resort, put default character somewhere
	this.me=new gMe();
	this.me.save_x=2; this.me.save_y=2;
      }
    } else{
      this.me=load_me;
    }
    if (this.me != null) this.register_object(this.me);

    g.actor_list.push(this.me);

    //load game state
    if(data.gs) {
      this.gs = this.create_object_raw(data.gs[0]);
      this.gs.load(data.gs);
      this.register_object(this.gs);
      g.actor_list.push(this.gs);
    } else {
      this.gs = this.create_object("gState","dungeon");
      this.register_object(this.gs);
      g.actor_list.push(this.gs);
    }

    //setup  screen
    this.gm=new gMap();
    this.gm.load(data.gm);

    //load object list and populate screen
    this.load_object_list(data.object_list);

    //reassociate me and gs
    if (this.me != null) {
      this.me.reassociate();
    }
    if (this.gs.reassociate) this.gs.reassociate();

    //place character back on saved coordinates
    if (this.me != null) {
      this.me.x=this.me.save_x;
      this.me.y=this.me.save_y;
      this.gm.placeTile(this.me,this.me.x,this.me.y);
    }
  }

  this.switch_screen=function(data,dest_x,dest_y) {
    //don't abandon game state or character, but switch the screen
    this.gm=new gMap();
    this.object_list=new Array();
    this.actor_list=new Array();

    if (typeof $ != "undefined") { clear_display(); }
    
    //update max ID
    if (data.maxID) {
      gGame.prototype.nextID=(data.maxID >= gGame.prototype.nextID ? data.maxID : gGame.prototype.nextID);
    }

    //setup screen
    this.gm=new gMap();
    this.gm.load(data.gm);

    //load object list and populate screen
    this.load_object_list(data.object_list);

    //register gs and me
    this.register_object(this.me);
    g.actor_list.push(this.me);
    this.register_object(this.gs);
    g.actor_list.push(this.gs);

    //see if inventory and events should go in actor list
    for (var i in this.me.pack) {
      var o=this.me.pack[i];
      if (o.act != null) this.actor_list.push(o);
    }

    for (var i in this.me.events) {
      var o=this.me.events[i];
      if (o.act != null) this.actor_list.push(o);
    }

    //place character back on specified dest coordinates
    this.me.x=dest_x;
    this.me.y=dest_y;
    this.gm.placeTile(this.me,this.me.x,this.me.y);
    this.me.onScreen=false;
  }

  this.load_object_list=function(obj_list) {
    var reassoc_list=new Array();

    for(i in obj_list) {
      var p = obj_list[i];
      var o=null;

      if (p[0]=="gTile") {
	//inline for speed

	o=new gTile();
	for (i in gTile_protos[p[1]]) {
	  o[i]=gTile_protos[p[1]][i];
	}

	for (i in gTile.prototype.savedVars) { 
	  var val=p.shift();
	  //use default for these variables when they don't exist
	  if(gTile.prototype.savedVars[i]=="image" && val=="") continue;
	  /*
	  if(gTile.prototype.savedVars[i]=="descrip" && val=="") continue;
	  if(gTile.prototype.savedVars[i]=="photo" && val=="") continue;
	  */
	  o[gTile.prototype.savedVars[i]] = val;
	}

	this.register_object(o);
	if (o.onMap) {
	  //this is optimized, no sorting done on insert;
	  this.gm.placeTileQuick(o,o.x,o.y);
	}	
      } else {
	o = this.create_object_raw(p[0]);
	o.load(p);
	
	this.register_object(o);
	if (o.onMap) {
	  this.gm.placeTile(o,o.x,o.y);
	}	
	
	// check if the the object needs reassociation and push onto the list
	if (o.reassociate != null) reassoc_list.push(o);
	if (o.act != null) g.actor_list.push(o);
      }
      //di.getImage(image_path + o.image);
    }

    // iterate over reassociation list and allow objects to reassociate
    for (i in reassoc_list) { 
      var o=reassoc_list[i];
      if (g.is_registered(o.id)) o.reassociate(); 
    }
  }
  
  this.elapse=function(t) {
    this.elapsed_time = this.elapsed_time + parseInt(t);
  }

  this.play=function() {
    if (this.elapsed_time > 0) {
      //check if we're dead
      if (g.me.hp <=0) {
	update_display();
	msg("You die...");
	end_game();
	return;
      }

      //let actors take turns
      for(var i=0; i< this.actor_list.length; i++) {
	if (this.is_registered(this.actor_list[i].id)) {
	  this.actor_list[i].act(this.elapsed_time);

	  //check if we're dead
	  if (g.me.hp <=0) {
	    update_display();
	    msg("You die...");
	    end_game();
	    return;
	  }

	} else {
	  this.actor_list.splice(i,1);
	  i--;
	}
      }
      this.elapsed_time=0;
    }
  }


  /* Temporary function to generate a new map when we can't load something */
  this.load_map=function() {
    clear_display();
    if (this.gm != null) delete this.gm;
    if (this.me != null) delete this.me;
    delete this.object_list;
    this.gm=new gMap();
    this.object_list=new Array();

    g.gm.generate();
  }

}
/* gGame class variables and class functions */

/* getNextID is a static function to implement
   an auto_increment unique identifier.
   This is used when registering each new created
   object */
gGame.prototype.nextID=1;  //static variable
gGame.prototype.getNextID=function() {
  var id=gGame.prototype.nextID;
  gGame.prototype.nextID++;
  return id;
}

/* ------------------------------
   gDoor implements enter behaviour, saving the
   current map and transferring character to a new map

*/

function gDoor() {
  this.zIndex=20;
  this.dest_tag="";
  this.dest_x=-1;
  this.dest_y=-1;
  this.gen={};
  this.image='door.jpg';
  this.object_type='gDoor';
  this.__proto__=gDoor.prototype;

  this.save=function() { return this.save_default("gDoor");  }
  this.load=function(o) { this.load_default(o,"gDoor"); }

  if (arguments.length > 0) {
    this.load_proto(arguments[0]);
  } 
}
//derives from gTile
gDoor.prototype=new gTile;

  /*
    who: object, the being that will enter through the door */
gDoor.prototype.enter = function(who) {
  //only works for character at this point
  if (this.enter_func != null) {
    this.enter_func();
  }
  g.si={door: this};
  switch_screen(this.id);
  return true;
}

gDoor_protos = {
  default1: { title: 'door', image: "door.jpg"} ,
  door_trap :{ enter: function(who) { if(g.gs.vars.trapped) {msg("Can't go back.. you're trapped!"); return false; } return gDoor.prototype.enter.call(this,who); }},
  stairs_down: { title: "stairs", dest_tag: "gen1", image: "stairsdown.jpg", enter_func: function() {
      if (typeof g.gs.level=="undefined") {
	g.gs.level=1;
      } else {
	g.gs.level+=1;
      }
    }}
}; 

gDoor.prototype.savedVars = ["dest_tag","dest_x","dest_y","gen"];


function gPushable() {
  this.image='bookshelf.jpg';
  this.zIndex=20;
  this.walkable=false;
  this.object_type="gPushable";
  this.__proto__=gPushable.prototype;
  this.test_walk=true;

  this.walk=function(who,xoffset,yoffset) {
    o_list=g.gm.tilesAt(this.x+xoffset,this.y+yoffset);
    for (var i in o_list) {
      var o=o_list[i];
      if(o.walkable==false) {
	if (who.object_type=="gMe") msg("It won't budge. Something must be behind it");
	return false;
      }
    }

    if (this.move(xoffset,yoffset)==true) {
      msg(conjugate(who,"push",this));
      return true;
    } 
    if (who.object_type=="gMe") msg("It won't budge. Something must be behind it");
    return false;
  }
}

gPushable.prototype=new gTile;
gPushable.prototype.parent=gTile.prototype;
gPushable_protos = {
  bookshelf: {  image: 'bookshelf.jpg',title: 'bookshelf', descrip: 'a large bookshelf' }
};



function gMap() {
  this.ar=new Array();
  this.screen_tag="";
  this.divList=new Array();
  this.dirtyList=new Array();
  this.fov=new Object();
  this.ar_width=0;
  this.ar_height=0;
  this.background_image="";
  this.bg_image_height=grid_size;
  this.bg_image_width=grid_size;
  this.bg_image_tile=true;
  this.sound="";
  this.descrip="";
  this.savedVars=['ar_width','ar_height','screen_tag','sound','descrip','background_image','bg_image_height','bg_image_width','bg_image_tile'];

  this.seen=function(x,y) {
    if (this.fov["z" + x + "_" + y]) return true;
    return false;
  }

  this.load= function(o) {
    for (var i in this.savedVars) { var n=this.savedVars[i]; this[n]=o[n]; }

    this.ar_width=parseInt(this.ar_width);
    this.ar_height=parseInt(this.ar_height);
    if (isNaN(this.ar_width)) this.ar_width=0;
    if (isNaN(this.ar_height)) this.ar_height=0;

    this.resize(this.ar_width,this.ar_height);
  }

  this.save = function() {
    var o = new Object();
    for (var i in this.savedVars) { var n=this.savedVars[i]; o[n]=this[n]; }
    return o;
  }

  this.moveTile = function (o,xoffset,yoffset) {
    if (o==null || o.onMap==false) return false;

    var new_x=o.x + parseInt(xoffset);
    var new_y=o.y + parseInt(yoffset);

    if (new_x >= this.ar_width || new_x < 0) return false;
    if (new_y >= this.ar_height || new_y < 0) return false;

    //search for the object in this list and remove it

    for (var i=0;i < this.ar[o.x][o.y].length; i++) {
      if (this.ar[o.x][o.y][i]==o) {
	this.ar[o.x][o.y].splice(i,1);
	break;
      }
    }

    o.x = new_x;
    o.y = new_y;
  
    //insert in decreasing zIndex order
    for (var i=0; i< this.ar[o.x][o.y].length; i++) {
      if (o.zIndex > this.ar[o.x][o.y][i].zIndex) break;
    }

    this.ar[o.x][o.y].splice(i,0,o);

    this.dirtyList.push(o);       //so we know to update this tile
    return true;
  }

  this.removeTile=function(o) {
    //completely removes tile from map 
    if (o.onMap==false) return;

    //search for the object in this list and remove it
    for (var i=0;i <this.ar[o.x][o.y].length; i++) {
      if (this.ar[o.x][o.y][i]==o) {
        this.ar[o.x][o.y].splice(i,1);
        break;
      }
    }
    o.onMap=false;
    o.x=-1; o.y=-1;
    this.dirtyList.push(o);
    return o;
  }

  this.placeTileQuick=function(o,x,y) {
    //insert in decreasing zIndex order
    for (var i=0; i< this.ar[x][y].length; i++) {
      if (o.zIndex > this.ar[x][y][i].zIndex) break;
    }

    this.ar[x][y].splice(i,0,o);
	return true;
  }

  this.placeTile=function(o,x,y) {
    if ( x >= this.ar_width || x < 0 ||  y >= this.ar_height || y < 0) return false;
    o.x=x;
    o.y=y;
    o.onMap=true;

    //insert in decreasing zIndex order
    for (var i=0; i< this.ar[x][y].length; i++) {
      if (o.zIndex > this.ar[x][y][i].zIndex) break;
    }

    this.ar[x][y].splice(i,0,o);
    this.dirtyList.push(o);
    return true;
  }
 
  this.topTile=function(x,y) {
    if ( x >= this.ar_width || y >= this.ar_height || x < 0 || y < 0) return null;
    var le=this.ar[x][y].length;
    if (le==0) return null;
    return (this.ar[x][y][0]);
  }

  this.tilesAt=function(x,y) {
    //returns empty array when out of bounds
    if ( x >= this.ar_width || y >= this.ar_height || x < 0 || y < 0) return new Array();
    return this.ar[x][y];
  }

  this.clearTile=function(x,y) {
    var o_list=this.tilesAt(x,y);
    for (var i in o_list) {
      g.unregister_object(o_list[i]);
    }
    if (o_list.length > 0) {
      this.ar[x][y]=new Array();
    }
    return true;
  }


  this.resize=function(x,y) {

    for(var i=0;i<x;i++) {
      if (!this.ar[i])
	this.ar[i]=new Array();

      for(var j=0;j<y;j++) {
	if (!this.ar[i][j])
	  this.ar[i][j]=new Array();
      }
    }
    this.fov=new Object();
    this.ar_width=x;
    this.ar_height=y;
  }



  this.generate=function() {

    this.resize(12,12);
    //some objects, wall and floor
    var f=g.create_object('gTile','floor');
    f.displayTile=true;

    var s=g.create_object('gTile','wall');
    var w=g.create_object('gPushable','bookshelf');

    for (var i=0;i<12;i++) {
      for (var k=0;k<12;k++) {
	var o=g.clone_object(f);
	this.placeTile(o,i,k);
      }
    }


    //place random shelves
    for (var i=0;i<12;i++) {
      var o=g.clone_object(w); 
      this.placeTile(o,rand(this.ar_width), rand(this.ar_height));
    }

    //place stairs
    var d=g.create_object('gDoor','door1');
    d.dest.x=rand(this.ar_width);
    d.dest.y=rand(this.ar_height);

    this.placeTile(d,rand(this.ar_width),rand(this.ar_height));


    for (var i=0;i<15;i++) {
      var o=g.clone_object(s); 
      this.placeTile(o,rand(this.ar_width), rand(this.ar_height));
    }

  }
} 

function get_game_coords_offset(pageX, pageY,offsetX,offsetY) {
  var x= pageX-offsetX;
  var y= pageY-offsetY;
    
  //calculate game coords
  var gc=get_coords();
  gx=parseInt((x-gc.left)/grid_size);
  gy=parseInt((y-gc.top)/grid_size);

  var c= { x: gx,
	   y: gy};
  return c;
}


function get_game_coords(pageX, pageY) {
  var p=$("#main").offset();
  var x= pageX-p.left;
  var y= pageY-p.top;
    
  //calculate game coords
  //  var gc=get_coords();
//  gx=parseInt((x-gc.left)/grid_size);
//  gy=parseInt((y-gc.top)/grid_size);

  //translate viewport coords
  gx=parseInt(x/grid_size) + di.x_offset;
  gy=parseInt(y/grid_size) + di.y_offset;
  
  var c= { x: gx,
	   y: gy};
  return c;
}



function game_to_encap(x,y) {
  var p=$("#main").offset();

  x = (x - di.x_offset) * grid_size;
  y = (y - di.y_offset) * grid_size;
//  x=x*grid_size;
// y=y*grid_size;
  x += p.left;
  y += p.top;
  var p=$("#encap").offset();
  x -= p.left;
  y -= p.top;

  var c= { x: x,
	   y: y};
  return c;
}



function get_coords() {
  var c= { left: parseInt($("#main").css("left").replace(/px/g,"")),
	   top: parseInt($("#main").css("top").replace(/px/g,""))};

  return c;
}

function scroll_map(xoffset,yoffset) {
  var c=get_coords();
  c.left+=parseInt(xoffset * grid_size);
  c.top+=parseInt(yoffset * grid_size);
  $("#main").css("left",c.left + "px").css("top",c.top + "px");
}

function move_div(x,y) {
  $("#main").css("left",x + "px").css("top",y + "px");    
}

function clear_display() {
  $("#main div").remove();
}

function update_display_css() {
  //this is before we switched to using canvas
  var o=null;

  var x_offset=0;
  var y_offset=0;
  var max_x=max_grid_x;
  var max_y=max_grid_y;
  var flag=0;

  if (di.background_image != g.gm.background_image) {
    di.background_image = g.gm.background_image;
    $("#main").css("background-image","url('" + image_path + di.background_image + "')");
    $("#main").css("width",g.gm.ar_width*grid_size);
    $("#main").css("height",g.gm.ar_height*grid_size);
  }

  //find bounds of view
  if (typeof editor == "undefined" && g.me != null && g.me.onMap) {
    x_offset = (g.me.x-((max_grid_x-1)/2));
    y_offset = (g.me.y-((max_grid_y-1)/2));
    max_x += x_offset;
    max_y += y_offset;

    if (di.use_fov)
      calc_fov(g.me.x, g.me.y, Math.floor((max_grid_x-1)/2), Math.floor((max_grid_y-1)/2));

  } else if (typeof editor != "undefined") {
    //in edit mode
    max_x=g.gm.ar_width;
    max_y=g.gm.ar_height;
  }

  for (x=(x_offset < di.f_x_min ? x_offset: di.f_x_min); x <= (max_x > di.f_x_max ? max_x : di.f_x_max); x++) {
    for (y=(y_offset < di.f_y_min ? y_offset: di.f_y_min); y <= (max_y > di.f_y_max ? max_y : di.f_y_max); y++) {
	var o=g.gm.topTile(x,y);
	if (o != null) { g.gm.dirtyList.push(o); }
    }
  }

  di.f_x_max=max_x; di.f_x_min=x_offset;
  di.f_y_max=max_y; di.f_y_min=y_offset;


  while (o=g.gm.dirtyList.pop()) {
    if (!g.is_registered(o.id)) {
      var d=$("div#o" + o.id);
      if (d.length != 0) d.remove();
      continue;
    }
    if (o.displayTile==true) {
      if (o.onMap==false) {
	//remove the div object if it exists
	var d=$("div#o" + o.id);
	if (d.length != 0) d.remove();
	o.onScreen=false;
	continue;
      }

      var out_of_bounds=(o.x < x_offset || o.x > max_x || o.y < y_offset || o.y > max_y);
      if (o.object_type=="gBeing") {
	out_of_bounds=out_of_bounds || (!di.in_fov(o.x,o.y));
      }
      if (out_of_bounds && o.onScreen==true) {
	var d=$("div#o" + o.id);
	if (d.length != 0) d.remove();
	o.onScreen=false;
	continue;
      }

      if (o.onScreen==true && !out_of_bounds) {
	//it exists, just update the position
	if (o.screenX != (o.x * grid_size) || o.screenY != (o.y * grid_size)) {
	  var d=$("div#o" + o.id);
	  if (d.length > 0) {
	    d.css("left",o.x * grid_size + "px").css("top",o.y * grid_size + "px");
	    o.screenX=o.x * grid_size;
	    o.screenY=o.y * grid_size;
	  }
	}
      } else if (o.onScreen==false && !out_of_bounds) {
	//put it on the map if it doesn't exist
	var j="<div class=\"tile\" id=\"o" + o.id + "\" style=\"";
	j=j+" left: " + o.x *grid_size + "px; ";
	j=j+" top: " + o.y *grid_size + "px; ";
	j=j+" background-image: url('" + image_path + o.image + "');";
	j=j+" width: " + o.tile_width * grid_size + "px; ";
	j=j+" height: " + o.tile_height * grid_size + "px; ";
	j=j+" z-index: " + o.zIndex + "; ";
	j=j+"\"></div>";
	//	if (flag != 1) {flag=1; alert(j); }
	$("#main").append(j);

	o.onScreen=true;
	o.screenX=o.x * grid_size;
	o.screenY=o.x * grid_size;
      }
    }
  }

  if (di.use_fov) {
    for(x=0;x<9;x++) {
      for(y=0;y<9;y++) {
	var xr=x+x_offset;
	var yr=y+y_offset;

	var d=$("#fov" + x + "_" + y);

	if ( (xr < 0 || xr >= g.gm.ar_width) || (yr < 0 || yr >= g.gm.ar_height) || !g.gm.seen(xr,yr)) {
	  //put a black box if needed
	  if (di.fov_disp[x][y] !=2) {
	    if (d.length > 0) { d.css("background-image", "url('" + image_path + "black.gif')"); }
	    else {
	      var j="<div class=\"fov_tile\" id=\"fov" + x + "_" + y + "\" style=\"";
	      j=j+" left: " + x *grid_size + "px; ";
	      j=j+" top: " + y *grid_size + "px; ";
	      j=j+" background-image: url('" + image_path + "black.gif');";
	      j=j+"\"></div>";
	      $("#fov").append(j);
	    }

	  }
	  di.fov_disp[x][y]=2;
	  //	  continue;
	} else if (!di.in_fov(xr,yr)) {
	  //dim this square if needed
	  if (di.fov_disp[x][y] !=1) {
	    if (d.length > 0) { d.css("background-image", "url('" + image_path + "blur.gif')"); }

	    else {
	      var j="<div class=\"fov_tile\" id=\"fov" + x + "_" + y + "\" style=\"";
	      j=j+" left: " + x *grid_size + "px; ";
	      j=j+" top: " + y *grid_size + "px; ";
	      j=j+" background-image: url('" + image_path + "blur.gif');";

	      j=j+"\"></div>";
	      $("#fov").append(j);
	    }

	  }
	  di.fov_disp[x][y]=1;
	  //continue;
	} else {
	  if (di.fov_disp[x][y] != 0) {
	    if (d.length > 0) d.remove();
	  }
	  di.fov_disp[x][y]=0;
	}
      }
    }
  }

  //center the map around the character. This won't happen if we're in edit mode
  if (typeof editor == "undefined" && g.me != null && g.me.onMap) {
    var x = -((g.me.x-((max_grid_x-1)/2)) * grid_size);
    var y = -((g.me.y-((max_grid_y-1)/2)) * grid_size);
    $("#main").css("left",x + "px").css("top",y + "px");    
  }
}


function update_char_stats() {
  if (g.me != null) {
    var hp_percent=Math.floor((g.me.hp*100)/g.me.max_hp);
    $("#hp_meter").css("width",hp_percent + "%");
    $("#hp_num").text("(" + g.me.hp + "/" + g.me.max_hp + ")");

    var mp_percent=Math.floor((g.me.mp*100)/g.me.max_mp);
    $("#mp_meter").css("width",mp_percent + "%");
    $("#mp_num").text("(" + g.me.mp + "/" + g.me.max_mp + ")");
  }
}

function update_display() {

  if (g.dead==true) return;

  //update char stats
  update_char_stats();

  //if no canvas support
  if (!di.ctx) { update_display_css(); return; }

  //we have canvas support
  var o=null;

  var x_offset=0;
  var y_offset=0;
  var max_x=max_grid_x;
  var max_y=max_grid_y;
  var im=null;

  //center the map around the character. This won't happen if we're in edit mode
  if (typeof editor == "undefined" && g.me != null && g.me.onMap) {
    x_offset = (g.me.x-((max_grid_x-1)/2));
    y_offset = (g.me.y-((max_grid_y-1)/2));
    max_x += x_offset;
    max_y += y_offset;

    di.x_offset=x_offset;
    di.y_offset=y_offset;
  } else if (typeof editor != "undefined") {
    //in edit mode
    max_x=g.gm.ar_width;
    max_y=g.gm.ar_height;
  }
	



  //this is to release the images drawn in IE Canvas
//  di.ctx.clearRect();

  di.ctx.fillStyle="rgb(0,0,0)";

  di.ctx.fillRect(0,0,grid_size * g.gm.ar_width, grid_size * g.gm.ar_height);
  var bg_img=null;

  var needsUpdate=false;
  

  if (g.gm.background_image && g.gm.background_image != "") {
    di.background_image = g.gm.background_image;
    bg_img=di.getImage(image_path + g.gm.background_image);
    if (bg_img==null) {
      needsUpdate=true;
    } else {
      if (g.gm.bg_image_tile != "true") {
	//no tile, just draw once
	di.ctx.drawImage(bg_img,(0 - x_offset) * grid_size, (0 - y_offset) * grid_size,g.gm.bg_image_width * grid_size,g.gm.bg_image_height * grid_size);
      } else {
	//nothing, if we tile, we'll catch it in the drawing
      }
    }
  }


  for (x=x_offset; x <= max_x; x++) {
    for (y=y_offset; y<= max_y; y++) {
      var o=g.gm.topTile(x,y);
      if (o !=null) {
	//TODO: this is for editor
	if (o.displayTile==false) {
	  di.ctx.globalAlpha = 0.4;
	}
	im=di.getImage(image_path + o.image);
	if (im==null) {
	  needsUpdate=true;
	} else {
	  di.ctx.drawImage(im,(x-x_offset) * grid_size, (y-y_offset) * grid_size,o.tile_width * grid_size,o.tile_height * grid_size);
	  di.ctx.globalAlpha = 1;
	}
      }	 else {
	if ((g.gm.bg_image_tile==true || g.gm.bg_image_tile=="true") && bg_img != null) {
	  //tile it
	  di.ctx.drawImage(bg_img,(x - x_offset) * grid_size, (y - y_offset) * grid_size, grid_size, grid_size);
	}
      }
    }
  }

  if (needsUpdate) {
    setTimeout("update_display()",300);
  }

  g.gm.dirtyList=new Array();
}

function msg(s) {
 if (typeof $=="undefined") return;	
 if (typeof s=="undefined" || s==null) return;
  if (s=="") return;
  //add this to the scrolling
  var l=$("div#msgboard > div.msg:first");
  if (l.size() != 0) l.removeClass("recent");

  var j=new jQuery("<div class='msg recent'></div>");
  j.text(s);

  $("div#msgboard").prepend(j);

  //clean up old msgs
  $("div#msgboard > div.msg:gt(30)").remove();

  /*
  var l=$("div#msgboard > div.msg:last");
  $("div#msgboard").append(j);
  x=$("div#msgboard div.msg").size() - 30;
  if (x > 0) {
    $("div#msgboard > div.msg:lt(" + x + ")").remove();
  }
  */
}

// --- GLOBALS
//these should be filled out in another file by PHP
/*
glob.game_id = 0;
glob.savegame_id = 0;
glob.screen_tag = '';
*/

grid_size=50;
creg=new Array();
image_path = '/images/';
sound_path = '/sounds/';
bg_image='blueback.jpg';

max_grid_x=9;
max_grid_y=9;

max_x=max_grid_x * grid_size;
max_y=max_grid_y * grid_size;


di=new gDisplay();


g=new gGame();
/* for UI state */

drag=new Object;
selection=new Object;
drag.inDrag=false;
drag.p=null;
