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

forked from: LD48 #23 The Children of DiscoDroid

Get Adobe Flash player
by myfork 12 May 2012

    Talk

    0954321982 at 07 Jun 2013 00:48
    stupid shit
    Embed
/**
 * Copyright myfork ( http://wonderfl.net/user/myfork )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/lu4O
 */

// forked from krasse's LD48 #23 The Children of DiscoDroid
package {

    import flash.display.Sprite;

    public class Main extends Sprite {
        
        public var game:Game;
             
        public function Main() {            
            game = new Game();
            addChild(game);
            game.init();
        }
        
    }
}

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.Bitmap;
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.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 rnd:Rndm = new Rndm(342);        
    public var fontsLoaded:Boolean = false;
    public var musicSequencer:MusicSequencer = 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 sfxPlayer:SfxPlayer = new SfxPlayer();
    
    
    public function Game() {
    }

    public function getGroundZ(x:Number, y:Number):Number {
        return currentLevel.groundMesh.getGroundZ(x, y);
    }


    public function reset():void {
        playerData = new PlayerData();
    }
    
    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 function addFort(x:Number, y:Number, level:Level, spawner:ObstacleSpawner):void {
    
        
    
        var sketch: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, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1]
        ];
        
        var wallSize:Number = 20;
        var wallHeight:Number = 40;

        var fortRect:Rectangle = new Rectangle(x, y, wallSize * sketch[0].length, wallSize * sketch.length);
        spawner.noPlaceRectangles.push(fortRect);
        
        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.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);
        
        
        var boss:NPC = new NPC();
        boss.npcType = BOSS_1;
        boss.health = 0.5;
        boss.attackInterval = 30;
        boss.maxSpeed = 2;
        boss.position.x = fortCenterX;
        boss.position.y = fortCenterY;
        boss.modeWhenAlone = WAIT_MODE;
        boss.mode = WAIT_MODE;
        boss.resistsDance = true;
        level.movingObjects.push(boss);

        level.boss = boss;
 
        trace("Your mission is to kill the big bad boss");
        trace("He is very close, but doesn't dance...");
        trace("Make other dance with 'D' and fight with 'F'");
 
        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];
                if (item == 1) {
                    var wallX:Number = i * wallSize + x;
                    var wallY:Number = j * wallSize + y;
                    
                    var wall:Wall = new Wall();
                    wall.position.x = wallX;
                    wall.position.y = wallY;
                    wall.size = wallSize;
                    wall.height = wallHeight;
                    level.solidObjects.push(wall);
                }
            }
        }
        
    }
    
    // index 0 is the intro scene level
    public function loadLevel(index:int):void {
        var level:Level = new Level();

        if (index == 0) {
            // Intro scene with a slow camera moving towards the robot
            

            var robot:NPC = new NPC();
            robot.npcType = BOSS_1;
            robot.position.x = 0;
            robot.position.y = 0;
            robot.position.z = 0;

            level.cameraController = new SlowApproachCameraController(this, mainCamera, robot);
            
            level.movingObjects.push(robot);
            
        } else {
            level.groundMesh = new GroundMesh();
            level.player = new Player();
            level.player.position = new Vector3D(level.startX, level.startY, 0);
            level.cameraController = new InGameCameraController(this, mainCamera);
            
            var rnd:Rndm = new Rndm(3425);
            var spawner:ObstacleSpawner = new ObstacleSpawner();
            level.objectSpawners.push(spawner);
            
            addFort(0, 500, level, spawner);
        }
        currentLevel = level;
    }
    
    public function init():void {
        inittrace(stage);
       
        graphics.beginFill(0x333333);
        graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
        graphics.endFill();
       
        var fl:FontLoader = new FontLoader();
        fl.load( "Bebas" );
        fl.addEventListener( Event.COMPLETE, function(ev :Event) :void {
            fontsLoaded = true;
        });          
        
        sionDriver = new SiONDriver();        sionDriver.play();
        
        sionPresets = new SiONPresetVoice();        

        
        mainCamera = new Camera3D();
        mainCamera.zoom = 1;
        mainCamera.focus = 200;
        
        // mainCamera.lookAt(new Vector3D(0.0, 0, 0), new Vector3D(0, 1, 0));
        
        scene = new Scene3D();
        
        view = new View3D({camera:mainCamera, x:250, y:200, scene: scene});
        // view.renderer = new QuadrantRenderer();
        addChild(view);
        
        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(1);
    }
    
    
    public function tick():void {
        try {
            tickAll(solidObjects);
            tickAll(movingObjects);
            tickAll(worldParticles);
            tickAll(screenParticles);
            for (var key:String in actionQueues) {
                tickAll(actionQueues[]);
            }
            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);
        }
        counter++;
    }
        
        
    public function onResize(e:Event):void {
    }

    public function onEnterFrame(e:Event):void {
        tick();
        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 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 = 10;
    public var outerRadius:Number = 300;
    
    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 removeMe:Boolean = false;

    public var initialized:Boolean = false;
    
    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);
    }
    
    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);
    }
    
    override public function tick(game:Game):void {
        super.tick(game);
        var level:Level = game.currentLevel;
        if (level && level.player) {
            var playerPosition:Vector3D = level.player.position;
            camera.x = playerPosition.x;
            camera.y = playerPosition.y - 60;
            camera.z = playerPosition.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(playerPosition.x, playerPosition.y, playerPosition.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);

        var playerPosition:Vector3D = obj.position;
        camera.x = playerPosition.x;
        camera.y = playerPosition.y - 30;
        camera.z = playerPosition.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(playerPosition.x, playerPosition.y, playerPosition.z), new Vector3D(0, 1, -1));
        
        obj.container.rotationZ = game.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 GameObject {

    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 addedStuff:Array = [];
    
    public var type:int = CENTER_SCREEN_MESSAGE;

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

        switch (type) {
            case CENTER_SCREEN_MESSAGE:
                for (var i:int=0; i<strings.length; i++) {
                    var tf:TextField = new TextField();
                    tf.embedFonts = true;
                    tf.defaultTextFormat = new TextFormat( "Bebas", 42, 0xFFCC00);
                    tf.text = strings[i];
                    game.stage.addChild(tf);
                    addedStuff.push(tf);
                }
                break;
        }
    }
    
    override public function removed(game:Game):void {
        for (var i:int=0; i<addedStuff.length; i++) {
            var tf:TextField = addedStuff[i];
            game.stage.removeChild(tf);
        }
    }
    
     override public function tick(game:Game):void {
         super.tick(game);
     }
    
    // public function testTextEffect(game:Game, s:String):void {
        // if (game.fontsLoaded) {
            // var tf :TextField = new TextField();
            // tf.embedFonts = true;
            // tf.defaultTextFormat = new TextFormat( "Bebas", 42, 0xFFCC00);
            // tf.text = s;
            // tf.x = Math.random() * game.stage.width;
            // tf.y = Math.random() * game.stage.height;
            // game.addChild(tf);        
            // var anim:TextAnim = new TextAnim(tf);                

            // anim.effects = function(block:TextAnimBlock):void {
                // block.scaleX = block.scaleY = 0;    
                // block.rotation = -120;    
                // Tweener.addTween(block, {rotation:0, scaleX:1, scaleY:1, time:.5, transition:"easeoutback"});
            // };
            
            // anim.blocksVisible = false;
            // anim.addEventListener(TextAnimEvent.COMPLETE, function():void {
                // setTimeout(function():void {
                    // anim.dispose();
                    // }, 1000);
            // });
            // anim.start();
        // }
    // }

}

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 globalActions:Array = [];
    public var actionQueues:Object = {};    

    public var cameraController:CameraController;
    public var player:Player;
    public var boss:NPC;
    public var groundMesh:GroundMesh;
    
    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 {
            game.tickAll(objectSpawners);
        } catch (error:Error) {
            trace("Error object spawners 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) {
            game.tickAll(actionQueues[key]);
        }
        game.tickAll(globalActions);

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

    }

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

}


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);
        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);
            }
        }
    }

}


