// Orangeview AJAX GAME ENGINE version 9
// Copyright (c) 2004-2008 all rights reserved
// Programmed by Christer Kaitila using PHP and mySQL
// http://www.orangeview.net
// We respectfully ask that all users refrain from copying,
// mirroring, utilizing or editing this javascript in any way.
// You may not use it for any purpose without purchasing a license.
// Orangeview Games offers contract development services:
// http://www.orangeviewgames.com/game_development/

var engine_version = "Version 951"; // packer sans debug using ;;;
var versionstring = "Orangeview Game Engine "+engine_version+" (c) 2008";
var debug = 0; // (0=none, 1=major events only, 2=verbose, 3=mega-debug)
if (!window.done_loading_project_specific_vars) { alert('ERROR: not done_loading_project_specific_vars.'); }

;;; if (debug) alert(versionstring+'\nDEBUG MODE '+debug+': Loading game engine...');

var ajax_request_count = 0;
var default_server_url = 'ajax.php?'; 	// add http:// etc?
var post_login_music_load_delay = 4444; // wait to load flash
var current_level = '';			// eg username or zone or blank
var no_chat_at_all = false; 		// turn off ajax chat and polling?
var display_chatlog = true;		// chatlog!
var max_chat_character_length = 25; 	// cap chat length
var download_buildings_after_login = true; // right away
var download_friendlist_after_login = true; // kool


var open_inventory_after_login = false;	// immediate inv window open?
var flash_sound_allowed = true; 	// false and no swfs get loaded
var flash_not_yet_inserted = true; 	// only rendered once
var currently_logged_in = false; 	// true after begin logged in
var chatdebug = false;			// LOCAL DEBUG TXT AJAX
var fps_verbose = false; 		// true for TONS of debug info
var page_has_loaded = false;		// onload event updates this
var display_player_list = false; 	// in server stats top left
var force_final_destination = true; 	// if previous walkxy not reached in time for next msg, warp first
var maxiumum_fps = 1000; 		// set to 1000 for really smooth gameplay.
var gui_owns_clicks = false; 		// if false, level clicks - used in some mouseovers to stop level move clicks
var no_mouseclick_below_this = 64; 	// pixels high chatbar ignore
var chatter_max_idle_time = (1000*35);	// seconds till inactivity delete
var bubba_max_idle_time = (1000*7);	// seconds till bubble goes away
var mega_debug = false; 		// all log entries get alert()
var tile_edit_mode = false;		// unused floor tile editor
var tile_edit_tilenum = 0;
var tile_edit_tilestate = 0;
var tile_edit_cursor = null;
var tile_edit_maxnum = 3;
var tile_edit_maxstate = 9;
var dirUP=8;				// global constants: directions enum
var dirDN=2;
var dirLF=4;
var dirRT=6;
var dirUR=9;
var dirUL=7;
var dirDR=3;
var dirDL=1;
var dirNONE=5;
// tunes streaming mp3 flash player
// ie6 fix: random filename for uncached (cached swfs don't load in IE6)
var tunes_flash_html = "<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0' width='224' height='14' id='CoffeeCup' align='middle'><param name='movie' value='tunes.swf'/><param name='quality' value='high' /><param name='scale' value='noscale' /><param name='salign' value='lt' /><param name='bgcolor' value='#ffffff' /><embed src='tunes.swf' quality='high' bgcolor='#ffffff' scale='noscale' salign='lt'  type='application/x-shockwave-flash' width='224' height='14' name='CoffeeCup' align='middle' pluginspage='http://www.macromedia.com/go/getflashplayer' /></object>";
//var uncached_tunes = "tunes.swf?random="+Math.random();
//var tunes_flash_html = "<object classid='clsid:d27cdb6e-ae6d-11cf-96b8-444553540000' codebase='http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0' width='224' height='14' id='CoffeeCup' align='middle'><param name='movie' value='"+uncached_tunes+"'/><param name='quality' value='high' /><param name='scale' value='noscale' /><param name='salign' value='lt' /><param name='bgcolor' value='#ffffff' /><embed src='"+uncached_tunes+"' quality='high' bgcolor='#ffffff' scale='noscale' salign='lt'  type='application/x-shockwave-flash' width='224' height='14' name='CoffeeCup' align='middle' pluginspage='http://www.macromedia.com/go/getflashplayer' /></object>";
// unused fixme?  walker, pusher, aabb all only used by movement_test() and movetowardsmouse() only? remove?
var walker = 0;
var flyer = 0;
var pushers = Array();
var pushercount = 0;
var pushradius = 64;
var howmanypushers = 50; // takes like 10+ seconds per hundred!
var howmanyaabbs = 0;
var movespeed = 1; // can't be too big since SOME overlap occurs
var aabbs = Array();
var aabbcount = 0;
// minigame play button first time pressing play button?
var alreadyplaying = false;

// arrays that hold sprites to look at - set in game_init
var everything = Array(); // all props we want to walk around
var everythingcount = 0;
var everybody = Array(); // all avatars we want to walk around
var everybodycount = 0;

// some gui elements would fire in-game clicks
var global_ignore_the_next_click = false;

//oop_next and prev avatars choices in login gui remember avatar choice state vars
var loginfaceelement = 0;
var loginbodyelement = 0;
var loginfacenum = 0;
if (!window.loginfaces) // already defined?
{
	alert('ERROR - the game specific code has not run...');
}

// logged in as what name?
var global_user_name = '';
var already_logged_in = false; // used by chat.js
var pendingpolltimerid = 0;
var chat_refresh_ms = 6666; // plus up to 1000 ms more random (eg 6.6-7.6 seconds)
var chat_first_submit_ever = true;
var chat_user_name = '';
var myname = ''; // IE6 error with global var scope...
var chat_session_id = 0;
var chat_avatar = loginfaces[0]; //'spike';
var playeronespawnpositionxy = [4900,4900];
var chat_x = 4900;
var chat_y = 4900;
if (window.specific_start_x)
{
	playeronespawnpositionxy = [specific_start_x,specific_start_y];
	chat_x = specific_start_x;
	chat_y = specific_start_y;
}
var chatarray = Array();
var chatloglinecount = 12; //20 //32; // how many addlog entries to show at one time
var chatarrayindex = 0;
var notpolling = true; // not YET!
var previousmychat = null;
var chatters = []; // a simple array of [name]=[xy] used by crappy respawner chat.js function fixme
var chattersprites = []; // pointer of known chatters sprites used in respawn
var chatters_count = 0;
var uniquenumber = 1000; // used by sprite.js document.write - unused?
var codequeue = Array();
var codequeuecount = 0; // start from here
var codequeue_next = 0; // which comes next
var floortilecount = 0; // unused except for debug info


function oop_login() // LOGIN button clicked
{	// grab user name, hide login gui, change avatar
	//if (already_logged_in) return;
	if (!window.game) return;
	if (!window.all_html_downloaded)
	{
		;;; if (debug) alert('Cannot login until all_html_downloaded.');
		return; // needs access to ids
	}
	//addlog('logging in!');
	game_sfxlogin();
	// change bottom chat bar face
	document.getElementById('chatface').src = CACHEURL+'chat_head_' + loginfaces[loginfacenum] + '.gif';
	global_user_name = document.getElementById('loginname').value;
	// special secret names:
	if (global_user_name=='debug') debug = true;
	//addlog('global_user_name='+global_user_name+' loginfacenum='+loginfacenum);
	game.playerone.changenametag(global_user_name);
	//game.playerone.changechat('Logged in ' + global_user_name);
	game.playerone.changechat(' '); // blank chat
	/*
	if (window.highdef_avatars)
	{	// doesn't use avatar_file_extension
		var newsheet = CACHEURL + "sheet_" + loginfaces[loginfacenum] + ".gif";
		;;; if (debug) addlog('DEBUG: high def avatar: '+newsheet);
		game.playerone.changesheet(newsheet);
	}
	else
	{
	*/
		//addlog('DEBUG: low def avatars');
		game.playerone.changeframestate(loginfacenum);
	//}
	if (document.getElementById('loginwindow')) document.getElementById('loginwindow').style.display='none'; // hide login gui
	if (document.getElementById('chatholder')) document.getElementById('chatholder').style.display='block'; // make chat bar visible
	if (document.getElementById('citymap')) document.getElementById('citymap').style.display='block';
	if (document.getElementById('td_username')) document.getElementById('td_username').innerHTML = "<nobr>Name: <b>" + global_user_name + "</b></nobr>";
	// optional gui components visible?
	if (document.getElementById('friendlist')) document.getElementById('friendlist').style.display='block';
	// open inventory immediately?
	if (open_inventory_after_login)
	{
		if (window.inventory_gui_show) inventory_gui_show();
	}
	// tell chat about us
	chat_login(global_user_name,loginfaces[loginfacenum]);
	// download inventory after a while - waits for first inv_grow
	// setTimeout("if (window.download_inventory) download_inventory();",4444);
	if (window.inventory_needs_loading) // download now! fixme
	{	// possible bug: race condition
		download_inventory();
		inventory_needs_loading = false;
	}

	if (window.download_friendlist_after_login && window.download_friendlist_now)
	{
		download_friendlist_now();
	}
	
	// start the txt msg poll after a longer while
	if (window.txtmsgenabled)
	{	// no txts if debug mode
		if (!debug)
		{
			setTimeout("if (window.begin_checking_for_txtmsgs) begin_checking_for_txtmsgs();",8888);
		}
		else
		{
			addlog('DEBUG: txtmsg system debug disabled.');
		}
	}

	// scroll the viewport to the starting position
	if (window.specific_start_x)
	{
		warpto(specific_start_x,specific_start_y);
	}

	if (display_chatlog)
	{
		if (document.getElementById('chatlog'))
			document.getElementById('chatlog').style.display = 'block';
	}
	// make the mouse cursor visible
	if (window.xhair_show) xhair_show();
	if (window.download_buildings_after_login)
	{	// download the current level (buildings)
		download_buildings('');
	}
	// launch the help window?
	if (window.buthelp) buthelp();

	// only in dev mode we can build outside - but saved in SELF named level...
	//;;; if (debug) && window.leveleditor_all_the_time) leveleditor_show();

	if (flash_sound_allowed  && flash_not_yet_inserted)
	{	// flash waits for page load due to cache flash bugs (page never stops loading)
		if (!debug)
		{
			flash_not_yet_inserted = false;
			setTimeout("insert_flash_content();",post_login_music_load_delay);
		}
		else
		{
			addlog('DEBUG: flash sound debug mute.');
		}
	}

	if (document.getElementById('all_warps')) // the warp buttons (exit signs, etc)
		document.getElementById('all_warps').style.display = 'block';

	;;; if (debug>1) addlog('DEBUG: oop_login completed');

	addlog(global_user_name+' has joined the game.');
	
	currently_logged_in = true;
}

function striphtml(thisstr)
{	// removes all html tags - fixme: chokes on tags inside comments etc
	return thisstr.replace(/(<([^>]+)>)/ig,"");
}

function ajaxerror(someinfo)
{
	;;; if (debug) addlog("<font color=red>"+striphtml(someinfo)+"</font>");
}

function queue(somecode)
{
	if (!somecode) return; // null, 0, ''
	if (somecode.length<2) return;
	codequeue[codequeuecount] = somecode; // remember to eval it later
	codequeuecount++;
}
function game_queue_frame()
{	// work a little more on LONG tasks
	//if (this.now_ms < this.queue_next_frame_time) return;
	//this.queue_next_frame_time = this.now_ms + this.queue_delay_ms;

	if (codequeuecount<1) return;

	if (codequeue_next<=codequeuecount)
	{
		//;;; if (debug) addlog('Running Queued Code '+codequeue_next+' of '+codequeuecount);
		try
		{
			var runme = codequeue[codequeue_next]; // avoid race condition
			codequeue[codequeue_next] = null; // save ram
			codequeue_next++;
			eval(runme);
			//fixme: to save ram, splice array, remove data
		}
		catch(e)
		{
			;;; if (debug) addlog('ERROR with codequeue['+runme+']');
		}
	}
}

function addlog(thistext) // add a string to the chat log
{
	//if ((!debug) && (!chatdebug)) return;
	if (!document.getElementById('chatlog')) return;
	//if (firstaddlog) { document.getElementById('chatlog').value=''; firstaddlog=false; }
	//if (mega_debug) alert(thistext);
	thistext = fulltrim(thistext);
	if (!thistext) return; // ===''
	;;; if (debug) thistext = prettytimestamp()+' '+thistext;
	//thistext = string_replace('&rsquo;',"'",thistext);
	// remember
	chatarray[chatarrayindex] = thistext;
	chatarrayindex++;
	finalhtml = '';
	// output only the recent ones
	if (chatarrayindex>=chatloglinecount) // -1 so the first 13 dont show
	{
		for (shownum=chatarrayindex; (shownum>(chatarrayindex-chatloglinecount+1)); shownum--)
		{
			// top down:
			//finalhtml += chatarray[shownum-1] + '<br>';
			// bottom up:
			finalhtml = chatarray[shownum-1] + '<br>' + finalhtml;
		}
		document.getElementById('chatlog').innerHTML = finalhtml;
	}
	else
	{	// fewer than chatloglinecount just show all.
		document.getElementById('chatlog').innerHTML =  chatarray.join('<br>');
	}
	// string concat ALL gets slow, using FORM input textarea yuk!
	//document.getElementById('chatlog').value += thistext + '\n';
	//document.getElementById('chatlog').scrollTop = document.getElementById('chatlog').scrollHeight;
}

function game_engine()
{ // game engine class constructor
	this.versionstring = versionstring;
	this.screen_center_x = 400; // browser pixel resolution divided by two
	this.screen_center_y = 300;
	this.initialized = 0;		// true = running
	this.init_time= 0;			// timestamp of first frame
	this.now_ms = 0;			// timestamp of current frame
	this.walkspeedpersecond = 82; //64; // pixels per second walking
	this.frame_ms = 1;			// ms since last frame (dt)
	this.total_ms = 0;			// total ms since first frame
	this.frame_count = 0;		// frame count since init
	this.fps = 0;				// framerate
	this.max_fps = maxiumum_fps; //1000, 20;	// affects the timer for game.frame();
	this.minimum_frame_ms = parseInt(1000 / this.max_fps); 	// 1000=1fps 100=10fps 50=20fps 25=40fps 20=50fps 10=100fps max
	// remember the playerone sprite apart from other chatters
	this.playerone = 0; 		// sprite for the user's avatar
	// default sprite sizes always changed
	//this.spriteh = 64;			// pixels per sprite
	//this.spritew = 64;			// pixels per sprite - DEFAULTS for radius and pusher widths!
	//this.spriteurl = CACHEURL+'s.gif';	// url for all sprites 32x32pixels
	// nice big smooth 28 frame explosion - unused fixme
	//this.fxw = 64; this.fxw2 = this.fxw/2; // was 112 for full size
	//this.fxh = 64; this.fxh2 = this.fxh/2;
	//this.fx_num_frames = 28; 	// was 16
	//this.fxurl = CACHEURL+'explosionstrip1_64x64.gif'; // unused fixme
	// html element IDs
	this.id_logger = 'logger';	// logger messages
	this.id_fps = 'fps';		// fps display
	// html elements (divs)
	this.ele_logger = 0;		// debug logger
	this.ele_fps = 0;			// debug fps stats display
	this.ele_status = 0;		// unused fixme
	this.ele_body = 0;			// unused maybe?
	// player one stats
	this.playername = '';		// name of player
	this.playerpass = '';		// password of player
	this.playercode = '';		// sessionid of player
	// active game entities
	this.sprites = Array();		// array of game_sprite objects
	this.sprite_count = 0;		// next free index
	this.fx = Array();			// array of game_sprite objects
	this.fx_count = 0;			// number of spawned fx
	this.mousex = 0;			// mouse x of last click in browser coords
	this.mousey = 0;			// mouse x of last click in browser coords
	this.mouselevelx = 0;		// mouse x offset inside the level (4000s)
	this.mouselevely = 0;		// mouse y offset inside the level (4000s)
	// function pointers
	this.logger = game_logger;	// output of logger messages - set to NULL for silence
	//;;; if (debug) this.logger = null; // no debug output means faster fps
	//this.logger = null;					// set to null for no debug and faster FPS
	this.init = game_init; 		// setup engine
	this.frame = game_frame; 	// animate one frame
	this.timestamp = game_timestamp;// generate a timestamp
	// sound effects
	this.sfxchat = game_sfxchat;
	this.sfxhover = game_sfxhover;
	this.sfxclick = game_sfxclick;
	this.sfxlogin = game_sfxlogin;
	this.sfxgun = game_sfxgun;
	this.sfxchop = game_sfxchop;
	// particle system fx
	this.party = game_party;	// spawn a particle system e.g. game.party(100,100);
	this.party_anim_fps = 20;	// framerate of the animation
	this.party_anim_ms = parseInt(1000 / this.party_anim_fps);
	this.party_anim_next_frame_time = 0;
	this.party_fx_per_party = 1; // 1 is just one explosion good enough.. // 6 is spamcool
	this.fx_next = 0; 			// next available particle to reuse
	this.fx_max = 20; 			// number of fx sprites to use
	// viewport and level
	this.viewport = 0;

	// starting camera position matches css
	this.viewport_current_px = 4320; // 0 //HARDCODED LEVEL SIZE MIDDLE - SEE THE CSS for #viewport top: left:
	this.viewport_current_px_float = 4320.0; // 0.0
	this.viewport_current_py = 4480; // 0
	this.viewport_current_py_float = 4480.0; // 0.0
	this.viewport_target_px = 4320; // 0
	this.viewport_target_py = 4480; // 0

	// inside the house - old version...
	//this.viewport_current_px = 4064;
	//this.viewport_current_px_float = 4064.0;
	//this.viewport_current_py = 5120;
	//this.viewport_current_py_float = 5120.0;
	//this.viewport_target_px = 4064;
	//this.viewport_target_py = 5120;

	this.viewportstyle = 0;
	this.level = 0;
	this.levelstyle = 0;
	this.spawnviewport = game_spawnviewport;
	this.spawnlevel = game_spawnlevel;
	// frame step functions
	this.viewport_frame = game_viewport_frame;
	this.fx_frame = game_fx_frame;
	this.viewport_arrowkey_movedelta = 128; // pixels per arrow key was 256
	this.avatar_frame = game_avatar_frame; // move all chatters around
	this.queue_frame = game_queue_frame; // function that splits up long code tasks
}

function body_to_state(thisbody) // returns int
{
	for (var ab=0; ab<loginfaces.length; ab++)
	{
		if (loginfaces[ab]==thisbody) // found it
		{
			//addlog('body_to_state(' + thisbody + ')=' + ab);
			return ab;
		}
	}
	return loginfaces[0]; // default valid
}

function spawn_debug_red_circle_xy_xy(minxy,maxxy)
{
	debugimage = spawnclonebyid('debugcollide');
	if (!debugimage) return;
	//addlog('Debug Circle: top:'+debugimage.style.top+' left:'+debugimage.style.left+' width:'+debugimage.style.width+' height:'+debugimage.style.height);
	debugimage.style.left=minxy[0]+'px';
	debugimage.style.top=minxy[1]+'px';
	debugimage.style.zIndex=maxxy[1]-1;//999999999;
	debugimage.style.width=(maxxy[0]-minxy[0])+'px';
	debugimage.style.height=(maxxy[1]-minxy[1])+'px';
	debugimage.style.display='block';
	// too spammy during debug
	//;;; if (debug>1) addlog('Debug Circle: top:'+debugimage.style.top+' left:'+debugimage.style.left+' width:'+debugimage.style.width+' height:'+debugimage.style.height);
}

function spawnaabb(minxy,maxxy)
{	// create an invisi-wall that is a rectangle
	;;; if (debug>1) addlog('aabb number '+aabbcount+' ('+minxy+','+maxxy+')');
		;;; if (debug) spawn_debug_red_circle_xy_xy(minxy,maxxy);
	aabbs[aabbcount] = new Object();
	aabbs[aabbcount].minxy = minxy;
	aabbs[aabbcount].maxxy = maxxy;
	aabbcount++;
}

function download_buildings(thislevel,thispin)
{	// download the current level editor buildings
	if (!thispin) thispin = '';
	if (!thislevel) thislevel = '';
	;;; if (debug) addlog('download_buildings ('+thislevel+') ('+thispin+') starting...');
	if (thispin!='')
		pendinglevelrequest = new ajax("level="+thislevel+"&user="+global_user_name+"&pin="+thispin,"level.php?");
	else
		pendinglevelrequest = new ajax("level="+thislevel+"&user="+global_user_name,"level.php?");
}

function relogin() // change avatar
{ // change avatar but do not login
	game_sfxclick();
	document.getElementById('loginwindow').style.display='block';
}

function helpnow()
{ 	// fixme: for now this just relogins - add a help window?
	relogin();
}

function oop_chat_mouseover(meme)
{ // button mouseover image
	//game_sfxhover();
	meme.src=cache_chat_over_gif_src;
}

function oop_chat_mouseout(meme)
{ // button mouseover image
	//game_sfxhover();
	meme.src=cache_chat_gif_src;
}

function oop_help_mouseover(meme)
{ // gui

	//game_sfxhover();
	meme.src=cache_help_over_gif_src;
}
function oop_help_mouseout(meme)
{ // gui
	//game_sfxhover();
	meme.src=cache_help_gif_src;
}

function oop_login_mouseover(meme)
{ // gui
	//;;; if (debug>1) addlog('DEBUG: oop_login_mouseover');
	game_sfxhover();
	meme.src=cache_login_over_gif_src;
}

function oop_login_mouseout(meme)
{ // gui
	//;;; if (debug>1) addlog('DEBUG: oop_login_mouseout');
	//game_sfxhover();
	meme.src=cache_login_gif_src;
}

function oop_login_mouseover_rightarrow(meme)
{ // gui
	game_sfxhover();
	meme.src=cache_login_rightarrow_gif_src;
}

function oop_login_mouseout_rightarrow(meme)
{ // gui
	//game_sfxhover();
	meme.src=cache_blank_gif_src;
}

function oop_login_mouseover_leftarrow(meme)
{ // gui
	game_sfxhover();
	meme.src=cache_login_leftarrow_gif_src;
}

function oop_login_mouseout_leftarrow(meme)
{ // gui
	//game_sfxhover();
	meme.src=cache_blank_gif_src;
}

