Sic/frame1.as

From Organic Design wiki
Legacy.svg Legacy: This article describes a concept that has been superseded in the course of ongoing development on the Organic Design wiki. Please do not develop this any further or base work on this concept, this is only useful for a historic record of work done. You may find a link to the currently used concept or function in this article, if not you can contact the author to find out what has taken the place of this legacy item.
//
// Liscenced under LGPL: www.gnu.org/copyleft/lesser.html
//
// Nad - 2003 - SIC-Games
// Frame1: GENERAL DECLARATIONS
//

FSCommand("fullscreen",true);

// GLOBALS
// SPACE
//   SPACE.CREATE
//   SPACE.FRAME
// TEMPLATE
// 3D OBJECT CLASSES
//   Spark
//   Ball
//   OBJECT CLASS
//   GUN FIRE CLASS
//   BULLET CLASS
//   GUN CLASS
//   STAR-BURST CLASS
//   TORUS CLASS
//   IMPLODE CLASS
//   STARFIELD CLASS
// 3D MOTION CLASSES
//   FLOAT
// INTERFACES
//    interfaces.addInterface
//    interfaces.deleteInterface
//    interfaces.sync
//    interfaces.ack
//    updateInterfaces
// NETWORK
//    processOutgoingEvents
//    processIncomingEvents
//    createEvent
//    sendMessage
//    processMessage
// SHELL

// _____________________________________________________________________________________________________________________ //
// GLOBALS
now = new Date();
time = now.getTime();
allSpaces = [];			// Keep track of all spaces so all their frame() can be executed
CWD = '';

// _____________________________________________________________________________________________________________________ //
// SPACE
// Initialise passed movieclip as a 3D space

function initialiseSpace(newSpace) {
	_root.allSpaces.push(newSpace);
	newSpace.create = _root.space.create;
	newSpace.frame = _root.space.frame;
	newSpace.active = true;				// Enables/disables space dynamics
	newSpace.space = newSpace;			// create() function used at all levels expects space to be this.space;
	newSpace.rotation = 0;				// global rotation of space
	newSpace.shipRotation = 0;			// global rotation of ship (which the screens are windows out of)
	newSpace.applySR = true;
	newSpace.zbuff = [];				// Z ordered symbol-refs of all 3D objects
	newSpace.zbLength = 0;				// Since zbuff is sorted, we can use an end-ptr instead of pushing/splicing
	newSpace.children = [];				// Top layer children
	newSpace.template = _root.template;	// Template object for creating new symbols into the space
	newSpace.zSCREEN = 1000;			// Distance from origin (eye) to screen
	}

// _____________________________________________________________________________________________________________________ //
//   SPACE.CREATE
//   Method: The space and all its objects inherit this method for creating child objects
//  	Creates a symbol for the 3D-object (spacer is used if classes[thisClass].symbol == null)
//		The passed data object extends the space.template used to initialise the new object
// - called from events: guid,time,method,class...
space = {};
space.create = function(parameters) {
	var space = this.space;
	var time = 1*parameters[1];
	if (time == 0) time = _root.time;
	// Name object from create-event-guid (so it can be referred to across peers)

	var instanceName = parameters[0];
	if (instanceName == 0) instanceName = _root.guid();
	var symbol, hasSymbol = false;
	var class = parameters[3];
	var classRef = _root.classes[class];
	if (class == 'obj') symbol = parameters[4];
	else symbol = classRef.symbol;
	// Create new child symbol using the 3D-object template (space.template)
	space.createEmptyMovieClip(instanceName, 10000-space.zbLength); // NOTE! 10000-layer cos z-buffering not working
	var child = space[instanceName];
	if (symbol != null) {
		child.createEmptyMovieClip('swf', 0);
		child.swf.loadMovie('./swf/'+symbol);
		hasSymbol = true;
		}
	var template = space.template;
	for (var i in template) child[i] = template[i];
	this.children.push(child);				// object available in hierachical update tree of frame() calls
	space.zbuff[space.zbLength++] = child;	// place object into z-buffer for layer sorting
	// Populate new child symbol/object
	for (var i in classRef) child[i] = classRef[i];
	child.space = space;
	child.class = class;
	child.parent = this;
	child.applySR = this.applySR;
	child.colour = new Color(child.swf);
	child.children = [];
	child.hasSymbol = hasSymbol;
	child.time = time;
	child._alpha = 0;
	child.die = false;
	// Set the objects' dynamics() and call its init() - parameters[3] is 3DO-class
	if (classRef.init != null) classRef.init.call(child, parameters); // names and stores parameters, creates children etc
	return child;
	};

