In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

The Children of DiscoDroid (Not competition entry)

The further development of Children of DiscoDroid.
This is not the LD23 competition entry

Full version:
http://www.springworldgames.com/cod

Timelapse video (making of):
http://www.youtube.com/watch?v=u0040HCQCQc

Playthrough video:
http://www.youtube.com/watch?v=1CVwv3KeApU
Get Adobe Flash player
by krasse 02 May 2012
  • Related works: 1
  • Talk

    makc3d at 24 Apr 2012 01:28
    oh damn, there is supposed to be a music - didn't notice because of dubstep in background
    krasse at 24 Apr 2012 16:21
    The music didn't really get into the game (yet), but I guess that you really refer to the brilliant sound effects?
    makc3d at 24 Apr 2012 22:19
    I noticed sion in the source. Also, the bigges problem for me was finding the boss. Maybe direction arrow could help? Also #2, when you gather an army of dancing men and go to kill boss, your motion slows down too much, almost to 0.
    krasse at 25 Apr 2012 14:42
    Yeah, a direction arrow is a good idea. That is currently part of my todo-list. I also agree about the dancing speed. I wanted to have this upgradable between the levels + battery life on your stereo :)
    krasse at 29 Apr 2012 01:39
    I have added an intro, a direction arrow towards the boss and generative music when you dance :)

    Tags

    Embed
/**
 * Copyright krasse ( http://wonderfl.net/user/krasse )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/tMnw
 */

package {

    import flash.display.Sprite;

    public class ChildrenOfDiscoDroid extends Sprite {
        
        public var game:Game;
             
        public function ChildrenOfDiscoDroid():void {            
            game = new Game();
            addChild(game);
            game.init();
        }
        
    }
}
import flash.geom.Matrix3D;

import flash.text.TextFieldAutoSize;

import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.geom.Vector3D;
import flash.geom.Rectangle;
import flash.ui.Keyboard;
import flash.events.MouseEvent;
import flash.utils.setTimeout;
import flash.text.TextFormat;
import flash.text.TextField;
import flash.display.*;
import flash.display.BitmapDataChannel;
import flash.display.BitmapData;
import flash.events.KeyboardEvent;
import flupie.textanim.*;
import net.wonderfl.utils.FontLoader;
import caurina.transitions.*;
import org.si.sion.events.*;
import org.si.sound.*;
import org.si.sound.patterns.*;
import org.si.sion.*;
import org.si.sion.sequencer.*;
import org.si.sound.synthesizers.*;
import org.si.sion.utils.*;
import flash.events.Event;
import flash.display.Sprite;
import flash.display.Stage;
import away3d.cameras.*;
import away3d.lights.*;
import away3d.containers.*;
import away3d.primitives.*;
import away3d.core.base.*;
import away3d.core.draw.*;
import away3d.core.render.*;
import away3d.materials.*;


// Main Modes
var SHOWING_INTRO:int = 0;
var PLAYING:int = 1;

// Intro modes


// Play modes
var LEVEL_INTRO:int = 0;
var PLAYING_LEVEL:int = 1;

class PlayerData {
    public var healthLevel:int = 0;
    public var batteryLevel:int = 0;
    public var speedLevel:int = 0;
    public var fightLevel:int = 0;
    public var musicRadiusLevel:int = 0;
    public var musicStrengthLevel:int = 0;    
    
}


class Game extends Sprite {


    public var playerData:PlayerData = new PlayerData();

    public var sionPresets:SiONPresetVoice;
    
    
    public var mode:int = PLAYING;
    public var introMode:int = 0;
    public var playMode:int = LEVEL_INTRO;
    
    public var counter:int = 0;
    public var counter2:int = 0;
    public var counter3:int = 0;
    
    public var sionDriver:SiONDriver;
        
    public var mainCamera:Camera3D;
    public var view:View3D;
    public var scene:Scene3D;        
    
    public var keysDown:Object = {};
    public var fontAvailable:Object = {};
    public var musicSequencer:MusicSequencer = null;
    public var musicProvider:GameSequenceDataProvider = null;
 
    public var worldParticles:Array = [];
    public var screenParticles:Array = [];
    public var movingObjects:Array = [];
    public var solidObjects:Array = [];
    
    public var globalActions:Array = [];
    public var actionQueues:Object = {};    
    
    public var currentLevel:Level;
    public var currentLevelIndex:int = 0;
    
    public var switchToLevel:int;
    public var shouldSwitchLevel:Boolean;
 
    public var fontLoadedCount:int = 0; 
 
    public var sfxPlayer:SfxPlayer = new SfxPlayer();
    
    public var interfaceContainer:DisplayObjectContainer;
    public var backgroundContainer:DisplayObjectContainer;
    
    
    public var losing:Boolean = false;
    public var winning:Boolean = false;
    
    
    public function Game() {
    }

    public function createScene():void {
        winning = false;
        losing = false;
    
        musicSequencer.stop();
    
        mainCamera = new Camera3D();
        mainCamera.zoom = 4;
        mainCamera.focus = 200;

        if (backgroundContainer) {
            removeChild(backgroundContainer);
        }
        backgroundContainer = new Sprite();
        addChild(backgroundContainer);
        
        scene = new Scene3D();        
        if (view) {
            removeChild(view);
        }
        view = new View3D({camera:mainCamera, x:250, y:200, scene: scene});        
        // view.renderer = new QuadrantRenderer();
        addChild(view);

        if (interfaceContainer) {
            removeChild(interfaceContainer);
        }
        interfaceContainer = new Sprite();
        addChild(interfaceContainer);
    }
 
    public function playerWon():void {
        if (!losing) {
            winning = true;
            sfxPlayer.playVictory(this);
            var action:SerialAction = new SerialAction();
            var textAction:TextEffectAction = new TextEffectAction();
            textAction.y = 20;
            textAction.sizeMultiplier = 2;
            textAction.fontName = "Bebas";
            var switchAction:SwitchLevelAction;
            if (currentLevelIndex == 5) {
                textAction.strings = ["You defeated them All!", "FANTASTIC!"];
                textAction.actionDuration = 180;
                switchAction = new SwitchLevelAction(0);
                action.actions.push(textAction);
                action.actions.push(switchAction);
                currentLevel.globalActions.push(action);
            } else {    
                textAction.strings = ["You won!", "GREAT!"];
                switchAction = new SwitchLevelAction(currentLevelIndex + 1);
                action.actions.push(textAction);
                action.actions.push(switchAction);
                
                currentLevel.globalActions.push(action);
            }
        }
    }

    
    public function playerDied():void {
        if (!winning) {
            losing = true;
            
            sfxPlayer.playLoss(this);
            
            var action:SerialAction = new SerialAction();
            var textAction:TextEffectAction = new TextEffectAction();
            textAction.y = 20;
            textAction.sizeMultiplier = 2;
            textAction.strings = ["Sorry, You Died..."];
            textAction.fontName = "Bebas";
            var restartAction:RestartLevelAction = new RestartLevelAction();
            action.actions.push(textAction);
            action.actions.push(restartAction);
            
            currentLevel.globalActions.push(action);
        }
    }
    
    public function getGroundZ(x:Number, y:Number):Number {
        return currentLevel.groundMesh.getGroundZ(x, y);
    }

    public function restartLevel():void {
        switchLevel(currentLevelIndex);
    }
    
    public function reset():void {
        playerData = new PlayerData();
    }

    public function allRemoved(arr:Array):void {
        for (var i:int = arr.length-1; i>= 0; i--) {
            var o:GameObject = arr[i];
            o.removed(this);
        }        
    }
    
    public function tickAll(arr:Array):void {
        for (var i:int = arr.length-1; i>= 0; i--) {
            var o:GameObject = arr[i];
            o.tick(this);
            if (o.removeMe) {
                o.removed(this);
                arr.splice(i, 1);                
            }
        }
    } 