function oop_next_avatar()
{ // login gui
	if (!window.loginfaces) return; // IE6 not done download
	game_sfxclick();
	if (!loginfaceelement) loginfaceelement = document.getElementById('loginface');
	if (!loginbodyelement) loginbodyelement = document.getElementById('loginbody');
	loginfacenum++;	if (loginfacenum>loginfaces.length-1) loginfacenum = 0;
	if (loginfaceelement) loginfaceelement.src = CACHEURL+'head_' + loginfaces[loginfacenum] + avatar_file_extension;
	if (loginbodyelement) loginbodyelement.src = CACHEURL+'body_' + loginfaces[loginfacenum] + avatar_file_extension;
	;;; if (debug>1) alert('DEBUG: oop_next_avatar: ' + loginfaceelement.src);
}

function oop_prev_avatar()
{ // login gui
	if (!window.loginfaces) return; // IE6 not done download
	game_sfxclick();
	if (!loginfaceelement) loginfaceelement = document.getElementById('loginface');
	if (!loginbodyelement) loginbodyelement = document.getElementById('loginbody');
	loginfacenum--;	if (loginfacenum<0) loginfacenum = loginfaces.length-1;
	if (loginfaceelement) loginfaceelement.src = CACHEURL+'head_' + loginfaces[loginfacenum] + avatar_file_extension;
	if (loginbodyelement) loginbodyelement.src = CACHEURL+'body_' + loginfaces[loginfacenum] + avatar_file_extension;
	;;; if (debug>1) alert('DEBUG: oop_prev_avatar: ' + loginfaceelement.src);
}

/*
// old
function clickplaybutton() // button sprite onclick
{ // login!

	;;; if (debug) addlog('Play button clicked!');
	//addlog('DEBUG - play button clicked!');
	game_sfxlogin();
	if (!alreadyplaying)
	{
		minigamecenter(); // walk to the right location
		document.getElementById('minigame').innerHTML = minigame_iframe_html; // open an iframe
		global_ignore_the_next_click = true;
		alreadyplaying = true;
	}
	else
	{
		document.getElementById('minigame').innerHTML = minigame_idle_html; // destroy it
		alreadyplaying = false;
	}
}
function minigamecenter() // unused omit fixme
{ // walk playerone to the right location
	//addlog('time to center the minigame!');
	warpto(3072,4864); // viewport - center the minigame - hardcoded minigame center location
	//game.playerone.walk_towards_xy([3072,4864]); // walk here? bah
}
*/

function warptoxy(thisxy)
{
	warpto(thisxy[0],thisxy[1]);
}

function warpto(thisx,thisy)
{ // teleport
	// center the viewport on this desired location
	cx = parseInt(getClientWidth() / 2);
	cy = parseInt(getClientHeight() / 2);
	game.viewport_target_px = thisx - cx;
	game.viewport_target_py = thisy - cy;
	//addlog('DEBUG: warping!');
	chat_x = thisx;
	chat_y = thisy;
	game.playerone.walkxy=[thisx,thisy];
	game.playerone.movexy(game.playerone.walkxy);
	// now scroll the viewport IMMEDIATELY
	game.levelstyle.top = -1 * game.viewport_target_py + 'px';
	game.levelstyle.left = -1 * game.viewport_target_px + 'px';
	game.viewport_current_py = game.viewport_target_py;
	game.viewport_current_px = game.viewport_target_px;
}

function minigameclick() // unused? hardcoded to center in on minigame location fixme - does not fire but as onclick in the iframe
{ // called from inside iframe by the puzzle game - permission denied cross frame js possibilities... fixme
	global_ignore_the_next_click = true;
	minigamecenter();
}

function getdirections(ax,ay,bx,by) // if the world was square
{ // unused?
	if      ((ay>by)&&(ax==bx)) return dirUP;
	else if ((ay<by)&&(ax==bx)) return dirDN;
	else if ((ax<bx)&&(ay==by)) return dirRT;
	else if ((ax>bx)&&(ay==by)) return dirLF;
	else if ((ay>by)&&(ax<bx)) return dirUR;
	else if ((ay>by)&&(ax>bx)) return dirUL;
	else if ((ay<by)&&(ax<bx)) return dirDR;
	else if ((ay<by)&&(ax>bx)) return dirDL;
	else return dirNONE; // equality
}

/*
function get_css_dir(ax,ay,bx,by) // a string e.g. '-32px' - up/dn/lf/rt only get chosen if perfectly level...
{ // unused?
	if      ((ay>by)&&(ax==bx)) return cssUP;
	else if ((ay<by)&&(ax==bx)) return cssDN;
	else if ((ax<bx)&&(ay==by)) return cssRT;
	else if ((ax>bx)&&(ay==by)) return cssLF;
	else if ((ay>by)&&(ax<bx)) return cssUR;
	else if ((ay>by)&&(ax>bx)) return cssUL;
	else if ((ay<by)&&(ax<bx)) return cssDR;
	else if ((ay<by)&&(ax>bx)) return cssDL;
	else return cssDL; // default
}
*/

function everybody_find_by_name(yourname)
{ // find pointer to sprite obj
	for (nextavatar in everybody)
	{
		if ((everybody[nextavatar]) && (everybody[nextavatar].name == yourname))
		{
			return everybody[nextavatar];
		}
	}
	return null;
}

function game_avatar_frame()
{ // move everybody around
	//addlog('game_avatar_frame'); // addlog is sometimes not yet loaded bug
	var blocking = true;
	var ignorecollisions = false; // true = no collisions at all!
	var speed = game.walkspeedpersecond * (game.frame_ms / 1000); // time dt speed - a few pixels depending upon fps
	//addlog('walk speed: ' + speed + ' pixels');
	for (nextavatar in everybody)
	{
		//addlog('walking ' + nextavatar + ' to ' + everybody[nextavatar].walkxy);
		if ((everybody[nextavatar]) && (everybody[nextavatar].visible) && (everybody[nextavatar].walking) )
		{
			everybody[nextavatar].trytowalk(speed); // works!
		}
	}
}

function game_party(px,py,pstyle) // re-use a particle system
{ // explosions and effects
	//if (this.logger) this.logger('spawning fx at '+px+','+py);

	// cycle through available sprites
	if (this.fx_next < (this.fx_max-1)) this.fx_next++; else this.fx_next = 0;
	// now respawn it starting from frame 1 (in case it was in use)
	this.fx[this.fx_next].changeframe(1); // avoid flicker
	this.fx[this.fx_next].move(px-this.fxw2,py-this.fxh2); // centered
	this.fx[this.fx_next].show();
	// this is CSS slow: four style adjustments to respawn.. optimize me:
	// idea: only move sprites offscreen when idle (no display: changes)

	if (pstyle > 1) // oh we want MANY of em do we?
	{
		// recursive
		pstyle--;
		babypx = px+Math.random()*this.fxw-(this.fxw2);
		babypy = py+Math.random()*this.fxh-(this.fxh2);
		this.party(babypx,babypy,pstyle); // works like a charm
	}
}

function game_logger(amsg) // debug log - gets slow with long string concats...
{ // spammy debug log
	if (mega_debug) alert(amsg);
	if (!debug) return;

	//// works but slow string concat: fixme:
	//if (!this.ele_logger) return;
	//if (this.ele_logger.value.length > 500)
	//{	// chop it down to size
	//	this.ele_logger.value = this.ele_logger.value.substring(0,500);
	//}
	//// concat
	//this.ele_logger.value = (this.timestamp()-this.init_time)+'ms '+amsg + "\n" + this.ele_logger.value;

	addlog(amsg); // way better
}

function game_sfxclick()
{ // sound
	//if (this.logger) this.logger('sfxclick');
	if (window.soundManager) soundManager.play('click');
}

function game_sfxchop()
{ // sound
	//if (this.logger) this.logger('game_sfxchop');
	if (window.soundManager) soundManager.play('chop');
}

function game_sfxlogin()
{ // sound
	//if (this.logger) this.logger('sfxlogin');
	if (window.soundManager) soundManager.play('login');
}

function game_sfxchat()
{ // sound
	//if (this.logger) this.logger('sfxchat');
	if (window.soundManager) soundManager.play('chat');
}

function game_sfxhover()
{ // sound
	//if (this.logger) this.logger('sfxhover');
	if (window.soundManager) soundManager.play('hover');
}

function game_sfxgun()
{ // sound
	//if (this.logger) this.logger('sfxgun');
	if (window.soundManager)
		soundManager.play('gun');
	else
		if (game.logger) game.logger('sfxgun ERROR - no sound engine!');
}

function game_viewport_frame()
{ // slowly scroll move towards the desired center point

	if (!viewport_can_scroll) return;

	if (!smooth_scroll_viewport)
	{
		// warp the camera immediately: no scrolling for better fps in IE
		this.viewportmove(this.viewport_target_px,this.viewport_target_py);
		return;
	}


	// smoothly scoll the viewport: works:
	// rise over run for proper angle
	var xdist = this.viewport_target_px - this.viewport_current_px;
	var ydist = this.viewport_target_py - this.viewport_current_py;

	if ((xdist != 0) || (ydist != 0)) // we need to move!
	{

		var xytotal = Math.abs(xdist)+Math.abs(ydist);
		var xratio = Math.abs(xdist/xytotal);
		var yratio = Math.abs(ydist/xytotal);

		// scroll the viewport at a constant speed (non-fps-dependent) (using accelleration and time)
		//var xspeed = pixelspersecondx * (this.frame_ms / 1000); // e.g. 0.001
		//var yspeed = pixelspersecondy * (this.frame_ms / 1000);
		// negative xy aren't allowed in viewport scroll it goes from 0..200000

		var xspeed=0;
		var yspeed=0;
		// how fast should we go (fast when far away, slow when close)
		var pixelspersecond = (Math.abs(xdist)*2+Math.abs(ydist)*2);///2;
		var totaldistancethisframe = pixelspersecond * (this.frame_ms / 1000);
		if (totaldistancethisframe<1) totaldistancethisframe=1; // fps dependent but stops sooner for better FPS in IE

		if (this.viewport_target_px < this.viewport_current_px)
			xspeed = -1 * totaldistancethisframe * xratio;
		else if (this.viewport_target_px > this.viewport_current_px)
			xspeed = totaldistancethisframe * xratio;

		if (this.viewport_target_py < this.viewport_current_py)
			yspeed = -1 * totaldistancethisframe * yratio;
		else if (this.viewport_target_py > this.viewport_current_py)
			yspeed = totaldistancethisframe * yratio;

		// don't overshoot
		if (Math.abs(xspeed) > Math.abs(xdist)) xspeed = xdist;
		if (Math.abs(yspeed) > Math.abs(ydist)) yspeed = ydist;

		if ((xspeed != 0) || (yspeed != 0))
		{
			//if (this.logger) this.logger('scrolling cur='+this.viewport_current_px+','+this.viewport_current_py	+' tar='+this.viewport_target_px+','+this.viewport_target_py	+' dist='+xdist+','+ydist +' spd='+xspeed+','+yspeed); // +' ratio='+xratio+','+yratio);
			this.viewportscroll(xspeed,yspeed); // this func return true if we needed to move
		}
	}
}

function game_fx_frame()
{ // crappy animation loop used by playerone demo
	// not time to update yet?
	if (this.now_ms < this.party_anim_next_frame_time) return;

	this.party_anim_next_frame_time = this.now_ms + this.party_anim_ms; // fx fps is only like 10fps

	//if (this.logger) this.logger('game_sprites_frame');
	// animate fx sprites
	//for (floop=0; floop < this.fx_count; floop++)
	for (floop in this.fx)
	{
		if (this.fx[floop].visible)		// otherwise dont think at all
		{
			//if (this.logger) this.logger('fx ' + floop + ' is visible');
			if (this.fx[floop].anim_delta != 0) 	// animated?
			{
				//if (this.logger) this.logger('fx ' + floop + ' is visible and animated ('+this.fx[floop].anim_delta+') using mode ' + this.fx[floop].anim_loop + ' num: ' + this.fx[floop].num + ' animloopstart='+this.fx[floop].animloopstart+' animloopend='+this.fx[floop].animloopend + ' flipflop:'  + this.fx[floop].flipflopcounter);

				if (this.fx[floop].anim_loop==walkcycle_1213) //2) // 1213 walk cycle hardcoded fixme
				{
					this.fx[floop].flipflopcounter++;
					if (this.fx[floop].flipflopcounter>3) this.fx[floop].flipflopcounter = 0;
					if (this.fx[floop].flipflopcounter==0) this.fx[floop].num = this.fx[floop].animloopstart;
					else if (this.fx[floop].flipflopcounter==1) this.fx[floop].num = this.fx[floop].animloopstart + 1;
					else if (this.fx[floop].flipflopcounter==2) this.fx[floop].num = this.fx[floop].animloopstart;
					else if (this.fx[floop].flipflopcounter==3) this.fx[floop].num = this.fx[floop].animloopstart + 2;
					//if (this.logger) this.logger('fx ' + floop + ' is now at frame ' + this.fx[floop].num);
				}
				else // must be regular loop
				{
					// inside animation range? 1..anim_frames
					if ((this.fx[floop].num < this.fx[floop].animloopend)) // this.fx[floop].anim_frames)) // pending frames?
					{
						this.fx[floop].num += this.fx[floop].anim_delta; // advance
						//if (this.logger) this.logger('fx ' + floop + ' is at frame ' + this.fx[floop].num);
					}
					else // past max frame!
					{
						//if (this.logger) this.logger('fx ' + floop + ' is at the end [' + this.fx[floop].num + '] of the animation');
						if (this.fx[floop].anim_loop==walkcycle_loop) // regular cycle LOOPED
						{
							//if (this.logger) this.logger('fx ' + floop + ' is LOOPING');
							this.fx[floop].num = this.fx[floop].animloopstart; //0; //1;
						}
						else // normal: go away after one animation
						// time to hide? if (this.fx[floop].anim_loop==0) // one timer?
						{
							//if (this.logger) this.logger('fx ' + floop + ' is HIDING');
							this.fx[floop].hide();
							this.fx[floop].num = -1; // go invisible first frame 0
						}
					} // loop anim
				}
				// update what frame is displayed via CSS
				this.fx[floop].changeframe(this.fx[floop].num);
			} // animated?
		} // visible?
		// float around all of em in a sine wave
		// this.fx[floop].move(200+32*floop+Math.cos((this.frame_count+floop) / 100)*(100+floop),200+Math.cos((this.frame_count+floop*10) / 100)*(100+floop*10));
	}
}

function sprite_removefromlevel()
{
	;;; if (debug) addlog('DEBUG: sprite_removefromlevel');

	if (this==game.playerone)
	{
		addlog('ERROR: trying to remove player one sprite from level!');
		return;
	}

	if (game.level && this.el)
	{	// remove the actual html element
		;;; if (debug>2) addlog('sprite_removefromlevel this element: ' + this.el);
		game.level.removeChild(this.el);
	}
}

function destroy_ghost(thisperson)
{
	if (everybody[thisperson])
	{
		if ((everybody[thisperson]!=game.playerone)	&& (thisperson!='Store Clerk')) // kool special
		{
			//;;; if (debug) addlog('destroying ghost ' + thisperson);
			//everybody[thisperson].hide();
			everybody[thisperson].removefromlevel();
			;;; if (debug>1) addlog('deleting assoc array element');
			delete everybody[thisperson];
			//everybody.splice(thisperson,1); // associative array index error? fixme?
		}
		else
		{
			;;; if (debug>1) addlog('Avoiding the deletion of '+thisperson);
		}
	}
}

function fade_old_bubs() // once per second is a bit spammy
{
	if ((!window.everybody) || (everybody.length<1) || !currently_logged_in)
	{
		//addlog('ERROR: there is no everybody yet...'); // this can happen pre login
		return;
	}

	// too spammy during debug
	//;;; if (debug>1) addlog('fade_old_bubs at game.now_ms:'+game.now_ms);

	for (nextavatar in everybody)
	{
	 	thisone = everybody[nextavatar];
		if (thisone)
		{
		 	if ((thisone.time_to_destroy>0) && (thisone.time_to_destroy < game.now_ms))
			{
	 			;;; if (debug>2) addlog('fade_old_bubs DESTROY GHOST everybody['+nextavatar+'].time_to_destroy:'+thisone.time_to_destroy+' < game.now_ms:'+game.now_ms);
	 			//thisone.changechat('destroy me!');
	 			destroy_ghost(nextavatar);
	 			//everybody[nextavatar].hide(); // fixme actually remove? recycle?
	 			//everybody[nextavatar] = null;
	 		}

		 	if ((thisone.fade_chat_after_timestamp>0) && (thisone.fade_chat_after_timestamp < game.now_ms))
			{
	 			;;; if (debug>2) addlog('fade_old_bubs FADE CHAT everybody['+nextavatar+'].fade_chat_after_timestamp:'+thisone.fade_chat_after_timestamp+' < game.now_ms:'+game.now_ms);
	 			thisone.fade_chat_after_timestamp = 0; // done
	 			thisone.changechat('.');
	 		}
	 	}
	}

	if ((game.playerone.fade_chat_after_timestamp>0) && (game.playerone.fade_chat_after_timestamp < game.now_ms))
	{
		;;; if (debug>1) addlog('Player One Fade Bub!');
		game.playerone.fade_chat_after_timestamp = 0; // done
		game.playerone.changechat('.'); //fade playerone chat!');
	}

}

function once_per_second() // runs once per second - heartbeat GLOBAL function
{ // heartbeat
	//if (game.logger) game.logger('once_per_second');
	if (window.fade_old_bubs) fade_old_bubs();
	setTimeout(once_per_second,1000);
}

function game_frame()
{ // runs every single frame
	var lastframetime = this.now_ms;
	this.now_ms = this.timestamp();
	this.frame_ms = this.now_ms - lastframetime;
	if (this.frame_ms<1) this.frame_ms = 1; // avoid div 0
	this.total_ms = this.now_ms - this.init_time;
	this.fps = parseInt(this.frame_count / (this.total_ms / 1000));
	this.frame_count++;

	// update fps debug info line
	// but not every frame
	//if (this.frame_count % 10 == 0) // this increases fps by 3-4

	// debug info like FPS display
	// scan all known avatars!
	// set innerHTML with TONS of debug info... spammy
	// this is slow to calculate every frame... fixme - once per second?
	// giant line of code here:
	;;; if (debug) { if (this.ele_fps) { var timestring = prettytimestamp(); var avatarlist = ""; for (eachavatar in everybody) { avatarlist += everybody[eachavatar].name + ':'+ parseInt(everybody[eachavatar].xy[0]) + ',' +  parseInt(everybody[eachavatar].xy[1]) + ':' + everybody[eachavatar].walkxy + '<br>'; } if (init_to_onload_timespan!=0) { init_time_html = '- Download: ' + (init_to_onload_timespan/1000) + ' seconds'; } else { init_time_html = '- Downloading...'; } if (!fps_verbose) { this.ele_fps.innerHTML = this.versionstring + '<br>Debug Mode: ['+timestring+'] frame:' + this.frame_count + ' time:' + this.total_ms + 'ms fps:' + parseInt(1000 / this.frame_ms) + ' avg:' + this.fps + '<br>mouse:' + (this.mousex+this.viewport_current_px) + ',' + (this.mousey+this.viewport_current_py) + ' x: ' + chat_x + ' y: ' + chat_y + ' ' + init_time_html + ' - Ajax #'+ajax_request_count; } else { this.ele_fps.innerHTML = this.versionstring + '<br>Debug Mode: ['+timestring+'] frame:' + this.frame_count + ' time:' + this.total_ms + 'ms<br>fps:' + parseInt(1000 / this.frame_ms) + ' avg:' + this.fps + ' mouse:' + (this.mousex+this.viewport_current_px) + ',' + (this.mousey+this.viewport_current_py) + ' view:' + this.viewport_current_px + ',' + this.viewport_current_py + ' chat_x: ' + chat_x + ' chat_y: ' + chat_y + '<br>Init: ' + init_start_timestamp + ' onload: ' + init_atend_timestamp + '<br>floors:' + floortilecount + ' things:' + everythingcount + ' bodies:' + everybodycount + ' boxes:' + aabbcount + init_time_html + '<br>' + avatarlist; } } }

	this.viewport_frame(); // move the viewport

	this.avatar_frame(); // maybe animate the avatars

	//if (!window.highdef_avatars) {
	this.fx_frame(); // maybe animate the explosions AND the walk animations
	//}

	// only run deferred code after login (delay spawning!)
	if (currently_logged_in)
		this.queue_frame(); // step through long code eval tasks

	// queue up next frame asap
	setTimeout('game.frame()',this.minimum_frame_ms); // animate constantly for the highest FPS possible...
}

function prettytimestamp()
{ // total game play time in mm:ss
	if (!window.game) return '00:00';
	var msperhour = 1000 * 60 * 60;
	var mspermin = 1000 * 60;
	var mspersec = 1000;
	var hours = 0; //parseInt(game.total_ms / msperhour);
	var minutes = parseInt((game.total_ms - (hours*msperhour)) / mspermin);
	if (minutes<10) minutes = '0'+minutes;
	var seconds = parseInt((game.total_ms - (hours*msperhour) - (minutes*mspermin)) / mspersec);
	if (seconds<10) seconds = '0'+seconds;
	return 	minutes+':'+seconds; //hours+':'+
}

function gridme(anumber,agrid)
{ // round to nearest
	return Math.round(anumber/agrid)*agrid;
}

function grid64(anumber)
{ // round to nearest 64
	return Math.round(anumber/64)*64;
}

function grid32(anumber)
{ // round to nearest 32
	return Math.round(anumber/32)*32;
}

function grid16(anumber)
{ // round to nearest 16
	return Math.round(anumber/16)*16;
}

function randy(min,max)
{
	return parseInt((Math.random()*(max-min))+min);
}