// _____________________________________________________________________________________________________________________ //
//   SPACE.FRAME
//   Method: Called every frame (not generic)
//   - this is the top level frame function called by SWF on frame2
space.frame = function() {
	var space = this.space;
	// Execute the template.frame() of each top-level child
	for (var i = this.children.length-1; i >= 0; --i) {
		var obj = this.children[i];
		obj.ax = obj.ay = obj.az = 0;
		obj.frame();
		// delete from this child list if marked for death
		if (obj.die) this.children.splice(i,1);
		}
	// Reduce the 3D coordinates to 2D for *ALL* objects with symbols (ie all layers, empty)
	// - ax,ay,az are absolute positions calculated in child frame() exec layers
	for (var i = 0; i < space.zbLength; i++) {
		var obj = space.zbuff[i];
		if (obj.die) obj.az = -100000000; // dead ones will get sorted to the end
		else {
			// Global rotation
			if (obj.applySR) {
				if (space.rotation != 0) obj.rotate('ax', 'az', space.rotation);
				// Ship rotation
				if (space.shipRotation != 0) {
					if (_root.network.thisIndex == 0) obj.rotate('ax', 'ay', space.shipRotation);
					else if (_root.network.thisIndex == 1) obj.rotate('ay', 'az', space.shipRotation);
					else if (_root.network.thisIndex == 2) obj.rotate('ay', 'ax', space.shipRotation);
					else if (_root.network.thisIndex == 3) obj.rotate('az', 'ay', space.shipRotation);
					}
				}
			// 3D -> 2D
			var m = space.zSCREEN/obj.az;
			obj._x = obj.ax*m;
			obj._y = obj.ay*m;
			// 3D -> Scale
			if (m < 0) m = 0; else if (m > 10) m = 10;
			obj._xscale = obj._yscale = obj.scale*m;
			// 3D -> Lighting
			//if (m <= 0.25) light = 1; else if (m < 1) light = (m-0.25)*10000/75; else light = 100;
			//light = light/2+50;
			//obj.colour.setTransform({ra:light, ga:light, ba:light});
			obj._alpha = obj.alpha;
			}
		}
	// Sort buffer by Z and synchronise rendering levels
	for (var i = 0; i < space.zbLength-1; i++) {
		var obj = space.zbuff[i];
		var maxz = obj.az, maxp = i;
		for (var j = i+1; j < space.zbLength; j++) if (space.zbuff[j].az > maxz) {
			maxz = space.zbuff[j].az;
			maxp = j;
			}
		if (maxp != i) {
			obj.swapDepths(space.zbuff[maxp]);
			space.zbuff[i] = space.zbuff[maxp];
			space.zbuff[maxp] = obj;
			}
		}

	// Delete nodes marked for death (they will have been sorted to the end of the buffer)
	while (space.zbuff[space.zbLength-1].die) space.zbuff[--space.zbLength].removeMovieClip();

	//logClr();
	//for (var i = 0; i < space.zbLength-1; i++) logAdd(space.zbuff[i].getDepth());

	};

// _____________________________________________________________________________________________________________________ //
// TEMPLATE
// - Create a set of methods and properties inherited by all symbols created in the space
// - various dynamics() and effects() can add and maintain other properties
template = {};
template.space = null;			// set by create() A ref to the space object
template.create = space.create;	// Inherits generic create() method
template.parent = null;			// Set by create(), a ref to the parent of this object
template.children = null;		// Children of this object (must be initialised by create)
template.die = false;			// Object deletion must be handled ny the space.frame()
template.motion = [];			// Motion functions to call in frame()
template.colour = null;			// Set by create() - objects colour transform
template.scale = 100;			// symbol scale
template.alpha = 100;			// symbol scale
template.time = 0;				// Objects' absolute birth time
template.x = 0;					// Objects relative position (ie relative to birth location and time)
template.y = 0;					//
template.z = 0;					//

// per-frame method for 3D-objects (not the space itself which has its own frame function)
// - frame() calculates the absolute position from the relative positions in the parent-child chain of each object
template.frame = function() {
	var p = [this.x, this.y, this.z];
	var t = _root.time-this.time;
	// Initialise symbol when loaded
	var swf = this.swf;
	if ((swf._width > 0) && (++swf.ctr == 1)) {
		swf._x = -swf._width/2;
		swf._y = -swf._height/2;
		}
	// Update objects dynamic properties
	if (!this.die) {
		if (this.dynamics != null) this.dynamics(t);
		// Execute this objects' current motion effects x += f(t)
		for (var i = 0; i < this.motion.length; i++)
			_root.motion[this.motion[i]].call(this, t);
		// Update absolute location for space.frame() to render
		this.ax += this.x;
		this.ay += this.y;
		this.az += this.z;
		}
	// Execute the frame() of each child
	// - if this is dead, propagate death to descendents
	for (var i = this.children.length-1; i >= 0; --i) {
		var obj = this.children[i];
		if (this.die) obj.die = true;
		else {
			obj.ax = this.ax;
			obj.ay = this.ay;
			obj.az = this.az;
			}
		obj.frame.call(obj);
		}
	// Restore x,y,z
	//this.x = p.shift();// *** bullet collision doesn't work properly when these are restored...?
	//this.y = p.shift();
	//this.z = p.shift();
	};