class ObstacleSpawner extends GridObjectSpawner {

    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);
        
        var rndVal:Number = rnd.random();
        
        if (rndVal < 0.3) {
            var npc:NPC = new NPC();
            npc.spawnInfo = spawnInfo;
            npc.containerPartOfScene = false;
            npc.position = spawnInfo.spawner.getPosition(spawnInfo, rnd);                
            level.movingObjects.push(npc);
            
        } else {        
            var tree:Tree = new Tree();
            tree.spawnInfo = spawnInfo;
            tree.containerPartOfScene = false;
            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);
        }
        // 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 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 = true;
    public var containerCheckRemovePeriod:int = 30;
    public var containerCheckAddPeriod:int = 30;
    
    
    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 removeCompletely(game:Game):void {
        game.scene.removeChild(container);
       if (spawnInfo) {
            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 (!game.currentLevel.groundMesh) {
            } else {
                if (!removeMe) {
                    if (containerPartOfScene && ((game.counter % containerCheckRemovePeriod) == 0)) {
                        if (shouldRemoveContainer(game)) {

                            game.scene.removeChild(container);
                            containerPartOfScene = false;
                        }
                    }
                    if (!containerPartOfScene && ((game.counter % containerCheckAddPeriod) == 0)) {
                        if (shouldAddContainer(game)) {
                            game.scene.addChild(container);
                            containerPartOfScene = true;
                        }
                    }
                }
            }
         } catch (error:Error) {
            trace("Error physical object update container part 1" + error.name + " " + error.message);
        }
        
        try {
        if ((game.counter % containerCheckRemovePeriod) == 0) {
            if (shouldRemoveCompletely(game)) {
                removeCompletely(game);

                game.scene.removeChild(container);
                containerPartOfScene = false;
                removeMe = true;
            }
        }
         } catch (error:Error) {
            trace("Error physical object update container part 2" + 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
                    rect = getTestRectangle(stepX, 0);
                    if (rect.intersects(solidRect)) {
                        stepX = 0;
                    }
                    rect = getTestRectangle(0, stepY);
                    if (rect.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();
        containerPartOfScene = false;
    }   

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

        game.scene.removeChild(container);
        if (spawnInfo) {
            spawnInfo.spawner.objectRemoved(this, spawnInfo);
        }
    }
    
}


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

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

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

    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 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);
    }
}


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.height = stemHeight * sizeFactor;
        stem.radius = 3 * sizeFactor;
        stem.z = -stem.height * 0.5;
        stem.yUp = false;
        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.material = new WireColorMaterial(0x006700);
            cone.radius = 10 * sizeFactor;
            cone.height = 35 * sizeFactor;
            cone.rotationY = 180;
            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, 10);
    }
}


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.height = size;
        cube.width = size;
        cube.depth = height;
        cube.z = -cube.height * 0.5;
        cube.material = new WireColorMaterial(0x333333);
        
        container.addChild(cube);        
        dimension = new Vector3D(size, size, size);
    }

}




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 = 100;
            var xDistance:Number = 150;
            // var faceDistanceSq:Number = faceDistance * faceDistance;
            
            var stepSize:int = 30;
            
            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);
    }
    
    
    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);
    }
    
    

}

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

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

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

}