function game_init()
{ // populate level, setup
	// what time is it?
	this.init_time = this.now_ms = this.timestamp();

	// unique guest user name
	if(document.getElementById('loginname'))
		document.getElementById('loginname').value = "Guest " + parseInt(Math.random()*899999+100000);

	// events need a global var, don't work inside this.objects
	if (this.logger) this.logger('Grabbing global events');
	// grab user input to global functions
	document.onmousemove = mousemove;
	// faster movement: don't wait for button release.. but gui happens after
	//v4: works: document.onmousedown = mouseclick;
	document.onmouseup = mouseclick; // this way click events fire first (eg arcade)
	//v4 no it breaks... trying to help GUI buttons get the clicks before level move
	//document.onclick = mouseclick;
	document.onkeydown = keydown;
	document.onkeypress = keypress;
	// mousewheel inits
	if (window.addEventListener) window.addEventListener('DOMMouseScroll', mousewheel, false);
	window.onmousewheel = document.onmousewheel = mousewheel;

	this.spawnviewport(); // open level tag.. and point to viewport functions

	// wait for sprites to go inside level? nah..
	this.spawnlevel();

	// IE6 is slow even if there are no sprites.
	if (window.sprite) // function exists?
	{
		// create the avatar sprite based on user settings
		if (this.logger) this.logger('Spawning playerone sprite');

		this.playerone = new sprite(spawnclonebyid('avatar'),0,0,0,walkcycle_loop_numframes,'',AVATARW,AVATARH,avatar_walkcycle_mode,0);

		//this.playerone.changenametag('Guest User');
		//this.playerone.changechat('Logging in ' + global_user_name);

		this.playerone.movexy(playeronespawnpositionxy);

		//this.playerone.show();
		// do not need to remember in everybody list - this happens during respawn
		// everybody[everybodycount++] = this.playerone;
		//this.playeronecenter = function()
		//{
		//	// hardcoded head height fixme
		//	var footxoffset = 0; //-16;
		//	var footyoffset = 0; // -60;
		//	var footx = this.viewport_current_px + parseInt(getClientWidth() / 2) + footxoffset; //parseInt(this.playerone.width / 2);
		//	var footy = this.viewport_current_py + parseInt(getClientHeight() / 2) + footyoffset; //(this.playerone.height - 8);
		//	this.playerone.move(footx,footy); // zIndex autosort
		//}
		// this.playeronecenter(); // feet at middle of screen
		// hidden till login: since its not animated yet
		// this.playerone.show();
		// put in the 'animate me please' list - done in chat respawn
		// game.fx[game.fx_count++] = this.playerone; // auto-animate test!
	}
	else
	{
		addlog('ERROR - no sprite class loaded.');
	}

	// if we used document.write only - unused
	// this.spawnlevel(); // close the level tag and grab it

	// grab fps and logger debug divs in ram
	this.ele_fps = document.getElementById(this.id_fps);
	this.ele_logger = document.getElementById(this.id_logger);

	// only do this in debug mode.. // unhide
	;;; if (debug) { if (this.ele_logger) this.ele_logger.style.display='block'; if (this.ele_fps) this.ele_fps.style.display='block'; if (this.logger) this.logger('Elements spawned'); if (this.logger) this.logger('Creating music engine'); }

	//no WRITE - old fixme:
	//document.write("<iframe name='music' id='music' width='2' height='2' scrolling='no' allowtransparency='true' src='music/index.html' style='position:absolute; top:-100px; left:-100px; width:2px; height:2px; border:2px solid red; padding:0; margin:0; background:black;'></iframe>");
	this.music = document.getElementById('music');
	//this.music.src = '';

	//if (this.logger) this.logger('Creating sfx engine script');
	//document.write("<scr"+"ipt type='text/javascript' language='javascript' src='sound-config.js'></scr"+"ipt>");

	if (this.logger) this.logger('Game Engine Initialization Complete.');
	this.initialized = true;
}

function game_viewportscroll(deltapx,deltapy) // returns true if it moves at all
{	// used in viewport frame - MAYBE move using FLOATS.
	var didwemove = false;

	this.viewport_current_py_float += deltapy;
	this.viewport_current_px_float += deltapx;

	//if (this.logger) this.logger('viewportscroll('+deltapx+','+deltapy+')=('+this.viewport_current_px+','+this.viewport_current_py+') scrollTop='+this.viewport.scrollTop);
	// only change if entire pixel change is required (for better fps)

	var newpy = Math.round(this.viewport_current_py_float); // float to int
	var newpx = Math.round(this.viewport_current_px_float); // float to int

	if (window.viewport_force_minimum_y) // defined?
	{
		if (newpy<viewport_force_minimum_y) newpy = viewport_force_minimum_y;
	}
	if (window.viewport_force_minimum_x) // defined?
	{
		if (newpx<viewport_force_minimum_x) newpx = viewport_force_minimum_x;
	}
	if (window.viewport_force_maximum_y) // defined?
	{
		if (newpy>viewport_force_maximum_y) newpy = viewport_force_maximum_y;
	}
	if (window.viewport_force_maximum_x) // defined?
	{
		if (newpx>viewport_force_maximum_x) newpx = viewport_force_maximum_x;
	}

	if (newpy != this.viewport_current_py) // different than current value?
	{
		this.viewport_current_py = newpy;

		// move the level - works except IE6 background image cache http thrash! fixed.
		this.levelstyle.top = -1 * newpy + 'px'; // works 2007

		// scroll the level instead nop
		//this.level.scrollTop = newpy;

		// scroll the BODY - NOP in IE6 and FF
		//this.ele_body.scrollTop = newpy;

		// scroll the viewport - IE6 invis NOP
		//this.viewport.scrollTop = newpy; // update CSS

		didwemove = true;
	}

	if (newpx != this.viewport_current_px) // different than current value?
	{
		this.viewport_current_px = newpx;

		// move the level - works 2007
		// IE6 bad FPS but works in all browsers...
		this.levelstyle.left = -1 * newpx + 'px';

		// scroll the level instead nop
		//this.level.scrollTop = newpy;

		// scroll the BODY - NOP in IE6 and FF
		//this.ele_body.scrollLeft = newpx;

		// scroll the viewport NOP
		//this.viewport.scrollLeft = newpx; // update CSS

		didwemove = true;
	}
	return didwemove;
}

function game_viewportmove(px,py)
{	// used in warpto

	if (window.viewport_force_minimum_y) // defined?
	{
		if (py<viewport_force_minimum_y) py = viewport_force_minimum_y;
	}
	if (window.viewport_force_minimum_x) // defined?
	{
		if (px<viewport_force_minimum_x) px = viewport_force_minimum_x;
	}
	if (window.viewport_force_maximum_y) // defined?
	{
		if (py>viewport_force_maximum_y) py = viewport_force_maximum_y;
	}
	if (window.viewport_force_maximum_x) // defined?
	{
		if (px>viewport_force_maximum_x) px = viewport_force_maximum_x;
	}

	this.viewport_current_py = py;
	this.viewport_current_px = px;
	// move the level around
	this.levelstyle.left = -1 * px + 'px';
	this.levelstyle.top = -1 * py + 'px';
}

function game_spawnviewport() // open the level tag for writing to in old-school document.write html insert old engine... fixme
{ 	// setup scrolling viewport and a few other eles
	if (this.logger) this.logger('game_spawnviewport');
	// minigame iframe html:
	// <iframe onclick='minigameclick()' id='minigameframe' src='http://www.orangeviewgames.com/community/puzzle/index.php' border='0' marginheight='0' marginwidth='0' frameborder='0' scrolling='no' noresize='noresize' allowtransparency='true'></iframe>
	this.viewport = document.getElementById('viewport');
	if (!this.viewport) addlog('ERROR - missing viewport!');
	this.viewportscroll = game_viewportscroll;
	this.viewportmove = game_viewportmove;
	// first frame instantly teleport to start pos
	//this.viewportmove(this.viewport_current_px,this.viewport_current_py);
}

function game_spawnlevel() // write level html fixme old
{ // setup entire level inside viewport
	if (this.logger) this.logger('game_spawnlevel');
	// WAIT FOR AFTER ALL SPRITES
	// no .write - fixme:
	// document.write("</div>"); // <!-- end div level -->
	this.level = document.getElementById('level');
	if (this.level)
		this.levelstyle = this.level.style;
	else
		addlog('ERROR - unable to find the level element.');
}

function getClientWidth()
{ // browser resolution
	if( typeof( window.innerWidth ) == 'number' ) {
	// most browsers
	myWidth = window.innerWidth;
	} else if( document.documentElement && ( document.documentElement.clientWidth ) ) {
	//IE 6+ in 'standards compliant mode'
	myWidth = document.documentElement.clientWidth;
	} else if( document.body && ( document.body.clientWidth ) ) {
	//IE 4 compatible
	myWidth = document.body.clientWidth;
	}
	if (myWidth>0) return myWidth;
	if (game.logger) game.logger('ERROR - Unable to determine the width of the window.');
	return 800; // failure
}

function getClientHeight()
{ // browser resolution
	if( typeof( window.innerWidth ) == 'number' ) {
	// most browsers
	myHeight = window.innerHeight;
	} else if( document.documentElement && ( document.documentElement.clientHeight ) ) {
	//IE 6+ in 'standards compliant mode'
	myHeight = document.documentElement.clientHeight;
	} else if( document.body && ( document.body.clientHeight ) ) {
	//IE 4 compatible
	myHeight = document.body.clientHeight;
	}
	if (myHeight>0) return myHeight;
	if (game.logger) game.logger('ERROR - Unable to determine the height of the window.');
	return 480; // failure
}

function nexttile()
{	// fixme: hard coded for 8x4 tiles of 128x128

	if (!tile_edit_mode) return;

	tile_edit_tilenum++;
	// loop around and wrap horiz for floors - hardcoded floor test.
	if (tile_edit_tilenum>tile_edit_maxnum) { tile_edit_tilenum=0; tile_edit_tilestate++; }
	if (tile_edit_tilestate>tile_edit_maxstate) { tile_edit_tilestate=0; }
	if (tile_edit_tilenum<0) { tile_edit_tilenum=tile_edit_maxnum; tile_edit_tilestate--; }
	if (tile_edit_tilestate<0) { tile_edit_tilestate=tile_edit_maxstate; }
	//;;; if (debug) addlog('next tile:'+tile_edit_tilenum+','+tile_edit_tilestate);
	tile_edit_cursor.changeframe(tile_edit_tilenum);
	tile_edit_cursor.changeframestate(tile_edit_tilestate);
	;;; if (debug) addlog('next tile '+tile_edit_tilenum+','+tile_edit_tilestate);
}

function prevtile()
{	// fixme: hard coded for 8x4 tiles of 128x128
	if (!tile_edit_mode) return;

	tile_edit_tilenum--;
	// loop around and wrap horiz for floors - hardcoded floor test.
	if (tile_edit_tilenum>tile_edit_maxnum) { tile_edit_tilenum=0; tile_edit_tilestate++; }
	if (tile_edit_tilestate>tile_edit_maxstate) { tile_edit_tilestate=0; }
	if (tile_edit_tilenum<0) { tile_edit_tilenum=tile_edit_maxnum; tile_edit_tilestate--; }
	if (tile_edit_tilestate<0) { tile_edit_tilestate=tile_edit_maxstate; }
	//;;; if (debug) addlog('prev tile:'+tile_edit_tilenum+','+tile_edit_tilestate);
	tile_edit_cursor.changeframe(tile_edit_tilenum);
	tile_edit_cursor.changeframestate(tile_edit_tilestate);
	;;; if (debug) addlog('prev tile '+tile_edit_tilenum+','+tile_edit_tilestate);
}

function mousewheel(event) // mousewheel rotation: not all browsers lowlevel
{
	var delta = 0;
	if (!event) event = window.event;
	if (event.wheelDelta) {
		delta = event.wheelDelta/120;
		if (window.opera) delta = -delta;
	} else if (event.detail) {
		delta = -event.detail/3;
	}
	;;; if (debug>1) addlog('mousewheel:'+delta);
	if (delta>0) nexttile();
	if (delta<0) prevtile();
	// fixme: if 0 or undef.. rotate anyways?
}

function mouseclick(anevent)
{ // global: does not work inside an object - no 'this'
	if (global_ignore_the_next_click)
	{
		global_ignore_the_next_click = false;
		return true; // alow passthru
	}

	if (window.hovering_the_inventory)
	{
		return true; // don't move
	}

	if (gui_owns_clicks) // button hovers etc
	{
		return true; // don't move
	}

	if (!window.already_logged_in) return true;

	if (anevent) // moz
	{
		game.mousex = anevent.pageX;
		game.mousey = anevent.pageY;
	}
	else // ie
	{
		game.mousex = window.event.clientX;
		game.mousey = window.event.clientY;
	}

	// chat grabs focus immediately - if not fully loaded can bug out...
	// if (document.getElementById('chattext')) { document.getElementById('chattext').focus(); }

	var gotheight = getClientHeight();

	// react to the click only if above a certain point (not on chatbar)
	if (game.mousey > (gotheight - no_mouseclick_below_this))
	{
		if (game.logger) game.logger('ignoring bottom click ' + game.mousey + ' > ' + (gotheight - no_mouseclick_below_this));
		return true; // alow passthru // instant out!
	}

	// capture it globally - used by varyous movement routines
	game.screen_center_x = parseInt(getClientWidth() / 2);
	game.screen_center_y = parseInt(gotheight / 2);

	// where inside the level coordinate system did we click?
	game.mouselevelx = game.mousex + game.viewport_current_px;
	game.mouselevely = game.mousey + game.viewport_current_py;

	chat_x = game.mouselevelx;
	chat_y = game.mouselevely;
	game.playerone.walkxy = [game.mouselevelx,game.mouselevely];
	game.playerone.walk_towards_xy([game.mouselevelx,game.mouselevely]); // walk destination

	// remember target location for submitchat form
	// fixme: remember these elements
	// fixme: insert above head (y-60)
	if (document.getElementById('xinput')) document.getElementById('xinput').value = game.mouselevelx;
	if (document.getElementById('yinput')) document.getElementById('yinput').value = game.mouselevely;

	// if (game.logger) game.logger('mouseclick ' + game.mousex + ',' + game.mousey);
	// game.party(game.mouselevelx,game.mouselevely,game.party_fx_per_party);
	// game.sfxclick();

	// scroll the viewport towards that new center point
	current_center_px = parseInt(getClientWidth() / 2);
	current_center_py = parseInt(gotheight / 2);
	deltax = game.mousex - current_center_px;
	deltay = game.mousey - current_center_py;
	newtargetx = game.viewport_current_px + deltax;
	newtargety = game.viewport_current_py + deltay;
	game.viewport_target_px = newtargetx;
	game.viewport_target_py = newtargety;

	// build/queued command waiting for a choice of location
	if (game.playerone.next_click_callback)
	{
		;;; if (debug) addlog('mouseclick running a callback on playerone');
		game.playerone.next_click_callback();
		game.playerone.next_click_callback=null;
		building_pending_sprite_being_dragged=false; // stop dragging
	}


	// fixme: tweak for 12 frame mini 4-dof
	// works: for 8-dof-5-frame:
	//_old_look_in_correct_direction(deltax,deltay);
	// done by sprite in start_walking

	if (tile_edit_mode)
	{
		// spawn a floor tile!
		// spawnfloortilexy(tile_edit_tilenum,[grid32(game.mouselevelx),grid32(game.mouselevely)],tile_edit_tilestate);
		// make a string we can submit, save, and eval:
		codestring = 'f('+tile_edit_tilenum+',['+grid32(game.mouselevelx)+','+grid32(game.mouselevely)+'],'+tile_edit_tilestate+');';
		;;; if (debug) addlog('tiles: ' + codestring);
		eval(codestring);
		// output it as a string for easy copying n pasting
		document.getElementById('level_editor_txt').innerHTML += codestring;
		//document.getElementById('level_editor_txt').style.display = 'block';
	}

	// put the x,y coordinate in the chat text NOT ON GRID
	;;; if (debug) { document.getElementById('chattext').value += '['+game.mouselevelx+','+game.mouselevely+'],'; }

	// debug sprite clone! works
	//test_clones(1,'floors');
	//test_clones(1,'walls');
	//test_clones(1,'props');
	//test_clones(1,'avatar');

	;;; if (debug) { if (game.logger) game.logger('click ['+game.mouselevelx+','+game.mouselevely+'] '+ game.mousex + ',' + game.mousey + ' c' + current_center_px + ',' + current_center_py + ' d' + deltax + ',' + deltay); }

	return true; // alow passthru of the click event
}

/*
function _old_look_in_correct_direction(deltax,deltay) // old style avatar x shifts
{ // change the playerone avatar sprite animation state to the correct direction
	if (game.playerone)
	{
		// if barely angled, use udlr - a range of 16 pixels before you hit an angles direction
		if ((Math.abs(deltay)<32) && (Math.abs(deltax)>32)) deltay=0; // force UPDN down to zero
		if ((Math.abs(deltax)<32) && (Math.abs(deltay)>32)) deltax=0; // *OR*  LFRT down to zero
		// rotate the sprite
		// if (game.logger) game.logger('changing playerone state to ' + get_css_dir(0,0,deltax,deltay));
		game.playerone.imgstyle.top = get_css_dir(0,0,deltax,deltay); // changeframestate function! fixme?
	}
}

// unused - works - playerone only
function look_in_correct_direction(deltax,deltay) // new animloopstart animloopend
{ // change the playerone avatar sprite animation state to the correct direction
	if (game.playerone)
	{
		// if barely angled, U/D/L/R almost never gets shown
		// works: use udlr - a range of 16 pixels before you hit an angles direction
		// if ((Math.abs(deltay)<32) && (Math.abs(deltax)>32)) deltay=0; // force UPDN down to zero
		// if ((Math.abs(deltax)<32) && (Math.abs(deltay)>32)) deltax=0; // *OR*  LFRT down to zero
		newdir = getdirections(0,0,deltax,deltay);
		if (newdir==dirNONE)
		{
			game.playerone.animloopstart = 0;
			game.playerone.animloopend = 0;
		}
		else
		{
			if (newdir==dirUP) game.playerone.animloopstart = 3; else
			if (newdir==dirDN) game.playerone.animloopstart = 6; else
			if (newdir==dirLF) game.playerone.animloopstart = 0; else
			if (newdir==dirRT) game.playerone.animloopstart = 6; else
			if (newdir==dirUL) game.playerone.animloopstart = 9; else
			if (newdir==dirUR) game.playerone.animloopstart = 3; else
			if (newdir==dirDL) game.playerone.animloopstart = 0; else
			if (newdir==dirDR) game.playerone.animloopstart = 6;
			game.playerone.animloopend = game.playerone.animloopstart + 2; // hardcoded 3 frame walk cycle
		}
		game.playerone.changeframe(game.playerone.animloopstart); // now
	}
}
*/

function mousemove(anevent)
{ // global: does not work inside an object
	if (anevent) // moz
	{
		game.mousex = anevent.pageX;
		game.mousey = anevent.pageY;
	}
	else // ie
	{
		anevent = window.event;
		game.mousex = anevent.clientX;
		game.mousey = anevent.clientY;
	}

	if (window.globalmousemoveevent) // does this function exist?
		globalmousemoveevent(game.mousex,game.mousey);

	// where inside the level coordinate system did we click?
	game.mouselevelx = game.mousex + game.viewport_current_px;
	game.mouselevely = game.mousey + game.viewport_current_py;

	if (tile_edit_mode)
	{
		gridxy = [grid32(game.mouselevelx),grid32(game.mouselevely)];
		if (!tile_edit_cursor)
		{
			addlog('Creating tile_edit_cursor');
			tile_edit_cursor = spawnfloortilexy(0,gridxy,0);
			tile_edit_cursor.st.opacity = '0.5'; // firefox only lowlevel
			tile_edit_cursor.show();
		}
		else
		{
			//;;; if (debug) addlog('moving cursor to ' + gridxy);
			tile_edit_cursor.movexy(gridxy);
		}
	}

	//;;; if (debug) addlog('mousemove ' + game.mousex + ',' + game.mousey);
}

// instant walk targets, non additive (does not match the viewport)
// game.playerone.walk_towards_xy([game.playerone.xy[0],game.playerone.xy[1]-game.viewport_arrowkey_movedelta]);
// game.playerone.walk_towards_xy([game.playerone.xy[0],game.playerone.xy[1]+game.viewport_arrowkey_movedelta]);
// game.playerone.walk_towards_xy([game.playerone.xy[0]-game.viewport_arrowkey_movedelta,game.playerone.xy[1]]);
// game.playerone.walk_towards_xy([game.playerone.xy[0]+game.viewport_arrowkey_movedelta,game.playerone.xy[1]]);
// game.playerone.walk_towards_xy([game.playerone.xy[0]-game.viewport_arrowkey_movedelta,game.playerone.xy[1]-game.viewport_arrowkey_movedelta]);
// game.playerone.walk_towards_xy([game.playerone.xy[0]-game.viewport_arrowkey_movedelta,game.playerone.xy[1]+game.viewport_arrowkey_movedelta]);
// game.playerone.walk_towards_xy([game.playerone.xy[0]+game.viewport_arrowkey_movedelta,game.playerone.xy[1]-game.viewport_arrowkey_movedelta]);
// game.playerone.walk_towards_xy([game.playerone.xy[0]+game.viewport_arrowkey_movedelta,game.playerone.xy[1]+game.viewport_arrowkey_movedelta]);

// fixme: arrow keys movetowardsxy need to add the center of screen resolution fixme