// Rotate an object
template.rotate = function(x, y, a) {
	a += Math.atan2(this[y], this[x]);
	var r = Math.sqrt(this[x]*this[x]+this[y]*this[y]);
	this[x] = Math.cos(a)*r;
	this[y] = Math.sin(a)*r;
	};

// _____________________________________________________________________________________________________________________ //
// 3D OBJECT CLASSES

// NOTE: (Not using languages' classes here as actionscript too lame-arse)
// Each class has:
// - init(data)	used to create and layout child objects within itself (and can instantiate an asscoiated symbol too)
// - dynamics()	called from it's frame() which then also calls space.update
//		each dynamics can call a set of effects functions depending on its state and time
//		NOTE: dynamics must base all changes on time
//		NOTE: dynamics are responsible for their coordinates being relative to parent
//				- this is because some dynamics update their own coodinates and some their childrens
// - classes may has only a symbol and no init() or dynamics()

// these phases and layers should be tree nodes
classes = {};
// _____________________________________________________________________________________________________________________ //
//   Spark
classes.spark = {};
classes.spark.init = function(parameters) {
	this.lat = parameters[4];
	this.lon = parameters[5];
	};
classes.spark.symbol = 'obj-spark.swf';
//   Ball
classes.ball = {};
classes.ball.init = function(parameters) {
	this.lat = parameters[4];
	this.lon = parameters[5];
	};
classes.ball.symbol = 'ball.swf';
// _____________________________________________________________________________________________________________________ //
//   OBJECT CLASS
classes.obj = {};
classes.obj.init = function(parameters) {
	this.create([0,0,'','implode',0,0,0]);
	this.shootable = false;
	this.motion = ['float'];
	var ri = this.time;
	this.rc = 1500;
	this.rr = 200+_root.rnd(ri,2)*1000;
	this.rt = 0.0005+_root.rnd(ri,1)*0.0005;
	this.v = 0.0001+_root.rnd(ri+5,1)*0.0004;
	if (_root.rnd(ri+5,2) > 0.5) this.v = -this.v;
	this.yr = 50+_root.rnd(ri+10,1)*500;
	this.yt = 0.001+_root.rnd(ri+10,2)*0.001;
	};
classes.obj.dynamics = function(t) {
	var swf = this.swf;
	// fade object in
	this.alpha = t/20;
	if (this.alpha > 100) this.alpha = this.shootable = 100;
	// set object frame
	this.f = 1+Math.floor((t/50)%swf._totalframes);
	swf.gotoAndStop(this.f);
	// Motion
	var r = this.rc+_root.motion.squareSine(t*this.rt)*this.rr;
	this.y = Math.sin(t*this.yt)*this.yr;
	r += Math.cos(t*this.yt)*this.yr;
	this.x = r*Math.sin(t*this.v);
	this.z = r*Math.cos(t*this.v);
	};
// _____________________________________________________________________________________________________________________ //
//   GUN FIRE CLASS
classes.fire = {};
classes.fire.symbol = 'obj-spark.swf';
classes.fire.init = function(parameters) {
	this.applySR = false;
	this.alpha = 0;
	this.state = false;
	};
classes.fire.dynamics = function(t) {
	this.swf.gotoAndStop(2);
	var gun = _root.gun;
	this.x = gun.object.ax+gun.ox[gun.frame]/10;
	this.y = gun.object.ay+gun.oy[gun.frame]/10;
	this.z = gun.object.az;
	if (this.state) {
		this.time = _root.time;
		this.state = false;
		_root.sounds.gun1.start();
		this.space.create([0,0,'','bullet']);
		_root.gun.lastShot = _root.time;
		}
	this.alpha = 100-t/2;
	if (this.alpha < 0) this.alpha = 0;
	};

// _____________________________________________________________________________________________________________________ //
//   BULLET CLASS
classes.bullet = {};
classes.bullet.symbol = 'obj-spark.swf';
classes.bullet.init = function(parameters) {
	this.applySR = false;
	this.x = _root.gun.fire.ax;
	this.y = _root.gun.fire.ay;
	this.z = _root.gun.fire.az;
	this.dx = (_root._xmouse-512) - _root.gun.fire.ax;
	this.dy = (_root._ymouse-384) - _root.gun.fire.ay;
	};