class Projectile extends MovingPhysicalObject {

    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 {      
    }
    
    
}

class Axe extends Projectile {

    public var damage:Number = 0.1;

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

    override public function tick(game:Game):void {
        super.tick(game);
        container.rotationY -= 15;
    }
    
    override public function init(game:Game):void {
        super.init(game);
        var cube:Cube = new Cube();
        cube.width = 10;
        cube.height = 2;
        cube.depth = 4;
        container.addChild(cube);
        // container.rotationZ = 
        
        container.rotationZ = 180 * Math.atan2(direction.y, direction.x) / Math.PI;        
        updateContainer(game);
        game.scene.addChild(container);
    }

    override public function hitsLivingObject(game:Game, obj:LivingObject):void {
        if (owner != obj && !removeMe) {
            removeMe = true;
            obj.doDamage(game, damage);
            // 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);
    }
    
}



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 health:Number = 1.0;
    public var dead:Boolean = false;
    
    public function doDamage(game:Game, damage:Number):void {
        if (!dead) {
            health -= damage;
            if (health <= 0.0) {
                die(game);
                dead = true;
                health = 0.0;
            }
        }
    }
    
    public function die(game:Game):void {
        if (this == game.currentLevel.player) {
            trace("You died, but I feel generous. Kill the Boss!");
            game.sfxPlayer.playLoss(game);
        } else {
            if (this == game.currentLevel.boss) {
                trace("You killed the big bad boss! Great!");
                game.sfxPlayer.playVictory(game);
            }
            game.sfxPlayer.playDeath(game);
            removeMe = true;
            spawnInfo = null;
        }
    }
    