// Handle User Input
function key_tab()	{ if (game.logger) game.logger('TAB/SHIFT/ALT/CTRL/ETC'); }
function key_up()	{ if (game.logger) game.logger('up'); game.viewport_target_py -= game.viewport_arrowkey_movedelta; game.playerone.look_in_correct_direction(0,-100); game.playerone.walk_towards_screen_center(); } // move_viewport_n(); }
function key_down()	{ if (game.logger) game.logger('down'); game.viewport_target_py += game.viewport_arrowkey_movedelta; game.playerone.look_in_correct_direction(0,100); game.playerone.walk_towards_screen_center(); } //game.playerone.walk_towards_xy([game.viewport_target_px,game.viewport_target_py]); } // move_viewport_s(); }
function key_left()	{ if (game.logger) game.logger('left'); game.viewport_target_px -= game.viewport_arrowkey_movedelta; game.playerone.look_in_correct_direction(-100,0); game.playerone.walk_towards_screen_center(); } //game.playerone.walk_towards_xy([game.viewport_target_px,game.viewport_target_py]); } //  move_viewport_w(); }
function key_right()	{ if (game.logger) game.logger('right'); game.viewport_target_px += game.viewport_arrowkey_movedelta; game.playerone.look_in_correct_direction(100,0); game.playerone.walk_towards_screen_center(); } //game.playerone.walk_towards_xy([game.viewport_target_px,game.viewport_target_py]); } //  move_viewport_e(); }
function key_home()	{ if (game.logger) game.logger('HOME'); game.viewport_target_py -= game.viewport_arrowkey_movedelta; game.viewport_target_px -= game.viewport_arrowkey_movedelta; game.playerone.look_in_correct_direction(-50,-50); game.playerone.walk_towards_screen_center(); } //game.playerone.walk_towards_xy([game.viewport_target_px,game.viewport_target_py]); } //  move_viewport_nw(); }
function key_end()	{ if (game.logger) game.logger('END');  game.viewport_target_py += game.viewport_arrowkey_movedelta; game.viewport_target_px -= game.viewport_arrowkey_movedelta; game.playerone.look_in_correct_direction(-50,50); game.playerone.walk_towards_screen_center(); } //game.playerone.walk_towards_xy([game.viewport_target_px,game.viewport_target_py]); } //  move_viewport_sw(); }
function key_pgup()	{ if (game.logger) game.logger('PGUP'); game.viewport_target_py -= game.viewport_arrowkey_movedelta; game.viewport_target_px += game.viewport_arrowkey_movedelta; game.playerone.look_in_correct_direction(50,-50); game.playerone.walk_towards_screen_center(); } //game.playerone.walk_towards_xy([game.viewport_target_px,game.viewport_target_py]); } //  move_viewport_ne(); }
function key_pgdn()	{ if (game.logger) game.logger('PGDN'); game.viewport_target_py += game.viewport_arrowkey_movedelta; game.viewport_target_px += game.viewport_arrowkey_movedelta; game.playerone.look_in_correct_direction(50,50); game.playerone.walk_towards_screen_center(); } //game.playerone.walk_towards_xy([game.viewport_target_px,game.viewport_target_py]); } //  move_viewport_se(); }
function key_esc()	{ if (game.logger) game.logger('esc'); } //  toggle_menu(); }
function key_enter()	{ if (game.logger) game.logger('ENTER'); if (window.submitchat) submitchat(); /* else fakesubmitchat(); */ }
function key_space()	{ if (game.logger) game.logger('SPACE'); } //  toggle_buttonbar(); }
function key_ins()	{ if (game.logger) game.logger('INSERT'); }
function key_del()	{ if (game.logger) game.logger('DELETE'); }
function key_0()	{ if (game.logger) game.logger('0'); }
function key_1()	{ if (game.logger) game.logger('1'); }
function key_2()	{ if (game.logger) game.logger('2'); }
function key_3()	{ if (game.logger) game.logger('3'); }
function key_4()	{ if (game.logger) game.logger('4'); }
function key_5()	{ if (game.logger) game.logger('5'); }
function key_6()	{ if (game.logger) game.logger('6'); }
function key_7()	{ if (game.logger) game.logger('7'); }
function key_8()	{ if (game.logger) game.logger('8'); }
function key_9()	{ if (game.logger) game.logger('9'); }

function keydown(evt)
{ // handle all keypresses
	//if (game.logger) game.logger('keydown');
	evt = (evt) ? evt : event;
	var charCode = (evt.charCode) ? evt.charCode : ((evt.which) ? evt.which : evt.keyCode);
	var target = (evt.target) ? evt.target : evt.srcElement;

	if (!target)
	{
		if (game.logger) game.logger('keydown: no target element');
	}
	else if ((target) && (target == this.ele_chatinput))
	{
		if (game.logger) game.logger('keydown: allowing all keys for chatinput');
		return true;  // all keys allowed
	}

	if (charCode == 39) 		key_right();
	else if (charCode == 37) 	key_left();
	else if (charCode == 40) 	key_down();
	else if (charCode == 38) 	key_up();
	else if (charCode == 27) 	key_esc();
	else if (charCode == 32) 	key_space();
	else if (charCode == 33) 	key_pgup();
	else if (charCode == 34) 	key_pgdn();
	else if (charCode == 35) 	key_end();
	else if (charCode == 36) 	key_home();
	else if ((charCode == 13) || (charCode == 3)) key_enter();
	else if (charCode == 96) 	key_ins(); // 0,ins when numlock is on
	else if (charCode == 45) 	key_ins(); // 0,ins
	else if (charCode == 97) 	key_end(); // 1,end
	else if (charCode == 98) 	key_down(); // 2,down
	else if (charCode == 99) 	key_pgdn(); // 3,pgdn
	else if (charCode == 100) 	key_left(); // 4,left
	else if (charCode == 101) 	key_space(); // 5,space
	else if (charCode == 102) 	key_right(); // 6,right
	else if (charCode == 103) 	key_home(); // 7,home
	else if (charCode == 104) 	key_up(); // 8,ups
	else if (charCode == 105) 	key_pgup(); // 9,pgup
	else if (charCode == 110) 	key_del(); // .,del when numlock is on
	else if (charCode == 46) 	key_del(); // .,del
	else if (charCode == 9) 	key_tab(); // tab
	else if (charCode == 144) 	key_tab(); // numlock
	else if (charCode == 16) 	key_tab(); // shift
	else if (charCode == 20) 	key_tab(); // capslock
	else if (charCode == 17) 	key_tab(); // ctrl
	else if (charCode == 18) 	key_tab(); // alt
	else if (charCode == 91) 	key_tab(); // 'windows' key
	else if (charCode == 93) 	key_tab(); // 'menu' key
	// regular number keys on top
	else if (charCode == 48) 	key_0();
	else if (charCode == 49) 	key_1();
	else if (charCode == 50) 	key_2();
	else if (charCode == 51) 	key_3();
	else if (charCode == 52) 	key_4();
	else if (charCode == 53) 	key_5();
	else if (charCode == 54) 	key_6();
	else if (charCode == 55) 	key_7();
	else if (charCode == 56) 	key_8();
	else if (charCode == 57) 	key_9();
	else if (charCode == 192) 	key_tab(); // tilde
	else if (charCode == 109) 	key_tab(); // - (minus) on both top AND numpad
	else if (charCode == 61) 	key_tab(); // = (equals) (and plus on top)
	else if (charCode == 107) 	key_tab(); // + (plus) (numpad)
	else if (charCode == 8) 	key_tab(); // <- (backspace)
	else
	{
		if (game.logger) game.logger('keydown: allowing unhandled key: ' + charCode);
		return true; // bubble to other objects (eg F11 for fullscreen)
	}

	// blockable arrow keys and pgup pgdn
	is_a_movement_key = (
		// arrows
		(charCode == 37) ||
		(charCode == 38) ||
		(charCode == 39) ||
		(charCode == 40) ||
		// pgup pgdn
		(charCode == 33) ||
		(charCode == 34) ||
		// numpad arrows
		(charCode == 98) ||
		(charCode == 100) ||
		(charCode == 102) ||
		(charCode == 104) ||
		// numpad pgup pgdn
		(charCode == 105) ||
		// home end
		(charCode == 36) ||
		(charCode == 35) ||
		(charCode == 99));

	if (is_a_movement_key)
	{
		//if (game.logger) game.logger('keydown: telling browser to ignore movement key: ' + charCode);
		// eg arrows and pgup pgdn - dont actually scroll web page
		return false; // don't bubbble to other events if we handled it
	}
	else
	{
		if (game.logger) game.logger('keydown: passing key event: ' + charCode);
		return true;
	}
}

function keypress(evt)
{ // handle all keypresses

	// broken: messy and not cross browser.
	// allow all keys..
	return true;

/*
	// this forces browsers to not scroll when people hit the arrow keys etc
	evt = (evt) ? evt : event;
	var charCode = (evt.charCode) ? evt.charCode : ((evt.which) ? evt.which : evt.keyCode);
	var target = (evt.target) ? evt.target : evt.srcElement;

	if ((target) && (target == this.ele_chatinput))
	{
		if (game.logger) game.logger('keypress: allowing all keys for chatinput');
		return true;  // all keys allowed
	}

	// some keys are 'blocked' in terms of default browser chrome
	// block arrow and scroll keys since opera etc. will scroll the body element!
	if (charCode == 122) // F11
	{
		if (game.logger) game.logger('keypress: allowing key ' + charCode);
		return true; // F11 (fullscreen) goes to the browser
	}
//	else
//	{
//		if (game.logger) game.logger('keypress: blocking key ' + charCode);
//		return false;
//	}

	// blockable arrow keys and pgup pgdn
	is_a_movement_key = (
		// arrows
		(charCode == 37) ||
		(charCode == 38) ||
		(charCode == 39) ||
		(charCode == 40) ||
		// pgup pgdn
		(charCode == 33) || // but also shift 1 (hmm....)
		(charCode == 34));
		// numpad arrows - in MOZ these are regular qwerty charCodes!
		//|| (charCode == 98) ||
		//(charCode == 100) ||
		//(charCode == 102) ||
		//(charCode == 104) ||
		// numpad pgup pgdn
		//(charCode == 105) ||
		//(charCode == 99));
	if (is_a_movement_key)
	{
		if (game.logger) game.logger('keypress: blocking movement key: ' + charCode);
		return false; // don't bubbble to other events if we handled it
	}
	else
	{
		if (game.logger) game.logger('keypress: allowing key: ' + charCode);
		return true;
	}
*/
}

/*
function fakesubmitchat()
{ // FAKE non live chat fixme
	//;;; if (debug) addlog('FAKE Submitting chat at ' + Date());
	// screwy but we are trying to avoid submit+timeout server spam
	// if (pendingpolltimerid) clearTimeout(pendingpolltimerid);
	myname = document.getElementById('nameinput').value;
	mybody = document.getElementById('bodyinput').value;
	myxpos = parseInt(document.getElementById('xinput').value);
	myypos = parseInt(document.getElementById('yinput').value);
	mychat = document.getElementById('chattext').value;
	if ((mychat.length) && (mychat != previousmychat))
	{
		//;;; if (debug) addlog('Submitting the string [' + mychat + ']');
		previousmychat = mychat; // avoid spam
		//pendingrequest = new ajax('n='+myname+'&a='+mybody+'&x='+myxpos+'&y='+myypos+'&c='+mychat);
		playerone_chat_bub = bub(myxpos,myypos,myname,mybody,mychat); // NOW!
	}
	//else
	//{
		//;;; if (debug) addlog('No new chat text: ignoring submit.');
	//}
	// clear the typing
	document.getElementById('chattext').value = '';
}
*/

function no_orphans(mytext,numlines)
{ // force text to take this many lines
	mytexta = mytext.split(' ');
	numwords = mytexta.length;
	if (numwords==0) return '';

	// three line special cases for few words:
	if ((numwords==1) && (numlines==3)) return '<br>'+mytexta[0]+'<br>';
	if ((numwords==2) && (numlines==3)) return mytexta[0]+'<br>'+mytexta[1];
	if ((numwords==3) && (numlines==3)) return mytexta[0]+'<br>'+mytexta[1]+'<br>'+mytexta[2];
	if ((numwords==4) && (numlines==3)) return mytexta[0]+'<br>'+mytexta[1]+' '+mytexta[2]+'<br>'+mytexta[3]+'<br>';
	if ((numwords==5) && (numlines==3)) return mytexta[0]+'<br>'+mytexta[1]+' '+mytexta[2]+'<br>'+mytexta[3]+' '+mytexta[4];

	// any other case:
	wordsperline = Math.ceil(numwords / numlines);
	my_formatted_text = '';
	for (word=0; word<numwords; word++)
	{
		my_formatted_text += mytexta[word];

		if (word<numwords-1) // not the last word?
		{
			if ((word%wordsperline)!=wordsperline-1)
				my_formatted_text += ' ';
			else
				my_formatted_text += '<br>\n';
		}
	}
	//addlog('no_orphans string is ['+mytext+'] forcelines='+numlines+' words='+numwords+' perline='+wordsperline+'\n\nNew string:\n'+my_formatted_text);
	return my_formatted_text;
}

function string_contains_fast(haystack,needle)
{ // nosanitychecks
	return (haystack.indexOf(needle) > -1);
}

function string_contains(haystack,needle)
{ // CaSe ignored - SLOW to execute
	if ((!haystack) || (!needle)) return false;
	if ((!haystack.length) || (!needle.length)) return false;
	haystack = haystack.toLowerCase();
	needle = needle.toLowerCase();
	return (haystack.indexOf(needle) > -1);
}

function spawnclonebyid(srcid)
{ // returns a clone of any html element - brand new DOM tree in the html!
	var foundit = document.getElementById(srcid);
	//var theworldelement = document.getElementById('viewport'); // FIXME redun! opti
	if (foundit)
	{
		theclone = document.getElementById(srcid).cloneNode(true); // true = children too
		// make it visible - give it a parent
		if (game.level)
		{
			game.level.appendChild(theclone);
		}
		else
		{
			if (document.body)
				document.body.appendChild(theclone);
			//addlog('ERROR - missing game.level - cloned into page body');
		}
		return theclone;
	}
	else
	{
		//addlog('ERROR - spawnclonebyid missing id ' + srcid);
		return null;
	}
}

function spawnfloortilexy(tilenum,tilexy,statenum)
{ // floors
	floortilecount++;
	//;;; if (debug) addlog('spawnfloortilexy ' + tilenum + ' ' + tilexy + ' state ' + statenum);
	var newclone = spawnclonebyid('floors');
	if (newclone)
	{
		//ms6 newsprite = new sprite(newclone,0,0,0,0,0,128,128); // 128x128 floors.jpg 112k
		newsprite = new sprite(newclone,0,0,0,0,0,Math.round(128*floor_scale),Math.round(128*floor_scale));
		newsprite.zoffset = -500; // under all layers // was -500
		newsprite.changeframe(tilenum);
		if (statenum>0) newsprite.changeframestate(statenum);
		//ms6 newsprite.movexy(tilexy,0); // ,0 is force_z
		newsprite.movexy([Math.round(tilexy[0]*floor_scale)+floor_offset,Math.round(tilexy[1]*floor_scale)+floor_offset],0); // ,0 is force_z
		newsprite.show();
		return newsprite;
	}
}

function spawnwalltilexy(tilenum,tilexy)
{ // walls - using aabb collision
	//if (game.logger) game.logger('spawnwalltilexy ' + tilenum + ' ' + tilexy);
	var newclone = spawnclonebyid('walls');
	if (newclone)
	{
		newsprite = new sprite(newclone,0,0,0,0,0,128,136);
		newsprite.changeframe(tilenum);
		newsprite.zoffset = -64; // middle tweak z-index
		newsprite.radius = 72; // overlap walls so you cant walk through them
		newsprite.movexy(tilexy);
		newsprite.show();
		// circular collide: works great:
		//everything[everythingcount++] = newsprite; // collision detect
		// horizontal aabb instead:
		newsprite.minxy = vectoradd(tilexy,[-72,-24]); // should be 64 and 16 but walker's radii
		newsprite.maxxy = vectoradd(tilexy,[72,24]);
		//aabbs[aabbcount++] = newsprite;
		aabbs[aabbcount] = newsprite; aabbcount++; // less ambiguous
	}
}

function spawn_circle_collider(tilexy,radius)
{
	// this bugs out with null as the highest number...
	everything[everythingcount] = new Object(); // should be a sprite normally
	everything[everythingcount].xy = [tilexy[0]+radius,tilexy[1]+radius];
	everything[everythingcount].radius = radius;
	everythingcount++;
	// cool debug red circle debugcollide
	//;;; if (debug) spawn_debug_red_circle_xy_xy([tilexy[0]-radius,tilexy[1]-radius],[tilexy[0]+radius,tilexy[1]+radius]);
	;;; if (debug) spawn_debug_red_circle_xy_xy([tilexy[0],tilexy[1]],[tilexy[0]+radius*2,tilexy[1]+radius*2]);
}

function spawnpropbyid(tileid,tilexy,radius,zoffs)
{ // props
	if (radius==undefined) radius = props_default_radius;
	;;; if (debug>1) addlog('spawnpropbyid ['+tileid+'] at ' + tilexy);
	var newclone = spawnclonebyid(tileid);
	if (newclone)
	{
		newsprite = new sprite(newclone);
		newsprite.radius = radius;
		if (zoffs!=undefined)
			newsprite.zoffset = zoffs;
		newsprite.movexy([Math.round(tilexy[0]*props_scale)+props_offset,Math.round(tilexy[1]*props_scale)+props_offset]);
		newsprite.show();
		if (radius>0)
		{	// zero means something like grass - you can walk thru it
			everything[everythingcount] = newsprite;
			everythingcount++; // less ambiguous
		}

		// cool debug red circle debugcollide
		;;; if (debug>1) spawn_debug_red_circle_xy_xy([tilexy[0]-radius,tilexy[1]-radius],[tilexy[0]+radius,tilexy[1]+radius]);
	}
}

function spawnproptilexy(tilenum,tilexy)
{ // props
	//if (game.logger) game.logger('spawnproptilexy ' + tilenum + ' ' + tilexy);
	var newclone = spawnclonebyid('props');
	if (newclone)
	{
		newsprite = new sprite(newclone);
		newsprite.changeframe(tilenum);
		newsprite.zoffset = 0; // overlay
		newsprite.movexy(tilexy);
		newsprite.show();
		//everything[everythingcount++] = newsprite; // collision detect
		everything[everythingcount] = newsprite;
		everythingcount++; // less ambiguous
	}
}

function test_clones(howmany,thename)
{ // must be done AFTER page load - unused? fixme?
	//addlog('testing '+howmany+' clones!');
	// test out cloning to create a sprite
	for (clones=0; clones<howmany; clones++)
	{
		var newclone = spawnclonebyid(thename);
		if (newclone)
		{
			// clone works perfectly now we need to grab our images without using id
			// they all are id='_prop' must getChild by pointer to first image inside
			// and the sprite's el is the original source prop not the new one..

			// give unique ids and respawn sprite
			// or respawn sprite using DOM

			//newclone.move = sprite_move; // pointers to source el
			//newclone.move(grid64(parseInt(Math.random()*1024)+4444),grid64(parseInt(Math.random()*1024)+4444));

			// use a POINTER to an html element and don't document.write
			//          new sprite(newclone,numstate,hovernum,clickfunc,animframes,imageurl,imagewidth,imageheight,animloop,framesgosideways
			newsprite = new sprite(newclone); //,0,0,0,0,'',128,64,0,0);

			// random location on the grid
			//newsprite.move(grid64(parseInt(Math.random()*2048)+4444),grid64(parseInt(Math.random()*2048)+4444));

			// where the mouse clicked in the world:
			newsprite.move(grid32(game.mouselevelx),grid32(game.mouselevely));
			newsprite.show();
			//addlog('DEBUG - clone '+clones+' has this innerHTML:\n'+newclone.innerHTML);

		}
		else
		{
			addlog('ERROR - test_clones no clone!');
			return;
		}
	}
	//addlog('done testing '+howmany+' clones!');
}

// CLIENT.JS
// lightweight remote ajax server data handler
// all params are optional - can omit data
// example server response: (movement, chat and 4x4 level spawning)
function c(txt)
{ // chatter respawner eg. "Joe45|norm|1000|2000|Hello."
	txta = txt.split('|');
	if (txta.length > 4)
	{
		// eg. respawnuser(aname,abody,ax,ay,achat)
		respawnuser(txta[0],txta[1],txta[2],txta[3],txta[4]);
	}
	else
	{
		addlog('ERROR: c('+txt+') is not 4 or more fields long.');
	}
}

function t(xy)
{ // teleport playerone to a location
	xta = xy.split('|');
}

function d(uniquename)
{ // destroy a floor or prop eg. "wall0045"?
}

function f(tilenum,xyarray,tileshift) // spawn a floor tile
{
	if (tileshift==null) tileshift=0; // default
	if (typeof xyarray[0] == 'number') // only ONE coord:
	{	// spawn one floor eg f(1,[100,100],3);
		spawnfloortilexy(tilenum,xyarray,tileshift);
	}
	else
	{
		for (loopa in xyarray)
		{
			spawnfloortilexy(tilenum,xyarray[loopa],tileshift);
		}
	}
}

function w(tilenum,xyarray) // spawn a wall
{ // spawn multiple wall tiles eg. "w(0,[[200,100],[4000,4000]]);"
	if (typeof xyarray[0] == 'number') // only one?
	{
		spawnwalltilexy(tilenum,xyarray);
	}
	else
	{
		for (loopa in xyarray) // an array of xy coord arrays
		{
			spawnwalltilexy(tilenum,xyarray[loopa]);
		}
	}
}

function p(tilenum,xyarray) // spawn a prop
{ // spawn multiple prop tiles eg. "p(0,[[200,100],[4000,4000]]);"
	if (typeof xyarray[0] == 'number') // only one?
	{
		spawnproptilexy(tilenum,xyarray);
	}
	else
	{
		for (loopa in xyarray)
		{
			spawnproptilexy(tilenum,xyarray[loopa]);
		}
	}
}

function sid(anid,xyarray,collisionradius)
{ // spawn multiple prop tiles by id eg. "sid('thisdiv',[[200,100],[4000,4000]]);"
	if (typeof xyarray[0] == 'number') // only one?
	{
		spawnpropbyid(anid,xyarray,collisionradius);
	}
	else
	{
		for (loopa in xyarray)
		{
			spawnpropbyid(anid,xyarray[loopa],collisionradius);
		}
	}
}


// CHAT.JS
// Orangeview Ajax Chat Engine ver 3
// (c) 2006 by Christer Kaitila
// Game developer for hire:
// http://www.orangeviewgames.com
//Joe45|m|4000|4000|Hello there.
//Sally|f|4100|4100|This is a test.
function chat_login(thisname,thisavatar)
{
	if (chatdebug) addlog('chat_login '+thisname+' '+thisavatar);
	if (already_logged_in) return;
	chat_user_name = thisname;
	chat_avatar = thisavatar;

	// update animatable sprites NOW on client so you move immediately
	respawnuser(thisname,thisavatar,chat_x,chat_y,''); // hmm spawn playerone

	if (chat_user_name!='')
	{
		//document.getElementById('chattext').value = 'Connected.';
		if (chatdebug) addlog('chatdebug: chat login for ' + thisname + ' avatar ' + thisavatar);
		pollchat(); // start the xy heartbeat
		//submitchat(); // first ajax is called here...
	}
 	already_logged_in = true; // for enter keypress submit
}