classes.bullet.dynamics = function(t) {
	this.swf.gotoAndStop(2);
	this.x = _root.gun.fire.ax + this.dx*t/200;
	this.y = _root.gun.fire.ay + this.dy*t/200;
	this.z = _root.gun.fire.az+t*5;
	if (this.z > 2000) this.die = true;
	// check all shootable objects for hit
	for (var i = 0; i < _root.game.children.length; i++) {
		var obj = _root.game.children[i];
		if (obj.shootable) {
			var dx = this.x - obj.x;
			var dy = this.y - obj.y;
			var dz = this.z - obj.z;
			var d2 = dx*dx+dy*dy+dz*dz;
			if (d2<5000) {
				// Hit! - do death and explosion sounds locally
				_root.sounds['die'+int(Math.random()*10)].start();
				createEvent('now,playSound,gun5,100', --_root.localguid);
				createEvent(''+(_root.time+490)+',playSound,gun4,100', --_root.localguid);
				_root.game.create([0,0,'','starBurst',10,0.25,obj.x,obj.y,obj.z]);
				// propagate the kill
				createEvent('now,objects.kill,'+obj._name, null);
				// Make sure obj can't be hit twice and bullet can't hit twice
				obj.shootable = false;
				this.die = true;
				i = _root.game.children.length;
				}
			}
		}
	};
// _____________________________________________________________________________________________________________________ //
//   GUN CLASS
classes.gun = {};
classes.gun.motion = ['float'];
classes.gun.init = function(parameters) {
	this.applySR = false;
	};
classes.gun.symbol = 'obj-gun.swf';
classes.gun.dynamics = function(t) {
	this.x = 0;
	this.y = 30;
	this.z = 100;
	this.scale = 25;
	this.swf.gotoAndStop(_root.gun.frame);
	};
// _____________________________________________________________________________________________________________________ //
//   STAR-BURST CLASS
classes.starBurst = {};
classes.starBurst.symbol = 'obj-spark.swf';
classes.subBurst = {};
// starBurst: stars,force,x,y,z
classes.starBurst.init = function(parameters) {
	var guid = parameters[0];
	this.stars = parameters[4];
	this.force = parameters[5];
	this.x = 1.0*parameters[6];
	this.y = 1.0*parameters[7];
	this.z = 1.0*parameters[8];
	this._visible = false;
	var ri = this.time;
	for (var i = 0; i < this.stars; i++) {
		var lat = _root.rnd(ri,1)*2*Math.PI;
		var lon = _root.rnd(ri+10,1)*2*Math.PI;
		ri+=20;
		var obj = this.create([0,this.time,'','subBurst',4,1.5,500,lat,lon,ri]);
		}
	};
// subBurst: stars,force,start,fadeout,lat,lon
classes.subBurst.init = function(parameters) {
	this.stars = parameters[4];
	this.force = parameters[5];
	this.start = parameters[6];
	this.lat = parameters[7];
	this.lon = parameters[8];
	var ri = 1*parameters[9];
	for (var i = 0; i < this.stars; i++) {
		var lat = _root.rnd(ri,3)*2*Math.PI;
		var lon = _root.rnd(ri+31,3)*2*Math.PI;
		ri+=51;
		var obj = this.create([0,0,'','spark',lat,lon]);
		obj.index = i;
		}
	};
classes.subBurst.symbol = 'obj-spark.swf';	
classes.starBurst.dynamics = function(t) {
	var onlyOne = true;
	// This is a subBurst
	if (this.start != null) {
		this.swf.gotoAndStop(4);
		if (t > this.start) {
			this.radius = (t-this.start)*this.force;
			onlyOne = false;
			}
		}
	else {
		this.swf.gotoAndStop(1);
		this.radius = 100+t*this.force;
		}
	this.force = this.force*0.97;
	if (this.alpha < 0) this.alpha = 0;
	var a = 100-(t/30);
	if (a < 0) {
		if (this.parent == this.space) this.die = true;
		a = 0;
		}
	// Update child positions: x = 0 + f(t) stylz
	for (var i = 0; i < this.children.length; i++) {
		var star = this.children[i];
		var s = this.radius*Math.cos(star.lon);
		star.x = s*Math.cos(star.lat+t/500);
		star.y = this.radius*Math.sin(star.lon)+t*t/8000;
		star.z = s*Math.sin(star.lat+t/500);
		if (onlyOne && (i>0)) star.alpha = 0; else star.alpha = a;
		}
	};