    override public function tick(game:Game):void {
        super.tick(game);
        
        var rect:Rectangle = getRectangle();
        
        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 (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 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 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;
        
        body = new Cylinder();
        body.material = new WireColorMaterial(bodyColor);
        body.segmentsW = 5;
        body.height = torsoLength;
        body.radius = torsoRadius;
        body.z = -torsoLength - legLength - offset;
        body.yUp = false;
        container.addChild(body);        

        head = new Sphere();
        head.material = new WireColorMaterial(headColor);
        head.yUp = false;
        head.segmentsH = 5;
        head.segmentsW = 5;
        head.radius = headLength * 0.5;
        head.z = -legLength - torsoLength - armRadius - headLength- offset;
        container.addChild(head);
        
        leftArm = new Cylinder();
        leftArm.material = new WireColorMaterial(armColor);
        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 = new WireColorMaterial(armColor);
        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);
        
        
        leftLeg = new Cylinder();
        leftLeg.material = new WireColorMaterial(legColor);
        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 = new WireColorMaterial(legColor);
        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);
        
        
        dimension = new Vector3D(body.radius * 2, body.radius * 2, 10);
    }

    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);
            
        } 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 {
        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;
    }
    
    override public function tick(game:Game):void {
        super.tick(game);
        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(game.counter * 0.8);
            rightArm.rotationZ = 40 * Math.sin(game.counter * 0.8);
            leftLeg.rotationY = 40 * Math.sin(game.counter * 0.8);
            rightLeg.rotationY = -40 * Math.sin(game.counter * 0.8);

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

class Player extends Humanoid {

    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 danceRadius:Number = 100;

    public var danceSpeed:Number = 1;
    public var maxSpeed:Number = 5;

    public var sceneLight:DirectionalLight3D;
    
    public var haveBall:Boolean = false;
    public var danceBall:ObjectContainer3D = null;
    public var sphere:Sphere;
    
    override public function init(game:Game):void {
        // Set colors and size here
        sizeFactor = 1.0;
        super.init(game);
    }
    
    override public function removed(game:Game):void {
    }
    
    override public function tick(game:Game):void {
        try {
            super.tick(game);
        } catch (error:Error) {
            trace("Error player super tick " + error.name + " " + error.message);
        }
        
        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;
        }
        dancers.length = 0;
        
        dancing = false;
        if (game.keysDown["68"]) {
            if (batteryLevel > 0.0) {
                dancing = true;
            }
        }
        if (!wasDancing && dancing) {
            game.sfxPlayer.playDancing(game);
        }        
        
        fighting = false;
        if (game.keysDown["70"]) {
            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 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);
                        }
                    }
                }
            }
            if (!haveBall) {
                if (danceBall == null) {
                    danceBall = new ObjectContainer3D();
                    sphere = new Sphere();
                    sphere.radius = 10;
                    danceBall.addChild(sphere);
                }
                game.scene.addChild(danceBall);
                haveBall = true;
            }
        } else {
            if (haveBall) {
                game.scene.removeChild(danceBall);
                haveBall = false;
            }
        }
        
        animateDancing = dancing;
    
        if (haveBall) {
            // Update ball
            danceBall.position = new Vector3D(position.x, position.y, position.z - 60);
            danceBall.rotationZ = game.counter * 2;
        }
        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 (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];
        }
        
        // if (Math.random() < 0.05) {
            // trace("player is ticked...");
        // }
    }
}



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

    public var containerRemoveDistance:Number = 320;
    public var containerAddDistance:Number = 200;
    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);
        
    }
    
    override public function shouldRemoveContainer(game:Game):Boolean {
        var pos:Vector3D = game.currentLevel.player.position;
        return Vector3D.distance(position, pos) > containerRemoveDistance;
    }

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

    override public function shouldRemoveCompletely(game:Game):Boolean {
        // This is not good... but it works better without complete removal
        return false;
        // var pos:Vector3D = game.currentLevel.player.position;
        // return Vector3D.distance(position, pos) > completeRemoveDistance;
    }

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