    public var forts:Array = [
        [
        [1, 1, 0, 0, 0, 1, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 2, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1]
        ],        
        [
        [1, 1, 1, 0, 0, 1, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 1, 1, 0, 1],
        [0, 0, 0, 1, 0, 0, 1],
        [0, 0, 0, 1, 3, 0, 1],
        [1, 0, 0, 1, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1]
        ],
        [
        [1, 1, 1, 1, 0, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 2, 0, 1],
        [1, 0, 1, 1, 1, 1, 0, 0, 1],
        [1, 0, 1, 0, 0, 1, 0, 0, 1],
        [1, 0, 1, 4, 0, 1, 0, 0, 1],
        [1, 0, 1, 0, 0, 1, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 1],
        [0, 1, 0, 0, 0, 0, 0, 1, 0],
        [0, 0, 1, 1, 1, 1, 1, 0, 0]
        ],
        [
        [1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
        [1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 2, 0, 0, 0],
        [1, 1, 0, 1, 0, 0, 0, 0, 0, 0],
        [0, 1, 2, 1, 0, 0, 0, 3, 0, 1],
        [0, 1, 0, 1, 0, 0, 0, 0, 0, 1],
        [0, 1, 1, 1, 1, 1, 1, 0, 0, 1],
        [1, 1, 0, 0, 0, 0, 1, 0, 0, 1],
        [1, 0, 0, 5, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        ],
        [
        [0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
        [1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 3, 0, 0, 0],
        [1, 1, 0, 1, 0, 0, 0, 0, 0, 0],
        [0, 1, 2, 1, 0, 0, 0, 4, 0, 1],
        [0, 1, 0, 1, 0, 0, 0, 0, 0, 1],
        [0, 1, 1, 1, 1, 1, 1, 0, 0, 1],
        [1, 1, 0, 0, 0, 0, 1, 0, 0, 1],
        [1, 0, 0, 5, 0, 6, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [0, 1, 1, 1, 1, 1, 1, 1, 1, 0]
        ]

    ];
    

    public function addFort(x:Number, y:Number, fortIndex:int, level:Level, spawner:ObstacleSpawner):void {
    
        var sketch:Array = forts[fortIndex];
        
        var wallSize:Number = 20;
        var wallHeight:Number = 40;

        var fortRect:Rectangle = new Rectangle(x, y, wallSize * sketch[0].length, wallSize * sketch.length);
        var noPlaceRect:Rectangle = fortRect.clone();
        noPlaceRect.inflate(wallSize * 2, wallSize * 2);
        spawner.noPlaceRectangles.push(noPlaceRect);
        
        var fortCenterX:Number = fortRect.x + fortRect.width * 0.5;
        var fortCenterY:Number = fortRect.y + fortRect.height * 0.5;
        
        var modifier:CircularConstantHeightModifier = new CircularConstantHeightModifier();
        modifier.innerRadius = Math.sqrt(2.0) * Math.max(fortRect.width / 2, fortRect.height / 2) + wallSize;
        modifier.outerRadius = modifier.innerRadius + 50;
        modifier.centerX = fortCenterX;
        modifier.centerY = fortCenterY;
        modifier.targetHeight = -20;
        
        level.groundMesh.heightModifiers.push(modifier);
 
        for (var j:int = 0; j<sketch.length; j++) {
            var arr:Array = sketch[j];
            for (var i:int = 0; i<arr.length; i++) {
            
                var item:int = arr[i];
                var wallX:Number = i * wallSize + x;
                var wallY:Number = j * wallSize + y;
                
                var npcType:int = -1;
                
                switch (item) {
                    case 0:
                        break;
                    case 1:                        
                        var wall:Wall = new Wall();
                        wall.position.x = wallX;
                        wall.position.y = wallY;
                        wall.size = wallSize;
                        wall.height = wallHeight;
                        level.solidObjects.push(wall);
                        break;
                    case 2:
                        npcType = BOSS_1;
                        break;
                    case 3:
                        npcType = BOSS_2;
                        break;
                    case 4:
                        npcType = BOSS_3;
                        break;
                    case 5:
                        npcType = BOSS_4;
                        break;
                    case 6:
                        npcType = BOSS_5;
                        break;
                }
                
                if (npcType != -1) {
                    var boss:NPC = new NPC();
                    boss.completeRemoveDistance = 99999999;
                    boss.npcType = npcType;
                    boss.position.x = wallX;
                    boss.position.y = wallY;
                    boss.modeWhenAlone = WAIT_MODE;
                    boss.mode = WAIT_MODE;
                    level.movingObjects.push(boss);
                    level.bosses.push(boss);
                }
            }
        }
        
    }

    public function addToActionQueue(queues:Object, name:String, action:GameAction):void {
        var queue:ActionQueue = queues[name];
        if (!queue) {
            queue = new ActionQueue();
            queues[name] = queue;
        }
        queue.arr.push(action);
    }
    
    // index 0 is the intro scene level
    public function loadLevel(index:int):void {

        var level:Level = new Level();

        var backgroundColor:uint = 0x000000;



        var i:int = 0;
        var rnd:Rndm = null;
        
        
        if (index == 0) {
            // Intro scene
 
            var bmd:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xff000000);
            
            rnd = new Rndm(3423);
            for (i=0; i<100; i++) {                
                bmd.setPixel32(rnd.integer(0, stage.stageWidth), rnd.integer(0, stage.stageHeight), 0xffffffff);
            }
            
            var bitmap:Bitmap = new Bitmap();
            bitmap.bitmapData = bmd;
            backgroundContainer.addChild(bitmap);
 

            var robot:Humanoid = new Humanoid();
            robot.bodyColor = 0x336633;
            robot.legColor = 0x888888;
            robot.armColor = robot.legColor;
            robot.headColor = 0xbbbbbb;
            robot.dancingSpeedFactor = 0.01;
            robot.animateDancing = true;
            robot.sizeFactor = 2.0;
            robot.position.x = 0;
            robot.position.y = 0;
            robot.position.z = 0;

            level.cameraController = new SlowApproachCameraController(this, mainCamera, robot);
            
            level.movingObjects.push(robot);

            var velZ:Number = 0.2;
            
            var planetDatas:Array = [
                // x, y, z, rotVel, velZ, radius, color
                [40, 200, 0, 0.1, velZ, 80, 0x442222], 
                [-140, 300, 200, 0.1, velZ, 30, 0x222222]
            ];
            
            for (i = 0; i<planetDatas.length; i++) {
                var data:Array = planetDatas[i];
                var planet:Planet = new Planet();
                planet.position.x = data[0];
                planet.position.y = data[1];
                planet.position.z = data[2];
                planet.rotVel = data[3];
                planet.velocity.z = data[4];
                planet.radius = data[5];
                planet.surfaceColor = data[6];
                level.movingObjects.push(planet);
            }
            
            var textEffect:TextEffectAction = new TextEffectAction();

            textEffect.fontName = "Aqua";
            textEffect.y = 10;
            textEffect.sizeMultiplier = 1.8;
            textEffect.strings = ["Children of DiscoDroid"];
            textEffect.actionDuration = 99999999;
            // addToActionQueue(level.actionQueues, "global", textEffect);
            level.globalActions.push(textEffect);


            var textArr:Array = ["In the year 2043,", 
                "the people of Earth finally got tired of all dancing robots", 
                " ",
                "They decided to launch all of them into space", 
                "On one such robot, called DiscoDroid, ", 
                "life has emerged on its metal body...", 
                " ",
                "You must now help one of those lifeforms to survive", 
                "Evil creatures have invaded from the dark side of the robot...",
                " ",
                "Press any key to start..."];
            
            
            for (i = 0; i<textArr.length; i++) {
                textEffect = new TextEffectAction();
                textEffect.fontName = "Bebas";
                textEffect.y = 220 + i * 22;
                textEffect.sizeMultiplier = 0.7;
                textEffect.strings = [textArr[i]];
                if (i == textArr.length - 1) {
                    textEffect.delay = 0;
                    textEffect.fontName = "Aqua";
                } else {
                    textEffect.delay = 80 * i;
                }
                textEffect.actionDuration = 99999999;
                // addToActionQueue(level.actionQueues, "global", textEffect);
                level.globalActions.push(textEffect);
            }
            var action:SwitchLevelWhenKeyPressedAction = new SwitchLevelWhenKeyPressedAction(1);
            action.actionDuration = 99999999;
            level.globalActions.push(action);
 
            musicProvider.song = INTRO_SONG;
            musicSequencer.play();
 
        } else {
        
            var infoDuration:int = 120;
            var textAction:TextEffectAction = new TextEffectAction();
            textAction.actionDuration = infoDuration;
            textAction.y = 20;
            textAction.sizeMultiplier = 2;
            textAction.strings = ["Level " + index];
            textAction.fontName = "Bebas";
            level.globalActions.push(textAction);

            if (index == 1) {
                var infoAction:TextEffectAction = new TextEffectAction();
                infoAction.actionDuration = 9999999999;
                infoAction.y = stage.stageHeight - 100;
                infoAction.sizeMultiplier = 1;
                infoAction.strings = ["Dance with 'A' (or 'D')", "Make dancers fight with 'S' (or 'F')"];
                infoAction.fontName = "Bebas";
                level.globalActions.push(infoAction);
            }
            
            musicProvider.song = DANCE_SONG;
            backgroundColor = 0x333333;
 
 
            level.groundMesh = new GroundMesh();
            level.player = new Player();
            level.player.position = new Vector3D(level.startX, level.startY, 0);
            level.cameraController = new InGameCameraController(this, mainCamera);
            
            rnd = new Rndm(3425);
            var spawner:ObstacleSpawner = new ObstacleSpawner();
            level.objectSpawners.push(spawner);

            var groundMeshSeed:int = 3423523;
            var spawnerSeed:int = 342342;
            var spawnerEnemies:Array = [MONSTER_1, MONSTER_2];
            var spawnerEnemyLikelihoods:Array = [2, 1];
            
 
            switch (index) {
                case 1:
                    addFort(0, 500, 0, level, spawner);
                    groundMeshSeed = 21352356;
                    spawnerSeed = 2838923;
                    break;
                case 2:
                    addFort(-500, 0, 1, level, spawner);
                    groundMeshSeed = 214523;
                    spawnerSeed = 2834982;
                    spawnerEnemyLikelihoods = [2, 2, 1];
                    spawnerEnemies = [MONSTER_1, MONSTER_2, MONSTER_3];
                    break;
                case 3:
                    addFort(-500, 1000, 2, level, spawner);
                    groundMeshSeed = 21423;
                    spawnerSeed = 283482;
                    spawnerEnemyLikelihoods = [1, 2, 2, 2, 1];
                    spawnerEnemies = [MONSTER_1, MONSTER_2, MONSTER_3, MONSTER_4, MONSTER_5];
                    break;
                case 4:
                    addFort(-500, -500, 3, level, spawner);
                    groundMeshSeed = 2314523;
                    spawnerSeed = 283495682;
                    spawnerEnemyLikelihoods = [1, 1, 2, 3, 2, 2, 1];
                    spawnerEnemies = [MONSTER_1, MONSTER_2, MONSTER_3, MONSTER_4, MONSTER_5, MONSTER_6, MONSTER_7];
                    break;
                case 5:
                    addFort(1000, 200, 4, level, spawner);
                    groundMeshSeed = 2214523;
                    spawnerSeed = 28349825;
                    spawnerEnemyLikelihoods = [1, 1, 2, 3, 3, 2, 1, 1];
                    spawnerEnemies = [MONSTER_1, MONSTER_2, MONSTER_3, MONSTER_4, MONSTER_5, MONSTER_6, MONSTER_7, MONSTER_8];
                    break;
            }
            spawner.enemyTypes = spawnerEnemies;
            spawner.enemyTypeLikelihoods = spawnerEnemyLikelihoods;
            spawner.seed = spawnerSeed;
            level.groundMesh.seed = groundMeshSeed;
        }
    
        graphics.beginFill(backgroundColor);
        graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
        graphics.endFill();

        currentLevel = level;
        currentLevelIndex = index;
    }
    
    public function loadAFont(fl:FontLoader, fontName:String):void {
            fl.load( fontName );
            fl.addEventListener( Event.COMPLETE, function(ev :Event) :void {
                fontLoadedCount++;                
                fontAvailable[fontName] = true;
            });              
    }
    
    public function init():void {
        inittrace(this);
       
//        var fontsToLoad = ["Aqua","Azuki","Cinecaption","Mona","Sazanami","YSHandy","VLGothic","IPAGP","IPAM","UmeUgo","UmePms","Bebas"];
        var fontsToLoad:Array = ["Aqua","Bebas"];
  
        for (var i:int = 0; i<fontsToLoad.length; i++) {
            var fl:FontLoader = new FontLoader();
            var fontName:String = fontsToLoad[i];
            loadAFont(fl, fontName);
        }
        
        sionDriver = new SiONDriver();
        sionDriver.play();
        
        sionPresets = new SiONPresetVoice();        

        musicProvider = new GameSequenceDataProvider(Math.round(Math.random() * 99999999 + 134));
        musicSequencer = new MusicSequencer(sionDriver, musicProvider);
        
        createScene();
        
        stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
        stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
        stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
        stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseClicked);            
        stage.addEventListener(Event.RESIZE, onResize);
        
        loadLevel(0);
    }
    
    public function switchLevel(newLevel:int):void {
        switchToLevel = newLevel;
        shouldSwitchLevel = true;
    }
    
    
    public function tick():void {
        if (shouldSwitchLevel) {
            if (currentLevel) {
                createScene();
            }
            loadLevel(switchToLevel);
            shouldSwitchLevel = false;
        }
        try {
            tickAll(solidObjects);
            tickAll(movingObjects);
            tickAll(worldParticles);
            tickAll(screenParticles);
            for (var key:String in actionQueues) {
                actionQueues[key].tick(this);
            }
            tickAll(globalActions);
        } catch (error:Error) {
            trace("Error global game tick " + error.name + " " + error.message);
        }
        try {
            if (currentLevel) {
                currentLevel.tick(this);
            }
        } catch (error:Error) {
            trace("Error level tick " + error.name + " " + error.message);
        }
        musicSequencer.tick();
        
        counter++;
    }
        
        
    public function onResize(e:Event):void {
    }

    public var tickTime:int = 0;    
    public var wantedTimeInterval:Number = 1000.0 / 25.0;    
    
    public function onEnterFrame(e:Event):void {
        var currentTime:int = flash.utils.getTimer();
             
        var ticks:int = 0;
        while (tickTime < currentTime) {
            tick();
            tickTime += wantedTimeInterval;
            ticks++;
            if (ticks > 4) {               
                tickTime = currentTime;
                break;
            }
        }
        if (ticks > 0) {
            view.render();
        }
    }

    public function keyDown(key:int):Boolean {
        return keysDown[key];
    }

    public function onMouseClicked(event:MouseEvent):void {                   
//         trace("Mouse clicked " + stage.mouseX + ", " + stage.mouseY);
    }

    public function onKeyDown(event:KeyboardEvent):void {            
        keysDown[event.keyCode] = true;
    }

    public function onKeyUp(event:KeyboardEvent):void {            
        keysDown[event.keyCode] = false;
    }
    
}

class ActionQueue {
    
    public var action:GameAction;
    
    public var arr:Vector.<GameAction> = new Vector.<GameAction>();
    
    public function tick(game:Game):void {
        if (!action) {
            if (arr.length > 0) {
                action = arr.shift();
            }
        } else {
            action.tick(game);
            if (action.removeMe) {
                action.removed(game);
                action = null;
            }
        }
    }
    
    
}

class RectangularConstantHeightModifier implements HeightModifier {

    public var innerRectangle:Rectangle = new Rectangle(0, 0, 100, 100);
    public var outerBorder:Number = 20;
    
    public var targetHeight:Number = -50;

    private var outerRectangle:Rectangle = null;
    
    public function applicable(x:Number, y:Number):Boolean {
        if (!outerRectangle) {
            outerRectangle = new Rectangle(innerRectangle.x - outerBorder, innerRectangle.y - outerBorder, innerRectangle.width + outerBorder * 2, innerRectangle.height + outerBorder * 2);
            // trace("creating outer rectangle " + outerRectangle);
        }
        // if (Math.random() < 0.01) {
            // trace("Checking applicable " + x + ", " + y + " rect: " + outerRectangle);
        // }
        return outerRectangle.contains(x, y);
    }
    
    public function applyModifier(x:Number, y:Number, z:Number):Number {
        // trace("applying height modifier " + x + " " + y);
        if (innerRectangle.contains(x, y)) {
            // trace("returning height " + targetHeight);
            return targetHeight;
        } else {
            // var fractionX:Number = Math.max(0, (outerBorder - Math.min(Math.abs(innerRectangle.x - x), Math.abs(innerRectangle.x + innerRectangle.width - x))) / outerBorder);
            // var fractionY:Number = Math.max(0, (outerBorder - Math.min(Math.abs(innerRectangle.y - y), Math.abs(innerRectangle.y + innerRectangle.height - y))) / outerBorder);
            
            return z;
        }
    }

}


class CircularConstantHeightModifier implements HeightModifier {

    public var centerX:Number = 0;
    public var centerY:Number = 0;
    public var innerRadius:Number = 100;
    public var outerRadius:Number = 150;
    
    public var targetHeight:Number = -50;
    
    public function applicable(x:Number, y:Number):Boolean {
        
        // if (Math.random() < 0.01) {
            // trace("Checking applicable " + x + ", " + y + " rect: " + outerRectangle);
        // }
        return distanceBetween(x, y, centerX, centerY) < outerRadius;
    }
    
    public function applyModifier(x:Number, y:Number, z:Number):Number {
        // trace("applying height modifier " + x + " " + y);
        var dist:Number = distanceBetween(x, y, centerX, centerY);
        
        var fraction:Number = (dist - innerRadius) / (outerRadius - innerRadius);
        
        if (fraction <= 0) {
            return targetHeight;
        } else if (fraction <= 1) {
            return fraction * z + (1.0 - fraction) * targetHeight;
        } else {
            return z;
        }
    }

}



class GameObject {
    public var team:int = 0;

    public var removeMe:Boolean = false;

    public var initialized:Boolean = false;

    public var counter:uint = 0;
    
    public function initIfNecessary(game:Game):void {
        if (!initialized) {
            init(game);
            initialized = true;
        }
    }
    
    public function init(game:Game):void {
    }
    
    public function tick(game:Game):void {
        initIfNecessary(game);
        counter++;
    }
    
    public function removed(game:Game):void {
        // Remove yourself from the scene etc.
    }
}

class CameraController extends GameObject {
    public var game:Game;
    public var camera:Camera3D;
    
    public function CameraController(game:Game, camera:Camera3D):void {
        this.game = game;
        this.camera = camera;
    }
    override public function tick(game:Game):void {
        super.tick(game);
    }
}

class InGameCameraController extends CameraController {    
    public function InGameCameraController(game:Game, camera:Camera3D):void {
        super(game, camera);
    }
    
    public var targetPosition:Vector3D = null;
    
    override public function tick(game:Game):void {
        super.tick(game);
        var level:Level = game.currentLevel;
        if (level && level.player) {
            var player:Player = level.player;
            
            var playerDirection:Vector3D = player.direction;
            var playerPosition:Vector3D = player.position;
            
            var wantedPosition:Vector3D = playerPosition.clone();
            
            var directionIncrement:Number = 50;
            wantedPosition.incrementBy(new Vector3D(player.direction.x * directionIncrement, player.direction.y * directionIncrement, 0));
            if (targetPosition == null) {
                targetPosition = wantedPosition.clone();
            }
            var smoothFactor:Number = 0.9;
            targetPosition.x = smoothFactor * targetPosition.x + (1.0 - smoothFactor) * wantedPosition.x;
            targetPosition.y = smoothFactor * targetPosition.y + (1.0 - smoothFactor) * wantedPosition.y;
            
            var factor:Number = 2;
            camera.zoom = 4;
            camera.x = targetPosition.x;
            camera.y = targetPosition.y - 70 * factor;
            camera.z = targetPosition.z - 70 * factor;

            // Do some cross product magic to get a nice up.
            // We have the forward vector and need a vector that is perpendicular to forward and z-axis
            // Alas: forward x z-axis gives the right vector. The result is then forward x right
            camera.lookAt(new Vector3D(targetPosition.x, targetPosition.y + 40, targetPosition.z), new Vector3D(0, 1, -1));
        }    
    }
}

class SlowApproachCameraController extends CameraController {    

    public var obj:PhysicalObject;

    public function SlowApproachCameraController(game:Game, camera:Camera3D, obj:PhysicalObject):void {
        super(game, camera);
        this.obj = obj;
    }
    
    
    override public function tick(game:Game):void {
        super.tick(game);

        if (obj.counter > 1) {
            var p:Vector3D = obj.position;
            camera.x = p.x;
            camera.y = p.y - 30;
            camera.z = p.z - 30;

            // Do some cross product magic to get a nice up.
            // We have the forward vector and need a vector that is perpendicular to forward and z-axis
            // Alas: forward x z-axis gives the right vector. The result is then forward x right
            camera.lookAt(new Vector3D(p.x, p.y, p.z), new Vector3D(0, 1, -1));
            
            
            // obj.container.rotationZ = counter * 0.5;
        }
        // trace("camera y " + camera.y);
    }
}

class GameAction extends GameObject {
    public var actionCounter:int = 0;
    public var actionDuration:int = 60;
    
    override public function tick(game:Game):void {
        super.tick(game);
        actionCounter++;
        if (actionCounter > actionDuration) {
            removeMe = true;
        }
    }
}

class SerialAction extends GameAction {

    public var actions:Vector.<GameAction> = new Vector.<GameAction>();
    
    public var actionIndex:int = 0;
    
    override public function removed(game:Game):void {
        for (var i:int = 0; i<actions.length; i++) {
            actions[i].removed(game);
        }
    }
    
    override public function tick(game:Game):void {
        // Remove me when the last action is done...
        if (actionIndex < actions.length) {
            var action:GameAction = actions[actionIndex];
            if (action.removeMe) {
                actionIndex++;
            } else {
                action.tick(game);
            }
        } else {
            removeMe = true;
        }
    }
    
}


 var CENTER_SCREEN_MESSAGE:int = 0;
 var SCREEN_INFO:int = 1;
 var WORLD_INFO:int = 2;


class TextEffectAction extends GameAction {

    public var x:Number = 0;
    public var y:Number = 0;
    
    public var sizeMultiplier:Number = 1.0;
        
    public var extraYStepLength:Number = 10;
    public var extraXStepLength:Number = 0;
    
    public var strings:Array = [];
    public var delay:int;
    
    public var addedStuff:Array = [];
    
    public var toAdd:Array = [];

    
    public var theType:int = CENTER_SCREEN_MESSAGE;

    public var initializedTF:Boolean = false;

    public var fontName:String = "Aqua";
    
    override public function init(game:Game):void {    
        super.init(game);

        initTF(game);
    }
    
    public function initTF(game:Game):void {
        if (game.fontAvailable[fontName]) {
            switch (theType) {
                case CENTER_SCREEN_MESSAGE:
                    var currentY:Number = y;
                    for (var i:int=0; i<strings.length; i++) {
                        var tf:TextField = new TextField();
                        tf.embedFonts = true;
                        tf.defaultTextFormat = new TextFormat( fontName, 20 * sizeMultiplier, 0xFFCC00);
                        tf.autoSize = TextFieldAutoSize.CENTER;
                        tf.text = strings[i];
                        tf.x = 0.5 * (game.stage.stageWidth - tf.textWidth);
                        tf.y = currentY;
                        tf.textColor = 0xffffff;
                        
                        toAdd.push(tf);
                        currentY += tf.textHeight + extraYStepLength;
                    }
                    break;
                default:
                    trace("Unknown text effect type " + theType);
                    break;
            }    
            initializedTF = true;
        }
    }
    
    override public function removed(game:Game):void {
        for (var i:int=0; i<addedStuff.length; i++) {
            var tf:TextField = addedStuff[i];
            game.interfaceContainer.removeChild(tf);
        }
    }
    
     override public function tick(game:Game):void {
         super.tick(game);
         if (! initializedTF) {
            initTF(game);
         } else {
            switch (theType) {
                case CENTER_SCREEN_MESSAGE:
                    if (actionCounter >= delay && toAdd.length > 0) {
                        for (var i:int = 0; i<toAdd.length; i++) {
                            game.interfaceContainer.addChild(toAdd[i]);                        
                            addedStuff.push(toAdd[i]);
                        }
                        toAdd.length = 0;
                    }
                    break;                    
            }
        }
         // trace("Ticking text effect " + game.counter);
     }
    

}



class SwitchLevelWhenKeyPressedAction extends GameAction {
    
    public var levelIndex:int = 0;
    
    public function SwitchLevelWhenKeyPressedAction(levelIndex:int = 0):void {
        super();
        this.levelIndex = levelIndex;
    }
    
     override public function tick(game:Game):void {
         super.tick(game);
         for (var key:String in game.keysDown) {
            if (game.keysDown[key]) {
                game.switchLevel(levelIndex);
                removeMe = true;
                break;
            }
         }
     }
   
}

class RestartLevelAction extends GameAction {
        
    public function RestartLevelAction():void {
        super();
    }
    
     override public function tick(game:Game):void {
         super.tick(game);
         game.restartLevel();
         removeMe = true;
     }   
}


class SwitchLevelAction extends GameAction {
    
    public var levelIndex:int = 1;
        
    public function SwitchLevelAction(levelIndex:int):void {
        super();
        this.levelIndex = levelIndex;
    }
    
     override public function tick(game:Game):void {
         super.tick(game);
         game.switchLevel(levelIndex);
         removeMe = true;
     }   
}



class Level extends GameObject {
    public var startX:Number = 50;
    public var startY:Number = 50;

    
    public var worldParticles:Array = [];
    public var screenParticles:Array = [];
    public var movingObjects:Array = [];
    public var solidObjects:Array = [];
    public var projectiles:Array = [];
    public var objectSpawners:Array = [];
    public var pickups:Array = [];
     
    public var globalActions:Array = [];
    public var actionQueues:Object = {};    

    public var cameraController:CameraController;
    public var player:Player;
    public var bosses:Array = [];
    public var groundMesh:GroundMesh;

    override public function removed(game:Game):void {
        super.removed(game);
        var arr:Vector.<Object3D> = game.scene.children;
        for (var i:int=0; i<arr.length; i++) {
            game.scene.removeChild(arr[i]);
        }
        // game.allRemoved(worldParticles);
        // game.allRemoved(screenParticles);
        game.allRemoved(movingObjects);
        game.allRemoved(pickups);
        // game.allRemoved(solidObjects);
        // game.allRemoved(projectiles);
        game.allRemoved(globalActions);
        // for (var key:String in actionQueues) {
            // game.allRemoved(actionQueues[key]);
        // }
        // player.removed(game);
        // cameraController.removed(game);
        // groundMesh.removed(game);
    }

    
    override public function tick(game:Game):void {
        try {
            if (player) {
                player.tick(game);
            }
        } catch (error:Error) {
            trace("Error player tick " + error.name + " " + error.message);
        }
        try {
            if (cameraController) {
                cameraController.tick(game);
            }
        } catch (error:Error) {
            trace("Error cam control tick " + error.name + " " + error.message);
        }
        try {
            game.tickAll(objectSpawners);
        } catch (error:Error) {
            trace("Error object spawners tick " + error.name + " " + error.message);
        }
        try {
            game.tickAll(pickups);
        } catch (error:Error) {
            trace("Error pickups tick " + error.name + " " + error.message);
        }

        game.tickAll(solidObjects);
        try {
            game.tickAll(movingObjects);
        } catch (error:Error) {
            trace("Error moving objects tick " + error.name + " " + error.message);
        }
        game.tickAll(worldParticles);
        game.tickAll(screenParticles);
        game.tickAll(projectiles);
        for (var key:String in actionQueues) {
            actionQueues[key].tick(game);
        }
        game.tickAll(globalActions);

        try {
            if (groundMesh) {
                groundMesh.tick(game);
            }
        } catch (error:Error) {
            trace("Error mesh tick " + error.name + " " + error.message);
        }

    }


}


interface ObjectSpawner {
    function tick(game:Game):void;
    
    function objectRemoved(obj:PhysicalObject, spawnInfo:SpawnInfo):void;
}

class GridObjectSpawner extends GameObject implements ObjectSpawner {

    public var noPlaceRectangles:Vector.<Rectangle> = new Vector.<Rectangle>();
    
    public var spawnInfos:Vector.<SpawnInfo> = new Vector.<SpawnInfo>();

    public var seed:int = 343243;
    public var gridSize:Number = 300;
    public var subGridCells:int = 20; // Totals cells 20 * 20
    public var updatePeriod:int = 33;
    public var restrictRadius:Number = 10;
    
    public var spawnDistance:Number = 200;
    
    public function getPosition(si:SpawnInfo, rnd:Rndm):Vector3D {
        var subGridSize:Number = gridSize / subGridCells;
        return new Vector3D(si.gridX * gridSize + si.subGridX * subGridSize + rnd.random() * subGridSize, 
            si.gridY * gridSize + si.subGridY * subGridSize + rnd.random() * subGridSize, 0);
    }
    
    public function findSpawnInfo(gridX:int, gridY:int, subGridX:int, subGridY:int):SpawnInfo {
        for (var i:int = 0; i<spawnInfos.length; i++) {
            var si:SpawnInfo = spawnInfos[i];
            if (si.matchData(gridX, gridY, subGridX, subGridY)) {
                return si;
            }
        }
        return null;
    }
    
    public function getSpawnCount(gridX:int, gridY:int, game:Game):int {
        return 5;
    }
    
    override public function tick(game:Game):void {
        if ((game.counter % updatePeriod) == 0) {
            try {
                var distance:Number = spawnDistance;
                
                var pos:Vector3D = game.currentLevel.player.position;
                var minGridX:int = Math.floor((pos.x - distance) / gridSize);
                var minGridY:int = Math.floor((pos.y - distance) / gridSize);
                var maxGridX:int = Math.floor((pos.x + distance) / gridSize);
                var maxGridY:int = Math.floor((pos.y + distance) / gridSize);

                var subGridSize:Number = gridSize / subGridCells;
                
                for (var gridX:int = minGridX; gridX <= maxGridX; gridX++) {
                    for (var gridY:int = minGridY; gridY <= maxGridY; gridY++) {
                        var count:int = getSpawnCount(gridX, gridY, game);
                        
                        var newSeed:uint = Math.abs(seed + gridX + gridY * seed);
                        var rnd:Rndm = new Rndm(newSeed);
                        
                        // trace("grid " + gridX + ", " + gridY + " gives seed " + newSeed + " counter: " + game.counter);

                        for (var i:int = 0; i<count; i++) {
                            var subGridX:int = rnd.integer(0, subGridCells);
                            var subGridY:int = rnd.integer(0, subGridCells);                
                            
                            var theX:Number = gridX * gridSize + subGridX * subGridSize;
                            var theY:Number = gridY * gridSize + subGridY * subGridSize;
                            
                            
                            var okToPlace:Boolean = true;
                            for (var j:int = 0; j<noPlaceRectangles.length; j++) {
                                if (noPlaceRectangles[j].contains(theX, theY)) {
                                    okToPlace = false;
                                    break;
                                }
                            }
                            
                            if (okToPlace) {
                                var si:SpawnInfo = findSpawnInfo(gridX, gridY, subGridX, subGridY);
                                if (si) {
                                    // Object already exists and should not be spawned again
                                } else {
                                    si = new SpawnInfo(this, gridX, gridY, subGridX, subGridY);
                                    spawnObject(si, game);
                                    // trace("Not find spawninfo " + si + " i:" + i + " counter: " + game.counter);
                                    spawnInfos.push(si);
                                }
                            }
                        }
                    }
                }
                
            } catch (error:Error) {
                trace("Error grid spawner tick " + error.name + " " + error.message);
            }
        }
    }
    
    public function spawnObject(spawnInfo:SpawnInfo, game:Game):void {
    }
    
    public function objectRemoved(obj:PhysicalObject, spawnInfo:SpawnInfo):void {
        // trace("someone calling objectRemoved() " + spawnInfo);
        var found:Boolean = false;
        for (var i:int = spawnInfos.length-1; i>=0; i--) {
            var si:SpawnInfo = spawnInfos[i];
            if (si.matchData(spawnInfo.gridX, spawnInfo.gridY, spawnInfo.subGridX, spawnInfo.subGridY)) {
                spawnInfos.splice(i, 1);
                found = true;
                break;
            }
        }
        if (!found) {
            trace("Could not find object with spawnInfo " + spawnInfo);
        }
        // } else {
            // trace("Removed object with spawnInfo " + spawnInfo);
        // }
    }

}


function getProbabilityDistribution(likelihoods:Array):Array {
    var result:Array = [];

    var length:int = likelihoods.length;

    var sum:Number = 0.0;
    var i:int = 0;
    for (i = 0; i < length; i++) {
        sum += likelihoods[i];
    }

    result[0] = likelihoods[0];
    for (i = 1; i < length; i++) {
        result[i] = (result[i - 1] + likelihoods[i]);
    }
    if (sum > 0.000000001) {
        for (i = 0; i < length; i++) {
            result[i] /= sum;
        }
    } else {
        // Setting all to the same person
        var increment:Number = 1.0 / length;
        for (i = 0; i < length; i++) {
            result[i] = (i+1) * increment;;
        }
    }
    return result;
}

function getProbabilityFractions(likelihoods:Array):Array {

    var result:Array = [];

    var length:int = likelihoods.length;

    var sum:Number = 0.0;
    var i:int = 0;
    for (i = 0; i < length; i++) {
        sum += likelihoods[i];
    }

    if (sum > 0.000000001) {
        for (i = 0; i < length; i++) {
            result[i] = likelihoods[i] / sum;
        }
    } else {
        // Setting all to the same person
        for (i = 0; i < length; i++) {
            result[i] = 1.0 / length;
        }
    }

    //    logit("ProbabilityFractions input: " + likelihoods + " result: " + result + "<br />");
    return result;
}


function sampleIndexIntegerDistribution(rnd:Rndm, cumulative:Array):int {
    var rndValue:Number = rnd.random();
    for (var j:int = 0; j < cumulative.length; j++) {
        if (rndValue < cumulative[j]) {
            return j;
        }
    }
    return 0; // This should never happen
}




class ObstacleSpawner extends GridObjectSpawner {

    public static var PINE_TREE:int = 0;
    public static var ROUND_TREE:int = 1;
    
    public static var BATTERY:int = 0;
    public static var HEALTH:int = 1;
    
    public var enemyLikelihood:Number = 2;
    public var pickupLikelihood:Number = 4;
    public var obstacleLikelihood:Number = 8;
    
    public var enemyTypes:Array = [MONSTER_1, MONSTER_2];
    public var obstacleTypes:Array = [PINE_TREE, ROUND_TREE];
    public var pickupTypes:Array = [BATTERY, HEALTH];
    
    public var enemyTypeLikelihoods:Array = [2, 1];
    public var obstacleTypeLikelihoods:Array = [1, 1];
    public var pickupTypeLikelihoods:Array = [3, 2];
    
    
    override public function getSpawnCount(gridX:int, gridY:int, game:Game):int {
        return 5;
    }
    
    override public function spawnObject(spawnInfo:SpawnInfo, game:Game):void {
  
        var level:Level = game.currentLevel;
        var theSeed:uint = Math.abs(spawnInfo.gridX + spawnInfo.gridY * 47829 + spawnInfo.subGridX + spawnInfo.subGridY * 424353);
        var rnd:Rndm = new Rndm(theSeed + seed);        
        var rndVal:Number = rnd.random();
        
        var spawnType:int = sampleIndexIntegerDistribution(rnd, 
            getProbabilityDistribution([enemyLikelihood, pickupLikelihood, obstacleLikelihood]));
        
        switch (spawnType) {
            case 0: // Enemy
                var npc:NPC = new NPC();
                npc.spawnInfo = spawnInfo;
                npc.position = spawnInfo.spawner.getPosition(spawnInfo, rnd);                
                
                npc.npcType = enemyTypes[sampleIndexIntegerDistribution(rnd, 
                    getProbabilityDistribution(enemyTypeLikelihoods))];
                
                level.movingObjects.push(npc);
                // trace("Spawning object " + spawnInfo + " movers: " + level.movingObjects.length);
                break;
            case 1: // Pickup

                var pickupType:int = sampleIndexIntegerDistribution(rnd, 
                    getProbabilityDistribution(pickupTypeLikelihoods));

                if (pickupType == 0) {
                    var batteryPickup:BatteryPickup = new BatteryPickup();
                    batteryPickup.spawnInfo = spawnInfo;
                    batteryPickup.position = spawnInfo.spawner.getPosition(spawnInfo, rnd);                
                    level.pickups.push(batteryPickup);                    
                } else if (pickupType == 1) {
                    var healthPickup:HealthPickup = new HealthPickup();
                    healthPickup.spawnInfo = spawnInfo;
                    healthPickup.position = spawnInfo.spawner.getPosition(spawnInfo, rnd);                
                    level.pickups.push(healthPickup);                                    
                }
                break;
            case 2: // Obstacle
                var tree:Tree = new Tree();
                tree.spawnInfo = spawnInfo;
                tree.type = rnd.random() < 0.5 ? 0 : 1;
                tree.sizeFactor = 0.6 + 0.8 * rnd.random();
                tree.position = spawnInfo.spawner.getPosition(spawnInfo, rnd);                
                level.solidObjects.push(tree);
                break;
        }
                // trace("Spawning object " + spawnInfo + " solids: " + level.solidObjects.length);
    }

}


class SpawnInfo {

    public var gridX:int;
    public var gridY:int;
    public var subGridX:int;
    public var subGridY:int
    public var spawner:GridObjectSpawner;
    
    public function matchData(gx:int, gy:int, sgx:int, sgy:int):Boolean {
        return gx == gridX && gy == gridY && sgx == subGridX && sgy == subGridY;
    }
    
    
    public function toString():String {
        return "{" + [gridX, gridY, subGridX, subGridY].join(", ") + "}";
    }
    
    public function SpawnInfo(spawner:GridObjectSpawner, gx:int, gy:int, sgx:int, sgy:int) {
        this.spawner = spawner;
        gridX = gx;
        gridY = gy;
        subGridX = sgx;
        subGridY = sgy;
    }
}


class PhysicalObject extends GameObject {
    public var spawnInfo:SpawnInfo;

    public var respawnWhenRemoved:Boolean = true;
    
    public var position:Vector3D = new Vector3D();
    public var direction:Vector3D = new Vector3D(0, 1, 0);
    
    public var dimension:Vector3D = new Vector3D(1, 1, 1); // Sizes 
    
    public var container:ObjectContainer3D;

    public var containerPartOfScene:Boolean = false;
    public var containerCheckPeriod:int = 15;
    
    
    public function getRectangle():Rectangle {
        return new Rectangle(position.x - dimension.x * 0.5, position.y - dimension.y * 0.5, dimension.x, dimension.y);
    }

    public function getTestRectangle(stepX:Number, stepY:Number):Rectangle {
        return new Rectangle(position.x - dimension.x * 0.5 + stepX, position.y - dimension.y * 0.5 + stepY, dimension.x, dimension.y);
    }
    
    public function shouldRemoveContainer(game:Game):Boolean {
        return false;
    }

    public function shouldAddContainer(game:Game):Boolean {
        return false;
    }

    public function shouldRemoveCompletely(game:Game):Boolean {
        return false;
    }

    public function containerRemoved(game:Game):void {
    }
    public function containerAdded(game:Game):void {
    }
    
    public function removeCompletely(game:Game):void {
        if (spawnInfo && respawnWhenRemoved) {
            spawnInfo.spawner.objectRemoved(this, spawnInfo);
        }    
    }
    
    public function updateContainer(game:Game):void {
        if (container.x != position.x) {
            container.x = position.x;
        }
        if (container.y != position.y) {
            container.y = position.y;
        }
        if (container.z != position.z) {
            container.z = position.z;
        }

        try {
            if (!removeMe && (counter % containerCheckPeriod) == 0) {
                if (containerPartOfScene && shouldRemoveContainer(game)) {
                    try {
                        game.scene.removeChild(container);
                        containerRemoved(game);
                        containerPartOfScene = false;
                     } catch (error:Error) {
                        trace("Error should remove container body " + error.name + " " + error.message);
                    }
                } else if (!containerPartOfScene && shouldAddContainer(game)) {
                    try {
                        game.scene.addChild(container);
                        containerAdded(game);
                        containerPartOfScene = true;                    
                     } catch (error:Error) {
                        trace("Error should add container body " + error.name + " " + error.message);
                    }
                } else if (!containerPartOfScene && shouldRemoveCompletely(game)) {
                    try {
                        removeMe = true;
                     } catch (error:Error) {
                        trace("Error remove completely call " + error.name + " " + error.message);
                    }
                }
            }
         } catch (error:Error) {
            trace("Error physical object update container part 1" + error.name + " " + error.message);
        }
        
   }

    public function updateMovement(stepX:Number, stepY:Number, stepZ:Number, game:Game):Array {
        if (stepX != 0 || stepY != 0 || stepZ != 0) {
        
            var rect:Rectangle = getTestRectangle(stepX, stepY);
        
            var solids:Array = game.currentLevel.solidObjects;
            
            
            for (var i:int = 0; i<solids.length; i++) {
                var solid:PhysicalObject = solids[i];
                
                var solidRect:Rectangle = solid.getRectangle();
                if (rect.intersects(solidRect)) {
                    // Must set at least one of stepX, stepY to 0
                    var rect2:Rectangle = getTestRectangle(stepX, 0);
                    if (rect2.intersects(solidRect)) {
                        stepX = 0;
                    }
                    rect2 = getTestRectangle(0, stepY);
                    if (rect2.intersects(solidRect)) {
                        stepY = 0;
                    }
                }
               
            }
         
            position.x += stepX;
            position.y += stepY;
            position.z += stepZ;
            
        }
        if (isNaN(position.x) || isNaN(position.y) || isNaN(position.z)) {
            trace("Nan detected");
        }
        updateContainer(game);
        return [stepX, stepY, stepZ];
    }
    
    override public function tick(game:Game):void {
        super.tick(game);
    }

    override public function init(game:Game):void {
        super.init(game);
        
        container = new ObjectContainer3D();
        if (spawnInfo) {
            container.name = spawnInfo.toString();
        }
    }   

    override public function removed(game:Game):void {
        super.removed(game);

        if (containerPartOfScene) {
            if (spawnInfo) {
                game.scene.removeChildByName(container.name);
            } else {
                game.scene.removeChild(container);
            }
            containerRemoved(game);
            containerPartOfScene = false;
        }
        removeCompletely(game);
    }
    
}


// Removes and adds their geometry automatically
class SelfManagedPhysicalObject extends PhysicalObject {

    public var containerRemoveDistance:Number = 300;
    public var containerAddDistance:Number = 300;
    public var completeRemoveDistance:Number = 700;
    
    override public function shouldRemoveContainer(game:Game):Boolean {
        if (game.currentLevel.player) {
            var pos:Vector3D = game.currentLevel.player.position;
            return Vector3D.distance(position, pos) > containerRemoveDistance;
        } else {
            return false;
        }

    }

    override public function shouldAddContainer(game:Game):Boolean {
        if (game.currentLevel.player) {
        var pos:Vector3D = game.currentLevel.player.position;
        return Vector3D.distance(position, pos) < containerAddDistance;
        } else {
            return false;
        }

    }

    override public function shouldRemoveCompletely(game:Game):Boolean {
        if (spawnInfo) {
            var pos:Vector3D = game.currentLevel.player.position;
            return Vector3D.distance(position, pos) > completeRemoveDistance;
        } else {
            return false;
        }
    }
    
    override public function tick(game:Game):void {
        super.tick(game);
        updateContainer(game);
    }
}


class SelfManagedPickup extends SelfManagedPhysicalObject {
    public var pickedUp:Boolean = false;

    override public function removeCompletely(game:Game):void {
        super.removeCompletely(game);
        // var arr:Array = game.currentLevel.pickups;
        // arr.splice(arr.indexOf(this), 1);
    }

    public function wasPickedUp(game:Game):Boolean {
        return false;
    }
    
    override public function tick(game:Game):void {
        super.tick(game);
        if (!pickedUp && Vector3D.distance(game.currentLevel.player.position, position) < 20) {
            var pu:Boolean = wasPickedUp(game);
            if (pu) {
                pickedUp = true;
                if (containerPartOfScene) {
                    game.scene.removeChild(container);
                    containerRemoved(game);
                    containerPartOfScene = false;
                }            
                removeMe = true;
                respawnWhenRemoved = false;
            }
        }
    }
    
    
}


class BatteryPickup extends SelfManagedPickup {

    public var sizeFactor:Number = 1.0;
    
    public var amount:Number = 50;
    
    override public function wasPickedUp(game:Game):Boolean {
        var player:Player = game.currentLevel.player;
        
        if (player.batteryLevel < player.maxBatteryLevel) {
            game.currentLevel.player.increaseBatteryLevel(amount);
            return true;
        } else {
            return false;
        }
    }
    
    override public function init(game:Game):void {
        super.init(game);
        
        var groundZ:Number = game.getGroundZ(position.x, position.y);
        
        position.z = groundZ;
        
        var stemHeight:Number = 10;
                
        var stem:Cylinder = new Cylinder();
        stem.height = stemHeight * sizeFactor;
        stem.radius = 3 * sizeFactor;
        stem.z = -stem.height * 0.5 - 5;
        stem.yUp = false;
        stem.segmentsW = 5;
        stem.segmentsH = 2;
        stem.material = new WireColorMaterial(0x444444);
        
        container.addChild(stem);        
        dimension = new Vector3D(stem.radius * 2, stem.radius * 2, 10);
    }


}


class HealthPickup extends SelfManagedPickup {

    public var sizeFactor:Number = 1.0;
    
    public var amount:Number = 0.5;
    
    override public function wasPickedUp(game:Game):Boolean {
        var player:Player = game.currentLevel.player;
        
        if (player.health < player.maxHealth) {
            game.currentLevel.player.increaseHealth(amount);
            return true;
        } else {
            return false;
        }
    }
    
    override public function init(game:Game):void {
        super.init(game);
        
        var groundZ:Number = game.getGroundZ(position.x, position.y);
        
        position.z = groundZ;
        
        var stemHeight:Number = 10;
                
        var stem:Cylinder = new Cylinder();
        stem.height = stemHeight * sizeFactor;
        stem.radius = 3 * sizeFactor;
        stem.z = -stem.height * 0.5 - 5;
        stem.yUp = false;
        stem.segmentsW = 5;
        stem.segmentsH = 2;
        stem.material = new WireColorMaterial(0xff1111);
        
        container.addChild(stem);        
        dimension = new Vector3D(stem.radius * 2, stem.radius * 2, 10);
    }


}


class SelfManagedSolidObject extends SelfManagedPhysicalObject {
    override public function removeCompletely(game:Game):void {
        super.removeCompletely(game);
        // var arr:Array = game.currentLevel.solidObjects;
        // arr.splice(arr.indexOf(this), 1);
                
    }

    override public function tick(game:Game):void {
        super.tick(game);
        
        var rect:Rectangle = getRectangle();

        var z:Number = container.z - dimension.z;

        var projectiles:Array = game.currentLevel.projectiles;
        for (var i:int = 0; i<projectiles.length; i++) {
            var proj:Projectile = Projectile(projectiles[i]);
            var pos:Vector3D = proj.position;
            if (pos.z > z && rect.contains(pos.x, pos.y)) {
               proj.hitsSolidObject(game, this);
            }
        }
    }

    
}


var NORMAL_TREE:int = 0;
var PINE_TREE:int = 1;

class Tree extends SelfManagedSolidObject {
    
    public var type:int = NORMAL_TREE;
    public var sizeFactor:Number = 1.0;

    
    
    override public function init(game:Game):void {
        super.init(game);
        
        var groundZ:Number = game.getGroundZ(position.x, position.y);
        
        position.z = groundZ;
        
        var stemHeight:Number = 30;
        
        switch (type) {
            case NORMAL_TREE:
                break;
            case PINE_TREE:
                stemHeight = 15;
                break;
        }
        
        var stem:Cylinder = new Cylinder();
        stem.openEnded = true;
        stem.height = stemHeight * sizeFactor;
        stem.radius = 3 * sizeFactor;
        stem.z = -stem.height * 0.5;
        stem.yUp = false;
        stem.segmentsW = 5;
        stem.segmentsH = 2;
        stem.material = new WireColorMaterial(0x335500);
        
        container.addChild(stem);        
        if (type == NORMAL_TREE) {
            var upper:Sphere = new Sphere();
            upper.material = new WireColorMaterial(0x006700);
            upper.radius = 20 * sizeFactor;
            upper.yUp = false;
            upper.z = -stem.height * 1.5 - upper.radius * 0.8;
            container.addChild(upper);
        } else if (type == PINE_TREE) {
            var cone:Cone = new Cone();
            cone.openEnded = true;
            cone.material = new WireColorMaterial(0x006700);
            cone.radius = 10 * sizeFactor;
            cone.height = 35 * sizeFactor;
            cone.rotationY = 180;
            cone.segmentsW = 5;
            cone.segmentsH = 2;
            cone.yUp = false;
            cone.z = -stem.height * 1.5 - cone.height * 0.5;
            container.addChild(cone);            
        }

        dimension = new Vector3D(stem.radius * 2, stem.radius * 2, 35 * sizeFactor);
    }
}


class Wall extends SelfManagedSolidObject {
    
    public var size:Number = 30;
    public var height:Number = 40;
    
    override public function init(game:Game):void {
        super.init(game);
        
        var groundZ:Number = game.getGroundZ(position.x, position.y);        
        position.z = groundZ;
                
        var cube:Cube = new Cube();
        cube.segmentsW = 1;
        cube.segmentsD = 1;
        cube.segmentsH = 1;
        cube.height = size;
        cube.width = size;
        cube.depth = height;
        cube.z = -cube.height * 0.5 - 5;
        cube.material = new WireColorMaterial(0x333333);
        
        container.addChild(cube);        
        dimension = new Vector3D(size, size, height);
    }

}




function distanceBetween(x1:Number, y1:Number, x2:Number, y2:Number):Number {
    var diffX:Number = x1 - x2;
    var diffY:Number = y1 - y2;
    return Math.sqrt(diffX * diffX + diffY * diffY);
}

function clampUint(x:Number, lower:Number, upper:Number):uint {
    var result:uint = x < lower ? lower : (x > upper ? upper : x);
    return result;
}

interface HeightModifier {
    function applicable(x:Number, y:Number):Boolean;
    
    function applyModifier(x:Number, y:Number, z:Number):Number;
}

interface ColorModifier {
    function applicable(x:Number, y:Number):Boolean;
    
    function applyModifier(x:Number, y:Number, z:Number, colArr:Vector.<Number>):void;
}


class GroundMesh extends PhysicalObject {
    public var mesh:Mesh;
    public var groundNoise:ClassicalNoise;
    public var seed:int = 12345;
    public var amplitude:int = 30;
    public var frequency:Number = 0.01;
    public var metalLevel:Number = -0.4;
    
    // Stored in pairs of faces. Existing faces
    public var existingFacePairs:Object = {};
    
    public var heightModifiers:Vector.<HeightModifier> = new Vector.<HeightModifier>();
    public var colorModifiers:Vector.<ColorModifier> = new Vector.<ColorModifier>();

    public var faceCount:int = 0;
    
    public function GroundMesh():void {        
    }
    
    public function createNoiseIfNecessary():void {
        if (!groundNoise) {
            groundNoise = new ClassicalNoise(new Rndm(seed));
        }
    }
    
    
    public function getGroundColor(x:Number, y:Number, z:Number):uint {
    
        var height:Number = -z;
        var red:Number = 0;
        var green:Number = 0;
        var blue:Number = 0;
        
        if (height > metalLevel * amplitude) {
            green = ((height / amplitude) + 1.0) * 0.5;
        } else {
            red = 0.5;
            green = 0.5;
            blue = 0.5;
        }
        
        // trace("ground color " + [red, green, blue].join(",") + " " + [x, y, z].join(","));

        if (colorModifiers.length > 0) {
            var colArr:Vector.<Number> = new Vector.<Number>();
            for (var i:int =0; i<colorModifiers.length; i++) {
                var cm:ColorModifier = colorModifiers[i];
                if (cm.applicable(x, y)) {
                    cm.applyModifier(x, y, z, colArr);
                }
            }
            red = colArr[0];
            green = colArr[1];
            blue = colArr[2];
        }
        
        var redUint:uint = clampUint(red * 255, 0, 255);
        var greenUint:uint = clampUint(green * 255, 0, 255);
        var blueUint:uint = clampUint(blue * 255, 0, 255);
        
        return (redUint << 16) | (greenUint << 8) | blueUint;
    }
    
    public function getGroundZ(x:Number, y:Number):Number {
        var noiseScale:Number = frequency;
        var noiseAmp:Number = amplitude;
        createNoiseIfNecessary();
        var inputX:Number = noiseScale * x;
        var inputY:Number = noiseScale * y;
        var result:Number = noiseAmp * groundNoise.noise(inputX, inputY, 0);
        
        var height:Number = -result;
        if (height < metalLevel * noiseAmp) {
            result = -metalLevel * noiseAmp;
        }
        for (var i:int =0; i<heightModifiers.length; i++) {
            var hm:HeightModifier = heightModifiers[i];
            if (hm.applicable(x, y)) {
                result = hm.applyModifier(x, y, result);
            }
        }
        return result;
    }
    
    
    override public function removed(game:Game):void {
        super.removed(game);
    }
    
    private function createFaceIfNecessary(stepSize:int, gridX:int, gridY:int):void {
    
        var xMap:Object = existingFacePairs[gridX];
        if (!xMap) {
            xMap = {};
            existingFacePairs[gridX] = xMap;
        }
        
        var pair:Array = xMap[gridY];
        if (!pair) {
            pair = [];

            var x:Number = gridX * stepSize;
            var y:Number = gridY * stepSize;
            var z:Number = getGroundZ(x, y);
            var v0:Vertex = new Vertex(x, y, z);
            var v1:Vertex = new Vertex(x + stepSize, y, getGroundZ(x + stepSize, y));
            var v2:Vertex = new Vertex(x + stepSize, y + stepSize, getGroundZ(x + stepSize, y + stepSize));
            var v3:Vertex = new Vertex(x, y + stepSize, getGroundZ(x, y + stepSize));
            var f1:Face = new Face(v0, v1, v2);
            var f2:Face = new Face(v0, v2, v3);
            var groundColor:uint = getGroundColor(x, y, z);
            var mat:WireColorMaterial = new WireColorMaterial(groundColor);
            f1.material = mat;
            f2.material = mat;
            
            mesh.addFace(f1);           
            mesh.addFace(f2);
            faceCount += 2;
            pair[0] = f1;
            pair[1] = f2;
            
            xMap[gridY] = pair;
        }        
    }

    private function removeFacesIfNecessary(stepSize:int, distance:Number, testX:Number, testY:Number):void {
        var gridX:Number = Math.floor(testX);
        var gridY:Number = Math.floor(testY);
        for (var existingGridX:String in existingFacePairs) {
            var xMap:Object = existingFacePairs[existingGridX];
            var found:Boolean = false;
            for (var existingGridY:String in xMap) {
                found = true;
                var x:Number = parseInt(existingGridX) * stepSize;
                var y:Number = parseInt(existingGridY) * stepSize;
                if (distance < distanceBetween(x, y, testX, testY)) {
                    var pair:Array = xMap[existingGridY];
                    mesh.removeFace(pair[0]);
                    mesh.removeFace(pair[1]);
                    faceCount -= 2;
                    delete xMap[existingGridY];
                }
            }
            if (!found) {
                delete existingFacePairs[existingGridX];
            }
        }

        // if (Math.random() < 0.02) {        
            // trace("Face count: " + faceCount + " vertex count: " + mesh.vertices.length);
        // }
    }
    
    
    public function updateMesh(game:Game):void {
        if (!mesh) {
            mesh = new Mesh();
        }
        
        if ((game.counter % 11) == 0) {         
    
            if (mesh.vertices.length > faceCount * 5) {
                // trace("Removed mesh faceCount: " + faceCount + " vertexCount: " + mesh.vertices.length);
                faceCount = 0;
                container.removeChild(mesh);
                existingFacePairs = {};
                mesh = new Mesh();
                container.addChild(mesh);
            }
            
            var positiveYDistance:Number = 300;
            var negativeYDistance:Number = 150;
            var xDistance:Number = 180;
            // var faceDistanceSq:Number = faceDistance * faceDistance;
            
            var stepSize:int = 35;
            
            var playerPos:Vector3D = game.currentLevel.player.position;
            
            var playerGridMinX:Number = Math.floor((playerPos.x - xDistance) / stepSize);
            var playerGridMaxX:Number = Math.ceil((playerPos.x + xDistance) / stepSize);
            var playerGridMinY:Number = Math.floor((playerPos.y - negativeYDistance) / stepSize);
            var playerGridMaxY:Number = Math.ceil((playerPos.y + positiveYDistance) / stepSize);
            
            var i:int = 0;
            
            
            for (i = playerGridMinX; i<playerGridMaxX; i++) {
                for (var j:int = playerGridMinY; j<playerGridMaxY; j++) {
                    createFaceIfNecessary(stepSize, i, j);
                }
            }
            removeFacesIfNecessary(stepSize, positiveYDistance * 1.2, playerPos.x, playerPos.y);
            
        }
    }
    
    override public function init(game:Game):void {
        super.init(game);
        updateMesh(game);
        container.addChild(mesh);
        game.scene.addChild(container);
        containerAdded(game);
        containerPartOfScene = true;
    }
    
    
    override public function tick(game:Game):void {
        super.tick(game);
        updateMesh(game);
    }
}


class MovingPhysicalObject extends PhysicalObject {

    public var velocity:Vector3D = new Vector3D();
    public var gravity:Vector3D = new Vector3D(0, 0, 0.05);

    public var collidesWithGround:Boolean = true;
    public var collidesWithObstacles:Boolean = true;
    
    public var inAir:Boolean = false;
    public var airAcceleration:Vector3D = new Vector3D();
    public var airResistanceFactor:Number = 0.1;

    
    override public function updateMovement(stepX:Number, stepY:Number, stepZ:Number, game:Game):Array {
        try {
            if (!inAir) {
                stepZ = 0.0;
                if (collidesWithGround) {
                    // The object follows the ground
                    try {
                        if (game.currentLevel.groundMesh) {
                            var futureX:Number = position.x + stepX;
                            var futureY:Number = position.y + stepY;
                            var groundZ:Number = game.currentLevel.groundMesh.getGroundZ(futureX, futureY);
                            position.z = groundZ;
                        }
                    } catch (error:Error) {
                        trace("Error moving update movement mesh stuff " + error.name + " " + error.message);
                    }

                }
            } else {
                // Flying freely in the air
                position.incrementBy(velocity);
                velocity.incrementBy(gravity);
                
                
                // trace("flying movement " + gravity.z + " " + velocity.z);
                
                if (game.currentLevel.groundMesh) {
                    var gZ:Number = game.currentLevel.groundMesh.getGroundZ(position.x, position.y);
                    if (position.z > gZ) {
                        hitsGround(game, gZ);
                    }
                }
                
                
            }
        } catch (error:Error) {
            trace("Error moving update movement " + error.name + " " + error.message);
        }
        return super.updateMovement(stepX, stepY, stepZ, game);
    }
    
    public function hitsGround(game:Game, groundZ:Number):void {
    }

    public function hitsPhysicalObject(game:Game, obj:PhysicalObject):void {
    }

    
    override public function tick(game:Game):void {
        super.tick(game);
    }
    
    

}


class Planet extends MovingPhysicalObject {

    public var radius:Number = 40;
    public var rotVel:Number = 0.1;
    public var surfaceColor:uint = 0xffffff;
    
    public function Planet():void {
        super();
        inAir = true;
        gravity = new Vector3D();
    }

    override public function tick(game:Game):void {
        super.tick(game);
        container.rotationY += rotVel;
        super.updateMovement(0, 0, 0, game);
    }

        
    override public function init(game:Game):void {
        super.init(game);
        var sphere:Sphere = new Sphere();
        sphere.radius = radius;
        sphere.material = new WireColorMaterial(surfaceColor);
        container.addChild(sphere);
        game.scene.addChild(container);
        containerAdded(game);
        containerPartOfScene = true;
    }


}

class Particle extends MovingPhysicalObject {
    public var particleDuration:int = 60;

    public function Particle():void {
        super();    
        inAir = true;
    }
    
    override public function tick(game:Game):void {
        super.tick(game);
     
        particleDuration--;
        if (particleDuration <= 0) {
            removeMe = true;
        }
        super.updateMovement(0, 0, 0, game);
    }

}

var FIRE_PARTICLE:int = 0;
var SMOKE_PARTICLE:int = 1;

class CommonParticle extends Particle {
    public var type:int = FIRE_PARTICLE;

    public function CommonParticle():void {
        super();    
    }
    override public function tick(game:Game):void {
        super.tick(game);
    }
}

class Object3DParticle extends Particle {

    public var object:Object3D;

    public function Object3DParticle(o:Object3D, vel:Vector3D, grav:Vector3D):void {
        
       super();

        this.object = o;
        this.velocity = vel;
        this.gravity = grav;
        position.x = o.x;
        position.y = o.y;
        position.z = o.z;
    }
    
    override public function tick(game:Game):void {
        super.tick(game);
        object.x = position.x;
        object.y = position.y;
        object.z = position.z;    
        // trace("object " + object.z);
    }
    
    override public function hitsGround(game:Game, groundZ:Number):void {
        super.hitsGround(game, groundZ);
        velocity = new Vector3D();
        gravity = new Vector3D();
    }

}


class Projectile extends MovingPhysicalObject {

    public var damage:Number = 0.1;
    public var sizeFactor:Number = 1.0;

    public var owner:GameObject;

    public function Projectile(owner:GameObject):void {
        this.owner = owner;
        inAir = true;
    }

    override public function tick(game:Game):void {
        try {
            super.tick(game);
            // if (Math.random() < 0.01) {
                // trace("proj position.z: " + position.z);
            // }
        } catch (error:Error) {
            trace("Error projectile tick " + error.name + " " + error.message);
        }
        try {
            super.updateMovement(0, 0, 0, game);
        } catch (error:Error) {
            trace("Error projectile update movement " + error.name + " " + error.message);
        }
    }

    override public function hitsGround(game:Game, groundZ:Number):void {
        super.hitsGround(game, groundZ);
    }

    public function hitsLivingObject(game:Game, obj:LivingObject):void {      
    }

    public function hitsSolidObject(game:Game, obj:PhysicalObject):void {      
    }
    
    
}

class Axe extends Projectile {

    public function Axe(owner:GameObject) {
        super(owner);
    }

    override public function tick(game:Game):void {
        super.tick(game);
        container.rotationY -= 30;
    }
    
    override public function init(game:Game):void {
        super.init(game);
        
        var height:Number = 10 * sizeFactor;
        var shaft:Cube = new Cube();
        shaft.width = height;
        shaft.height = height * 0.15;
        shaft.depth = height * 0.15;
        shaft.material = new WireColorMaterial(0x884422);
        container.addChild(shaft);
        
        var blade:Cube = new Cube();
        blade.width = height * 0.3;
        blade.height = height * 0.1;
        blade.depth = height * 0.7;
        blade.material = new WireColorMaterial(0x444444);
        var pos:Vector3D = new Vector3D(height * 0.5, 0, 0);
        blade.position = pos;
        container.addChild(blade);
        
        // container.rotationZ = 
        
        container.rotationZ = 180 * Math.atan2(direction.y, direction.x) / Math.PI;        
        updateContainer(game);
        game.scene.addChild(container);
        containerAdded(game);
        containerPartOfScene = true;
    }

    override public function hitsLivingObject(game:Game, obj:LivingObject):void {
        if (owner != obj && !removeMe) {
            removeMe = true;
            obj.doDamage(game, damage, velocity);
            // trace("Axe hit something living...");
            game.sfxPlayer.playAxeHitsLiving(game);
        }
    }
    
    override public function hitsGround(game:Game, groundZ:Number):void {
        super.hitsGround(game, groundZ);
        removeMe = true;        
        // trace("Removing axe " + groundZ + " " + position.z);
        game.sfxPlayer.playAxeHitsGround(game);
    }

    override public function hitsSolidObject(game:Game, obj:PhysicalObject):void {      
        super.hitsSolidObject(game, obj);
        removeMe = true;        
        // trace("Removing axe " + position.z);
        game.sfxPlayer.playAxeHitsGround(game);
    }

    
}



class Stone extends PhysicalObject {
}


class Hole extends PhysicalObject {
    override public function tick(game:Game):void {
        super.tick(game);
    }

}

class Fire extends PhysicalObject {
    override public function tick(game:Game):void {
        super.tick(game);
    }

}
class Goal extends PhysicalObject {
    override public function tick(game:Game):void {
        super.tick(game);
    }

}


class Sign extends PhysicalObject {
    public var texts:Array = [];
    override public function tick(game:Game):void {
        super.tick(game);
    }

}
class Pickup extends PhysicalObject {
    override public function tick(game:Game):void {
        super.tick(game);
    }
}


class LivingObject extends MovingPhysicalObject {


    public var maxHealth:Number = 1.0;
    public var health:Number = 1.0;
    public var dead:Boolean = false;

    public var dying:Boolean = false;
    public var dyingCounter:int = 0;
    public var maxDyingCounter:int = 0;

    public var projectileHitDimension:Vector3D;
    
    public function doDamage(game:Game, damage:Number, damageDir:Vector3D):void {
        if (!dead && !dying) {
            health -= damage;
            if (health <= 0.0) {
                dying = true;
                dyingCounter = 30;
                maxDyingCounter = dyingCounter;
                health = 0.0;
                startsDying(game);
            }
        }
    }
    
    public function startsDying(game:Game):void {
    }    
    
    public function die(game:Game):void {
        if (this == game.currentLevel.player) {
            game.playerDied();
        } else {
            // Check if all bosses are dead
            var allDead:Boolean = true;
            for (var i:int = 0; i<game.currentLevel.bosses.length; i++) {
                var boss:NPC = game.currentLevel.bosses[i];
                if (!boss.dead) {
                    allDead = false;
                    break;
                }
            }
            if (allDead) {
                game.playerWon();
            }
            game.sfxPlayer.playDeath(game);
            removeMe = true;
            spawnInfo = null;
        }
    }

    public function getProjectileHitRectangle():Rectangle {
        return new Rectangle(position.x - projectileHitDimension.x * 0.5, position.y - projectileHitDimension.y * 0.5, 
            projectileHitDimension.x, projectileHitDimension.y);
    }
    
    override public function tick(game:Game):void {
        super.tick(game);
        
        var rect:Rectangle = getProjectileHitRectangle();

        if (dying) {
            dyingCounter--;
            // trace("Dying. " + dyingCounter);
            if (dyingCounter <= 0) {
                dead = true;
                dying = false;
                die(game);
                // trace("dead");
            }
        } else if (!dead) {
            var z:Number = container.z - dimension.z;

            var projectiles:Array = game.currentLevel.projectiles;
            for (var i:int = 0; i<projectiles.length; i++) {
                var proj:Projectile = Projectile(projectiles[i]);
                if (proj.owner.team != team) { // Only hit in other teams
                    var pos:Vector3D = proj.position;
                    if (pos.z > z && rect.contains(pos.x, pos.y)) {
                       proj.hitsLivingObject(game, this);
                    }
                }
            }
        }
    }

}

var WALKING:int = 0;
var RUNNING:int = 1;
var DANCING:int = 2;
var STANDING:int = 4;

class Humanoid extends LivingObject {
    public var sizeFactor:Number = 1.0;

    public var dancingSpeedFactor:Number = 1.0;
    
    public var legColor:uint = 0xff0000;
    public var armColor:uint = 0xffffff;
    public var bodyColor:uint = 0xffffff;
    public var headColor:uint = 0x345623;
    
    public var leftArm:Cylinder;
    public var rightArm:Cylinder;

    public var leftLeg:Cylinder;
    public var rightLeg:Cylinder;

    public var head:Sphere;
    public var body:Cylinder;
    
    public var animationState:int = STANDING;
    public var animateAttacking:Boolean = false;
    public var animateDancing:Boolean = false;
    
    public var headZ:Number = 0;

    public var hit:Boolean = false;
    public var hitCounter:int = 0;
    public var maxHitCounter:int = 0;
    public var hitDirection:Vector3D;
    
    public var blinking:Boolean = false;
    public var blinkCounter:int = 0;
    public var blinkMaterial:WireColorMaterial;
    
    public var headMaterial:WireColorMaterial;
    public var bodyMaterial:WireColorMaterial;
    public var legMaterial:WireColorMaterial;
    public var armMaterial:WireColorMaterial;
    
    
    override public function doDamage(game:Game, damage:Number, damageDir:Vector3D):void {
        super.doDamage(game, damage, damageDir);
        if (!dead && !dying) {
            hit = true;
            hitCounter = 3;
            maxHitCounter = hitCounter;
            hitDirection = damageDir.clone();
            hitDirection.normalize();            
        }
        blinking = true;
        blinkCounter = 5;
    }
    
    public function createHumanoidGeometry(game:Game):void {
        var armLength:Number = 8 * sizeFactor;
        var armRadius:Number = 1.3 * sizeFactor;
        var legLength:Number = 10 * sizeFactor;
        var legRadius:Number = 1.5 * sizeFactor;
        var torsoLength:Number = 10 * sizeFactor;
        var torsoRadius:Number = 4 * sizeFactor;
        var headLength:Number = 6 * sizeFactor;

        var offset:Number = -5 * sizeFactor;

        blinkMaterial = new WireColorMaterial(0xffffff);
        blinkMaterial.wireColor = 0xffffff;
        
        body = new Cylinder();
        bodyMaterial = new WireColorMaterial(bodyColor);
        body.material = bodyMaterial;
        body.segmentsW = 5;
        body.height = torsoLength;
        body.radius = torsoRadius;
        body.z = -torsoLength - legLength - offset;
        body.yUp = false;
        container.addChild(body);        

        head = new Sphere();
        headMaterial = new WireColorMaterial(headColor);
        head.material = headMaterial;
        head.yUp = false;
        head.segmentsH = 5;
        head.segmentsW = 5;
        head.radius = headLength * 0.5;
        head.z = -legLength - torsoLength - armRadius - headLength- offset;
        container.addChild(head);
        
        headZ = -legLength - torsoLength - armRadius - headLength - offset - head.radius;

        armMaterial = new WireColorMaterial(armColor);
        
        leftArm = new Cylinder();
        leftArm.material = armMaterial;
        leftArm.yUp = false;
        leftArm.segmentsW = 5;
        leftArm.height = armLength;
        leftArm.radius = armRadius;
        leftArm.z = -legLength - torsoLength - armRadius- offset;
        leftArm.y = body.radius;
        leftArm.rotationX = 45;
        container.addChild(leftArm);

            
        rightArm = new Cylinder();
        rightArm.material = armMaterial;
        rightArm.yUp = true;
        rightArm.segmentsW = 5;
        rightArm.height = armLength;
        rightArm.radius = armRadius;
        rightArm.z = -legLength - torsoLength - armRadius- offset;
        rightArm.y = -body.radius;
        rightArm.rotationX = 45;
        container.addChild(rightArm);
        
        
        legMaterial = new WireColorMaterial(legColor);

        leftLeg = new Cylinder();
        leftLeg.material = legMaterial;
        leftLeg.yUp = false;
        leftLeg.segmentsW = 5;
        leftLeg.height = legLength;
        leftLeg.radius = legRadius;
        leftLeg.z = -legLength * 1.3- offset;
        leftLeg.y = body.radius * 0.5;
        leftLeg.rotationX = 10;
        container.addChild(leftLeg);

        rightLeg = new Cylinder();
        rightLeg.material = legMaterial;
        rightLeg.yUp = false;
        rightLeg.segmentsW = 5;
        rightLeg.height = legLength;
        rightLeg.radius = legRadius;
        rightLeg.z = -legLength * 1.3- offset;
        rightLeg.y = -body.radius * 0.5;
        rightLeg.rotationX = -10;
        container.addChild(rightLeg);        
        
        projectileHitDimension = new Vector3D(torsoRadius * 3, torsoRadius * 3, Math.abs(headZ));
        dimension = new Vector3D(torsoRadius * 2, torsoRadius * 2, Math.abs(headZ));
    }

    override public function init(game:Game):void {
        try {
            super.init(game);
        } catch (error:Error) {
            trace("Error humanoid super init " + error.name + " " + error.message);
        }
            // Adding the body of the player            
        try {        
            createHumanoidGeometry(game);            
        } catch (error:Error) {
            trace("Error humanoid create geometry" + error.name + " " + error.message);
        }
            
        try {
            updateContainer(game);
            game.scene.addChild(container);
            containerAdded(game);
            containerPartOfScene = true;
            
        } catch (error:Error) {
            trace("Error humanoid init update container" + error.name + " " + error.message);
        }
    }

    override public function updateMovement(stepX:Number, stepY:Number, stepZ:Number, game:Game):Array {
        if (!dying && !dead) {
            var result:Array = super.updateMovement(stepX, stepY, stepZ, game);

            var newStepX:Number = result[0];
            var newStepY:Number = result[1];
            if (newStepX != 0 || newStepY != 0) {
                if (Math.sqrt(newStepX * newStepX + newStepY * newStepY) > 2) {
                    animationState = RUNNING;
                } else {
                    animationState = WALKING;
                }
            } else if (newStepX == 0 && newStepY == 0) {
                animationState = STANDING;
            }
            
            return result;
        } else {
            return [0, 0, 0];
        }
    }

    public function getRandomDieVelocity(game:Game):Vector3D {
        var angle:Number = Math.random() * Math.PI * 2;
        var upAngle:Number = 0.5 * Math.PI - Math.PI * 0.25 * Math.random();
        
        var speed:Number = 2 + Math.random() * 3;
        
        var upFrac:Number = Math.cos(upAngle);
        
        return new Vector3D(Math.cos(angle) * upFrac * speed, Math.sin(angle) * upFrac * speed, -Math.sin(upAngle) * speed);
    }
    
    override public function startsDying(game:Game):void {
        var particles:Array = game.currentLevel.worldParticles;
        var gravity:Vector3D = new Vector3D(0, 0, 0.3);
        particles.push(new Object3DParticle(leftArm, getRandomDieVelocity(game), gravity)); 
        particles.push(new Object3DParticle(rightArm, getRandomDieVelocity(game), gravity)); 
        particles.push(new Object3DParticle(leftLeg, getRandomDieVelocity(game), gravity)); 
        particles.push(new Object3DParticle(rightLeg, getRandomDieVelocity(game), gravity)); 
        particles.push(new Object3DParticle(body, getRandomDieVelocity(game), gravity)); 
        particles.push(new Object3DParticle(head, getRandomDieVelocity(game), gravity)); 
    }    
    
    override public function tick(game:Game):void {
        super.tick(game);
        
        if (blinking) {
            blinkCounter--;
            
            head.material = blinkMaterial;
            body.material = blinkMaterial;
            leftArm.material = blinkMaterial;
            rightArm.material = blinkMaterial;
            leftLeg.material = blinkMaterial;
            rightLeg.material = blinkMaterial;
    
            if (blinkCounter <= 0) {               
                blinking = false;
            }
            
            if (!blinking) {             
                head.material = headMaterial;
                body.material = bodyMaterial;
                leftArm.material = armMaterial;
                rightArm.material = armMaterial;
                leftLeg.material = legMaterial;
                rightLeg.material = legMaterial;                
            }    
        }
        
        if (dying || dead) {
            
            // leftArm.z += 0.5;
            // leftArm.y += 0.5;
        } else {
         
            if (hit) {
                hitCounter--;
                
                var hitFraction:Number = hitCounter / maxHitCounter;
                
                container.rotationY = -hitFraction * 30;
                if (hitCounter == 0) {
                    hit = false;
                }
                container.rotationZ = 180 * Math.atan2(hitDirection.y, hitDirection.x) / Math.PI;            
            } else {
                container.rotationZ = 180 * Math.atan2(direction.y, direction.x) / Math.PI;            
            }

            
            
            switch (animationState) {
                case RUNNING:
                    leftArm.rotationZ = 40 * Math.sin(game.counter * 0.6);
                    rightArm.rotationZ = 40 * Math.sin(game.counter * 0.6);
                    leftLeg.rotationY = 40 * Math.sin(game.counter * 0.6);
                    rightLeg.rotationY = -40 * Math.sin(game.counter * 0.6);
                    break;
                case WALKING:
                    leftArm.rotationZ = 40 * Math.sin(game.counter * 0.3);
                    rightArm.rotationZ = 40 * Math.sin(game.counter * 0.3);
                    leftLeg.rotationY = 40 * Math.sin(game.counter * 0.3);
                    rightLeg.rotationY = -40 * Math.sin(game.counter * 0.3);
                    break;
                case STANDING:
                    leftArm.rotationZ = 10;
                    rightArm.rotationZ = 10;
                    leftLeg.rotationY = 0;
                    rightLeg.rotationY = 0;
                    break;
            }
            if (animateDancing) {
                leftArm.rotationZ = 40 * Math.sin(dancingSpeedFactor * game.counter * 0.8);
                rightArm.rotationZ = 40 * Math.sin(dancingSpeedFactor * game.counter * 0.8);
                leftLeg.rotationY = 40 * Math.sin(dancingSpeedFactor * game.counter * 0.8);
                rightLeg.rotationY = -40 * Math.sin(dancingSpeedFactor * game.counter * 0.8);

                container.rotationZ = 50 * Math.sin(dancingSpeedFactor * game.counter * 0.4);
                // rightArm.rotationZ = 180 + 40 * Math.sin(game.counter * 0.8);
            }
        }
    }
    
    
}

class MeterBar extends Sprite {

    public var text:String = "Value";
    public var barColor:uint = 0xff0000;
    public var fraction:Number = 1.0;
    
    public var maxWidth:Number = 80;
    public var maxHeight:Number = 15;
    
    public function setFraction(f:Number):void {
        this.fraction = f;
        graphics.clear();
        graphics.beginFill(barColor);
        graphics.drawRect(0, 0, fraction * maxWidth, maxHeight);
        graphics.endFill();
    }
    
    public function tick(game:Game):void {
    }

}

class Player extends Humanoid {

    public var maxBatteryLevel:Number = 100.0;
    public var batteryLevel:Number = 100.0;

    public var dancers:Vector.<NPC> = new Vector.<NPC>();
    
    public var dancing:Boolean = false;
    public var fighting:Boolean = false;
    
    public var danceSpeed:Number = 3;
    public var maxSpeed:Number = 5;

    public var sceneLight:DirectionalLight3D;
    
    public var haveBall:Boolean = false;
    public var danceBall:ObjectContainer3D = null;
    public var sphere:Sphere;
    public var sphereRadius:Number = 10.0;
    public var lightConeHeight:Number = 80.0;
    public var lightCones:Array = [];
    public var lightFrequencies:Array = [];
    public var lightPhases:Array = []; 
    
    public var healthBar:MeterBar;
    public var batteryBar:MeterBar;

    public var bossPointers:Vector.<ObjectContainer3D> = new Vector.<ObjectContainer3D>();
    public var bossPointerCones:Vector.<Cone> = new Vector.<Cone>();
    public var haveBossPointers:Vector.<Boolean> = new Vector.<Boolean>();    
    
    public function Player():void {
        super();
        maxHealth = 1.0;
        health = maxHealth;
        team = 1;
    }

    public function increaseHealth(amount:Number):void {
        health += amount;
        if (health > maxHealth) {
            health = maxHealth;
        }
        healthBar.setFraction(health / maxHealth);
    }

    
    public function increaseBatteryLevel(amount:Number):void {
        batteryLevel += amount;
        if (batteryLevel > maxBatteryLevel) {
            batteryLevel = maxBatteryLevel;
        }
        batteryBar.setFraction(batteryLevel / maxBatteryLevel);
    }
    
    override public function doDamage(game:Game, damage:Number, damageDir:Vector3D):void {
        super.doDamage(game, damage, damageDir);
        healthBar.setFraction(health / maxHealth);
    }
    
    override public function init(game:Game):void {
        // Set colors and size here
        sizeFactor = 1.0;
        super.init(game);
        healthBar = new MeterBar();
        healthBar.x = 10;
        healthBar.y = 10;
        game.interfaceContainer.addChild(healthBar);
        healthBar.setFraction(1.0);
        
        batteryBar = new MeterBar();
        batteryBar.x = 10;
        batteryBar.y = 30;
        batteryBar.barColor = 0x999999;
        game.interfaceContainer.addChild(batteryBar);
        batteryBar.setFraction(1.0);
    }
    
    override public function removed(game:Game):void {
        super.removed(game);
        game.interfaceContainer.removeChild(healthBar);
    }
    
    override public function tick(game:Game):void {
        try {
            super.tick(game);
        } catch (error:Error) {
            trace("Error player super tick " + error.name + " " + error.message);
        }
        healthBar.tick(game);
        
        var stepX:Number = 0;
        var stepY:Number = 0;
        
        var speed:Number = maxSpeed;
        if (dancing) {
            speed = danceSpeed;
        }
        var wasDancing:Boolean = dancing;
        
        if (game.keysDown[Keyboard.UP]) {
            // trace("moving up");
            stepY += speed;
        }
        if (game.keysDown[Keyboard.DOWN]) {
            stepY += -speed;
        }
        if (game.keysDown[Keyboard.LEFT]) {
            stepX += -speed;
        }
        if (game.keysDown[Keyboard.RIGHT]) {
            stepX += speed;
        }
        
        // Reset all dancers
        for (var i:int = 0; i<dancers.length; i++) {
            var d:NPC = dancers[i];
            d.mode = d.modeWhenAlone;
            d.team = 0;
        }
        dancers.length = 0;
        
        dancing = false;
        if ((game.keysDown["68"] || game.keysDown["65"]) && !dead && !dying) {
            if (batteryLevel > 0.0) {
                dancing = true;
                batteryLevel -= 0.15;
                batteryBar.setFraction(batteryLevel / maxBatteryLevel);
            }
        }
        if (!wasDancing && dancing) {
            game.musicSequencer.play();
            //game.sfxPlayer.playDancing(game);
        }        
        if (wasDancing && !dancing) {
            game.musicSequencer.stop();
        }
        
        fighting = false;
        if (game.keysDown["70"] || game.keysDown["83"]) {
            if (dancing) {
                // All dancers fight!
                fighting = true;
            }
        }
        
        if (dancing) {
            
            var movers:Array = game.currentLevel.movingObjects;

            var newMode:int = DANCE_MODE;
            if (fighting) {
                newMode = DANCE_FIGHT_MODE;
            }
            
            for (var k:int=0; k<movers.length; k++) {
                if (movers[k] is NPC) {
                    var mover:NPC = NPC(movers[k]);
                    if (!mover.resistsDance) {
                        var danceRadius:Number = mover.musicResistDistance;
                        var dVec:Vector3D = new Vector3D(mover.position.x - position.x, mover.position.y - position.y);
                    
                        var dist:Number = dVec.length;
                        if (dist < danceRadius) {
                            mover.mode = newMode;
                            mover.danceSpeed = danceSpeed;
                            dancers.push(mover);
                            mover.team = 1;
                        }
                    }
                }
            }
            if (!haveBall) {
                if (danceBall == null) {
                    danceBall = new ObjectContainer3D();
                    sphere = new Sphere();
                    var mat:WireColorMaterial = new WireColorMaterial(0xffffff);
                    mat.thickness = 0;
                    mat.alpha = 0.5;
                    sphere.material = mat;
                    sphere.radius = sphereRadius;
                    danceBall.addChild(sphere);
                    var lightCount:int = 5;
                    
                    for (i=0; i<lightCount; i++) {
                        var lightCone:Cone = new Cone();
                        lightCone.height = lightConeHeight;
                        lightCone.radius = 7;
                        lightCone.yUp = false;

                        var frac:Number = i / lightCount;
                        
                        var matrix:Matrix3D = new Matrix3D();
                        matrix.identity();
                        matrix.appendTranslation(0, 0, -lightCone.height * 0.5 - sphereRadius);
                        matrix.appendRotation(135, new Vector3D(1, 0, 0));
                        matrix.appendRotation(frac * 360, new Vector3D(0, 0, 1));
                        
                        var coneMat:WireColorMaterial = new WireColorMaterial(0x00ffff);
                        coneMat.wireAlpha = 0.4;
                        coneMat.alpha = 0.1;
                        lightCone.material = coneMat;
                        lightCone.transform = matrix;
                        danceBall.addChild(lightCone);
                        lightCones.push(lightCone);
                    }
                }
                for (i=0; i<lightCones.length; i++) {
                    lightPhases[i] = Math.random() * 40;
                    lightFrequencies[i] = Math.random() * 0.15 + 0.05;
                }

                
                
                game.scene.addChild(danceBall);
                haveBall = true;
            }
        } else {
            if (haveBall) {
                game.scene.removeChild(danceBall);
                haveBall = false;
            }
        }
        
        animateDancing = dancing;
    
        if (stepX != 0 && stepY != 0) {
            var sqrt2:Number = Math.sqrt(2.0);
            stepX /= sqrt2;
            stepY /= sqrt2;
        }        

        if (stepX != 0 || stepY != 0) {
            var temp:Vector3D = new Vector3D(stepX, stepY);
            temp.normalize();
            direction.x = temp.x;
            direction.y = temp.y;
        }
        var realSteps:Array;
        try {
            realSteps = super.updateMovement(stepX, stepY, 0, game);
        } catch (error:Error) {
            trace("Error player update movement " + error.name + " " + error.message);
        }

        if (haveBall) {
            // Update ball
            danceBall.position = new Vector3D(position.x, position.y, position.z - 80);
            danceBall.rotationZ = game.counter * 2;
            for (i = 0; i<lightCones.length; i++) {
                var cone:Cone = lightCones[i];
                matrix = new Matrix3D();
                frac = i / lightCones.length;
                matrix.identity();
                matrix.appendTranslation(0, 0, -lightConeHeight * 0.5 - sphereRadius);
                matrix.appendRotation(135 + 20 * Math.sin(lightFrequencies[i] * counter + lightPhases[i]), new Vector3D(1, 0, 0));
                matrix.appendRotation(frac * 360, new Vector3D(0, 0, 1));
                cone.transform = matrix;
            }
        }

        
        // if (dancers.length > 0) {
            // trace("Dancers: " + dancers.length);
        // }
        for (var j:int = 0; j<dancers.length; j++) {
            var dancer:NPC = dancers[j];
            dancer.danceStepX = realSteps[0];
            dancer.danceStepY = realSteps[1];
        }


        var bossCount:int = game.currentLevel.bosses.length;
        bossPointers.length = bossCount;
        haveBossPointers.length = bossCount;
        bossPointerCones.length = bossCount;
        
        for (i = 0; i<bossCount; i++) {
            var boss:NPC = game.currentLevel.bosses[i];

            var bossVec:Vector3D = boss.position.subtract(position);
            if (!boss.dead && bossVec.length > 150) {
                var bossPointer:ObjectContainer3D = bossPointers[i];
                if (!haveBossPointers[i]) {
                    if (!bossPointer) {                        
                        bossPointer = new ObjectContainer3D();
                        var bossPointerCone:Cone = new Cone();
                        bossPointerCones[i] = bossPointerCone;
                        bossPointerCone.radius = 1.8;
                        bossPointerCone.height = 14;
                        bossPointerCone.segmentsW = 5;
                        bossPointer.addChild(bossPointerCone);
                        bossPointers[i] = bossPointer;
                    }
                    game.scene.addChild(bossPointer);
                    haveBossPointers[i] = true;
                }
                bossVec.normalize();
                var pointerDist:Number = 70;
                
                
                
                bossPointer.x = position.x + bossVec.x * pointerDist;
                bossPointer.y = position.y + bossVec.y * pointerDist;
                bossPointer.z = Math.min(position.z, game.getGroundZ(bossPointer.x, bossPointer.y) - 10);
                
                var oscDist:Number = 3 * Math.sin(game.counter * 0.2);
                bossPointer.x += oscDist * bossVec.x;
                bossPointer.y += oscDist * bossVec.y;
                
                bossPointer.rotationZ = -90 + 180 * Math.atan2(bossVec.y, bossVec.x) / Math.PI;        

            } else {
                if (haveBossPointers[i]) {
                    game.scene.removeChild(bossPointers[i]);
                    haveBossPointers[i] = false;
                }
            }
        }
        // if (Math.random() < 0.05) {
            // trace("player is ticked...");
        // }
    }
}



// Removes and adds their geometry automatically
class SelfManagedHumanoid extends Humanoid {

    public var containerRemoveDistance:Number = 300;
    public var containerAddDistance:Number = 300;
    public var completeRemoveDistance:Number = 2000;

   override public function removeCompletely(game:Game):void {
        super.removeCompletely(game);
        // var arr:Array = game.currentLevel.movingObjects;
        // arr.splice(arr.indexOf(this), 1);
        if (containerPartOfScene) {
            game.scene.removeChild(container);
            containerPartOfScene = false;
        }
    }
    
    override public function shouldRemoveContainer(game:Game):Boolean {
        if (game.currentLevel.player) {
            var pos:Vector3D = game.currentLevel.player.position;
            return Vector3D.distance(position, pos) > containerRemoveDistance;
        } else {
            return false;
        }

    }

    override public function shouldAddContainer(game:Game):Boolean {
        if (game.currentLevel.player) {
            var pos:Vector3D = game.currentLevel.player.position;
            return Vector3D.distance(position, pos) < containerAddDistance;
        } else {
            return false;
        }

    }

    override public function shouldRemoveCompletely(game:Game):Boolean {
        if (game.currentLevel.player) {
            var pos:Vector3D = game.currentLevel.player.position;
            return Vector3D.distance(position, pos) > completeRemoveDistance;
        } else {
            return false;
        }
    }

    
    override public function tick(game:Game):void {
        super.tick(game);
        updateContainer(game);
    }
}



var BOSS_1:int = 1;
var BOSS_2:int = 2;
var BOSS_3:int = 3;
var BOSS_4:int = 4;
var BOSS_5:int = 5;

var MONSTER_1:int = 21;
var MONSTER_2:int = 22;
var MONSTER_3:int = 23;
var MONSTER_4:int = 24;
var MONSTER_5:int = 25;
var MONSTER_6:int = 26;
var MONSTER_7:int = 27;
var MONSTER_8:int = 28;


var PATROL_MODE:int = 0;
var WAIT_MODE:int = 1;
var ATTACK_MODE:int = 2;
var FIGHT_MODE:int = 3;
var DANCE_MODE:int = 4;
var DANCE_FIGHT_MODE:int = 5;

var HITTING_ATTACK:int = 0;
var PROJECTILE_ATTACK:int = 1;

var AXE_PROJECTILE:int = 0;

var MOVE_RIGHT:int = 0;
var MOVE_DOWN:int = 1;
var MOVE_LEFT:int = 2;
var MOVE_UP:int = 3;

var patrolPatterns:Array = [
    {pattern: [MOVE_RIGHT, MOVE_DOWN, MOVE_LEFT, MOVE_UP], lengths: [60, 120, 60, 120]},
    {pattern: [MOVE_LEFT, MOVE_DOWN, MOVE_RIGHT, MOVE_UP], lengths: [60, 120, 60, 120]},
    {pattern: [MOVE_RIGHT, MOVE_DOWN, MOVE_LEFT, MOVE_UP], lengths: [120, 120, 120, 120]},
    {pattern: [MOVE_LEFT, MOVE_DOWN, MOVE_RIGHT, MOVE_UP], lengths: [120, 60, 120, 60]},
    {pattern: [MOVE_RIGHT, MOVE_LEFT], lengths: [120, 120]},
    {pattern: [MOVE_LEFT, MOVE_RIGHT], lengths: [120, 120]},
    {pattern: [MOVE_LEFT, MOVE_RIGHT, MOVE_UP, MOVE_DOWN], lengths: [120, 120, 120, 120]}
];


class NPC extends SelfManagedHumanoid {
    public var npcType:int = MONSTER_1;

    public var realVelocity:Vector3D = new Vector3D();
    
    public var resistsDance:Boolean = false;
    public var danceStepX:Number = 0;
    public var danceStepY:Number = 0;
    public var danceSpeed:Number = 1;
    
    public var maxSpeed:Number = 1;
    public var maxAttackSpeed:Number = 1.3;
    
    public var mode:int = PATROL_MODE;
    public var modeWhenAlone:int = PATROL_MODE;    
    public var attackType:int = PROJECTILE_ATTACK;
    public var projectileType:int = AXE_PROJECTILE;
    
    public var damage:Number = 0.1;
    
    public var modeUpdateInterval:int = 13;
    
    public var sightLength:Number = 80;
    public var leaveAloneLength:Number = 200;
    public var fightDistance:Number = 50;
    public var closestFriendDistance:Number = 25;
    
    public var musicResistDistance:Number = 100;
    
    public var attackCounter:int = 1000;
    public var attackInterval:int = 50;
    
    public var patrolDirection:Vector3D = new Vector3D(1, 0);
    public var patrolIndex:int = 0;
    public var patrolCounter:int = 0;
    public var patrolPattern:Vector.<int> = Vector.<int>([MOVE_RIGHT, MOVE_DOWN, MOVE_LEFT, MOVE_UP]);
    public var patrolStepsPattern:Vector.<int> = Vector.<int>([60, 120, 60, 120]);
    
    public var currentMovement:Vector3D = new Vector3D();
    public var currentDirection:Vector3D = new Vector3D(1, 0);

    public var projectileSpeed:Number = 3;

    public var hasHealthBar:Boolean = true;
    public var healthBar:MeterBar;
    public var healthBarVisible:Boolean = false;
    
    public function updateHealthBar(game:Game):void {
        if (healthBarVisible && hasHealthBar) {
            
            // var mat:Matrix3D = game.mainCamera.viewMatrix;
    
         
            var screenVert:Vector3D = game.mainCamera.screen(game.scene, new Vertex(container.x, container.y, container.z + headZ - 20)); // + headZ));
            
            // var screenPos:Vector3D = mat.transformVertex(position);            
        
            healthBar.x = screenVert.x + game.stage.stageWidth * 0.5; // - healthBar.maxWidth * 0.5;
            healthBar.y = screenVert.y + game.stage.stageHeight * 0.5;
            
            
            // trace(screenPos.x + " " + screenPos.y);
            healthBar.setFraction(health / maxHealth);
        }
    }
    
    override public function containerAdded(game:Game):void {
        super.containerAdded(game);
        if (hasHealthBar) {
            if (!healthBar) {
                healthBar = new MeterBar();
                healthBar.maxWidth = 70;
                healthBar.maxHeight = 6;
            }
            game.interfaceContainer.addChild(healthBar);
            healthBarVisible = true;
            updateHealthBar(game);
        }
    }

    override public function containerRemoved(game:Game):void {
        super.containerRemoved(game);
        if (healthBar) {
            game.interfaceContainer.removeChild(healthBar);
        }
        healthBarVisible = false;
    }

    public function getHealthFromLevel(level:int):Number {
        return level * 0.1 + 0.3;
    }

    public function getAttackIntervalFromLevel(level:int):int {
        return Math.max(5, 60 - level * 2);
    }

    public function getDamageFromLevel(level:int):Number {
        return 0.1 + 0.05 * level;
    }

    public function getFightDistanceFromLevel(level:int):Number {
        return 50 + 12 * level;
    }
    
    public function getSpeedFromLevel(level:int):Number {
        return 1 + level * 0.2;
    }

    public function getProjectileSpeedFromLevel(level:int):Number {
        return 1.5 + level * 0.5;
    }
    
    public function getSightLengthFromLevel(level:int):Number {
        return 80 + level * 10;
    }
    
    public function getMusicResistDistanceFromLevel(level:int):Number {
        return 100 - level * 10;
    }

    
    override public function init(game:Game):void {
        // Set colors and size here
        var healthLevel:int = 1;
        var attackIntervalLevel:int = 1;
        var damageLevel:int = 1;
        var speedLevel:int = 1;
        var projectileSpeedLevel:int = 1;
        var fightDistanceLevel:int = 1;
        var sightLengthLevel:int = 1;
        var musicResistDistanceLevel:int = 1;

        var patrolIndex:int = 0;
        
        sizeFactor = 1.0;
        headColor = 0xffffff;
        armColor = 0x444444;
        bodyColor = 0xff3333;
        legColor = 0x000000;
        switch (npcType) {
            case MONSTER_1:
                headColor = 0xffffff;
                armColor = 0x444444;
                bodyColor = 0xff3333;
                legColor = 0x111111;
                patrolIndex = 0;
                sightLengthLevel = 1;
                break;
            case MONSTER_2:
                headColor = 0x00ff00;
                bodyColor = 0x111111;
                armColor = 0xff3333;
                sizeFactor = 1.2;
                healthLevel = 2;
                attackIntervalLevel = 2;
                speedLevel = 2;
                musicResistDistanceLevel = 2;
                fightDistanceLevel = 2;
                patrolIndex = 1;
                sightLengthLevel = 2;
                projectileSpeedLevel = 2;
                break;
            case MONSTER_3:
                sizeFactor = 1.3;
                attackIntervalLevel = 3;
                projectileSpeedLevel = 2;
                speedLevel = 5;
                musicResistDistanceLevel = 3;
                fightDistanceLevel = 3;
                patrolIndex = 2;
                sightLengthLevel = 3;
                healthLevel = 3;
                break;
            case MONSTER_4:
                sizeFactor = 1.3;
                attackIntervalLevel = 4;
                damageLevel = 2;
                patrolIndex = 3;
                speedLevel = 4;
                fightDistanceLevel = 4;
                musicResistDistanceLevel = 4;
                sightLengthLevel = 5;                
                projectileSpeedLevel = 3;
                healthLevel = 4;
                break;
            case MONSTER_5:
                sizeFactor = 1.4;
                attackIntervalLevel = 5;
                patrolIndex = 4;
                speedLevel = 5;
                fightDistanceLevel = 4;
                musicResistDistanceLevel = 5;
                sightLengthLevel = 6;                
                projectileSpeedLevel = 4;
                healthLevel = 5;
                break;
            case MONSTER_6:
                sizeFactor = 1.5;
                attackIntervalLevel = 6;
                patrolIndex = 5;
                speedLevel = 6;
                fightDistanceLevel = 4;                
                musicResistDistanceLevel = 6;
                sightLengthLevel = 6;                
                projectileSpeedLevel = 5;
                healthLevel = 6;
                break;
            case MONSTER_7:
                sizeFactor = 1.6;
                attackIntervalLevel = 7;
                patrolIndex = 6;
                speedLevel = 6;
                fightDistanceLevel = 5;
                musicResistDistanceLevel = 6;
                sightLengthLevel = 6;                
                projectileSpeedLevel = 5;
                healthLevel = 7;
                break;
            case MONSTER_8:
                sizeFactor = 1.7;
                attackIntervalLevel = 8;
                patrolIndex = 4;
                speedLevel = 6;
                fightDistanceLevel = 6;
                musicResistDistanceLevel = 6;
                sightLengthLevel = 6;                
                projectileSpeedLevel = 5;
                healthLevel = 8;
                break;
            case BOSS_1:
                sizeFactor = 1.7;
                headColor = 0xff0000;
                armColor = 0x222222;
                bodyColor = 0x33ff33;
                legColor = 0x0000ff;
                attackIntervalLevel = 4;
                resistsDance = true;
                modeWhenAlone = WAIT_MODE;
                speedLevel = 2;
                fightDistanceLevel = 4;
                damageLevel = 3;
                sightLengthLevel = 4;                
                projectileSpeedLevel = 3;
                healthLevel = 3;
                break;
            case BOSS_2:
                sizeFactor = 2;
                headColor = 0x000000;
                armColor = 0xff0000;
                bodyColor = 0xffffff;
                legColor = 0x00ff00;
                attackIntervalLevel = 6;
                resistsDance = true;
                modeWhenAlone = WAIT_MODE;
                speedLevel = 4;
                fightDistanceLevel = 6;
                damageLevel = 5;
                sightLengthLevel = 5;                
                projectileSpeedLevel = 5;
                healthLevel = 6;
                break;
            case BOSS_3:
                sizeFactor = 2.5;
                headColor = 0xff0000;
                armColor = 0x222222;
                bodyColor = 0x33ff33;
                legColor = 0x0000ff;
                attackIntervalLevel = 8;
                resistsDance = true;
                modeWhenAlone = WAIT_MODE;
                speedLevel = 5;
                fightDistanceLevel = 6;
                damageLevel = 7;
                sightLengthLevel = 6;
                projectileSpeedLevel = 5;
                healthLevel = 8;
                break;
            case BOSS_4:
                sizeFactor = 3;
                headColor = 0xff0000;
                armColor = 0x222222;
                bodyColor = 0x33ff33;
                legColor = 0x0000ff;
                attackIntervalLevel = 10;
                resistsDance = true;
                modeWhenAlone = WAIT_MODE;
                speedLevel = 6;
                fightDistanceLevel = 6;
                damageLevel = 9;
                sightLengthLevel = 6;
                projectileSpeedLevel = 5;
                healthLevel = 10;
                break;
            case BOSS_5:
                sizeFactor = 3.5;
                headColor = 0xff0000;
                armColor = 0x222222;
                bodyColor = 0x33ff33;
                legColor = 0x0000ff;
                attackIntervalLevel = 10;
                resistsDance = true;
                modeWhenAlone = WAIT_MODE;
                speedLevel = 6;
                fightDistanceLevel = 8;
                damageLevel = 10;
                sightLengthLevel = 7;
                projectileSpeedLevel = 5;
                healthLevel = 15;
                break;
        }
        
        fightDistance = getFightDistanceFromLevel(fightDistanceLevel);
        attackInterval = getAttackIntervalFromLevel(attackIntervalLevel);
        health = getHealthFromLevel(healthLevel);
        damage = getDamageFromLevel(damageLevel);
        maxSpeed = getSpeedFromLevel(speedLevel);
        maxAttackSpeed = maxSpeed * 1.2;
        projectileSpeed = getProjectileSpeedFromLevel(projectileSpeedLevel);
        musicResistDistance = getMusicResistDistanceFromLevel(musicResistDistanceLevel);
        sightLength = getSightLengthFromLevel(sightLengthLevel);
        leaveAloneLength = sightLength * 1.8;

        patrolPattern = Vector.<int>(patrolPatterns[patrolIndex].pattern);
        patrolStepsPattern = Vector.<int>(patrolPatterns[patrolIndex].lengths);
        
        try {
            super.init(game);
        } catch (error:Error) {
            trace("Error npc super init " + error.name + " " + error.message);
        }
    }

    
    public function updateMode(game:Game):void {
        
        var player:Player = game.currentLevel.player;
        var playerPos:Vector3D = player.position;
        
        var distanceToPlayer:Number = Vector3D.distance(playerPos, position);
        
        var playerVisible:Boolean = distanceToPlayer < sightLength;
        
        var playerDead:Boolean = player.dead;
        
        switch (mode) {
            case PATROL_MODE:
            case WAIT_MODE:
                if (playerVisible) {
                    mode = ATTACK_MODE;
                }
                break;
            case ATTACK_MODE:
                if (distanceToPlayer > leaveAloneLength) {
                    mode = modeWhenAlone;
                }
                if (distanceToPlayer <= fightDistance) {
                    mode = FIGHT_MODE;                 
                }
                break;
            case FIGHT_MODE:
                if (distanceToPlayer > fightDistance) {
                    mode = ATTACK_MODE;
                }
                break;
        }
        if (playerDead) {
            mode = modeWhenAlone;
        }
    }    
    
    public function getWantedMovement(game:Game, movement:Vector3D, direction:Vector3D):void {
    
        var wantedDir:Vector3D = new Vector3D();

        var shouldStandStill:Boolean = false;
                
        switch (mode) {
            case WAIT_MODE:
                shouldStandStill = true;
                break;
            case PATROL_MODE:
                patrolCounter++;
                var patrolSteps:int = patrolStepsPattern[patrolIndex];
                if (patrolCounter > patrolSteps) {
                    patrolIndex = (patrolIndex + 1) % patrolPattern.length;    
                    patrolCounter = 0;
                }
                patrolSteps = patrolStepsPattern[patrolIndex];
                var intDir:int = patrolPattern[patrolIndex];
                switch (intDir) {
                    case MOVE_RIGHT:
                        patrolDirection.x = 1;
                        patrolDirection.y = 0;
                        break;
                    case MOVE_DOWN:
                        patrolDirection.x = 0;
                        patrolDirection.y = -1;
                        break;
                    case MOVE_LEFT:
                        patrolDirection.x = -1;
                        patrolDirection.y = 0;
                        break;
                    case MOVE_UP:
                        patrolDirection.x = 0;
                        patrolDirection.y = 1;
                        break;
                }
                wantedDir.x = patrolDirection.x;
                wantedDir.y = patrolDirection.y;
                break;
            case ATTACK_MODE:
            case FIGHT_MODE:
                var playerPos:Vector3D = game.currentLevel.player.position;
                
                var distanceToPlayer:Number = Vector3D.distance(playerPos, position);
                var diffVec:Vector3D = new Vector3D(playerPos.x - position.x, playerPos.y - position.y);
                var sign:Number = 1;
                if (distanceToPlayer < 0.5 * fightDistance) {
                    sign = -1;
                } else if (distanceToPlayer < 0.8 * fightDistance) {
                    shouldStandStill = true;
                }

                wantedDir.x = sign * diffVec.x;
                wantedDir.y = sign * diffVec.y;
                
                diffVec.normalize();
                direction.x = diffVec.x; // Face the player while possibly moving away from him
                direction.y = diffVec.y;
                break;
            case DANCE_MODE:
            case DANCE_FIGHT_MODE:
                wantedDir.x = danceStepX;
                wantedDir.y = danceStepY;
                if (danceStepX == 0 && danceStepY == 0) {
                    shouldStandStill = true;
                }
                break;
        }        

        
        
        if (wantedDir.length > 0.001) {
            var speed:Number = maxSpeed;
            if (mode == DANCE_MODE || mode == DANCE_FIGHT_MODE) {
                speed = danceSpeed;
            }
            if (mode == FIGHT_MODE || mode == ATTACK_MODE) {
                speed = maxAttackSpeed;
            }
            wantedDir.normalize();
            movement.x = wantedDir.x * speed;
            movement.y = wantedDir.y * speed;
            
            if (mode != ATTACK_MODE && mode != FIGHT_MODE) {
                
                direction.x = wantedDir.x;
                direction.y = wantedDir.y;
            }
        }
        
        if (shouldStandStill) {
            movement.x = 0;
            movement.y = 0;         
        }

        var movers:Array = game.currentLevel.movingObjects;
        
        for (var i:int=0; i<movers.length; i++) {
            var mover:MovingPhysicalObject = MovingPhysicalObject(movers[i]);
            if (mover != this) {
                // Move away from other guys
                var dVec:Vector3D = new Vector3D(mover.position.x - position.x, mover.position.y - position.y);
                
                var dist:Number = dVec.length;
                if (dist < closestFriendDistance) {
                    dVec.normalize();
                    movement.x -= dVec.x;
                    movement.y -= dVec.y;
                }
            }
        }
        
        // movement.x += Math.random() * 0.2 - 0.1;
        // movement.y += Math.random() * 0.2 - 0.1;
    }

    public function getProjectile(game:Game, targetPos:Vector3D):Projectile {
        var axe:Axe = new Axe(this);

        var pos:Vector3D = position.clone();
        pos.z -= 25 * sizeFactor;        
        axe.position = pos;

        var diff:Vector3D = targetPos.clone();
        diff.decrementBy(pos);
        diff.normalize();
        var vertLength:Number = Math.sqrt(diff.x * diff.x + diff.y * diff.y);
        
        var dir:Vector3D = direction.clone();
        dir.z = 0;
        dir.normalize();
        if (mode == DANCE_FIGHT_MODE) {
            diff.z = 0;
            vertLength = 1.0;
        }
        dir = new Vector3D(dir.x * vertLength, dir.y * vertLength, diff.z);
        dir.normalize();

        axe.direction = dir;
        
        // trace("axe damage " + damage);
        var velocity:Vector3D = axe.direction.clone();
        velocity.normalize();
        velocity.scaleBy(projectileSpeed);
        velocity.incrementBy(realVelocity);
        axe.velocity = velocity;
        game.sfxPlayer.playThrowAxe(game);
        return axe;
    }    
    
    public function performAttack(game:Game, targetPos:Vector3D):void {
        switch (attackType) {
            case PROJECTILE_ATTACK:
                // Add projectile
                var projectile:Projectile = getProjectile(game, targetPos);
                projectile.damage = damage;
                projectile.sizeFactor = sizeFactor;
                // trace("Throwing axe " + projectile.position);
                game.currentLevel.projectiles.push(projectile);
                break;
        }
    }
    
    public function updateAction(game:Game):void {
        animateAttacking = false;
        animateDancing = false;
        if (mode == FIGHT_MODE || mode == DANCE_FIGHT_MODE) {
            animateAttacking = true;
            if (attackCounter > attackInterval) {
                var targetPos:Vector3D = game.currentLevel.player.position.clone();
                targetPos.z += game.currentLevel.player.headZ;
                performAttack(game, targetPos);
                attackCounter = 0;
            }
        }
        if (mode == DANCE_FIGHT_MODE || mode == DANCE_MODE) {
            animateDancing = true;
        }
        attackCounter++;
    }
    
    override public function tick(game:Game):void {
        try {
            super.tick(game);
        } catch (error:Error) {
            trace("Error npc super tick " + error.name + " " + error.message);
        }
       
        if (game.currentLevel.player) {
            if ((game.counter % modeUpdateInterval) == 0) {
                updateMode(game);
            }
            
            // var movement:Vector3D = new Vector3D();
            
            getWantedMovement(game, currentMovement, currentDirection);
                                
            direction.x = currentDirection.x;
            direction.y = currentDirection.y;
            
            updateAction(game);
            
            updateHealthBar(game);
        }
        
        try {
            var realMovement:Array = super.updateMovement(currentMovement.x, currentMovement.y, 0, game);
            realVelocity = new Vector3D(realMovement[0], realMovement[1], 0);
            
        } catch (error:Error) {
            trace("Error npc update movement " + error.name + " " + error.message);
        }
        // if (Math.random() < 0.02) {
             // trace("npc is ticked... " + game.currentLevel.movingObjects.length);
         // }

    }
}



class SfxPlayer {

    // sionDriver.noteOn(m.note, m.voice, length, delay, 0, m.track);

    
    public function playVictory(game:Game):void {
        var guitar:SiONVoice = game.sionPresets["valsound.guitar1"];
        game.sionDriver.noteOn(60, guitar, 4, 0, 0, 0);
        game.sionDriver.noteOn(64, guitar, 4, 4, 0, 0);
        game.sionDriver.noteOn(67, guitar, 4, 8, 0, 0);
        game.sionDriver.noteOn(72, guitar, 4, 12, 0, 0);
    }

    public function playLoss(game:Game):void {
        var guitar:SiONVoice = game.sionPresets["valsound.guitar2"];
        game.sionDriver.noteOn(60, guitar, 4, 0, 0, 0);
        game.sionDriver.noteOn(63, guitar, 4, 4, 0, 0);
        game.sionDriver.noteOn(66, guitar, 4, 8, 0, 0);
    }

    public function playDeath(game:Game):void {
        var guitar:SiONVoice = game.sionPresets["valsound.guitar3"];
        game.sionDriver.noteOn(60, guitar, 2, 0, 0, 0);
        game.sionDriver.noteOn(64, guitar, 2, 0, 0, 0);
        game.sionDriver.noteOn(68, guitar, 2, 0, 0, 0);
    }
    
    public function playAxeHitsLiving(game:Game):void {
        var voice:SiONVoice = game.sionPresets["saw"];
        game.sionDriver.noteOn(10, voice, 1, 0, 0, 0);
    }

    public function playAxeHitsGround(game:Game):void {
        var voice:SiONVoice = game.sionPresets["snoise"];
        game.sionDriver.noteOn(10, voice, 1, 0, 0, 0);
    }
    
    
    public function playThrowAxe(game:Game):void {
        var voice:SiONVoice = game.sionPresets["noise"];
        game.sionDriver.noteOn(10, voice, 1, 0, 0, 0);
    }
    

}



var DANCE_SONG:int = 0;
var INTRO_SONG:int = 1;


class GameSequenceDataProvider implements SequenceDataProvider {    

    public var minorProbability:Number = 0.5;

    private var presets:SiONPresetVoice;
    
    public var bassDrum:SiONVoice;
    public var snareDrum:SiONVoice;
    public var hihat:SiONVoice;
    
    public var bass:SiONVoice;
    public var piano:SiONVoice;
    public var guitar:SiONVoice;
    public var pad:SiONVoice;

    public var song:int = DANCE_SONG;
    
    public var rnd:Rndm;
    
    public function GameSequenceDataProvider(seed:int = 123456):void {
        rnd = new Rndm(seed);
    
        presets = new SiONPresetVoice();        
        
        bassDrum = presets["bassdrumm"];
        snareDrum = presets["snare"];
        hihat = presets["closedhh"];
        
        bass = presets["valsound.bass1"];
        piano = presets["valsound.piano1"];
        guitar = presets["valsound.guitar1"];
        pad = presets["valsound.strpad1"];
    }    
    
    
    public function getRandomPiano(rnd:Rndm):SiONVoice {
        return presets["valsound.piano" + rnd.integer(1, 20)];
    }

    public function getRandomBass(rnd:Rndm):SiONVoice {
        return presets["valsound.bass" + rnd.integer(1, 54)];
    }

    public function getRandomGuitar(rnd:Rndm):SiONVoice {
        return presets["valsound.guitar" + rnd.integer(1, 4)];
    }
    
    // All lengths are given in 16th of a beat
    public var bassMotifs:Array = [
        {lengths: [64], indices: [0, 0], rests: [0], strengths: [1]}, // Bass
        {lengths: [32, 32], indices: [0, 0], rests: [0], strengths: [1]}, // Bass
        {lengths: [16, 16, 16, 16], indices: [0, 0, 0, 0], rests: [0], strengths: [1]}, // Bass
        {lengths: [32, 32], indices: [0, 2], rests: [0], strengths: [1]}, // Bass fifths
        {lengths: [16, 16, 16, 16], indices: [0, 2, 0, 2], rests: [0], strengths: [1]}, // Bass fifths
        {lengths: [32, 32], indices: [0, 3], rests: [0], strengths: [1]}, // Bass octaves
        {lengths: [16, 16, 16, 16], indices: [0, 3, 0, 3], rests: [0], strengths: [1]} // Bass octaves
    ];
    
    public var arpeggioMotifs:Array = [
        {lengths: [16, 16, 16, 16], indices: [0, 1, 2, 1], rests: [0], strengths: [1]},
        {lengths: [8, 8, 8, 8, 8, 8, 8, 8], indices: [0, 1, 2, 1], rests: [0], strengths: [1]},
        {lengths: [16, 8, 8, 16, 8, 8], indices: [0, 1, 2, 1], rests: [0], strengths: [1]},
        {lengths: [16, 16, 16, 16], indices: [0, -1, 1, 0], rests: [0], strengths: [1]},
        {lengths: [8, 8, 8, 8, 8, 8, 8, 8], indices: [0, -1, 1, 0], rests: [0], strengths: [1]},
        {lengths: [16, 8, 8, 16, 8, 8], indices: [0, -1, 1, 0], rests: [0], strengths: [1]},
        {lengths: [16, 16, 16, 16], indices: [1, 0, -1, -2], rests: [0], strengths: [1]},
        {lengths: [8, 8, 8, 8, 8, 8, 8, 8], indices: [1, 0, -1, -2], rests: [0], strengths: [1]},
        {lengths: [16, 8, 8, 16, 8, 8], indices: [1, 0, -1, -2], rests: [0], strengths: [1]},
        {lengths: [16, 16, 16, 16], indices: [-1, 0, 1, 2], rests: [0], strengths: [1]},
        {lengths: [8, 8, 8, 8, 8, 8, 8, 8], indices: [-1, 0, 1, 2], rests: [0], strengths: [1]},
        {lengths: [16, 8, 8, 16, 8, 8], indices: [-1, 0, 1, 2], rests: [0], strengths: [1]}
    ];
    
    public var blockChordMotifs:Array = [
        {lengths: [64], chordNoteCount: [3], rests: [0], strengths: [1]},
        {lengths: [32, 32], chordNoteCount: [3, 4], rests: [0], strengths: [1]},
        {lengths: [48, 16], chordNoteCount: [3], rests: [0], strengths: [1]},
        {lengths: [32, 16, 8, 8], chordNoteCount: [3], rests: [0], strengths: [1]},
        {lengths: [16, 16, 32], chordNoteCount: [3], rests: [0], strengths: [1]},
        {lengths: [32, 32/3, 32/3, 32/3], chordNoteCount: [3], rests: [0, 0, 1, 0], strengths: [1]},
        {lengths: [24, 8, 32/3, 32/3, 32/3], chordNoteCount: [3], rests: [0], strengths: [1]}
    ];
    
    public var voiceMotifs:Array = [
        {lengths: [16 * 4], indices: [0], rests: [0], strengths: [1.0]},
        {lengths: [16 * 2, 16, 16], indices: [0, 1, 0], rests: [0], strengths: [1.0]},
        {lengths: [16 * 3, 16], indices: [0, -1], rests: [0], strengths: [1.0]}
    ];
    
    public var percussionMotifs: Array = [
        {lengths: [16, 16, 16, 16], indices: [0, 0, 0, 0], strengths: [1, 0.75, 0.85, 0.7], rests: [0]}, // bass drum, four to the floor
        {lengths: [16, 16, 16, 16], indices: [-1, 1, -1, 1], strengths: [1, 0.75, 0.85, 0.7], rests: [1, 0, 1, 0] }, // Snare
        {lengths: [8, 8, 8, 8, 8, 8, 8, 8], indices: [2, 2, 2, 2, 2, 2, 2, 2], strengths: [0.7], rests: [0]} // hihat 8ths
    ];

    public var fillMotifs: Array = [
        {lengths: [16, 16, 16, 16], indices: [1], strengths: [1, 0.75, 0.85, 0.7], rests: [1, 0, 0, 0]}, // Snare fill 1
        {lengths: [16, 16, 8, 8, 8, 4, 4], indices: [1], strengths: [1, 0.75, 0.85, 0.7], rests: [0]} // Snare fill 2
    ];

    
    // All harmony lengths are in beats
    public var harmonicRythms: Array = [
        {lengths: [4, 4, 4, 4], chordRoots: [0, 1, 3, 4], worksWithMinor: false, strengths: [1] },
        {lengths: [4, 4, 4, 4], chordRoots: [0, 3, 4, 0], worksWithMinor: true, strengths: [1] },
        {lengths: [4, 4, 4, 4], chordRoots: [0, -2, 3, 4], worksWithMinor: true, strengths: [1] },
        {lengths: [4, 4, 4, 4], chordRoots: [0, 4, 0, 4], worksWithMinor: true, strengths: [1] },
        {lengths: [4, 4, 4, 4], chordRoots: [0, 3, 0, 4], worksWithMinor: true, strengths: [1] },
        {lengths: [4, 4, 4, 4], chordRoots: [0, 4, 0, 3], worksWithMinor: true, strengths: [1] },
        {lengths: [4, 4, 4, 4], chordRoots: [0, -2, 1, 3], worksWithMinor: false, strengths: [1] }
    ];
    

    // Determines the pattern of the motifs to use, not the actual indices of the motifs.
    // The it is just a matter of choosing the 0, 1, and 2 motifs
    public var motifPatterns:Array = [
        [0, 0, 0, 0],
        [0, 0, 0, 1],
        [0, 1, 0, 1],
        [0, 1, 0, 2],
        [0, 0, 1, 0],
        [0, 1, 2, 1]
    ];

    public function getIndexPattern(motifCount:int):Array {
        var pattern:Array = motifPatterns[rnd.integer(0, motifPatterns.length)];
        var count:int = 0;
        var i:int = 0;
        for (i = 0; i<pattern.length; i++) {
            count = Math.max(count, pattern[i] + 1);
        }
        var arr:Array = [];
        for (i = 0; i<count; i++) {
            arr[i] = rnd.integer(0, motifCount); // Doesn't make them unique, but wth
            // trace("count " + motifCount + " result: " + arr[i]);
        }
        var result:Array = [];
        for (i = 0; i<pattern.length; i++) {
            result[i] = arr[pattern[i]]
        }
        return result;
    }
    
    public function renderChordMotif(result:SequenceData, motifIndex:int, chord:ChordInfo, timeMillis:Number, beatMillis:Number, voice:SiONVoice, octaves:int, motif:Object):void {        
        for (var j:int = 0; j<motif.lengths.length; j++) {
            var length:Number = motif.lengths[j];
            var millisLength:Number = (length * beatMillis) / 16.0;
            var isRest:Boolean = (motif.rests[j % motif.rests.length] == 1);
            
            if (!isRest) {
                var index:int = motif.indices[j % motif.indices.length];
                var note:int = chord.getAbsoluteNoteFromChordRootIndex(index);
                note += octaves * 12;
                result.messages.push(new NoteOn(note, 127, timeMillis, millisLength, voice, 0));
            }
            timeMillis += millisLength;
        }
    }
    
    public function renderArpeggioMotif(result:SequenceData, motifIndex:int, chord:ChordInfo, timeMillis:Number, beatMillis:Number, voice:SiONVoice, octaves:int):void {        
        var motif:Object = arpeggioMotifs[motifIndex];
        renderChordMotif(result, motifIndex, chord, timeMillis, beatMillis, voice, octaves, motif);
    }

    public function renderBassMotif(result:SequenceData, motifIndex:int, chord:ChordInfo, timeMillis:Number, beatMillis:Number, voice:SiONVoice, octaves:int):void {        
        var motif:Object = bassMotifs[motifIndex];
        renderChordMotif(result, motifIndex, chord, timeMillis, beatMillis, voice, octaves, motif);
    }
    
    public function renderBlockChordMotif(result:SequenceData, motifIndex:int, chord:ChordInfo, timeMillis:Number, beatMillis:Number, voice:SiONVoice):void {
        var blockChordMotif:Object = blockChordMotifs[motifIndex];
        for (var j:int = 0; j<blockChordMotif.lengths.length; j++) {
            var length:Number = blockChordMotif.lengths[j];
            var millisLength:Number = (length * beatMillis) / 16.0;
            var isRest:Boolean = (blockChordMotif.rests[j % blockChordMotif.rests.length] == 1);
            
            if (!isRest) {
                var chordNoteCount:int = blockChordMotif.chordNoteCount[j % blockChordMotif.chordNoteCount.length];
                for (var k:int =0; k<chordNoteCount; k++) {
                    var note:int = chord.getAbsoluteNoteFromChordRootIndex(k);
                    result.messages.push(new NoteOn(note, 127, timeMillis, millisLength, voice, 0));
                }
            }
            timeMillis += millisLength;
        }
    }
    
    public function renderVoiceMotif(result:SequenceData, motifIndex:int, harmonyIndex:int, chordNote:int):void {        
    }

    public function renderPercussion(result:SequenceData, percussionIndex:int, timeMillis:Number, beatMillis:Number):void {        
        var motif:Object = percussionMotifs[percussionIndex];
        for (var i:int = 0; i<motif.lengths.length; i++) {
            var lengthMillis:Number = (motif.lengths[i] * beatMillis) / 16;
            
            var voice:SiONVoice = bassDrum;
            var index:int = motif.indices[i % motif.indices.length];
            var isRest:Boolean = (motif.rests[i % motif.rests.length] == 1);
            switch (index) {
                case 0:
                    voice = bassDrum;
                    break;
                case 1:
                    voice = snareDrum;
                    break;
                case 2:
                    voice = hihat;
                    break;
                default:
                    voice = null;
                    break;
            }
            if (voice != null && !isRest) {
                result.messages.push(new NoteOn(40, 127, timeMillis, beatMillis, voice, 0));
            }
            timeMillis += lengthMillis;
        }
    }

    
    public function getSequenceData():SequenceData {
        var result:SequenceData = new SequenceData();

        piano = getRandomPiano(rnd);
        bass = getRandomBass(rnd);        
        guitar = getRandomGuitar(rnd);        
        
        
        var chordInfo:ChordInfo = new ChordInfo();
        var chords:Array = [];
        
        var i:int = 0;        
        var note:int = 60;
        var j:int = 0;
        var chord:ChordInfo = null;

        var hrBeats:int = 0;
        
        var hr:Object = harmonicRythms[rnd.integer(0, harmonicRythms.length)];

        var scale:Array = [0, 2, 4, 5, 7, 9, 11];
        if (hr.worksWithMinor) {
            // Select minor or major scale
            if (rnd.random() < minorProbability) {
                scale = [0, 2, 3, 5, 7, 8, 10];
            }
        }
        var scaleBase:int = 60;
        if (rnd.random() < 0.2 && song != INTRO_SONG) {
            scaleBase = 61;
        }
        
        
        for (i = 0; i<hr.lengths.length; i++) {
            chord = new ChordInfo();
            chord.scale = scale;
            chord.scaleBase = scaleBase;
            chord.chordRoot = hr.chordRoots[i % hr.chordRoots.length];
            chord.beats = hr.lengths[i];
            chords.push(chord);
            hrBeats += chord.beats;
        }
        
        result.length = hrBeats * 500;
        var beatMillis:Number = result.length / result.beatCount;
 
        var bassIndices:Array = getIndexPattern(bassMotifs.length);
        var arpeggioIndices:Array = getIndexPattern(arpeggioMotifs.length);
        var blockChordIndices:Array = getIndexPattern(blockChordMotifs.length);
        
        var addBass:Boolean = true;
        var addArpeggio:Boolean = true;
        var addBlockChords:Boolean = true;
        var addBassDrum:Boolean = true;
        var addSnare:Boolean = true;
        var addHihats:Boolean = true;
        var addFill:Boolean = true;

        
        var rndVal:Number = rnd.random();
        if (rndVal < 0.1) {
            addArpeggio = false;
            addBlockChords = false;
        } else if (rndVal < 0.2) {
            addBlockChords = false;
            addBass = false;
        } else if (rndVal < 0.3) {
            addArpeggio = false;
            addBass = false;
        } else if (rndVal < 0.4) {
            addArpeggio = false;
        } else if (rndVal < 0.5) {
            addBlockChords = false;
        } else if (rndVal < 0.6) {
            addBass = false;
        }


        rndVal = rnd.random();
        if (rndVal < 0.1) {
            addBassDrum = false;
            addSnare = false;
        } else if (rndVal < 0.2) {
            addHihats = false;
            addBassDrum = false;
        } else if (rndVal < 0.3) {
            addHihats = false;
            addSnare = false;
        } else if (rndVal < 0.4) {
            addSnare = false;
        } else if (rndVal < 0.5) {
            addBassDrum = false;
        } else if (rndVal < 0.6) {
            addHihats = false;
        }

         if (song == INTRO_SONG) {    
            addBass = false;
            addBassDrum = true;
            addHihats = false;
            addSnare = false;            
            addBlockChords = false;
            addArpeggio = false;
         }
        
        for (i = 0; i<hrBeats; i++) {
            var currentTime:Number = beatMillis * i;
                       
          
            var harmonyIndex:int = i / 4;
            if ((i % 4) == 0) {
                // Start of harmony, get the chord
                chord = chords[harmonyIndex];
                
                // Add percussion motifs
                if (addBassDrum) {
                    renderPercussion(result, 0, currentTime, beatMillis);
                }
                if (addSnare) {
                    renderPercussion(result, 1, currentTime, beatMillis);
                }
                if (addHihats) {
                    renderPercussion(result, 2, currentTime, beatMillis);
                }
                
                if (addBass) {
                    // Add bass                    
                    renderBassMotif(result, bassIndices[harmonyIndex], chord, currentTime, beatMillis, bass, -2);
                }
                if (addArpeggio) {
                    // Add arpeggio
                    renderArpeggioMotif(result, arpeggioIndices[harmonyIndex], chord, currentTime, beatMillis, guitar, 1);
                }
                
                if (addBlockChords) {
                    // Add block chord motif
                    renderBlockChordMotif(result, blockChordIndices[harmonyIndex], chord, currentTime, beatMillis, piano);                
                }
            }         
        }
        
        return result;        
    }

}


interface SequenceDataProvider {
   function getSequenceData():SequenceData;
}


class NoteOn {
    public var note:int = 60;
    public var velocity:int = 64;
    public var time:uint = 0;
    public var length:uint = 250;
    public var voice:SiONVoice = null;
    public var track:int = 0;

    public function NoteOn(note:int = 60, velocity:int = 64, time:uint = 0, length:uint = 250, voice:SiONVoice = null, track:int = 0) {
        this.note = note;
        this.velocity = velocity;
        this.time = time;
        this.length = length;
        this.voice = voice;
        this.track = track;
    }
    
    public function copy():NoteOn {
        return new NoteOn(note, velocity, time, length, voice, track);
    }

}

class SequenceData {
    public var messages:Vector.<NoteOn> = new Vector.<NoteOn>();
    public var length:uint = 8000;
    public var beatCount:uint = 16;        
    
    public function split(time:uint):Array {
        var result:SequenceData = new SequenceData();
        var rest:SequenceData = new SequenceData();
        for (var i:int=0; i<messages.length; i++) {
            var message:NoteOn = messages[i];
            if (message.time <= time) {
                result.messages.push(message);
            } else {
                rest.messages.push(message);
            }
        }
        return [result, rest];
    }

}


var SEQUENCER_STOP_MODE:int = 0;
var SEQUENCER_PLAY_MODE:int = 1;

class MusicSequencer {
    
    private var sequenceData:SequenceData = null;
    private var sequenceTime:uint = 0;

    private var millisPer16thBeat:Number;
    private var beatsPerSecond:Number;
    
    private var sionDriver:SiONDriver = null;    
    
    private var dataProvider:SequenceDataProvider = null;
    
    private var mode:int = SEQUENCER_STOP_MODE;
    
    public function MusicSequencer(driver:SiONDriver, dataProvider:SequenceDataProvider):void {
        this.sionDriver = driver;
        this.dataProvider = dataProvider;
    }
    
    public function stop():void {
        mode = SEQUENCER_STOP_MODE;
    }
    
    public function play():void {
        mode = SEQUENCER_PLAY_MODE;
    }

    
    public function tick():void {
        if (mode == SEQUENCER_STOP_MODE) {
        
            sequenceData = null;
            
        } else {
            var currentTime:uint = flash.utils.getTimer();
            
            var extraOffset:uint = 400;
            
            if (!sequenceData) {
                sequenceData = dataProvider.getSequenceData();
                sequenceTime = currentTime;
                millisPer16thBeat = (sequenceData.length / sequenceData.beatCount) / 4.0;
                beatsPerSecond = 1000 * (sequenceData.beatCount / sequenceData.length);
            }
            if (currentTime + extraOffset > sequenceTime + sequenceData.length) {
                // Time to get the next data
                sequenceTime = sequenceTime + sequenceData.length;
                sequenceData = dataProvider.getSequenceData();
                millisPer16thBeat = (sequenceData.length / sequenceData.beatCount) / 4.0;
                beatsPerSecond = 1000 * (sequenceData.beatCount / sequenceData.length);
            }
            
            sionDriver.bpm = beatsPerSecond * 60;
            
            var splitted:Array = sequenceData.split(currentTime - sequenceTime + extraOffset);        
            
            sequenceData = splitted[1];
            var toRender:SequenceData = splitted[0];
            
            var messages:Vector.<NoteOn> = toRender.messages;
                        
            
            
            for (var i:int=0; i<messages.length; i++) {
                var m:NoteOn = messages[i];
                
                var length:Number = m.length / millisPer16thBeat;

                var delayMillis:Number = m.time + sequenceTime - currentTime;
                var delay:Number = Math.max(0, delayMillis / millisPer16thBeat);
                
                // trace(length + " " + sionDriver.bpm + " " + delay);
                     
                
                var track:SiMMLTrack = sionDriver.noteOn(m.note, m.voice, length, delay, 0, m.track);
                track.velocity = m.velocity;
                
            }

        }
    }

}


function positiveMod(a:int, b:int):int { return a >= 0 ? a % b : (b + a % b) % b ; }
class ChordInfo { // Musical stuff
    public var beats:int = 4; public var scaleBase:int = 60; public var scale:Array = [0, 2, 4, 5, 7, 9, 11];
    public var chordRoot:int = 0; public var chordOffsets:Array = [0, 2, 4];    
    public function getChordRootPositionAbsoluteOffsets():Array {
        var result:Array = []; var scaleIndices:Array = getChordRootPositionScaleIndices();
        var first:int = scaleIndices[0]; var firstAbsolute:int = getAbsoluteNote(scaleBase, scale, first);
        var diff:int = firstAbsolute - scaleBase;
        for (var i:int=0; i<scaleIndices.length; i++) { result[i] = getAbsoluteNote(scaleBase, scale, scaleIndices[i]) - firstAbsolute + diff; }
        return result; }
    public function getChordRootPositionScaleIndices():Array { var result:Array = [];
        for (var i:int = 0; i<chordOffsets.length; i++) { result.push(chordRoot + chordOffsets[i]); }
        return result; }
    public function getAbsoluteNoteFromChordRootIndex(index:int):int {
        var chordOffsets:Array = getChordRootPositionAbsoluteOffsets(); var first:int = chordOffsets[0];
        for (var i:int=0; i<chordOffsets.length; i++) { chordOffsets[i] -= first; }
        return getAbsoluteNote(scaleBase + first, chordOffsets, index); }
    public function pitchClassDistance(c1:int, c2:int):int { return Math.min(Math.abs(c1 - c2), 12 - Math.abs(c1 - c2)); }
    public function getAbsoluteNote(absoluteBaseNote:int, offsets:Array, index:int):int { var offsetIndex:int = 0;
        var octaveOffset:int = 0; offsetIndex = positiveMod(index, offsets.length);
        if (index >= 0) { octaveOffset = Math.floor(index / offsets.length);
        } else { octaveOffset = -Math.floor((-index + offsets.length - 1) / offsets.length);
        } return absoluteBaseNote + 12 * octaveOffset + offsets[offsetIndex]; }
    public function getClosestNoteWithPitchClasses(absoluteNote:int, pitchClasses:Array):int {
        var notePitchClass:int = absoluteNote % 12; var minDistance:int = 99999;  var closestPitchClass:int = 0;
        for (var i:int = 0; i < pitchClasses.length; i++) { var distance:int = pitchClassDistance(notePitchClass, pitchClasses[i]);
            if (distance < minDistance) { minDistance = distance; closestPitchClass = pitchClasses[i]; } }
        if (((absoluteNote + minDistance) % 12) == closestPitchClass) { return absoluteNote + minDistance;
        } else if (((absoluteNote - minDistance) % 12) == closestPitchClass) { return absoluteNote - minDistance;
        } else { return Math.floor(absoluteNote / 12) * 12 + closestPitchClass; } }
    public function getAbsoluteNoteFromScaleIndex(index:int):int { return getAbsoluteNote(scaleBase, scale, index); }
    public function offsetScale(absoluteNote:int, offset:int):int { var result:int = absoluteNote; var indexChr:Array = getScaleIndexAndChromaticOffsetForAbsoluteNote(result);
        var scaleIndex:int = indexChr[0] + offset; var absNote:int = getAbsoluteNoteFromScaleIndex(scaleIndex);
        result = absNote; return result; }
    public function offsetChord(absoluteNote:int, offset:int):int { var result:int = absoluteNote;
        var indexChr:Array = getChordRootIndexAndChromaticOffsetForAbsoluteNote(result);
        result = getAbsoluteNoteFromChordRootIndex(indexChr[0] + offset); return result; }
    public function getChordRootIndexAndChromaticOffsetForAbsoluteNote(absoluteNote:int):Array { var increments:Array = getChordRootPositionAbsoluteOffsets();
        var firstInc:int = increments[0]; var baseNote:int = scaleBase + firstInc;
        for (var i:int=0; i<increments.length; i++) { increments[i] -= firstInc; }
        var result:Array = getScaleIndexAndChromaticOffsetForAbsoluteNoteStatic(absoluteNote, baseNote, increments);
        return result; } 
    public function getScaleIndexAndChromaticOffsetForAbsoluteNote(absoluteNote:int):Array { return getScaleIndexAndChromaticOffsetForAbsoluteNoteStatic(absoluteNote, scaleBase, scale); }
    public static function getScaleIndexAndChromaticOffsetForAbsoluteNoteStatic(absoluteNote:int, baseNote:int, increments:Array):Array {
        var chromaticOffset:int = 0; var resultIndex:int = 0;  var absDiff:int = absoluteNote - baseNote; var diffOctave:int = 0;    
        var normalizedNote:int = absDiff;  while (normalizedNote < 0) { normalizedNote += 12; diffOctave--; }
        while (normalizedNote > 11) { normalizedNote -= 12; diffOctave++; } var shortestAbsDistance:int = 9999999;
        for (var i:int = 0; i < increments.length; i++) {
            if (increments[i] == normalizedNote) { resultIndex = i + diffOctave * increments.length; chromaticOffset = 0; break;
            } else { var diff:int = normalizedNote - increments[i];
                if (Math.abs(diff) < shortestAbsDistance) { shortestAbsDistance = Math.abs(diff);
                    resultIndex = i + diffOctave * increments.length; chromaticOffset = diff; } } }
        return [resultIndex, chromaticOffset]; }
}

function inittrace(s:Game):void { WTrace.initTrace(s);} var trace:Function;
class WTrace { // Mr trace
        private static var FONT:String = "Fixedsys";
        private static var SIZE:Number = 12;
        private static var TextFields:Array = [];
        private static var trace_stage:Game;        
        public static function initTrace(stg:Game):void { trace_stage = stg; trace = wtrace; }        
        private static function scrollup():void { if (TextFields.length > 30) { var removeme:TextField = TextFields.shift(); trace_stage.interfaceContainer.removeChild(removeme);
                removeme = null; } for(var x:Number=0;x<TextFields.length;x++) { (TextFields[x] as TextField).y -= SIZE*1.2;}}
    
        public static function wtrace(... args):void { var s:String=""; var tracefield:TextField; for (var i:int;i < args.length;i++) {
                if (i != 0) s+=" "; s+=args[i].toString(); } tracefield= new TextField(); tracefield.autoSize = "left"; tracefield.text = s;
            tracefield.y = trace_stage.stage.stageHeight - 40; var tf:TextFormat = new TextFormat(FONT, SIZE);tracefield.setTextFormat(tf);
            trace_stage.interfaceContainer.addChild(tracefield); scrollup(); TextFields.push(tracefield); } }
            
class Rndm { // Mr uniform
    protected var _seed:uint=0;
    protected var _currentSeed:uint=0;
    public function Rndm(seed:uint=1) { _seed = _currentSeed = seed; }
    public function get seed():uint {return _seed;}
    public function set seed(value:uint):void {_seed = _currentSeed = value;}    
    public function get currentSeed():uint { return _currentSeed; }
    public function random():Number { return (_currentSeed = (_currentSeed * 16807) % 2147483647)/0x7FFFFFFF+0.000000000233; }    
    // float(50); // returns a number between 0-50 exclusive
    // float(20,50); // returns a number between 20-50 exclusive
    public function float(min:Number,max:Number=NaN):Number { if (isNaN(max)) { max = min; min=0; } return random()*(max-min)+min; }  
    // boolean(); // returns true or false (50% chance of true)
    // boolean(0.8); // returns true or false (80% chance of true)
    public function boolean(chance:Number=0.5):Boolean {return (random() < chance);    }            
    // integer(50); // returns an integer between 0-49 inclusive
    // integer(20,50); // returns an integer between 20-49 inclusive
    public function integer(min:Number,max:Number=NaN):int {if (isNaN(max)) { max = min; min=0; } return Math.floor(float(min,max));    }
    // reset(); // resets the number series, retaining the same seed
    public function reset():void {_seed = _currentSeed;}}

    
class ClassicalNoise { // Mr Perlin
    private var grad3:Array; private var perm:Array; private var p:Array;
 public function ClassicalNoise(r:Rndm):void { // Classic Perlin noise in 3D, for comparison 
    grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0], [1,0,1],[-1,0,1],[1,0,-1],[-1,0,-1], [0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]];
    this.p = []; var i:int; for (i=0; i<256; i++) { p[i] = Math.floor(r.random()*256);}
    perm = []; for(i=0; i<512; i++) { perm[i]=p[i & 255]; } }
    public function dot(g:Array, x:Number, y:Number, z:Number):Number { return g[0]*x + g[1]*y + g[2]*z; }
    public function mix(a:Number, b:Number, t:Number):Number { return (1.0-t)*a + t*b; }
    public function fade(t:Number):Number { return t*t*t*(t*(t*6.0-15.0)+10.0); }
    public function noise(x:Number, y:Number, z:Number):Number { var X:Number = Math.floor(x);var Y:Number = Math.floor(y); var Z:Number = Math.floor(z);
        x = x - X; y = y - Y; z = z - Z; X = X & 255;Y = Y & 255; Z = Z & 255;
        var gi000:Number = this.perm[X+this.perm[Y+this.perm[Z]]] % 12; var gi001:Number = this.perm[X+this.perm[Y+this.perm[Z+1]]] % 12;
        var gi010:Number = this.perm[X+this.perm[Y+1+this.perm[Z]]] % 12; var gi011:Number = this.perm[X+this.perm[Y+1+this.perm[Z+1]]] % 12;
        var gi100:Number = this.perm[X+1+this.perm[Y+this.perm[Z]]] % 12; var gi101:Number = this.perm[X+1+this.perm[Y+this.perm[Z+1]]] % 12;
        var gi110:Number = this.perm[X+1+this.perm[Y+1+this.perm[Z]]] % 12; var gi111:Number = this.perm[X+1+this.perm[Y+1+this.perm[Z+1]]] % 12;
        var n000:Number= this.dot(this.grad3[gi000], x, y, z); var n100:Number= this.dot(this.grad3[gi100], x-1, y, z);
        var n010:Number= this.dot(this.grad3[gi010], x, y-1, z); var n110:Number= this.dot(this.grad3[gi110], x-1, y-1, z);
        var n001:Number= this.dot(this.grad3[gi001], x, y, z-1); var n101:Number= this.dot(this.grad3[gi101], x-1, y, z-1);
        var n011:Number= this.dot(this.grad3[gi011], x, y-1, z-1); var n111:Number= this.dot(this.grad3[gi111], x-1, y-1, z-1);
        var u:Number = this.fade(x); var v:Number = this.fade(y); var w:Number = this.fade(z);
        var nx00:Number = this.mix(n000, n100, u); var nx01:Number = this.mix(n001, n101, u); var nx10:Number = this.mix(n010, n110, u);
        var nx11:Number = this.mix(n011, n111, u); var nxy0:Number = this.mix(nx00, nx10, v); var nxy1:Number = this.mix(nx01, nx11, v);
        var nxyz:Number = this.mix(nxy0, nxy1, w); return nxyz; } }