classes.subBurst.dynamics = classes.starBurst.dynamics;
// _____________________________________________________________________________________________________________________ //
//   TORUS CLASS
classes.torus = {};
// torus: points,radius,speed (has been converted specifically to target object)
classes.torus.init = function(parameters) {
	this.applySR = false;
	this.points = 1*parameters[4];
	this.radius = 1.0*parameters[5];
	this.speed = 1.0*parameters[6];
	for (var i = 0; i < this.points; i++) this.create([0,0,'','spark',50]);
	};
classes.torus.dynamics = function(t) {
	this.x = _root._xmouse-512;
	this.y = _root._ymouse-384;
	this.Z = 1000;
	var s;
	if (++this.ctr%50<12) s = 0; else s = t/250;
	var sec = Math.round(t/100);
	for (var i = 0; i < this.children.length; i++) {
		var point = this.children[i];
		point.swf.gotoAndStop(1);
		var angle = 2*Math.PI*i/this.children.length+this.angle/this.children.length+s;
		var r = this.parent.radius+this.radius*Math.sin(angle);
		point.x = this.x+r*Math.cos(this.angle);
		point.y = this.y+this.radius*Math.cos(angle);
		point.z = this.z+r*Math.sin(this.angle);
		point.scale = 200;
		if (s == 0) point._visible = (sec%2==0); else point._visible = true;
		}
	};

// _____________________________________________________________________________________________________________________ //
//   IMPLODE CLASS
classes.implode = {};
classes.implode.init = function(parameters) {
	this.stars = 25;
	this.radius = 200;
	this.x = 1.0*parameters[4];
	this.y = 1.0*parameters[5];
	this.z = 1.0*parameters[6];
	var ri = this.time;
	for (var i = 0; i < this.stars; i++) {
		var lat = _root.rnd(ri,1)*2*Math.PI;
		var lon = _root.rnd(ri+10,1)*2*Math.PI;
		var obj = this.create([0,0,'','spark',lat,lon]);
		obj.v = _root.rnd(ri+20,1)*100+10;
		obj.scale = 50;
		ri+=30;
		}
	};
classes.implode.dynamics = function(t) {
	this.radius = 500-t/3;
	if (this.radius < 100) this.radius = 100;
	var a = t/10;
	if (a > 200) this.die = true;
	if (a > 100) a = 200-a;
	for (var i = 0; i < this.children.length; i++) {
		var star = this.children[i];
		var r = this.radius*star.v/50;
		var s = r*Math.cos(star.lon);
		star.x = s*Math.cos(star.lat+t/star.v/2);
		star.y = r*Math.sin(star.lon);
		star.z = s*Math.sin(star.lat+t/star.v/2);
		star.alpha = a/2;
		}
	};

// _____________________________________________________________________________________________________________________ //
//   STARFIELD CLASS
classes.starField = {};
classes.starField.init = function(parameters) {
	this.stars = 100;
	this.radius = 2000;
	var ri = this.time;
	for (var i = 0; i < this.stars; i++) {
		var lat = _root.rnd(ri,1)*2*Math.PI;
		var lon = _root.rnd(ri+10,1)*2*Math.PI;
		var obj = this.create([0,0,'','spark',lat,lon]);
		obj.v = _root.rnd(ri+20,1)*100+10;
		obj.scale = 50;
		ri+=30;
		}
	};
classes.starField.dynamics = function(t) {
	this.radius = 500-t/3;
	if (this.radius < 100) this.radius = 100;
	var a = t/10;
	if (a > 200) this.die = true;
	if (a > 100) a = 200-a;
	for (var i = 0; i < this.children.length; i++) {
		var star = this.children[i];
		var r = this.radius*star.v/50;
		var s = r*Math.cos(star.lon);
		star.x = s*Math.cos(star.lat+t/star.v/2);
		star.y = r*Math.sin(star.lon);
		star.z = s*Math.sin(star.lat+t/star.v/2);
		star.alpha = a/2;
		}
	};

// _____________________________________________________________________________________________________________________ //
// 3D MOTION CLASSES
// - 3D Motion functions called by an objects' frame() if in its motion[] list

//   FLOAT
// Adds the metaparticles movement to passed object
motion = {};
motion.float = function(t) {
	var a = t/400;
	var m = (this._width+this._height)*this.scale/10000;
	var api4 = a*Math.PI/4;
	var api8 = a*Math.PI/8;
	var cospi4 = Math.cos(api4);
	var cospi8 = Math.cos(api8);
	var sinpi4 = Math.sin(api4);
	var sinpi8 = Math.sin(api8);
	this.x += m*cospi4*sinpi8;
	this.y += m*cospi4*cospi8;
	this.z += m*sinpi4;
	};