var MONSTER_1:int = 0;
var MONSTER_2:int = 1;
var MONSTER_3:int = 2;
var BOSS_1:int = 3;

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 MOVE_RIGHT:int = 0;
var MOVE_DOWN:int = 1;
var MOVE_LEFT:int = 2;
var MOVE_UP:int = 3;

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

    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 mode:int = PATROL_MODE;
    public var modeWhenAlone:int = PATROL_MODE;    
    public var attackType:int = PROJECTILE_ATTACK;
    
    public var modeUpdateInterval:int = 13;
    
    public var sightLength:Number = 80;
    public var leaveAloneLength:Number = 200;
    public var fightDistance:Number = 50;
    public var closestFightDistance:Number = 30;
    public var closestFriendDistance:Number = 20;
    
    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;
    
    override public function init(game:Game):void {
        // Set colors and size here
        health = 0.3;
        sizeFactor = 1.0;
        switch (npcType) {
            case MONSTER_1:
                headColor = 0xffffff;
                armColor = 0x444444;
                bodyColor = 0xff3333;
                legColor = 0x000000;
                break;
            case MONSTER_2:
                sizeFactor = 1.2;
                break;
            case MONSTER_3:
                sizeFactor = 1.4;
                break;
            case BOSS_1:
                sizeFactor = 1.7;
                headColor = 0xff0000;
                armColor = 0x222222;
                bodyColor = 0x33ff33;
                legColor = 0x0000ff;
                break;
        }
        try {
            super.init(game);
        } catch (error:Error) {
            trace("Error npc super init " + error.name + " " + error.message);
        }
    }

    
    public function updateMode(game:Game):void {
        
        var playerPos:Vector3D = game.currentLevel.player.position;
        
        var distanceToPlayer:Number = Vector3D.distance(playerPos, position);
        
        var playerVisible:Boolean = distanceToPlayer < sightLength;
        
        
        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;
        }
    }    
    
    public function getWantedMovement(game:Game, movement:Vector3D, direction:Vector3D):void {
    
        var wantedDir:Vector3D = new Vector3D();

        var shouldStandStill:Boolean = false;
                
        switch (mode) {
            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 < closestFightDistance) {
                    sign = -1;
                } else if (distanceToPlayer < 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;
            }
            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):Projectile {
        var axe:Axe = new Axe(this);
        axe.direction = direction.clone();
        axe.direction.z -= 0.3;
        axe.direction.normalize();
        
        var pos:Vector3D = position.clone();
        pos.z -= 25 * sizeFactor;
        
        axe.position = pos;
        var velocity:Vector3D = axe.direction.clone();
        velocity.normalize();
        velocity.scaleBy(projectileSpeed);
        axe.velocity = velocity;
        game.sfxPlayer.playThrowAxe(game);
        return axe;
    }    
    
    public function performAttack(game:Game):void {
        switch (attackType) {
            case PROJECTILE_ATTACK:
                // Add projectile
                var projectile:Projectile = getProjectile(game);
                // 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) {
                performAttack(game);
                attackCounter = 0;
            }
            attackCounter++;
        }
        if (mode == DANCE_FIGHT_MODE || mode == DANCE_MODE) {
            animateDancing = true;
        }
    }
    
    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);
        }
        
        try {
            super.updateMovement(currentMovement.x, currentMovement.y, 0, game);
        } catch (error:Error) {
            trace("Error player update movement " + error.name + " " + error.message);
        }
        // if (Math.random() < 0.02) {
             // trace("npc is ticked... " + game.currentLevel.movingObjects.length);
         // }

    }
}