function chatkeypress(evt)
{
	evt = (evt) ? evt : event;
	var charCode = (evt.charCode) ? evt.charCode : ((evt.which) ? evt.which : evt.keyCode);
	var target = (evt.target) ? evt.target : evt.srcElement;
	if (chatdebug) addlog('chat keypress: '+charCode);
	if ((charCode == 13) || (charCode == 3)) // ENTER
	{
		if ((chat_user_name!='') && (already_logged_in))
			submitchat();
		else //maybe just login on enter key:
		{
			if (!already_logged_in)
			{
				oop_login();
				return false; // dont submit, treat as button press
			}
		}

	}

	return true;
}

function string_replace(needle,replacement,haystack)
{
	myregexp = new RegExp(needle,'gi');
	return haystack.replace(myregexp,replacement);
}

function trim(thisstr)
{
	return thisstr.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,"");
}

function fulltrim(thisstr)
{
	return thisstr.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,"").replace(/\s+/g," ");
}

function submitchat()
{

	if (no_chat_at_all) return;

	if (!currently_logged_in) return;

	if (!document.getElementById('chattext')) return;

	// screwy but we are trying to avoid submit+timeout server spam
	// if (pendingpolltimerid) clearTimeout(pendingpolltimerid);

	// focus the shockwave
	// if (document.getElementById("gameid")) document.getElementById("gameid").focus();

	if (chat_user_name=='') // fixme: unique-ize
		chat_user_name = document.getElementById('nameinput').value;

	// remember playone's destination for ajax
	chat_x = parseInt(game.playerone.walkxy[0]);
	chat_y = parseInt(game.playerone.walkxy[1]);

	//myname = document.getElementById('nameinput').value;
	myname = chat_user_name;
	//if (chatdebug) addlog('Submitting chat from ' + myname + ' at ' + Date());

	//mybody = document.getElementById('bodyinput').value;
	//myxpos = parseFloat(document.getElementById('xinput').value);
	//myypos = parseFloat(document.getElementById('yinput').value);
	mybody = chat_avatar; // hmm fixme
	mychat = document.getElementById('chattext').value;

	// update the bubba RIGHT AWAY locally
	/*
	if (game.playerone)
	{
		//addlog('yes!');
		game.playerone.changechat(mychat);
	}
	*/

	if ((mychat=='') || (mychat==' '))
	{
		//addlog('cls chat');
		mychat='.'; // silent blank - clears chat!
	}

	// blank is okay - ALWAYS SUBMIT
	//if ((mychat.length)) // && (mychat != previousmychat))
	if (true)
	{
		//if (chatdebug) addlog('Submitting the string [' + mychat + ']');
		previousmychat = mychat; // avoid spam?
		//ajaxurl = 'm='+chat_session_id+'&

		if (!window.myname) addlog('ERROR - missing myname...');

		ajaxurl = 'n='+myname+'&a='+mybody+'&c='+mychat+'&x='+chat_x+'&y='+chat_y;
		//if (chatdebug) addlog('chatdebug info: Submitting [' + ajaxurl + ']');
		pendingrequest = new ajax(ajaxurl);
	}
	else
	{
		if (chatdebug) addlog('chatdebug: No new chat text: ignore submit? Send pos??');
		//ajaxurl = 'n='+myname+'&a='+mybody+'&x='+myxpos+'&y='+myypos+'&c='';
		//if (chatdebug) addlog('Submitting [' + ajaxurl + ']');
		//pendingrequest = new ajax(ajaxurl);
	}

	// clear the typing
	document.getElementById('chattext').value = '';

	// maybe start polling?
	if (window.notpolling)
	{
		;;; if (debug) addlog('chatdebug: starting polling from submitchat');
		pollchat();
	}

}

function respawnuser(aname,abody,ax,ay,achat)
{	// chat - either update or create an avatar
	if (chatdebug) addlog('respawnuser '+aname+','+abody+','+ax+','+ay+','+achat);

	// sanity:
	if (isNaN(ax) || isNaN(ay))
	{
		addlog('respawnuser got a NaN: '+aname+','+abody+','+ax+','+ay+','+achat);
	}

	ax = parseInt(ax);
	ay = parseInt(ay);
	var message_xy = [ax,ay];

	if (chatters[aname]==undefined)
	{
		if (chatdebug) addlog('spawning new user ' + aname + ' body ' + abody + ' at ' + ax + ','+ ay);

		if (aname==global_user_name) // this is player one! fixme: server can change: eg badwords and symbols
		{
			;;; if (debug) addlog(aname +' IS playerone: ' + global_user_name);
			newsprite = game.playerone;
		}
		else
		{
			;;; if (debug) addlog(aname +' has joined the game!');
			var newclone = spawnclonebyid('avatar'); // clone html divs
			if (newclone)
			{	//sprite(newclone,numstate,hovernum,clickfunc,animframes,imageurl,imagewidth,imageheight,animloop,framesgosideways
				//newsprite = new sprite(newclone,0,0,0,5,'',32,70,2,0); // ping pong walk 5 frame

				newsprite = new sprite(newclone,0,0,0,walkcycle_loop_numframes,'',AVATARW,AVATARH,avatar_walkcycle_mode,0); // ping pong walk 5 frame

			}
			// new user needs to inform the friendlist of online status
			// redun below
			// if (window.friend_online) friend_online(aname,true);
		}

		if (newsprite)
		{
			newsprite.move(ax-1,ay); // the -1 is so you walk, get PUSHED (aabb/etc) and stop.
			newsprite.walkxy = [ax-1,ay]; // same destination
			//;;; if (debug) newsprite.changenametag(aname+'('+ax+','+ay+')'); else
			newsprite.title = aname; // save in <a> title for CLICK friendlist
			newsprite.changenametag(aname);
			newsprite.changechat(achat);
			newsprite.look = abody;

			newsprite.show(); // flickers but might help kickstart IE6 gif anim

			if (window.highdef_avatars)
			{
				var newsheet = CACHEURL + "sheet_" + abody + ".gif";
				;;; if (debug) addlog('DEBUG: new avatar changing sheet to ' + newsheet);
				newsprite.changesheet(newsheet);
			}
			else // normal gif sheet
			{
				newsprite.changeframestate(body_to_state(abody)); // switch to the right avatar (state)
			}

			chattersprites[aname] = newsprite; // remember for chat
			// remember for anim
			game.fx[game.fx_count] = newsprite;
			game.fx_count++; // less ambiguous
			// remember for collision
			everybody[everybodycount] = newsprite;
			everybodycount++; // less ambiguous
			newsprite.walk_towards_xy(message_xy); // start and stop walking
		}

		chatters_count++;
	}
	else	// re-use old avatar: walk towards new location
	{
		//if (chatdebug) addlog('chatdebug='+chatdebug+' updating ' + aname + ' at ' + ax + ','+ ay);
		newsprite=chattersprites[aname];

		// NEW destination?
		if (!vector_equals(message_xy,newsprite.walkxy))
		{
			//newsprite.move(ax,ay);
			//newsprite.walkxy = [ax,ay]; // remember destination
			// client agree: if we failed to get to previous location, warp there
			// so that we start walking from the correct place
			if (force_final_destination)
			{
				if (newsprite!=game.playerone)
				{
					if ((newsprite.xy[0]!=newsprite.walkxy[0]) || (newsprite.xy[1]!=newsprite.walkxy[1]))
					{
						;;; if (debug>1) addlog(aname +' failed to reach previous walkxy ('+newsprite.walkxy+'), warping first...');
						newsprite.movexy(newsprite.walkxy); // force location
					}
				}
				//else addlog('dont force playerone around!');
			}

			if (newsprite!=game.playerone)
			{
				;;; if (debug>1) addlog(aname +' is now walking towards '+newsprite.walkxy+' starting from '+newsprite.xy);
				newsprite.walk_towards_xy(message_xy);
			}
			// else ignore movements from server for player one
		}
		newsprite.changenametag(aname); 	// not required?
		newsprite.changechat(achat);		// not required?
	}

	// timestamp the user (could use game_timestamp())
	if (newsprite) newsprite.time_to_destroy = game.now_ms + chatter_max_idle_time;
	if (newsprite==game.playerone) newsprite.time_to_destroy = 0; // NEVER destroy player one

	chatters[aname]=[ax,ay,achat]; // store all we need
	updateserverstatshtml(); // name list on top left, chatter count

	// maybe change online/offline friendlist status
	if (window.friend_online) friend_online(aname,true);

}

var stats_element = 0;
function updateserverstatshtml()
{
	//addlog('new server stats html');

	var allnames = "";

	if (display_player_list)
	{
		for (onename in chatters)
		{
			// update the server stats name list
			allnames += "<a title='Click here to meet "+onename+" at "+chatters[onename][0]+","+chatters[onename][1]+"' class='subtle' href='javascript:global_ignore_the_next_click=1;warpto("+chatters[onename][0]+","+chatters[onename][1]+");'>"+onename+"</a>";
			// also include their last chat?
			//if (chatters[onename][2]!=null) { allnames += ' <i>'+chatters[onename][2]+'</i>'; }
			allnames += '<br>';
		}
	}

	if (!stats_element) stats_element = document.getElementById('serverstats');
	stats_element.innerHTML = engine_version + "<br>" + /*"1024 users<br>" +*/ chatters_count + " online<br><br><br>" + allnames;
}

function pollchat()
{
	if (no_chat_at_all) return;
	//if (chatdebug) addlog('chatdebug MODE. Polling at ' + Date());

	// if there is a pending ajax request, dont bother sending a poll at all!
	if (!ajax_pending) // optimization
	{
		pendingrequest = new ajax('n='+chat_user_name+'&a='+chat_avatar+'&x='+chat_x+'&y='+chat_y);
	}

	notpolling = false;
	pendingpolltimerid = setTimeout('pollchat()',chat_refresh_ms+parseInt(Math.random()*1000)); // 5555 to 6555 ms
}

function ajax_onreadystatechange()
{	// bug - no THIS so no overlapping queries...

	if (ajax_global_previous_xmlhttp_object==null)
	{
		addlog('ERROR ajax_onreadystatechange noticed that ajax_global_previous_xmlhttp_object is null.');
		return;
	}

	//addlog('debug xmlhttp_onreadystatechange: ' + ajax_global_previous_xmlhttp_object.readyState + ' complete:' + ajax_global_previous_xmlhttp_object_completed);
	if (ajax_global_previous_xmlhttp_object.readyState == 4 && !ajax_global_previous_xmlhttp_object_completed)
	{
		;;; if (debug>1) addlog('DEBUG: ajax completed:\n'+ajax_global_previous_xmlhttp_object.responseText);
		ajax_global_previous_xmlhttp_object_completed = true; // buggy browsers fire this twice
		run_json(ajax_global_previous_xmlhttp_object.responseText);
		ajax_pending = false; // allow another
	}
}
// fixme: bug on overlapping requests...
var ajax_pending = false;
var ajax_global_previous_xmlhttp_object = null;
var ajax_global_previous_xmlhttp_object_completed = false;

function ajax_connect(sURL, sVars)
{
	;;; if (debug>1) addlog('ajax_connect:\n'+sURL+sVars);

	//if (chatdebug) addlog('Ajax POST ' + sVars.length + ' bytes: ' + sURL + sVars);
	if (!this.xmlhttp)
	{
		addlog('Ajax error: ajax_connect missing xmlhttp');
		return false;
	}

	ajax_pending = true; // don't allow overlap
	ajax_global_previous_xmlhttp_object = this.xmlhttp;
	ajax_global_previous_xmlhttp_object_completed = false;

	try
	{	// force no caching on buggy browsers
		// with timestamp in url: + game.now_ms
		if (sURL!='') sVars += '&r=' + parseInt(Math.random() * 100000); // unique URL every time
		//if (chatdebug) sVars = ''; // always the same query if local

		// url encoded kills the sURL! we get 404 errors from server.
		// sVars = encodeURIComponent(sVars);

		ajax_request_count++;
		this.xmlhttp.open('GET', sURL+sVars, true);
		this.xmlhttp.onreadystatechange = ajax_onreadystatechange;

		this.xmlhttp.send('');
	}
	catch(z) { return false; }
	return true;
}

function run_json(runme) // multiple chats are lost? not anymore due to waits...
{
	;;; if (debug>2) addlog('debug run_json:\n'+runme);

	if (!runme) return;

	if (window.chat_first_submit_ever)
	{
		window.chat_first_submit_ever = false;
		//;;; if (debug) addlog('ajax.run_json: Successfully logged into the chat server!');
		//\nresponse was:'+this.xmlhttp.responseText); // chat_user_name
	}

	if (runme.length > 0)
	{
		//;;; if (debug) addlog('Ajax GOT '+this.responseText.length+' bytes.'); //:'+this.xmlhttp.responseText);

		try
		{
			eval(runme); // remote scripting
		}
		catch(thiserror)
		{
			//;;; if (debug) addlog('ERROR: Ajax eval exception: ' + e);
			if (window.ajaxerror) // half refeshed page is possible
				ajaxerror('AJAX ERROR: ' + /* thiserror + */ '\n\nThe code was:\n' + runme); // fixme
		}

		if (window.notpolling) // during reload this can sometimes not exist yet
		{
			;;; if (debug) addlog('chatdebug: starting polling from run_json');
			// then start in a few seconds
			pendingpolltimerid = setTimeout('pollchat()',chat_refresh_ms+parseInt(Math.random()*1000)); // start listening
		}
	}
}


function ajax(aquery,urlspecified) // Simple AJAX RPC Class Constructor
{
	// THIS IS A ONE-LINER AJAX CLASS - GOTTA LOVE IT!
	// this class is used for a multithreaded remote scripting
	// game engine - the server sends javascript that the client runs.
	//
	// Example:
	//
	// var ajax = new ajax('hello');
	//
	// no other code is required.

	// too spammy during debug to show chat polling, only show non chat urls
	if (urlspecified)
	{
		;;; if (debug) addlog('DEBUG: NON-CHAT ajax query url:'+urlspecified);
		;;; if (debug) addlog('DEBUG: '+aquery);
	}

	// only allow a SINGLE query at a time
	if (ajax_pending)
	{
		if (urlspecified)
			qcode = "nextajax = new ajax('"+aquery+"','"+urlspecified+"');";
		else
			qcode = "nextajax = new ajax('"+aquery+"');";

		;;; if (debug) addlog('DEBUG: ajax_pending. Waiting...');

		setTimeout(qcode,1666);

		return null; // hmm... fixme?
	}

	this.connect = ajax_connect;

	// Inits
	this.serverurl = default_server_url;
	this.xmlhttp = false;

	// initialize
	try { this.xmlhttp = new XMLHttpRequest(); }
	// handle buggy browsers
	catch (e) { try { this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); }
	catch (e) { try { this.xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); }
	catch (e) { this.xmlhttp = false; }}}
	if (!this.xmlhttp)
	{
		alert('ERROR: Your browser does not support AJAX.\nPlease upgrade your web browser.');
		return null; // no ajax support in this browser
	}

	if (aquery) // run NOW and json the server response
	{
		if (urlspecified) this.serverurl = urlspecified;
		this.connect(this.serverurl,aquery);
	}

	return this;
}

/*
function process_shockwave_data(thisdata) // unused?
{ // unused fixme
	addlog('Shockwave:' + thisdata);
	thisdat = thisdata.split('=');
	if (thisdat.length==2) // eg "something=55"
	{
		chat_session_id = thisdat[1];
	}
}
*/






/*
if (chatdebug || debug) // either makes this visible
{  // make a debug chatlog visible
	if (display_chatlog)
	{
		if (document.getElementById('chatlog'))
			document.getElementById('chatlog').style.display = 'block';
	}
	if (chatdebug) addlog('Chat debug mode.  Not connected to server.');
	;;; if (debug) addlog('Engine debug mode.');
}
*/





// immediately start listening to keyboard
// already set in game.init:
//document.onkeypress = chatkeypress;
// end CHAT.JS

// MOVEMENT.JS
// Christer's New BLOB Movement Test
// arrays of sprites
var users = Array();
var walls = Array();
var props = Array();
// all world objects
var world = [users,walls,props];
// cangetblocked parameter help:
//     if true, you get stuck in dead ends and only slide a bit (solid mode)
//     if false, you always get there and only TRY to avoid collisions (ghost mode)

// crappy buggy function..
/*
function keepmovingspritetowards(x,y,thissprite,cangetblocked,movespeed,nocollisionsatall)
{
	;;; if (debug) addlog('keepmovingspritetowards x='+x+' y='+y+' s='+thissprite+' b='+cangetblocked+' m='+movespeed+' n='+nocollisionsatall);

	// follow mouse blindly:
	// thissprite.movexy([x,y]);
	// return;
	// optimization: skip if already there
	if ((x==thissprite.xy[0]) && (y==thissprite.xy[1]))
	{
		thissprite.walking = false;
		thissprite.anim_delta = 0; // no anim while standing
		return;
	}
	// move one pixel at a time!
	// fixme: movespeed = min(movespeed,distance);
	if (x<thissprite.xy[0]) thissprite.xy[0]-=movespeed; // fixme: proportional spds like viewport
	if (x>thissprite.xy[0]) thissprite.xy[0]+=movespeed;
	if (y<thissprite.xy[1]) thissprite.xy[1]-=movespeed;
	if (y>thissprite.xy[1]) thissprite.xy[1]+=movespeed;
	//var pushedxy = [x,y]; // move fullspeed to mouse
	// always gets there: thissprite.xy is always at 'desired' xy

	if (nocollisionsatall)
	{	// fast out - works great
		thissprite.movexy(thissprite.xy);
		return;
	}

	if (!cangetblocked)
		var pushedxy = thissprite.xy; // COPY vector
	// for multiple pushes, we need to run this exponential times..
	// fixme: but if the pushers are SORTED by distance..
	// or just make sure pushers are far apart..
	// but luckily in block mode the effects are cumulative
	// over multiple frames to be nearly PERFECT!

	// avoid everybody
	for (i=0; i<everybodycount; i++)
	{
		if (!cangetblocked)	// ghost:
		{
			// so thissprite.xy is always at 'desired' xy
			pushedxy = vectorpushaway(pushedxy,everybody[i].xy,pushradius);
		}
		else	// blockable:
		{
			// overwrite current position for pure WALL blocking
			// (and getting stuck is therefore possible)
			thissprite.xy = vectorpushaway(thissprite.xy,everybody[i].xy,pushradius);
		}
	}
	if (!cangetblocked)
		thissprite.movexy(pushedxy);
	else
		thissprite.movexy(thissprite.xy);

	// circle blobs unused!
	for (i=0; i<pushercount; i++)
	{
		if (!cangetblocked)	// ghost:
		{
			// so thissprite.xy is always at 'desired' xy
			pushedxy = vectorpushaway(pushedxy,pushers[i].xy,pushradius);
		}
		else	// blockable:
		{
			// overwrite current position for pure WALL blocking
			// (and getting stuck is therefore possible)
			thissprite.xy = vectorpushaway(thissprite.xy,pushers[i].xy,pushradius);
		}
	}
	if (!cangetblocked)
		thissprite.movexy(pushedxy);
	else
		thissprite.movexy(thissprite.xy);

	// bbox test are possible as well! works! UNUSED
	for (i=0; i<aabbcount; i++)
	{
		if (!cangetblocked)	// ghost:
		{
			// so thissprite.xy is always at 'desired' xy
			// bboxes too
			pushedxy = vectorpushaway_aabb(pushedxy,aabbs[i].xymin,aabbs[i].xymax);
		}
		else	// blockable:
		{
			// overwrite current position for pure WALL blocking
			// bboxes too
			thissprite.xy = vectorpushaway_aabb(thissprite.xy,aabbs[i].xymin,aabbs[i].xymax);
		}
	}
	if (!cangetblocked)
		thissprite.movexy(pushedxy);
	else
		thissprite.movexy(thissprite.xy);
}
*/
var circlepushxy_count = 0;	// how many collision detections do we need to do?
var circlepushxy_vec = Array(); // of vectors
var circlepushxy_rad = Array(); // of floats
// hardcoded: bench, fountain1, fountain2, button
//circlepushxy_vec = [[4900,4575],[4325,4715],[4380,4715],[4935,4915]];
//circlepushxy_rad = [64,64,64,64];
//circlepushxy_count = 4;

function avoidallobjects(desiredxy) // smooth pathfinding around objects!
{
	//for (i=0; i < circlepushxy_count; i++)
	for (i in circlepushxy_vec)
	{
		desiredxy = vectorpushaway(desiredxy,circlepushxy_vec[i],circlepushxy_rad[i]);
	}
	return desiredxy;
}

function walkaroundeverybody(desiredxy) // smooth pathfinding around objects!
{
	if (!window.everybody) return null;
	//for (i=0; i < everybody.length; i++)
	for (i in everybody)
	{
		if (everybody[i]) // exists and not null?
		{
			desiredxy = vectorpushaway(desiredxy,everybody[i].xy,32); // hardcoded avatar radius
		}
	}
	return desiredxy;
}

function walkaroundeverything(desiredxy) // smooth pathfinding around objects!
{
	if (!window.everything) return null;
	//for (i=0; i < everything.length; i++)
	for (i in everything)
	{
		if (everything[i]) // exists and not null?
		{
			// hardcoded offsets: works with old props not blobs
			// propfootpos = [everything[i].xy[0]+everything[i].w2,everything[i].xy[1]+everything[i].h-(everything[i].radius)];
			propfootpos = [everything[i].xy[0]-64,everything[i].xy[1]-64]; // wall upwards.... hack fixme
			// assumes radius 64...	everything[i].radius
			desiredxy = vectorpushaway(desiredxy,propfootpos,32);
		}
		//else addlog('missing everything['+i+' of '+everything.length+'] is = ' + everything[i]);
		// is nothing
	}
	return desiredxy;
}