motion.squareSine = function(t) {
	var i = Math.floor(t/Math.PI)%6;
	if ((i == 1) || (i == 2)) return -1;
	if (i > 3) return 1;
	return Math.cos(t);
	};

// _____________________________________________________________________________________________________________________ //
// INTERFACES
// the swf addresses the object structure directly (its hash)

// maintains a list of the active interfaces
// - first when ordered by key (guid) is timeServer
// - timeServer sends current time after it hasn't recvd a time for >5s
interfaces = {};
interfaces.active = {};

// These functions are methods so they can be executed by event-messages
// - addInterface should propagate an ack event so it gains everyones info
//    interfaces.addInterface
interfaces.addInterface = function(parameters) {
	var guid = parameters.shift();
	var method = parameters.shift();
	var i = parameters[1];
	logAdd('interfaces.addInterface('+i+')');
	var ai = _root.interfaces.active;
	ai[i] = 1;
	updateInterfaces();
	createEvent('now,interfaces.ack,'+_root.network.thisInterface, null);
	};

//    interfaces.deleteInterface
interfaces.deleteInterface = function(parameters) {
	logAdd('interfaces.deleteInterface()');
	var guid = parameters.shift();
	var time = parameters.shift();
	var method = parameters.shift();
	var i = parameters[0];
	var ai = _root.interfaces.active;
	delete ai[i];
	updateInterfaces();
	};

//    interfaces.sync
interfaces.sync = function(parameters) {
	logAdd('sync');
	_root.timeError = getTimer();
	if (parameters[3] == _root.network.thisInterface) return; // don't ack our own sync
	if (_root.network.thisIndex != 0) return; // only the first interface responds to syncs
	createEvent('now,interfaces.ack,'+_root.network.thisInterface+','+_root.time, null);
	};
	
// Receiving an ack from another IF
// - set time from ack;
//    interfaces.ack
interfaces.ack = function(parameters) {
	var guid = parameters.shift();
	var method = parameters.shift();
	var i = parameters[1];
	if (i == _root.network.thisInterface) return; // don't ack ourselves
	logAdd('interfaces.ack('+i+')');
	var ai = _root.interfaces.active;
	ai[i] = 1;
	updateInterfaces();
	};

// Currently just logs
//    updateInterface
function updateInterfaces() {
	var ai = _root.interfaces.active;
	var orderedList = [];
	for (var guid in ai) orderedList.push(guid);
	orderedList.sort();
	for (var i = 0; i < orderedList.length; i++) {
		var j = orderedList[i];
		if (j == _root.network.thisInterface) _root.network.thisIndex = i;
		j = j.substr(-6);
		logAdd('   '+i+' : SWF-'+j.toUpperCase());
		}
	var index = _root.network.thisIndex;
	logAdd('   My index is '+index);
	logAdd('   I am '+_root.network.thisInterface);
	var p = _root.demo.personas;
	var i = index%4;
	_root.demo.setPersona(p[i]);
	logAdd("   I have an index, so I've set my persona and angle...");
	logAdd("   I am the "+p[i]+".");
	_root.game.rotation = i*Math.PI/2;
	logAdd("   My space is rotated by "+(_root.game.rotation/Math.PI)+' Pi radians.');
	}

// _____________________________________________________________________________________________________________________ //
// NETWORK
network = new XMLSocket();
network.port = 2012;
network.thisGuid = guid();
network.thisPeer = null; // defined after first message from our peer
network.peers = [];
network.thisInterface = null; // known after peer known
network.incomingEvents = {};
network.outgoingEvents = {};
network.finishedEvents = {};

// When connected to our peer, send out info
network.onConnect = function(success) {
	_root.network.connected = success;
	sendMessage('port='+network.port+'&add=interface&guid='+network.thisGuid, '');
	};

// Decode incoming message
network.onData = function(data) {
	var msg = data.split("|");
	var subject = msg[0];
	var content = msg[1];
	// split subject query-string and extract our peer and our name
	logAdd(subject);
	var qs = subject.split("&");
	for (var i = 0; i < qs.length; i++) {
		var j = qs[i].split("=");
		if (j[0] == 'to') _root.network.thisInterface = j[1];
		if ((j[0] == 'from') && (_root.network.thisPeer == null)) {
			var peer = _root.network.thisPeer = j[1];
			logAdd('My peer is '+peer);
			// make a guess at the persona (%n of last ip part)
//			var p = _root.demo.personas;
//			var k = peer.split('.');
//			k = k[k.length-1].split(':');
//			p = p[(k[0]%_root.demo.personas.length)-1];
//			_root.demo.setPersona(p);
//			logAdd("   I'll take a guess at my persona from my own IP number...");
//			logAdd("   I am the "+p+".");
			}
		if (j[0] == 'peers') {
			_root.network.peers = j[1].split(',');
			_root.network.peers.push(_root.network.thisPeer);
			}
		}
	// Split content quey-string and execute each event (guid=time,cmd,parameters...)
	qs = content.split("&");
	for (var i = 0; i < qs.length; i++) {
		var j = qs[i].split("=");
		if (_root.network.finishedEvents[guid] == null) createEvent(j[1], j[0]); // parameters, [guid]
		}
	};