class TestProvider implements SequenceDataProvider {    

    private var sionPresets:SiONPresetVoice;
    
    public function TestProvider():void {
            sionPresets = new SiONPresetVoice();        
    }    
       
    public function renderChordMotif(motifIndex:int, harmonyIndex:int):void {        
    }

    public function renderVoiceMotif(motifIndex:int, harmonyIndex:int):void {        
    }

    public function renderPercussion(percussionIndex:int, harmonyIndex:int):void {        
    }

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

        var beatMillis:Number = result.length / result.beatCount;
        
// note:int = 60, velocity:int = 64, time:uint = 0, length:uint = 250, voice:SiONVoice = null, track:int = 0     

        var chordInfo:ChordInfo = new ChordInfo();
//        chordInfo.chordRoot = chordInfo.chordRoot + (Math.random() < 0.5 ? -4 : Math.random() < 0.5 ? -2 : 1);
//        chordInfo.chordRoot = positiveMod(chordInfo.chordRoot, 7);      

//        chordInfo.chordRoot = chordRoots[chordCounter];
//        chordCounter = (chordCounter + 1) % chordRoots.length;        
//        chordInfo.scaleBase = 65;
                // var voice:SiONVoice = voices[patternIndex];
                
                    // var note:int = chordInfo.getAbsoluteNoteFromChordRootIndex(index);
                    // // trace("index " + index + " gave note " + note);
                    // var noteMillis:uint = beatMillis * length / 4.0;
                    // if (restArr[i % restArr.length] == 0) {
                        // result.messages.push(new NoteOn(note, 64, currentPosition, length * beatMillis / 4.0, voice, 0));                
                    // }
                    // currentPosition += noteMillis;
        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];
    }

}


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

    private var sionDriver:SiONDriver = null;    
    
    private var dataProvider:SequenceDataProvider = null;
    
    
    public function MusicSequencer(driver:SiONDriver, dataProvider:SequenceDataProvider):void {
        this.sionDriver = driver;
        this.dataProvider = dataProvider;
    }
    
    public function tick():void {
        var currentTime:uint = flash.utils.getTimer();
        
        var extraOffset:uint = 400;
        
        if (!sequenceData) {
            sequenceData = dataProvider.getSequenceData();
            sequenceTime = currentTime;
        }
        if (currentTime + extraOffset > sequenceTime + sequenceData.length) {
            // Time to get the next data
            sequenceTime = sequenceTime + sequenceData.length;
            sequenceData = dataProvider.getSequenceData()
        }
        var beatsPerSecond:Number = 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;
        
        
        var millisPer16thBeat:Number = (toRender.length / toRender.beatCount) / 4.0;
        
        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;
            
        }

        
    }

}


class SfxPlayer {

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

    public function playDancing(game:Game):void {
        var bass:SiONVoice = game.sionPresets["valsound.percus1"];
        var snare:SiONVoice = game.sionPresets["valsound.percus2"];
        game.sionDriver.noteOn(60, bass, 4, 0, 0, 0);
        game.sionDriver.noteOn(60, snare, 4, 2, 0, 0);
        game.sionDriver.noteOn(60, bass, 4, 4, 0, 0);
        game.sionDriver.noteOn(60, snare, 4, 6, 0, 0);
        game.sionDriver.noteOn(60, bass, 4, 8, 0, 0);
        game.sionDriver.noteOn(60, snare, 4, 10, 0, 0);
        game.sionDriver.noteOn(60, bass, 4, 12, 0, 0);
        game.sionDriver.noteOn(60, snare, 4, 14, 0, 0);
    }
    
    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);
    }
    

}

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:Stage):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:Stage;        
        public static function initTrace(stg:Stage):void { trace_stage = stg; trace = wtrace; }        
        private static function scrollup():void { if (TextFields.length > 30) { var removeme:TextField = TextFields.shift(); trace_stage.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.stageHeight - 40; var tf:TextFormat = new TextFormat(FONT, SIZE);tracefield.setTextFormat(tf);
            trace_stage.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; } }