/*
function movement_test() // unused test function
{
	// after about 100, the cpu load is significant at 500 it is unplayable.
	// follows the mouse, gets blocked
	walker = new sprite(spawnclonebyid('walkme'));
	walker.xy = [500,500];
	walker.movexy(walker.xy);
	// follows the mouse, gets blocks but always arrives (ghost)
	flyer = new sprite(spawnclonebyid('flyme'));
	flyer.xy = [100,100];
	flyer.movexy(flyer.xy);
	// a circular wall clone
	for (i=0; i < howmanypushers; i++)
	{
		pushers[pushercount] = new sprite(spawnclonebyid('pushme')); // clone!
		pushers[pushercount].xy = [200+parseInt(Math.random()*1500),parseInt(100+Math.random()*1200)];
		pushers[pushercount].movexy(pushers[pushercount].xy);
		pushercount++;
	}
	// hardcoded locations for testing:
	// bench: 4900,4575
	pushers[pushercount] = new sprite(spawnclonebyid('pushme'));
	pushers[pushercount].xy = [4900,4575];
	pushers[pushercount].movexy(pushers[pushercount].xy);
	pushercount++;
	// fountain: 4325,4715 and 4380,4715
	pushers[pushercount] = new sprite(spawnclonebyid('pushme'));
	pushers[pushercount].xy = [4325,4715];
	pushers[pushercount].movexy(pushers[pushercount].xy);
	pushercount++;
	pushers[pushercount] = new sprite(spawnclonebyid('pushme'));
	pushers[pushercount].xy = [4380,4715];
	pushers[pushercount].movexy(pushers[pushercount].xy);
	pushercount++;
	// button: 4935,4915
	pushers[pushercount] = new sprite(spawnclonebyid('pushme'));
	pushers[pushercount].xy = [4935,4915];
	pushers[pushercount].movexy(pushers[pushercount].xy);
	pushercount++;
	// a rectangular wall
	var onerad = pushradius/2;
	for (i=0; i < howmanyaabbs; i++)
	{
		aabbs[aabbcount] = new sprite(spawnclonebyid('aabb')); // clone!
		aabbs[aabbcount].xy = [400+parseInt(Math.random()*2000),parseInt(400+Math.random()*1500)];
		// remember larger rectangle to save fps in ther loops
		aabbs[aabbcount].xymin = [aabbs[aabbcount].xy[0]-onerad,aabbs[aabbcount].xy[1]-onerad];
		aabbs[aabbcount].xymax = [400+aabbs[aabbcount].xy[0]+onerad,100+aabbs[aabbcount].xy[1]+onerad];
		aabbs[aabbcount].movexy(aabbs[aabbcount].xy);
		aabbcount++;
	}
}
function movetowardsmouse() // unused test timer function
{
	if (window.game)
	{
		keepmovingspritetowards(game.mousex, //+ game.viewport_current_px,
					game.mousey, //+ game.viewport_current_py,
					walker,true,2);
		keepmovingspritetowards(game.mousex, //+ game.viewport_current_px,
					game.mousey, //+ game.viewport_current_py,
					flyer,false,1);
	}
	setTimeout('movetowardsmouse()',1);
}
*/
// movement_test(); // spawn a bunch of blockers now
// movetowardsmouse(); // TEST BLOBS NOW!
// end MOVEMENT.JS

// VECTORMATH.JS
// 2D Mini Vector Math by Christer Kaitila (c) 2007
// intended to be ultra small and run fast in javascript
// no error checking or abstract class constructors
// this is a lightweight 2d vector tool that uses
// simple arrays for fast code and small download size
// uses very few temporary variables for better gc and spd
// allows simplistic code such as
// answer = vectornalmalize([999,555]);
// instead of this
// temp = new vectorclass(999,555);
// answer = temp.normalize();
// cw rotational angles as cardinal directions:
var anglenorth = 0;
var anglenortheast = 45;
var angleeast = 90;
var anglesoutheast = 135;
var anglesouth = 180;
var anglesouthwest = 225;
var anglewest = 270;
var anglenorthwest = 315;
// unit vectors for angular movement
var vectornorth = vectorangle(anglenorth);
var vectornortheast = vectorangle(anglenortheast);
var vectoreast = vectorangle(angleeast);
var vectorsoutheast = vectorangle(anglesoutheast);
var vectorsouth = vectorangle(anglesouth);
var vectorsouthwest = vectorangle(anglesouthwest);
var vectorwest = vectorangle(anglewest);
var vectornorthwest = vectorangle(anglenorthwest);

function vectorstring(srcxy) // vector to string e.g. "(3,7)"
{	// rarely used - remove? fixme.
	return '('+srcxy[0]+','+srcxy[1]+')';
}

function vectorlength(xy) // returns the size of one vector
{
	return Math.sqrt((xy[0]*xy[0])+(xy[1]*xy[1]));
}

function vectornormal(xy) // returns the size 1.0 vector
{
	var xylength = vectorlength(xy);
	return [(xy[0]/xylength) , (xy[1]/xylength)];
}

// extra vector rotate and distace code unfinished...
// A point <x,y> can be rotated around the origin <0,0>
// by running it through the following equations
// to get the new point <x',y'> :
// x' = cos(theta)*x - sin(theta)*y
// y' = sin(theta)*x + cos(theta)*y
// where theta is the angle by which to rotate the point.
//
//function vectordistance(fromxy,toxy) // returns the distance between two vectors
//
//{
//	//dx = Ax-Bx
//	//dy = Ay-By
//	//distance = sqrt(dx*dx + dy*dy)
//	var xdist = fromxy[0]-toxy[0];
//	var ydist = fromxy[1]-toxy[1];
//	//var xytotal = Math.abs(xdist)+Math.abs(ydist);
//	//var xratio = Math.abs(xdist/xytotal);
//	//var yratio = Math.abs(ydist/xytotal);
//	// pythagoras root of the square of both sides for length of other side
//	return Math.sqrt((xdist*xdist)+(ydist*ydist));
//}

function vectorxdistance(fromxy,toxy)
{	// returns the x distance between two vectors
	return (fromxy[0]-toxy[0]);
}

function vectorydistance(fromxy,toxy)
{	// returns the y distance between two vectors
	return (fromxy[1]-toxy[1]);
}

function vectordistance(fromxy,toxy)
{	// returns the distance between two vectors
	var xdist = (fromxy[0]-toxy[0]);
	var ydist = (fromxy[1]-toxy[1]);
	return Math.sqrt((xdist*xdist)+(ydist*ydist));
}

function vectorfastsquareddistance(fromxy,toxy)
{	// returns the distance^2 between two vectors - faster code - no square root used
	var xdist = (fromxy[0]-toxy[0]);
	var ydist = (fromxy[1]-toxy[1]);
	return (xdist*xdist)+(ydist*ydist);
}

function vectordirection(fromxy,toxy) // a normalized vector of size 1 pointing towards dest - BUGGY
{	// fixme: buggy? only positive?
	//;;; if (debug) addlog('vectordirection('+fromxy+','+toxy+')='+[(fromxy[0]*toxy[0])/(fromxy[1]*toxy[1]),(fromxy[1]*toxy[1])/(fromxy[0]*toxy[0])]);
	return 	[(fromxy[0]*toxy[0])/(fromxy[1]*toxy[1]),(fromxy[1]*toxy[1])/(fromxy[0]*toxy[0])];
}

function vectorscale(srcxy,scale)
{ // multiply a vector by a number
	//;;; if (debug) addlog('vectorscale('+srcxy+','+scale+')='+[srcxy[0]*scale,srcxy[1]*scale]);
	return [srcxy[0]*scale,srcxy[1]*scale];
}

function vectoradd(onexy,twoxy)
{ // add two vectors
	return [onexy[0]+twoxy[0],onexy[1]+twoxy[1]];
}

function vector_equals(onexy,twoxy)
{	// return true or false
	return ((onexy[0]==twoxy[0]) && (onexy[1]==twoxy[1]));
}

function vectorsubtract(onexy,twoxy) // subtract two vectors
{
	//;;; if (debug) addlog('vectorsubtract('+onexy+','+twoxy+')='+[onexy[0]-twoxy[0],onexy[1]-twoxy[1]]);
	return [onexy[0]-twoxy[0],onexy[1]-twoxy[1]];
}

function vectormultiply(onexy,twoxy) // multiply two vectors
{
	return [onexy[0]*twoxy[0],onexy[1]*twoxy[1]];
}

function vectordotproduct(axy,bxy) // useful to determine angle between the two vectors
{
    return ((axy[0] * bxy[0]) + (axy[1] * bxy[1]));
}

function vectorcrossproduct(axy,vec) // cross product of a vector
{	// untested I think this might be buggy fixme
	return [(axy.y*vec.x)-(axy.x*vec.y),(axy.x*vec.y)-(axy.y*vec.x)];
}

function vectorangle(theangle,thepoint) // rotate vector in any direction
{
	if (!theangle) return thepoint; // no change
	if (thepoint==null) thepoint = [1,1]; // default to unit size
	var xi = Math.cos(theangle)*thepoint[0] - Math.sin(theangle)*thepoint[1];
	var yi = Math.sin(theangle)*thepoint[0] - Math.cos(theangle)*thepoint[1];
	return [xi,yi];
}

function vectorleft(srcxy) // rotate a unit vector 90 deg to the left
{
	return vectorangle(srcxy,-90); // untested
}

function vectorright(srcxy) // rotate a unit vector 90 deg to the right
{
	return vectorangle(srcxy,90); // untested
}

function vectorback(srcxy) // rotate a unit vector 180 deg
{
	return vectorangle(srcxy,180); // untested
}

function vectorisinside(walkxy,blockxy,blockradius)
{
	return (vectordistance(walkxy,blockxy) <= blockradius);
}

// my 5th or 6th pathfinding algorithm
// this time simplicity of code is the goal
// this function takes a point and pushes it away from another
// used for simplistic pathfinding and movement of game sprites
// I remember the days of grids, huge sparse arrays, tons of vars
// and other failed movement code - so many versions...
// this one uses circular distances to push away the avatar
// for smoother, more 'rounded' movement, with less programming
var buggy = false;
function vectorpushaway(walkxy,blockxy,blockradius)
{	// returns a new location outside the block if required

	if (isNaN(walkxy[0]))
	{
		addlog('ERROR: vectorpushaway got a NaN');
		if (!buggy) { addlog('Damn NaNs'); buggy = true; }
		//return [4444,4444]; // inject non error location...
	}

	if (!blockradius) blockradius = 32; // default sanity fixme use var
	var howfar = vectordistance(walkxy,blockxy);
	//var debugthis = false; // spammy collision debug.
	//;;; if (debugthis) addlog('vectorpushaway walk:'+walkxy+' block:'+blockxy+' radius:'+blockradius+' dist:'+howfar);
	if (howfar<1)	// if 'exactly' overlapping fixme? <1? <0.1? ==0?
	{
		//if (debugthis) addlog('vectorpushaway got a distance of nearly zero!');//  Pushing right');
		return walkxy;
		//return [walkxy[0]+blockradius,walkxy[1]]; // simply move RIGHT - forever?! loops!
		// idea: remember previous location so you don't jump around?
		// OR nudge RIGHT one to avoid div 0 and run through below?
		// howfar=1; walkxy[0]++;
		// OR best for multiplayers standing on top of each other:
		// randomly scatter around like a conversation circle?
		// or deterministic positions for ontop players for games?
		// OR USE CLUSTER Array:
		// groups of up to 8 avatars standing on the same spot
		// form a circle of blockradius radius around the first guy:
		// var avatar_cluster_xy=[[0,0],angleeast,anglewest,anglenortheast,anglesouthwest,anglenorthwest,anglesoutheast,anglenorth,anglesouth];
		// what if more than 8? random circle scatter? or FARTHER AWAY?
	}
	if (howfar < blockradius)
	{
		//;;; if (debug) addlog('colliding with a circle');
		// bug: if exactly overlapping, a bug.. NAN? div by 0?
		// what to do at origin: push UPwards?
		var overlap = blockradius - howfar;
		// point outside my sphere and push outward
		var pushvec = vectorsubtract(blockxy,walkxy); // vectordirection(blockxy,walkxy); // already norm
		var pushdir = vectornormal(pushvec);
		var nudgexy = vectorscale(pushdir,overlap);
		var finalxy = vectorsubtract(walkxy,nudgexy); // shove away
		//;;; if (debugthis) addlog('<b>Inside:</b>' + '<br>walkxy=' + walkxy + '<br>blockxy=' + blockxy + '<br>blockradius=' + blockradius + '<br>howfar=' + howfar + '<br>overlap=' + overlap + '<br>pushvec=' + pushdir + '<br>pushdir=' + pushdir + '<br>nudgexy=' + nudgexy + '<br>finalxy=' + finalxy);
		return finalxy;
	}
	// default:
	//;;; if (debugthis) addlog('<b>Outside:</b>' + '<br>walkxy=' + walkxy + '<br>blockxy=' + blockxy + '<br>blockradius=' + blockradius + '<br>howfar=' + howfar);
	return walkxy; // not too close ( > blockradius ) DO NOTHING
}

// this function runs surprisingly slow - use globals?
// axis aligned bounding boxes push a circle around:
//var debugaabb = true;
function vectorpushaway_aabb(walkxy,minxy,maxxy)
{	// returns a new location outside the block if required
	var closestdist = 99999999;
	var checkdist = 0;
	// are we INSIDE the box?
	if ((walkxy[0] > minxy[0]) && (walkxy[1] > minxy[1]) &&
		(walkxy[0] < maxxy[0]) && (walkxy[1] < maxxy[1]))
	{
		//;;; if (debug) addlog('colliding with an aabb');
		// force outside to closest edge
		// minx?
		checkdist = (walkxy[0]-minxy[0]);
		if (checkdist < closestdist) { closestedge = 1; closestdist = checkdist; }
		// miny?
		checkdist = (walkxy[1]-minxy[1]);
		if (checkdist < closestdist) { closestedge = 2; closestdist = checkdist; }
		// maxx?
		checkdist = (maxxy[0]-walkxy[0]);
		if (checkdist < closestdist) { closestedge = 3; closestdist = checkdist; }
		// maxy?
		checkdist = (maxxy[1]-walkxy[1]);
		if (checkdist < closestdist) { closestedge = 4; closestdist = checkdist; }
		if (closestedge==1) walkxy[0] = minxy[0]; // minx
		else if (closestedge==2) walkxy[1] = minxy[1]; // miny
		else if (closestedge==3) walkxy[0] = maxxy[0]; // maxx
		else if (closestedge==4) walkxy[1] = maxxy[1]; // maxy
		//;;; if (debugaabb) { document.getElementById('pushdebug').innerHTML = '<b>AABB INSIDE:</b>' + '<br>oldxy=' + oldxystr + '<br>walkxy=' + vectorstring(walkxy) + '<br>minxy=' + vectorstring(minxy) + '<br>maxxy=' + vectorstring(maxxy) + '<br>closestedge=' + closestedge; debugaabb = false; } // only output the FIRST TIME
		return walkxy;
	}
	// default:
	return walkxy; // not inside: DO NOTHING
}
// end VECTORMATH.JS

// SPRITE.JS
// if myid is a string, it document.writes some fresh HTML - bad - unused!
// if myid is an HTML element, it uses existing pointers only!
function sprite_to_string()
{
	return "[sprite:"+this.name+"@"+this.xy+"]";
}

function sprite(myid,numstate,hovernum,clickfunc,animframes,imageurl,imagewidth,imageheight,animloop,framesgosideways) // object class constructor
{
	if (!window.game)
	{
		addlog('ERROR - No game engine object! Cannot spawn a new sprite.');
		addlog('('+myid+','+numstate+','+hovernum+','+clickfunc+','+animframes+','+imageurl+','+imagewidth+','+imageheight+','+animloop+','+framesgosideways+')');
		return null;
	}
	// override the tostring for nice debug output
	this.toString = sprite_to_string;
	this.fade_chat_after_timestamp = 0;
	var use_this_element = 0;
	var use_this_image = 0;
	if (typeof myid != 'string') // must be a pointer to an html element in ram
	{
		//if (game.logger) game.logger('grabbing a sprite via pointer!');
		use_this_element = myid; // copy pointer and force myid to be a string again
		use_this_element.id += ''+uniquenumber; // append to cloned non unique id
		myid = use_this_element.id; // now treat normally now that it is unique
		mychildren = use_this_element.getElementsByTagName("img");
		if (mychildren[0]) // found an img inside
		{
			use_this_image = mychildren[0];
			use_this_image.id += ''+uniquenumber; // append to cloned non unique id
			//if (game.logger) game.logger('Found a divimg sprite ok.');
		}
		else //  assume 2007 layered blob divs - 'avatar'
		{
			mychildren = use_this_element.getElementsByTagName("div");
			if (mychildren[1]) // > 2 layers, bg then sprite sheet
			{
				mysheets = mychildren[1].getElementsByTagName("div");	// sub div
				if (mysheets[0])
				{
					use_this_image = mysheets[0];
					use_this_image.id += ''+uniquenumber; // append to cloned non unique id
					//if (game.logger) game.logger('Found a multi-layer avatar div ok.');
				}
				//else
				//{
				//	// allow this: for trees, hardcoded div sprites.. etc
				//	//if (game.logger) game.logger('ERROR - cannot find avatar sprite sheet.');
				//}
				// grab specific layers
				this.bubbleelement = mychildren[3]; //  hardcoded bubble is 4th layer
				this.chatelement = mychildren[4]; //  hardcoded bubble is 4th layer
				//this.nametagelement = mychildren[5]; //  hardcoded nametag is 4th layer
				// nametag is an <a>, not div number 5 - beta4
				this.nametagelement = use_this_element.getElementsByTagName("a")[0];
			}
			else if (mychildren[0]) // one sub div only
		{
				use_this_image = mychildren[0];
				use_this_image.id += ''+uniquenumber; // append to cloned non unique id
				//if (game.logger) game.logger('Found a single divdiv sprite ok.');
			}
			//else
			//{
				// this is okay - we dont always need a sheet
				// if (game.logger) game.logger('ERROR - cannot find any sprite sheets.');
			//}
		}
		//fixme: we could CHANGE the image .src right now
		uniquenumber++;
	}
	// used to test for ==null, but then it allowed 0 as a param
	if (!framesgosideways) framesgosideways = false; // if true it animates from LEFT to RIGHT (avatars)
	if (!animloop) animloop = false;
	if (!myid) return null;
	if (!numstate) numstate = 0;
	num = 0; // start on first frame at init - FIXME: check horiz or not and flip state spriteoffset html!
	if (!imageurl) imageurl = game.spriteurl; // default image for a sprite
	var imgid = '_'+myid;
	var tagtype = 'div';
	var eventhtml = '';
	var tag_extra = '';
	var animloopstart = 0;
	var animloopend = 2;
	var myclass = 's'; // sprite image 32x32
	if (!imagewidth) imagewidth = game.spritew; // default
	if (!imageheight) imageheight = game.spriteh; // default
	this.xy = [4444,4444]; // can become NaN,NaN
	this.walkxy = [4444,4444];
	this.walking = false;
	this.flipflopcounter = 0; // for 1213 walk cycle
	this.destination_arrival_callback = null; // run a func when we get to dest via walking
	this.next_click_callback = null; // run a func when we next click the ground
	this.h = imageheight;
	this.w = imagewidth;
	this.w2 = parseInt(imagewidth/2);
	this.radius = this.w2; //parseInt(Math.sqrt((this.h*this.h)+(this.w*this.w));
	this.zoffset = 0; // for z depth sorting tweaks (floors vs props)
	var spriteoffset = (-1 * num * imageheight)+'px'; // calc frame y
	this.framesgosideways = framesgosideways;
	this.visible = true;
	this.id = myid;					// remember my unique name
	this.st = null;
	if (use_this_element)
		this.el = use_this_element;	// pointer to the clipped div ready to move around
	else
		this.el = document.getElementById(myid);	// pointer to the clipped div ready to move around
	if (this.el)
	{
		this.st = this.el.style;			// pointer to the style that updates the x,y and hidden
	}
	else
	{
		if (game.logger) game.logger('ERROR: Missing element in sprite constructor: ' + myid);
	}
	this.height = imageheight; // remember
	this.width = imagewidth; // for later
	if (use_this_image)
		this.img = use_this_image; 	// pointer to the image element inside the div ready to change frames
	else
		this.img = document.getElementById(imgid); 	// pointer to the image element inside the div ready to change frames
	if (!this.img)
	{
		// hardcoded sprites and spawned divs that dont have the right # of layers (like trees) can't change frames.
		//if (game.logger) game.logger('ERROR - missing image ('+imgid+') in a sprite ('+myid+')!');
		this.imgstyle = null;
	}
	else
	{
		this.imgstyle = this.img.style;			// pointer to the style that updates the image frame num
	}
	// frame number:
	this.num = num;
	// animation state shift
	this.numstate = numstate;				// what frame number (which sprite)
	if (animframes!=null)
	{	// automatically animated (explosions)
		this.anim_frames = animframes;		// how many frames to cycle through
		this.anim_delta = 1;			// 0 for no animation, -1 for backwards, 1 for forwards
		this.anim_loop = animloop;			// 0 for hide after max frame, 1 for cycle, 2 for pingpong
		this.animloopstart = num;
		this.animloopend = num+animframes;
	}
	else
	{	// not animated
		this.anim_frames = 0;			// number of frames in animation (1 for no animation)
		this.anim_delta = 0;			// 0 for no animation, -1 for backwards, 1 for forwards
		this.anim_loop = 0;			// 0 for hide after max frame, 1 for cycle, 2 for pingpong
		this.animloopstart = 0;
		this.animloopend = 0;
	}
	this.frameheight = imageheight;		//game.fxh;
	this.framewidth = imagewidth;		//game.fxw;
	this.hovernum = hovernum;			// what frame number (which sprite)
	this.name = "";
	this.chat = "";
	this.prevchat = "zzz";
	// pointers to functions
	this.move = sprite_move;			// functions on two floats
	this.movexy = sprite_movexy;		// functions on an array
	this.push_away = sprite_push_away; // avoid all colliders, calls movexy
	this.walk_towards_xy = sprite_walk_towards_xy; // start walking
	this.walk_towards_screen_center = sprite_walk_towards_screen_center; // just find the middle of the screen
	this.hide = sprite_hide;
	this.show = sprite_show;
	this.changeframe = sprite_changeframe;		// view next frame
	this.changeframestate = sprite_changeframestate; // use next animation
	this.changesheet = sprite_changesheet; // change img src
	this.changenametag = sprite_changenametag;
	this.changechat = sprite_changechat;
	this.trytowalk = sprite_trytowalk; // nice collision away movement
	this.look_in_correct_direction = sprite_look_in_correct_direction;
	this.removefromlevel = sprite_removefromlevel; // game.level.removeChild(this.el)
	return this; // not required stops lint warning
}