// Send a message to the associated peer.pl
function sendMessage(subject, content) {
	logAdd('Sending ('+subject+')');
	_root.network.send(subject+"|"+content); // SWF automatically appends \x00
	}

// _____________________________________________________________________________________________________________________ //
	
// Sends queue of local events needing propagation
//    processOutgoingEvents
function processOutgoingEvents() {
	var oe = _root.network.outgoingEvents;
	var fe = _root.network.finishedEvents;
	// Build a query-string of the queued-events
	var msg = [];
	for (var guid in oe) msg.push(guid+"="+oe[guid]);
	var qs = msg.join("&");
	// Send queued and unprocessed events if any
	if (qs.length > 0) sendMessage('', qs);
	// Clear outgoing events buffer now that they've been propagated
	_root.network.outgoingEvents = {};
	}

// Executes events which were received before their birth-time
//    processIncomingEvents
function processIncomingEvents() {
	var ie = _root.network.incomingEvents;
	var fe = _root.network.finishedEvents;
	_root.del = [];
	for (var guid in ie) createEvent(ie[guid], guid); // inefficient way - calls createEvent every time
	// Can't delete items in ie while looping through ie
	while (_root.del.length > 0) delete ie[_root.del.pop()];
	}

// Create a new event locally
//    createEvent
function createEvent(parameters, guid) {
	var ie = _root.network.incomingEvents;
	var oe = _root.network.outgoingEvents;
	var fe = _root.network.finishedEvents;
	var plist = parameters.split(",");
	var t = 1*plist[0];
	// If no guid, event is new and needs to be propagated outward and also queued in incoming for local creation
	if (guid == null) {
		guid = _root.guid();
		oe[guid] = parameters;
		} else if (fe[guid]) return;
	ie[guid] = parameters;
	// Execute events' method if birth-time past, else queue in incomings
	if (t <= _root.time) {
		var path = '_root.'+plist[1];
		plist[0] = t;
		plist.unshift(guid);
		var eventMethod = eval(path);
		var parent = eval(parent(path));
		logAdd(parameters);
		eventMethod.call(parent, plist);
		_root.del.push(guid);
		fe[guid] = true;
		}
	return guid;
	}

// _____________________________________________________________________________________________________________________ //

function guid() {
	return 'g'+(Math.random()+getTimer());
	}

function parent(s) {
	return s.substr(0,s.lastIndexOf('.'));
	}

// Sends log output to its associated perl-peer for output
// - use logPattern = ^\. to filter since they all start with ...guid
function logAdd(entry) {
	//sendMessage('log='+entry, '');
	if (_root.shell._visible == false) return;
	_root.shell.lines.push(entry);
	while (_root.shell.lines.length>_root.shell.buffer) _root.shell.lines.shift();
	_root.shell.text = _root.shell.lines.join("\n");
	_root.shell.setTextFormat(_root.shell.format);
	}
	
function logClr() {
	_root.shell.lines = [];
	}

function tab(list, w) {
	for (var i = 0; i < list.length; i++) {
		var pad = '                                                  ';
		var s = list[i];
		var l = s.length;
		s = s.substr(0,w);
		if (s.length < w) s = s+p.substr(0,w-l);
		}
	}