function direction_to_frame(thisdir)
{
	if (thisdir==dirLF) return (0*walkcycle_loop_numframes); else
	if (thisdir==dirDL) return (0*walkcycle_loop_numframes); else
	if (thisdir==dirDN) return (1*walkcycle_loop_numframes); else
	if (thisdir==dirDR) return (1*walkcycle_loop_numframes); else
	if (thisdir==dirRT) return (1*walkcycle_loop_numframes); else
	if (thisdir==dirUL) return (2*walkcycle_loop_numframes); else
	if (thisdir==dirUP) return (3*walkcycle_loop_numframes); else
	if (thisdir==dirUR) return (3*walkcycle_loop_numframes); else
	return 0;
}

// works and is used
function sprite_look_in_correct_direction()
{ // change avatar sprite animation state to the correct direction depending on walkxy and xy
	// if barely angled, U/D/L/R almost never gets shown
	// works: use udlr - a range of 16 pixels before you hit an angles direction
	// if ((Math.abs(deltay)<32) && (Math.abs(deltax)>32)) deltay=0; // force UPDN down to zero
	// if ((Math.abs(deltax)<32) && (Math.abs(deltay)>32)) deltax=0; // *OR*  LFRT down to zero

	newdir = getdirections(this.xy[0],this.xy[1],this.walkxy[0],this.walkxy[1]);

	/*
	if (window.highdef_avatars)
	{
		if (newdir==dirNONE)
		{
			this.changeframe(0); // idle
		}
		else
		{
			this.changeframe(1); // walking
		}
		if (newdir==dirUP) this.changeframestate(2); else
		if (newdir==dirDN) this.changeframestate(0); else
		if (newdir==dirLF) this.changeframestate(0); else
		if (newdir==dirRT) this.changeframestate(1); else
		if (newdir==dirUL) this.changeframestate(2); else
		if (newdir==dirUR) this.changeframestate(3); else
		if (newdir==dirDL) this.changeframestate(0); else
		if (newdir==dirDR) this.changeframestate(1); else
		this.changeframestate(0);

		if (window.isie6) // hack fixme
		{
			var oldvalue = this.img.style.backgroundImage;
			this.img.style.backgroundImage = 'url('+cache_blank_gif_src+')';
			this.img.style.backgroundImage = oldvalue;
		}

	}
	else
	{
	*/
		//dupe
		//newdir = getdirections(this.xy[0],this.xy[1],this.walkxy[0],this.walkxy[1]);
		if (newdir==dirNONE)
		{
			this.animloopstart = 0;
			this.animloopend = 0;
		}
		else
		{
			this.animloopstart = direction_to_frame(newdir);
			this.animloopend = this.animloopstart + walkcycle_loop_numframes-1; //2; // hardcoded 3 frame walk cycle
		}
		this.changeframe(this.animloopstart); // now!
	//}
}

function sprite_trytowalk(movespeed)
{ // collision detection movement
	var x = this.walkxy[0];
	var y = this.walkxy[1];
	var cangetblocked = true;
	var nocollisionsatall = false;
	//;;; if (debug) addlog(this.name+'.trytowalk x='+x+' y='+y+' s='+this+' b='+cangetblocked+' m='+movespeed+' n='+nocollisionsatall);
	// optimization: skip if already there
	//if ((x==this.xy[0]) && (y==this.xy[1])) // fractions: never fires.
	var howfar = vectordistance(this.xy,this.walkxy);
	if (howfar < movespeed) // round off fractional tiny distances
	{ // time to stop walking - arrived at my destination dupe 2
		//;;; if (debug) addlog(this+' has arrived near enough ('+howfar+') to '+ this.walkxy);
		this.walking = false;
		this.anim_delta = 0; // no anim while standing
		this.movexy(this.walkxy); // sets this.xy
		if (window.highdef_avatars)
		{
			this.changeframe(0); // idle
		}
		if (this==game.playerone)
		{
			if (window.xwalkto_hide) xwalkto_hide(); // hide the walk xhair icon
		}
		if (this.destination_arrival_callback)
		{
			this.destination_arrival_callback();
			this.destination_arrival_callback = null;
		}
		return;
	}

	// works!
	/*
	// slowly calculate the speed ratios
	var xratio = Math.abs(vectorxdistance(this.xy,this.walkxy)/howfar);
	var yratio = Math.abs(vectorydistance(this.xy,this.walkxy)/howfar);
	// move straight there
	if (x<this.xy[0]) this.xy[0]-=movespeed * xratio; else
	if (x>this.xy[0]) this.xy[0]+=movespeed * xratio;
	if (y<this.xy[1]) this.xy[1]-=movespeed * yratio; else
	if (y>this.xy[1]) this.xy[1]+=movespeed * yratio;
	*/
	// works! slicker way: grab normalized vector, mult by speed, add to current pos
	this.xy = vectoradd(this.xy,vectorscale(vectornormal(vectorsubtract(this.walkxy,this.xy)),movespeed));
	// no collisions?
	if (nocollisionsatall) // teleport!
	{	// fast out - works great
		this.movexy(this.xy);
		return;
	}
	// no blocking? (ghost walk around always succeeds
	if (!cangetblocked)
		var pushedxy = this.xy; // COPY vector
	// for multiple pushes, we need to run this exponential times..
	// fixme: but if the pushers are SORTED by distance..
	// or just make sure pushers are far apart..
	// but luckily in block mode the effects are cumulative
	// over multiple frames to be nearly PERFECT!
	// avoid everybody except yourself.
	//for (i=0; i<everybodycount; i++)
	for (i in everybody)
	{
		if (everybody[i])
		{
			if (everybody[i]!=this) // dont bump into yourself.
			{
				if (!cangetblocked)	// ghost:
				{
					// so this.xy is always at 'desired' xy
					pushedxy = vectorpushaway(pushedxy,everybody[i].xy,everybody[i].radius);
				}
				else	// blockable:
				{
					// overwrite current position for pure WALL blocking
					// (and getting stuck is therefore possible)
					// normally this gets run:
					this.xy = vectorpushaway(this.xy,everybody[i].xy,everybody[i].radius);
				}
			}
		}
	}
	if (!cangetblocked)
		this.movexy(pushedxy);
	else
		this.movexy(this.xy); // normally this is what gets called

	// avoid every THING
	//for (i=0; i<everythingcount; i++)
	for (i in everything)
	{
		if (everything[i])
		{
			if (!cangetblocked)
			{
				pushedxy = vectorpushaway(pushedxy,everything[i].xy,everything[i].radius);
			}
			else
			{
				this.xy = vectorpushaway(this.xy,everything[i].xy,everything[i].radius);
			}
		}
	}
	if (!cangetblocked)
		this.movexy(pushedxy);
	else
		this.movexy(this.xy);

	// avoid every AABB (axis aligned bounding box)
	//for (i=0; i<aabbcount; i++)
	for (i in aabbs)
	{
		if (aabbs[i])
		{
			if (!cangetblocked)
			{
				pushedxy = vectorpushaway_aabb(pushedxy,aabbs[i].minxy,aabbs[i].maxxy);
			}
			else
			{
				this.xy = vectorpushaway_aabb(this.xy,aabbs[i].minxy,aabbs[i].maxxy);
			}
		}
	}
	if (!cangetblocked)
		this.movexy(pushedxy);
	else
		this.movexy(this.xy);

}


function sprite_push_away() // away from colliders
{
	// avoid everybody except yourself.
	//for (i=0; i<everybodycount; i++)
	for (i in everybody)
	{
		if (everybody[i])
		{
			if (everybody[i]!=this) // dont bump into yourself.
			{
				// overwrite current position for pure WALL blocking
				// (and getting stuck is therefore possible)
				// normally this gets run:
				this.xy = vectorpushaway(this.xy,everybody[i].xy,everybody[i].radius);
			}
		}
	}
	//this.movexy(this.xy); // normally this is what gets called

	// avoid every THING
	//for (i=0; i<everythingcount; i++)
	for (i in everything)
	{
		if (everything[i])
		{
			this.xy = vectorpushaway(this.xy,everything[i].xy,everything[i].radius);
		}
	}
	//this.movexy(this.xy);

	// avoid every AABB (axis aligned bounding box)
	for (i in aabbs)
	{
		if (aabbs[i])
		{
			this.xy = vectorpushaway_aabb(this.xy,aabbs[i].minxy,aabbs[i].maxxy);
		}
	}

	this.movexy(this.xy);
}

function object_touching_xy(thisxy) // is this xy inside a collision area? if so, return ptr to that object
{
	for (i in everybody)
	{
		if (everybody[i])
		{
			if (vectorisinside(thisxy,everybody[i].xy,everybody[i].radius))	return everybody[i];
		}
	}
	for (i in everything)
	{
		if (everything[i])
		{
			if (vectorisinside(thisxy,everything[i].xy,everything[i].radius)) return everything[i];;
		}
	}
	for (i in aabbs)
	{
		if (aabbs[i])
		{
			if (vectorisinside(thisxy,aabbs[i].minxy,aabbs[i].maxxy)) return aabbs[i];
		}
	}
	return null;
}

function closest_thing(xy,toofar) // nearest everything
{
	var foundit = null;
	var mindist = 99999999;
	var my_dist = 0;
	if (!toofar) toofar = 99999999;
	for (i in everything)
	{
		if (everything[i])
		{
			my_dist = vectordistance(xy,everything[i].xy);
			if ((my_dist < toofar) && (my_dist < mindist))
			{
				mindist = my_dist;
				foundit = everything[i];
			}
		}
	}
	return foundit;
}

function vector_push_away(oldxy) // away from colliders
{	// dont overwrite var, return a new one
	var thisxy = [oldxy[0],oldxy[1]];
	//;;; if (debug) addlog('vector_push_away old: '+thisxy);
	//for (i=0; i<everybodycount; i++)
	for (i in everybody)
	{
		if (everybody[i])
		{
			thisxy = vectorpushaway(thisxy,everybody[i].xy,everybody[i].radius);
		}
	}
	//for (i=0; i<everythingcount; i++)
	for (i in everything)
	{
		if (everything[i])
		{
			thisxy = vectorpushaway(thisxy,everything[i].xy,everything[i].radius);
		}
	}
	//for (i=0; i<aabbcount; i++)
	for (i in aabbs)
	{
		if (aabbs[i])
		{
			thisxy = vectorpushaway_aabb(thisxy,aabbs[i].minxy,aabbs[i].maxxy);
		}
	}
	//;;; if (debug) addlog('vector_push_away new: '+thisxy);
	return thisxy;
}




function sprite_changenametag(newname)
{
	if (this.name == newname) return;

	if (this.nametagelement)
		this.nametagelement.innerHTML = newname;

	this.name = newname; // store it too
}

function sprite_changechat(newchat)
{
	if (this.chat==newchat) return; // ignore chat spam/dupes
	if (this.prevchat==newchat) return; // ignore chat spam/dupes

	// replace html entities...? utf-8? strange apostrophe problem...
	newchat = newchat.split('&039;').join("'");

	if (this.bubbleelement)
	{
		if ((newchat=='') || (newchat=='&nbsp;') || (newchat==' ') || (newchat=='\n') || (newchat=='.')|| (newchat=='_'))
		{	// blank out chat
			if (this.chatelement) this.chatelement.innerHTML = '';
			this.bubbleelement.style.display='none';
			this.fade_chat_after_timestamp = 0; // no need to fade
		}
		else
		{
			if (this.chatelement) this.chatelement.innerHTML = no_orphans(newchat,3); // split 3lines
			this.bubbleelement.style.display='block';
			this.fade_chat_after_timestamp = game.now_ms + bubba_max_idle_time;
			// update the chatlog now that we know it is 'new'
			addlog(this.name+': ' + newchat);
		}
	}

	this.prevchat = this.chat;
	this.chat = newchat;
}

function sprite_move(px,py,force_z) // move around
{
	if (isNaN(px)) return;
	if (isNaN(py)) return;

	// fixme: hardcoded foot offset
	this.xy = [px,py]; //+60]; // remember for collision detection in movement.js (SLOWS FPS?)
	// already there, so ignore previous walk target? no
	//this.walkxy = [px,py];

	//if (game.logger) game.logger('sprite_move id='+this.id+' xy='+px+','+py);
	if (!this.st)
	{
		if (this.style)
		{
			this.st = this.style;
		}
		else
		{
			if (game.logger) game.logger('ERROR - sprite_move found no style.');
			return;
		}
	}

	this.px = px;
	this.py = py;
	this.st.top = parseInt(py) + 'px'; //0+py to handle NaNs? parseInt won't help?
	this.st.left = parseInt(px) + 'px';

	if (force_z!=null) // zero is valid...
		this.st.zIndex = parseInt(force_z);
	else
		this.st.zIndex = parseInt(py+this.h+this.zoffset); // auto-depth-sort using height and offset!

}

function sprite_hide(px,py) // make invisible
{	// fixme: clunky: buggy? px offset? dupe?
	if (this.px>-100000) this.move(this.px+999999,this.py+999999);
	this.move(this.px-999999,this.py-999999);
	this.st.display = 'none';
	this.visible = false;
}

function sprite_changesheet(fn) // change img src
{
	if (!this.img)
	{
		addlog('ERROR: Missing img in a sprite sheet!');
		return;
	}

	// fixme: no animated gif backgrounds
	// if they start invisible in in IE6 (browser bug)

	//addlog('DEBUG: sprite_changesheet:\nold img.src='+this.img.style.backgroundImage+'\nnew img.src='+fn);

	// IE6 bug: start the gif animation? nop
	//this.img.src = '';
	//this.img.src = cache_blank_gif_src; // hmm fixme ie6 bug
	//this.img.src = fn;

	// div bg anim gifs work in FF but stay on frame 1 in IE
	//if (window.isie6) // browser hack to restart gif animation
	//this.img.style.backgroundImage = 'url('+cache_blank_gif_src+')';

	this.img.style.backgroundImage = 'url('+fn+')';
}

function sprite_changeframe(num) // change which frame is displayed
{
	//if (game.logger) game.logger('sprite_changeframe id='+this.id+' num='+num+' frameheight=' + this.frameheight);
	this.num=num;
	if (this.imgstyle)
	{
		if (this.framesgosideways) // avatar sideways frames
		{
			this.imgstyle.left = (-1 * num * this.framewidth) + 'px';
		}
		else // explosion, vertical frames
		{
			this.imgstyle.top = (-1 * num * this.frameheight) + 'px';
		}
	}
	else
	{
		if (game.logger) game.logger('ERROR - missing image style pointer for a sprite id ' + this.id);
	}
}

function sprite_changeframestate(num)
{	// shift over one column in the anim sprite
	this.numstate = num;
	if (this.imgstyle)
	{
		if (this.framesgosideways) // old unused avatar sideways frames, vertical states
		{
			this.imgstyle.top = (-1 * num * this.frameheight) + 'px';
			//addlog('debug changeframestate('+num+') width is ' + this.framewidth + ' topcss=' + this.imgstyle.left)
		}
		else // explosion, vertical frames, horiz states, same as avatar sheet 2007
		{
			this.imgstyle.left = (-1 * num * this.framewidth) + 'px';
			//addlog('debug changeframestate('+num+') width is ' + this.framewidth + ' leftcss=' + this.imgstyle.left)
		}
	}
	else
	{
		addlog('ERROR - missing image style pointer for a sprite id ' + this.id);
	}
}

function sprite_movexy(pxy,force_z) // move around using a 2d array vector
{
	if ((pxy==null) || (pxy[0]==undefined) || (isNaN(pxy[0])))
	{
		;;; if (debug) addlog('ERROR: sprite_movexy ' + pxy);
		return;
	}
	this.move(pxy[0],pxy[1],force_z);
}

function sprite_show(px,py) // make visible
{
	if (this.px<-900000) this.move(this.px+999999,this.py+999999);
	this.st.display = 'block';
	this.visible = true;
}

function sprite_walk_towards_screen_center()
{
	// redun?
	game.screen_center_x = parseInt(getClientWidth()/2);
	game.screen_center_y = parseInt(getClientHeight()/2);
	var gohere = [game.viewport_target_px+game.screen_center_x,game.viewport_target_py+game.screen_center_y];
	;;; if (debug) addlog('game.screen_center_x='+game.screen_center_x+' game.screen_center_y='+game.screen_center_y);
	;;; if (debug) addlog('game.viewport_target_px='+game.viewport_target_px+' game.viewport_target_py='+game.viewport_target_py);
	;;; if (debug) addlog('sprite_walk_towards_screen_center: '+gohere);
	this.walk_towards_xy(gohere);
}

function sprite_walk_towards_xy(newxy)
{  // start walking towards a new walk destination

	// params are optional: start walking
	if (newxy==null) newxy = this.walkxy; // use last setting

	if (vector_equals(newxy,this.xy)) // I've arrived at my destination
	{
		//;;; if (debug) addlog(this.name+'.walk_towards_xy(' + newxy + ') is ALREADY at ' + this.xy + ' no need to walk.');
		this.anim_delta = 0; // no anim standing
		this.walking = false;

		if (window.xwalkto_hide) xwalkto_hide(); // hide the walkxy icon dupe

		return; // dont walk if already there
	}

	//;;; if (debug) addlog(this.name+'.walk_towards_xy(' + newxy + ') starting from ' + this.xy);
	this.walkxy = newxy; // remember
	this.anim_delta = 1; // animate walking steps
	this.walking = true;
	// face sprite in the correct direction:
	this.look_in_correct_direction(); // redun in keypress..?
}
// end SPRITE.JS

// server location select
function joinserver(thisone)
{	// fixme: game is only on one server for now
	gotit = document.getElementById('locationgui');
	if (gotit) gotit.style.display='none';
	gotit = document.getElementById('thecurtain');
	if (gotit) gotit.style.display='none';
}


/*
// phatpad houses
var housecache_inside = new Image();
var housecache_outside = new Image();
var phatpad_interior_center_xy = [4444,14688]; // just inside the door
// arcade
var arcadecache_inside = new Image();
var arcadecache_outside = new Image();
var arcade_interior_center_xy = [14100,14632]; // just inside the door
var arcade_exit_location_xy = [3920,4900]; // just ouside the door
function houseexit()
{
	if (window.game_sfxlogin) game_sfxlogin();
	warptoxy(game.playerone.exit_interior_xy);
}
function houseover(me)
{
	me.src='';
	if (housecache_inside.src=='') housecache_inside.src = CACHEURL+'phatpad_outside_mouseover.jpg';
	me.src = housecache_inside.src;
}
function houseout(me)
{
	if (housecache_outside.src=='') housecache_outside.src = CACHEURL+'phatpad_outside_512.jpg';
	me.src = housecache_outside.src;
}
function houseclick(me)
{
	if (window.game_sfxlogin) game_sfxlogin();
	game.playerone.exit_interior_xy = game.playerone.xy; //walkxy; // remember current location
	warptoxy(phatpad_interior_center_xy);
}
function arcadeexit()
{
	if (window.game_sfxlogin) game_sfxlogin();
	warptoxy(arcade_exit_location_xy); // hardcoded
}
function arcadeover(me)
{
	me.src='';
	if (arcadecache_inside.src=='') arcadecache_inside.src = CACHEURL+'arcade_outside_800x600_hover.gif';
	me.src = arcadecache_inside.src;
}
function arcadeout(me)
{
	if (arcadecache_outside.src=='') arcadecache_outside.src = CACHEURL+'arcade_outside_800x600.gif';
	me.src = arcadecache_outside.src;
}
function arcadeclick(me)
{
	if (window.game_sfxlogin) game_sfxlogin();
	game.playerone.exit_interior_xy = game.playerone.walkxy; // remember it
	warptoxy(arcade_interior_center_xy);
}
*/


function precache_gui_images()
{	// oldschool gui image ram copies for faster mouseovers
	;;; if (debug) { addlog('Precaching gui images at '+game_timestamp()); }
	// this slows down init time...
	cache_blank_gif.src = cache_blank_gif_src;
	cache_login_gif.src = cache_login_gif_src;
	cache_login_over_gif.src = cache_login_over_gif_src;
	cache_avatars_gif.src = cache_avatars_gif_src;

/*
	cache_phone_button.src = cache_phone_button_src;
	//cache_chat_gif.src = cache_chat_gif_src;
	//cache_chat_over_gif.src = cache_chat_over_gif_src;
	//cache_help_gif.src = cache_help_gif_src;
	//cache_help_over_gif.src = cache_help_over_gif_src;
	cache_login_leftarrow_gif.src = cache_login_leftarrow_gif_src;
	cache_login_rightarrow_gif.src = cache_login_rightarrow_gif_src;
	cache_login_head_blue.src = cache_login_head_blue_src;
	cache_login_head_spike.src = cache_login_head_spike_src;
	cache_login_head_red.src = cache_login_head_red_src;
	cache_login_head_norm.src = cache_login_head_norm_src;
	cache_login_head_tails.src = cache_login_head_tails_src;
	cache_login_head_braids.src = cache_login_head_braids_src;
	cache_button_gif.src = cache_button_gif_src;
	cache_button_hover_gif.src = cache_button_hover_gif_src;
	cache_padpad_hover_gif.src = cache_padpad_hover_src;
	cache_arcade_hover_gif.src = cache_arcade_hover_src;
*/
}

function levelshow()
{
	var mgele = document.getElementById('level');
	if (mgele) mgele.style.display='block';
}

function levelhide()
{
	var mgele = document.getElementById('level');
	if (mgele) mgele.style.display='none';
}

function minigameshow()
{
	var mgele = document.getElementById('mg');
	if (mgele) mgele.style.display='block';
}

function minigamehide()
{
	var mgele = document.getElementById('mg');
	if (mgele) mgele.style.display='none';
}

function minigamerender(someswf)
{
	if (debug) addlog('DEBUG: minigamerender '+someswf);
	var minigame_rez_x = getClientWidth();
	var minigame_rez_y = getClientHeight();
	var mgele = document.getElementById('mgg');
	if (mgele)
	{
		// full screen dynamic sizing
		mgele.style.top = minigame_t_margin + 'px';
		mgele.style.left = minigame_s_margin + 'px';
		mgele.style.margin = '0';
		mgele.style.width = minigame_rez_x - minigame_s_margin - minigame_s_margin + 'px';
		mgele.style.height = minigame_rez_y - minigame_t_margin - minigame_b_margin + 'px';
		mgele.innerHTML=mini_html_start+someswf+mini_html_mid+someswf+mini_html_end;
	}

}

function minigame(thisone)
{
	if (window.game_sfxlogin) game_sfxlogin();
	// fixme: IE6 cache hack the filenames with ?random="+Math.random()
	if (thisone==0) minigamerender("minibunch.swf");
	else if (thisone==1) minigamerender("minisudoku.swf");
	else if (thisone==2) minigamerender("minicubebuster.swf");
	else if (thisone==3) minigamerender("minipool.swf");
	else minigamerender("minisudoku.swf"); // error tolerated
	minigameshow();
	levelhide();
}

function minigamequit()
{
	if (window.game_sfxlogin) game_sfxlogin();
	
	var mgele = document.getElementById('mgg');
	if (mgele)
	{
		mgele.innerHTML = "<br>"; // no more flash
	}
	
	levelshow();
	minigamehide();
}

function chattext_onclick(thiselement)
{
	thiselement.focus();
	if (!window.debug)
		thiselement.value=''; // clear input
	else
		thiselement.select(); // just select it
}

function chattext_onkeypress()
{
	//addlog('chattext_onkeypress length '+document.getElementById('chattext').value.length);
	if (document.getElementById('chattext').value.length>max_chat_character_length-1)
	{
		//addlog('DEBUG: chat keypress ignored!');
		//addlog('chattext_onkeypress length TOO BIG '+document.getElementById('chattext').value.length);
		document.getElementById('chattext').value = document.getElementById('chattext').value.substring(0,max_chat_character_length);
		return false; // ignored by the browser??
	}
	else
	{
		return true;
	}
}

function chattext_onchange() // rarely fires but in case they copy n paste...
{
	//addlog('chattext_onchange length '+document.getElementById('chattext').value.length);
	if (document.getElementById('chattext').value.length>max_chat_character_length)
	{
		//addlog('DEBUG: chat change too big!');
		//addlog('chattext_onchange length TOO BIG '+document.getElementById('chattext').value.length);
		document.getElementById('chattext').value = document.getElementById('chattext').value.substring(0,max_chat_character_length);
	}
}

var animated_water_on = false;
function toggle_animated_water(thiselement)
{
	if (!animated_water_on)
	{
		alert('WORK IN PROGRESS\n\nAnimated water adds a 1MB download\n(15-30 seconds with no terrain so be patient)\nand does not work as well in IE6 (use Firefox/Opera/Netscape/etc to test).\nOn fast new computers (>2Ghz,>512MB) the shore will look amazing,\nbut this effect still needs work and eats CPU power like crazy.');
		thiselement.innerHTML = 'Animated Water ON';
		animated_water_on = true;
		thestyle = find_css_style('div.bg1_inside');
		if (thestyle) thestyle.backgroundImage = "url("+CACHEURL+"water_animated.gif)";
		thestyle = find_css_style('div.floor_sheet');
		if (thestyle)
		{
			if (window.isie6) // fixme lame browser hack
				thestyle.backgroundImage = "url("+CACHEURL+"floors.gif)";
			else
				thestyle.backgroundImage = "url("+CACHEURL+"floors.png)";
		}
	}
	else
	{
		thiselement.innerHTML = 'Animated Water OFF';
		animated_water_on = false;
		thestyle = find_css_style('div.bg1_inside');
		if (thestyle) thestyle.backgroundImage = "url("+CACHEURL+"water.gif)";
		thestyle = find_css_style('div.floor_sheet');
		if (thestyle) thestyle.backgroundImage = "url("+CACHEURL+"floors.jpg)";
		alert('WORK IN PROGRESS\n\nAnimated water is now turned off.\nNotice that the game runs much smoother.');
	}
}

function toggle_smooth_scrolling(thiselement)
{
	if (!smooth_scroll_viewport)
	{
		thiselement.innerHTML = 'Smooth Scrolling ON';
		alert('Note that with smooth scrolling ON, the walking animation only changes when the browser is not busy scrolling the viewport (a bug in IE6 only).');
		smooth_scroll_viewport = true;
	}
	else
	{
		thiselement.innerHTML = 'Smooth Scrolling OFF';
		alert('Note that with smooth scrolling OFF, the walking animation works in IE6.');
		smooth_scroll_viewport = false;
	}
}

function toggle_debug(thiselement)
{
	if (!debug)
	{
		thiselement.innerHTML = 'Debug Mode ON';
		debug = true;
	}
	else
	{
		thiselement.innerHTML = 'Debug Mode OFF';
		if (game.ele_fps) game.ele_fps.innerHTML = '';
		debug = false;
	}
}

function find_css_style(selectorname)
{
	var targetrule = null;
	var debugall = '';

	var mysheet=document.styleSheets[0];
	if (!mysheet)
	{
		;;; if (debug) addlog('ERROR: could not find stylesheet.');
		return null;
	}

	var myrules=mysheet.cssRules ? mysheet.cssRules : mysheet.rules; // ff vs ie
	if (!myrules)
	{
		;;; if (debug) addlog('ERROR: could not find stylesheet rules.');
		return null;
	}

	for (i=0; i<myrules.length; i++)
	{
		debugall += myrules[i].selectorText.toLowerCase() + ', ';
		if ((myrules[i].selectorText) && (myrules[i].selectorText.toLowerCase()==selectorname))
		{ 	// found it!
			targetrule=myrules[i];
			break;
		}
	}

	if ((targetrule) && (targetrule.style))
	{
		return targetrule.style;
	}
	else
	{
		;;; if (debug) addlog('ERROR: unable to find style for '+selectorname);
		;;; if (debug) addlog(myrules.length+' CSS RULES FOUND:\n'+debugall);
		return null;
	}
}


















function insert_flash_content()
{
		//addlog('DEBUG: page has loaded!  Starting 3 flash objects...');

		// uses document.write for flash detect, so must run during load...
		//if (window.init_sound_effects)
		//{
		//	init_sound_effects(); // sound-config.js
		//}

		var tunesdiv = document.getElementById('tunes');
		if (tunesdiv)
		{
			tunesdiv.innerHTML = tunes_flash_html;
		}

		var ambienceiframe = document.getElementById('music');
		if (ambienceiframe)
		{
			ambienceiframe.src = 'music/index.html';
		}
}


function onloadevent()
{	// the entire page has finished loading
	page_has_loaded = true;
	init_atend_timestamp = game_timestamp();
	init_to_onload_timespan = init_atend_timestamp - init_start_timestamp;
	;;; if (debug) { addlog('DEBUG MODE'); addlog('Init started at '+init_start_timestamp); addlog('Page loaded at '+init_atend_timestamp); addlog('Total init + download timespan = '+init_to_onload_timespan); }
	if (document.getElementById('progressbar'))
	{
		document.getElementById('progressbar').style.display='none';
	}
}















// 2008 abstract sprite (using a sheet) spawner - needs nothing in index.html just CSS classes
function spawn2008(spawnxy,tiletl,tilebr,footxy,collideradius,sheetclass,divclass,hovercallback,clickcallback,outcallback,zofs,nocollide)
{
	if (spawnxy==undefined) return null;
	if (divclass==undefined)
	{
		divclass = 'foliage';
		if (sheetclass==undefined) sheetclass = 'foliage_sheet';
	}
	if (tiletl==undefined) tiletl = [0,0];
	if (tilebr==undefined) tilebr = [64,64];
	var spawnwidth = tilebr[0] - tiletl[0];
	var spawnheight = tilebr[1] - tiletl[1];
	if (collideradius==undefined) collideradius = Math.round(spawnwidth/2);
	
	if (footxy==undefined)
	{
		if ((collideradius < 0) || (typeof(collideradius)!='number'))
			footxy = [Math.round(spawnwidth/2),spawnheight]; // if aabb collide and not footpos, assume bottom mid
		else
			footxy = [Math.round(spawnwidth/2),spawnheight-Math.round(spawnwidth/2)]; // if circle collide and not footpos - assume centered
	}
	
	var spawnzoffset = (spawnheight-footxy[1]);
	if (zofs!=null) spawnzoffset+=zofs; // for floor tiles, etc.
	spawnzoffset += spawnxy[1]; // y coord
	if (spawnzoffset<0) spawnzoffset=0; // not underground..

	var newsprite = null;

	//;;; if (debug) addlog('spawn2008 '+spawnxy+' t'+tiletl+','+tilebr+' f'+footxy+' s'+sheetclass+' d'+divclass+' w'+spawnwidth+' h'+spawnheight+' r'+collideradius+' z'+spawnzoffset);
	;;; if (debug) addlog('spawn2008 '+spawnxy+' '+sheetclass+' '+divclass);

	// create a fresh new html element sprite
	var newdiv = document.createElement('div');
	newdiv.className = divclass;
	newdiv.style.width = spawnwidth+'px';
	newdiv.style.height = spawnheight+'px';
	newdiv.style.marginLeft = -1*(/*Math.round(spawnwidth/2)-*/footxy[0])+'px';
	newdiv.style.marginTop = -1*(/*spawnheight-*/footxy[1])+'px';
	newdiv.style.zIndex = spawnzoffset;

	//;;; if (debug) addlog('The new div style marginTop:\n'+ newdiv.style.marginTop);

	if (sheetclass!=null) // we can skip the sheet if we want
	{
		var newsheet = document.createElement('div');
		newsheet.className = sheetclass;
		newsheet.style.left = -1*tiletl[0]+'px';
		newsheet.style.top = -1*tiletl[1]+'px';
		newdiv.appendChild(newsheet);
	}

	// put it into the world
	if (window.game && game.level)
	{
		game.level.appendChild(newdiv);
	}
	else
	{
		;;; if (debug) addlog('ERROR: missing game.level in spawn2008().');
	}

	// add some functionality and pointers
	newsprite = new sprite(newdiv);
	newsprite.zoffset = -spawnzoffset;
	newsprite.movexy(spawnxy);
	newsprite.show();

	;;; if (debug) addlog('spawn2008 sprite created...');
	
	if (collideradius!=null)
	{

		// handle automatic aabb measured
		if (collideradius < 0) // special_case
		{
			;;; if (debug) addlog('DEBUG: spawn2008 got a negative collideradius - calc aabb');
			collideradius=[[-footxy[0],-footxy[1]],[-footxy[0]+spawnwidth,-footxy[1]+spawnheight]];
		}

		if (typeof(collideradius)=='number')
		{
			if (collideradius>0)
			{	// collides?
				newsprite.radius = collideradius;

				if (!nocollide)
				{
					;;; if (debug>1) addlog('spawn2008 colliding to a circle');
					everything[everythingcount] = newsprite;
					everythingcount++;
					;;; if (debug) spawn_debug_red_circle_xy_xy([spawnxy[0]-collideradius,spawnxy[1]-collideradius],[spawnxy[0]+collideradius,spawnxy[1]+collideradius]);
				}
			}
		}
		else // assume an array of arrays if not a number
		{
			if (!nocollide)
			{
				// this aabb doesnt MOVE - fixme: pushaway new func?
				;;; if (debug) addlog('DEBUG: spawn2008 colliding to an aabb'); // poo ;;; if (debug)
				spawnaabb(	[spawnxy[0]+collideradius[0][0],spawnxy[1]+collideradius[0][1]],
						[spawnxy[0]+collideradius[1][0],spawnxy[1]+collideradius[1][1]]);
			}
		}
		// fixme todo: make squashed 2:1 circle everything collider
	}

	;;; if (debug) addlog('spawn2008 setting callbacks...');

	// fire mouse events? fixme: both pointers are the same.
	if (hovercallback)
	{
		//newsprite.el.onmouseover = hovercallback;
		newdiv.onmouseover = hovercallback;
	}
	if (clickcallback)
	{
		//newsprite.el.onclick = clickcallback;
		newdiv.onclick = clickcallback;
	}
	if (outcallback)
	{
		//newsprite.el.onmouseout = outcallback;
		newdiv.onmouseout = outcallback;
	}

	return newsprite;
}







function tooltip_show(thistext,thistopcss,thisleftcss)
{
	atooltip = document.getElementById('tooltip');
	if (atooltip)
	{
		atooltip.style.top = thistopcss;
		atooltip.style.left = thisleftcss;
		atooltip.innerHTML = thistext;
 		atooltip.style.display = 'block';
	}
}

function tooltip_hide()
{
	atooltip = document.getElementById('tooltip');
	if (atooltip)
	{
		atooltip.style.display = 'none';
	}
}









var alertokfunction = null;
var alertyesfunction = null;
var alertnofunction = null;
function alertok()
{
	alert_hide();
	if (alertokfunction) alertokfunction();
}
function alertyes()
{
	alert_hide();
	if (alertyesfunction) alertyesfunction();
}
function alertno()
{
	alert_hide();
	if (alertnofunction) alertnofunction();
}
function alert_yesno(thistext,ayesfunction,anofunction)
{
	gui_owns_clicks = true;
	aele = document.getElementById('alerttext');
	if (aele) aele.innerHTML = thistext;
	aele = document.getElementById('alert');
	if (aele) aele.style.display = 'block';
	aele = document.getElementById('alertbutton');
	if (aele) aele.style.display = 'none';
	aele = document.getElementById('alertyesbutton');
	if (aele) aele.style.display = 'block';
	aele = document.getElementById('alertnobutton');
	if (aele) aele.style.display = 'block';
	if (ayesfunction) alertyesfunction = ayesfunction; else alertyesfunction = null;
	if (anofunction) alertnofunction = anofunction; else alertnofunction = null;
}
function alert_show(thistext)
{
	gui_owns_clicks = true;
	aele = document.getElementById('alertbutton');
	if (aele) aele.style.display = 'block';
	aele = document.getElementById('alertyesbutton');
	if (aele) aele.style.display = 'none';
	aele = document.getElementById('alertnobutton');
	if (aele) aele.style.display = 'none';
	aele = document.getElementById('alerttext');
	if (aele) aele.innerHTML = thistext;
	aele = document.getElementById('alert');
	if (aele) aele.style.display = 'block';
}
function alert_hide()
{
	gui_owns_clicks = false;
	if (window.game_sfxclick) game_sfxclick();
	aele = document.getElementById('alert');
	if (aele) aele.style.display = 'none';
}


/*
// handy functiones unused
// so we can call [1,2,3].map(runthisfunctiononeveryelement);
Array.prototype.map = function(f)
{
	var returnArray=[];
	for (i=0; i<this.length; i++)
	{
    		returnArray.push(f(this[i]));
	}
  	return returnArray;
};
// so we can use if (something.isArray())
// buggy Object.prototype.isArray = function() { return this.constructor == Array; };

String.prototype.rot13 = function() // dirt simple obfuscation
{
	return this.replace(/[a-zA-Z]/g, function(c) { return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);});
};
*/



var leveleditorison = false;
var leveleditorfirsttime = true;
function leveleditortoggle()
{
	if (window.game_sfxclick) game_sfxclick();

	;;; if (debug) addlog('leveleditortoggle');

	if (!window.buildables)
	{
		addlog('ERROR: missing buildables array.');
		return;
	}

	var le_window = document.getElementById('leveleditor');
	var le_title = document.getElementById('leveleditortitle');
	var le_choices = document.getElementById('leveleditorchoices');

	if (le_window)
	{
		if (leveleditorison)
		{
			le_window.style.display = 'none';
			leveleditorison = false;
			return;
		}
		// else make visible
		if (leveleditorfirsttime) // build choices
		{
			;;; if (debug) addlog('leveleditor listing choices');

			leveleditorfirsttime = false;
			le_title.innerHTML = "Click an object to purchase it,<br>then click the desired location.";

			var choiceshtml = '';
			for (nextchoice in buildables)
			{
				;;; if (debug>1) addlog('leveleditor found choice '+nextchoice);
				choiceshtml += "<a class='leveleditora' href='javascript://' onmouseover='leveleditmouseover();' onmouseout='leveleditmouseout();' title='Click to buy a "+nextchoice+".' onclick=\"startbuilding('"+nextchoice+"');\" style='background-position:0px 0px;'>"+nextchoice+"</a>";
			}
			le_choices.innerHTML = choiceshtml;
		}
		le_window.style.display = 'block';
		leveleditorison = true;
	}
}

function leveleditmouseover()
{
	if (window.game_sfx_hover) game_sfx_hover();
	gui_owns_clicks = false;
}

function leveleditmouseout()
{
	gui_owns_clicks = false;
}

function leveleditor_show()
{
	gui_owns_clicks = true; // don't allow clicks on level
	leveleditorison = false;
	leveleditortoggle(); // switch to true
}
function leveleditor_hide()
{
	gui_owns_clicks = false; // allow clicks on level
	leveleditorison = true;
	leveleditortoggle(); // switch to false
}

var building_pending_sprite_being_dragged = false;
var building_pending_sprite = null;
var building_pending_name = null;
function startbuilding(aname)
{ 	// about to place a prop
	;;; if (debug) addlog('DEBUG: startbuilding ' + aname + ' at ' + game.playerone.xy);
	if (!window.leveleditor_all_the_time) leveleditor_hide();
	var roundxy = [Math.round(game.playerone.xy[0]),Math.round(game.playerone.xy[1])];
	building_pending_sprite = spawnbuilding(aname,roundxy,true); // no collide
	building_pending_name = aname;
	// fixme todo: drag it around, transparent
	// then in xhair onclick if not null, place and SAVE
	building_pending_sprite.el.style.opacity = '0.25';
	//finishbuilding();
	// wait for player to click again on ground
	game.playerone.next_click_callback = startbuildinghere;
	building_pending_sprite_being_dragged = true;

}

function startbuildinghere()
{
	;;; if (debug) addlog('startbuildinghere ' + game.playerone.walkxy);
	building_pending_sprite_being_dragged = false;
	// wait for player to get there, then build.
	game.playerone.destination_arrival_callback = finishbuilding;
}

function finishbuilding()
{	// save the newly constructed item to db
	if (window.game_sfxchop) game_sfxchop(); // fixme: hammer sound?

	if (building_pending_sprite)
	{
		// finally it exists. now it can collide:
		everything[everythingcount] = building_pending_sprite;
		everythingcount++;
		;;; if (debug) spawn_debug_red_circle_xy_xy([building_pending_sprite.xy[0]-building_pending_sprite.radius,building_pending_sprite.xy[1]-building_pending_sprite.radius],[building_pending_sprite.xy[0]+building_pending_sprite.radius,building_pending_sprite.xy[1]+building_pending_sprite.radius]);
		building_pending_sprite.el.style.opacity = '1.0';
	}
	building_pending_sprite = null; // stop dragging
	building_pending_sprite_being_dragged = false; // dupe

	var roundxy = [Math.round(game.playerone.xy[0]),Math.round(game.playerone.xy[1])];
	//var roundxy = [Math.round(game.playerone.walkxy[0]),Math.round(game.playerone.walkxy[1])];
	var builddata = "level="+global_user_name+"&build="+building_pending_name+","+roundxy[0]+","+roundxy[1];
	;;; if (debug) addlog('DEBUG: finishbuilding posting data:');
	;;; if (debug) addlog('DEBUG: ' + builddata);
	// now post it to the server
	var pendingbuildrequest = new ajax(builddata,"level.php?");
	// build requests dont output anything
}

// remember them so we can 'unload'
var buildings_on_this_level = Array();
var buildings_on_this_level_num = 0;
function spawnbuilding(aname,xyarray,nocollide,overfunc,clickfunc,outfunc)
{
	;;; if (debug>1) addlog('DEBUG: spawnbuilding (#'+buildings_on_this_level_num+') = ' + aname);
	var thenewsprite = null;
	if (!buildables[aname])
	{
		// ;;; if (debug) 
		addlog('ERROR: spawnbuilding could not find: '+aname);
		return null;
	}
	if (typeof xyarray[0] == 'number')
	{
		xyarray = [xyarray];
		//thenewsprite = spawn2008(xyarray,buildables[aname][0],buildables[aname][1],buildables[aname][2],buildables[aname][3],'building_sheet','buildings',buildables[aname][4],buildables[aname][5],buildables[aname][6]);
		//buildings_on_this_level[buildings_on_this_level_num] = thenewsprite;
		//buildings_on_this_level_num++;
	}
	for (loopa in xyarray)
	{
		// xy can be fractional
		spawnxy = [Math.round(xyarray[loopa][0]),Math.round(xyarray[loopa][1])];
		
		thenewsprite = spawn2008(spawnxy,buildables[aname][0],buildables[aname][1], // spawnxy,tiletl,tilebr 
								buildables[aname][2],buildables[aname][3], // footxy,collideradius 
								'building_sheet','buildings', // sheetclass,divclass
								overfunc ? overfunc : buildables[aname][4], // hovercallback
								clickfunc ? clickfunc : buildables[aname][5], // clickcallback
								outfunc ? outfunc : buildables[aname][6], // outcallback
								buildables[aname][7],nocollide); // zofs,nocollide
		buildings_on_this_level[buildings_on_this_level_num] = thenewsprite;
		buildings_on_this_level_num++;
	}
	return thenewsprite; // pointer to the last one
}

function destroy_buildings(thislevel)
{	// remove the previously visible buildings fixme: allow sub levels for chunks?
	if (!thislevel) return;
	;;; if (debug>1) addlog('destroy_buildings level: '+thislevel);
	if (buildings_on_this_level_num>0)
	{
		for (nextone in buildings_on_this_level)
		{
			;;; if (debug>1) addlog('destroying building '+nextone);
			array_seek_and_destroy(everything,buildings_on_this_level[nextone]);
			buildings_on_this_level[nextone].removefromlevel();
			delete buildings_on_this_level[nextone];
		}
	}
}

function array_seek_and_destroy(thisarray,thisone)
{	// search for a pointer and remove from array
	if (!thisone) return;
	if (!thisarray) return;
	for (maybethisone in everything)
	{
		if (thisarray[maybethisone]==thisone)
		{
			;;; if (debug) addlog('DEBUG: everything_remove found one and destroyed it.');
			delete thisarray[maybethisone];
			return;
		}
	}
}

function anybuttover()
{
	gui_owns_clicks = true;
	if (window.game_sfxhover) game_sfxhover();
}

function anybuttout()
{
	gui_owns_clicks = false;
}





// program execution begins immediately BEFORE onload
var game = new game_engine();		// create global game object
game.init();				// create all game resources
game.frame();				// start animating now!
setTimeout(once_per_second,1000);	// start the heartbeat
if (window.precache_many_images)
{
	queue("precache_gui_images();");	// precache some images for mouseovers - takes time - deferred until after inits
}
if (window.load_level)
{
	queue("load_level(0);"); 		// IE6 does this very slowly...
}
;;; if (debug>1) alert('DEBUG: Game engine loaded completely.');