// _____________________________________________________________________________________________________________________ //
// SHELL
function initialiseShell() {
_root.createTextField('shell',5,0,0,1024,768);
var shell = _root.shell;
shell.multiline = true;
shell.wordWrap = true;
shell.border = false;
shell.selectable = false;
shell.wordwrap = false;
shell.format = new TextFormat();
shell.format.color = 0xff00;
shell.format.font = 'Courier New';
shell.format.size = 24;
shell.lines = [];
shell.buffer = 28;
shell._visible = false;
shell.state = 0;
shell.onKeyDown = function() {
	var shell = _root.shell;
	_root.perl.perlScroll = false;
	_root.demo._visible = _root.demo.active = false;
	_root.game._visible = _root.game.active = true;
	_root.environment.lastActivity = _root.time;
	// Shell/game control key
	if (Key.isDown(Key.CONTROL) && Key.getCode() == Key.HOME)
		shell._visible = (shell.state = ++shell.state%3) != 0;
	// Return if shell invisible, else do command entry/execution
	if (!shell._visible) return;
	var s = shell.lines;
	var l = shell.lines.length-1;
	var start = _root.CWD.length+16;
	if (Key.getCode() == Key.DELETEKEY) s[l] = s[l].substr(0,start);
	else if (Key.getCode() == Key.BACKSPACE) {
		if (s[l].length > start) s[l] = s[l].substr(0, s[l].length-1);
		}
	else if (Key.getCode() == Key.ENTER) {
		var input = s[l].substr(start,1000);
		if (input != '') {
			// Extract command and data portions of input
			var obj;
			var cmd = input.split(' ');
			var parameters;
			if (cmd.length > 1) parameters = input.substr(input.indexOf(' ')+1,1000);
			cmd = cmd[0];
			if ((cmd == '?') || (cmd == 'help')) {
				// help - lame, cmd code and syntax strings should be in shell object
				logAdd('   sickShell Commands:');
				logAdd('      demo   view   peers');
				logAdd('      ls   cd   set   sync   time');
				logAdd('      interfaces   perl   connect');
				logAdd('   Keys:');
				logAdd('      CTRL + HOME  : enable/disable game and shell');
				}
			else if (cmd == 'ls') {
				// ls
				if (parameters == null) obj = refFromPath(_root.CWD); else obj = refFromPath(parameters);
				var n=0;
				for (var i in obj) {
					var t = typeOf(obj[i]);
					if (obj == _root) {
						if (t == 'object' || t == 'movieclip') {s.push('   '+i+' ('+t+')'); n++;}
						} else {s.push('   '+i+' ('+t+')'); n++;}
					}
				logAdd(''+n+' object(s).');
				}
			else if (cmd == 'cd') {
				// cd
				if (parameters == '..') _root.CWD = fixPath(parent(_root.CWD));
				else if (parameters != null) {
					obj = refFromPath(parameters);
					if (obj != null) _root.CWD = fixPath(parameters);
					}
				}
			else if (cmd == 'set') {
				// set
				var j = parameters.split('=');
				obj = refFromPath(j[0]);
				var t = typeOf(obj);
				if ((t != 'undefined') && (t != 'object') && (t != 'movieclip') && (t != 'function')) {
					obj=j[1];
					logAdd('   '+j[0]+' set to '+j[1]);
					} else logAdd('Could not set '+j[0]+' to '+j[1]);
				}
			else if (cmd == 'connect') {
				// connect
				sendMessage('connect='+parameters, '');
				logAdd('Connection requested');
				}
			else if (cmd == 'sync') {
				// sync
				_root.timeError = -getTimer();
				logAdd('Time reset.');
				}
			else if (cmd == 'time') {
				// time
				logAdd('Time : '+time);
				}
			else if (cmd == 'interfaces') {
				// interfaces
				updateInterfaces();
				}
			else if (cmd == 'peers') {
				// peers
				for (var i = 0; i < _root.network.peers.length; i++)
					logAdd('   '+i+' : '+_root.network.peers[i]);
				logAdd('   My peer is '+_root.network.thisPeer);
				}
			else if (cmd == 'view') {
				// view
				_root.environment.angle = 0.0+parameters;
				logAdd('   View angle set to '+_root.environment.angle);
				}
			else if (cmd == 'demo') {
				// demo
				if (parameters == null) {
					_root.demo._visible = _root.demo.active = true;
					_root.game._visible = _root.game.active = false;
					}
				else {
					_root.demo.setPersona(parameters);
					logAdd('   Persona : '+_root.demo.persona);
					}
				}
			else if (cmd == 'rand') {
				// rand
				logAdd(prand(100));
				}
			else logAdd('Unknown command: '+input);
			};
		prompt();
		}
	else s[l] += String.fromCharCode(Key.getAscii());
	while (s.length > _root.shell.buffer) s.shift();
	_root.shell.text = s.join("\n");
	_root.shell.setTextFormat(_root.shell.format);
	};
Key.addListener(shell);
prompt();
}

function prompt() {
	_root.shell.lines.push("sickShell:_root"+_root.CWD+'$');
	}

// todo: change events to use this
function refFromPath(path) {
	if (path == '.') return _root;
	var cwd = _root.CWD;
	if (path == '') path = cwd;
	else if (path.indexOf('.') != 0) path = cwd+'.'+path;
	var ref = eval('_root'+path);
	if (ref == null) logAdd("Couldn't resolve object!");
	return ref;
	}

// todo: get rid of this shit
function fixPath(path) {
	var cwd = _root.CWD;
	if (cwd == '.') cwd = '';
	if (path.indexOf('.') != 0) path = cwd+'.'+path;
	return path;
	}