Browse Source

Playable scripted NinjaSnowWar.
Disabled garbage collection from CScriptArray again because of some very odd "objects not getting deleted" bugs.
Unprepare script contexts before garbage collection.

Lasse Öörni 15 years ago
parent
commit
bb67b6d7fe

+ 1 - 1
Bin/Data/NinjaSnowWar.xml

@@ -45,7 +45,7 @@
     <EnemySpawnRate>1</EnemySpawnRate>
     <EnemySpawnRate>1</EnemySpawnRate>
     <EnemySpawnPosition>0 1000 12000</EnemySpawnPosition>
     <EnemySpawnPosition>0 1000 12000</EnemySpawnPosition>
     <EnemySpawnOffset>4000</EnemySpawnOffset>
     <EnemySpawnOffset>4000</EnemySpawnOffset>
-    <EnemySpawnVelocity>0 1000 -2500</EnemySpawnVelocity>
+    <EnemySpawnVelocity>0 1000 -3000</EnemySpawnVelocity>
     <PowerUpSpawnRate>15</PowerUpSpawnRate>
     <PowerUpSpawnRate>15</PowerUpSpawnRate>
     <PowerUpSpawnOffset>4000</PowerUpSpawnOffset>
     <PowerUpSpawnOffset>4000</PowerUpSpawnOffset>
     <PowerUpSpawnHeight>5000</PowerUpSpawnHeight>
     <PowerUpSpawnHeight>5000</PowerUpSpawnHeight>

+ 174 - 0
Bin/Data/Scripts/AIController.as

@@ -0,0 +1,174 @@
+const float initialAggression = 0.0025;
+const float initialPrediction = 3000;
+const float initialAimSpeed = 5;
+const float deltaAggression = 0.00005;
+const float deltaPrediction = -20;
+const float deltaAimSpeed = 0.15;
+const float maxAggression = 0.01;
+const float maxPrediction = 2000;
+const float maxAimSpeed = 20;
+
+float aiAggression = initialAggression;
+float aiPrediction = initialPrediction;
+float aiAimSpeed = initialAimSpeed;
+
+void resetAI()
+{
+    aiAggression = initialAggression;
+    aiPrediction = initialPrediction;
+    aiAimSpeed = initialAimSpeed;
+}
+
+void makeAIHarder()
+{
+    aiAggression += deltaAggression;
+    if (aiAggression > maxAggression)
+        aiAggression = maxAggression;
+
+    aiPrediction += deltaPrediction;
+    if (aiPrediction < maxPrediction)
+        aiPrediction = maxPrediction;
+
+    aiAimSpeed += deltaAimSpeed;
+    if (aiAimSpeed > maxAimSpeed)
+        aiAimSpeed = maxAimSpeed;
+}
+
+class AIController
+{
+    void control(Ninja@ ninja, Entity@ entity, float timeStep)
+    {
+        Scene@ scene = entity.getScene();
+        RigidBody@ body = entity.getComponent("RigidBody");
+
+        // Get closest ninja on the player's side
+        Entity@ targetEntity;
+        Ninja@ targetNinja;
+        RigidBody@ targetBody;
+        array<Entity@> entities = scene.getScriptedEntities("Ninja");
+        float closestDistance = M_INFINITY;
+        for (uint i = 0; i < entities.length(); ++i)
+        {
+            Ninja@ otherNinja = cast<Ninja>(entities[i].getScriptObject());
+            RigidBody@ otherBody = entities[i].getComponent("RigidBody");
+            if (otherNinja.side == SIDE_PLAYER)
+            {
+                float distance = (body.getPhysicsPosition() - otherBody.getPhysicsPosition()).getLength();
+                if (distance < closestDistance)
+                {
+                    @targetEntity = entities[i];
+                    @targetNinja = otherNinja;
+                    @targetBody = otherBody;
+                    closestDistance = distance;
+                }
+            }
+        }
+
+        if ((@targetEntity != null) && (targetNinja.health > 0))
+        {
+            ninja.controls.set(CTRL_FIRE, false);
+            ninja.controls.set(CTRL_JUMP, false);
+
+            float deltaX = 0.0f;
+            float deltaY = 0.0f;
+
+            // Aim from own head to target's feet
+            Vector3 ownPos(body.getPhysicsPosition() + Vector3(0, 90, 0));
+            Vector3 targetPos(targetBody.getPhysicsPosition() + Vector3(0, -90, 0));
+            float distance = (targetPos - ownPos).getLength();
+
+            // Use prediction according to target distance & estimated snowball speed
+            Vector3 currentAim(ninja.getAim() * Vector3(0, 0, 1));
+            float predictDistance = distance;
+            if (predictDistance > 5000) predictDistance = 5000;
+            Vector3 predictedPos = targetPos + targetBody.getLinearVelocity() * predictDistance / aiPrediction;
+            Vector3 targetAim = (predictedPos - ownPos);
+
+            // Add distance/height compensation
+            float compensation = max(targetAim.getLength() - 1500, 0);
+            targetAim += Vector3(0, 0.005, 0) * (compensation * compensation);
+
+            // X-aiming
+            targetAim.normalize();
+            Vector3 currentYaw(currentAim.x, 0, currentAim.z);
+            Vector3 targetYaw(targetAim.x, 0, targetAim.z);
+            currentYaw.normalize();
+            targetYaw.normalize();
+            deltaX = clamp(Quaternion(currentYaw, targetYaw).getYaw(), -aiAimSpeed, aiAimSpeed);
+
+            // Y-aiming
+            Vector3 currentPitch(0, currentAim.y, 1);
+            Vector3 targetPitch(0, targetAim.y, 1);
+            currentPitch.normalize();
+            targetPitch.normalize();
+            deltaY = clamp(Quaternion(currentPitch, targetPitch).getPitch(), -aiAimSpeed, aiAimSpeed);
+
+            ninja.controls.yaw += 0.1 * deltaX;
+            ninja.controls.pitch += 0.1 * deltaY;
+
+            // Firing? if close enough and relatively correct aim
+            if ((distance < 2500) && (currentAim.dotProduct(targetAim) > 0.75))
+            {
+                if (random(1.0) < aiAggression)
+                    ninja.controls.set(CTRL_FIRE, true);
+            }
+
+            // Movement
+            ninja.dirChangeTime -= timeStep;
+            if (ninja.dirChangeTime <= 0)
+            {
+                ninja.dirChangeTime = 0.5 + random(1.0);
+                ninja.controls.set(CTRL_UP|CTRL_DOWN|CTRL_LEFT|CTRL_RIGHT, false);
+
+                // Far distance: go forward
+                if (distance > 3000)
+                    ninja.controls.set(CTRL_UP, true);
+                else if (distance > 600)
+                {
+                    // Medium distance: random strafing, predominantly forward
+                    float v = random(1.0);
+                    if (v < 0.8)
+                        ninja.controls.set(CTRL_UP, true);
+                    float h = random(1.0);
+                    if (h < 0.3)
+                        ninja.controls.set(CTRL_LEFT, true);
+                    if (h > 0.7)
+                        ninja.controls.set(CTRL_RIGHT, true);
+                }
+                else
+                {
+                    // Close distance: random strafing backwards
+                    float v = random(1.0);
+                    if (v < 0.8)
+                        ninja.controls.set(CTRL_DOWN, true);
+                    float h = random(1.0);
+                    if (h < 0.4)
+                        ninja.controls.set(CTRL_LEFT, true);
+                    if (h > 0.6)
+                        ninja.controls.set(CTRL_RIGHT, true);
+                }
+            }
+
+            // Random jump, if going forward
+            if ((ninja.controls.isDown(CTRL_UP)) && (distance < 1000))
+            {
+                if (random(1.0) < (aiAggression / 5.0))
+                    ninja.controls.set(CTRL_JUMP, true);
+            }
+        }
+        else
+        {
+            // If no target, walk idly
+            ninja.controls.set(CTRL_ALL, false);
+            ninja.controls.set(CTRL_UP, true);
+            ninja.dirChangeTime -= timeStep;
+            if (ninja.dirChangeTime <= 0)
+            {
+                ninja.dirChangeTime = 1 + random(2.0);
+                ninja.controls.yaw += 0.1 * (random(600.0) - 300.0);
+            }
+            if (ninja.isSliding)
+                ninja.controls.yaw += 0.2;
+        }
+    }
+}

+ 0 - 7
Bin/Data/Scripts/GameObject.as

@@ -12,8 +12,6 @@ const int SIDE_ENEMY = 2;
 
 
 class GameObject : ScriptObject
 class GameObject : ScriptObject
 {
 {
-    Controls controls;
-    Controls prevControls;
     bool onGround;
     bool onGround;
     bool isSliding;
     bool isSliding;
     float duration;
     float duration;
@@ -37,11 +35,6 @@ class GameObject : ScriptObject
     {
     {
     }
     }
 
 
-    void setControls(const Controls&in newControls)
-    {
-        controls = newControls;
-    }
-
     void updateFixed(float timeStep)
     void updateFixed(float timeStep)
     {
     {
         // Disappear when duration expired
         // Disappear when duration expired

+ 15 - 0
Bin/Data/Scripts/Ninja.as

@@ -1,4 +1,5 @@
 #include "Scripts/GameObject.as"
 #include "Scripts/GameObject.as"
+#include "Scripts/AIController.as"
 
 
 const int ANIM_MOVE = 1;
 const int ANIM_MOVE = 1;
 const int ANIM_ATTACK = 2;
 const int ANIM_ATTACK = 2;
@@ -14,9 +15,13 @@ const Vector3 ninjaThrowPosition(0, 20, 100);
 const float ninjaThrowDelay = 0.1;
 const float ninjaThrowDelay = 0.1;
 const float ninjaDrawDistance = 15000;
 const float ninjaDrawDistance = 15000;
 const float ninjaCorpseDuration = 3;
 const float ninjaCorpseDuration = 3;
+const int ninjaPoints = 250;
 
 
 class Ninja : GameObject
 class Ninja : GameObject
 {
 {
+    Controls controls;
+    Controls prevControls;
+    AIController@ controller;
     bool okToJump;
     bool okToJump;
     bool smoke;
     bool smoke;
     float inAirTime;
     float inAirTime;
@@ -101,6 +106,10 @@ class Ninja : GameObject
             return;
             return;
         }
         }
 
 
+        // AI control if controller exists
+        if (@controller != null)
+            controller.control(this, entity, timeStep);
+
         RigidBody@ body = entity.getComponent("RigidBody");
         RigidBody@ body = entity.getComponent("RigidBody");
         AnimationController@ controller = entity.getComponent("AnimationController");
         AnimationController@ controller = entity.getComponent("AnimationController");
 
 
@@ -275,6 +284,12 @@ class Ninja : GameObject
                 deathDir = 1;
                 deathDir = 1;
 
 
             playSound("Sounds/SmallExplosion.wav");
             playSound("Sounds/SmallExplosion.wav");
+            
+            VariantMap eventData;
+            eventData["Points"] = ninjaPoints;
+            eventData["DamageSide"] = lastDamageSide;
+            sendEvent("Points", eventData);
+            sendEvent("Kill", eventData);
         }
         }
 
 
         deathTime += timeStep;
         deathTime += timeStep;

+ 152 - 16
Bin/Data/Scripts/NinjaSnowWar.as

@@ -1,4 +1,5 @@
-// Partial remake of NinjaSnowWar in script
+// Remake of NinjaSnowWar in script
+// Does not support load/save, or multiplayer yet.
 
 
 #include "Scripts/LightFlash.as"
 #include "Scripts/LightFlash.as"
 #include "Scripts/Ninja.as"
 #include "Scripts/Ninja.as"
@@ -10,7 +11,13 @@ const float mouseSensitivity = 0.125;
 const float cameraMinDist = 25;
 const float cameraMinDist = 25;
 const float cameraMaxDist = 500;
 const float cameraMaxDist = 500;
 const float cameraSafetyDist = 30;
 const float cameraSafetyDist = 30;
+const int initialMaxEnemies = 5;
+const int finalMaxEnemies = 25;
+const int maxPowerups = 5;
+const int incrementEach = 10;
 const int playerHealth = 20;
 const int playerHealth = 20;
+const float enemySpawnRate = 1;
+const float powerupSpawnRate = 15;
 
 
 Scene@ gameScene;
 Scene@ gameScene;
 Camera@ gameCamera;
 Camera@ gameCamera;
@@ -21,8 +28,15 @@ BorderImage@ healthBar;
 BorderImage@ sight;
 BorderImage@ sight;
 
 
 Controls playerControls;
 Controls playerControls;
+Controls prevPlayerControls;
 bool paused = false;
 bool paused = false;
 bool gameOn = false;
 bool gameOn = false;
+int score = 0;
+int hiscore = 0;
+int maxEnemies = 0;
+int incrementCounter = 0;
+float enemySpawnTimer = 0;
+float powerupSpawnTimer = 0;
 
 
 void start()
 void start()
 {
 {
@@ -34,7 +48,10 @@ void start()
     startGame();
     startGame();
 
 
     subscribeToEvent("Update", "handleUpdate");
     subscribeToEvent("Update", "handleUpdate");
+    subscribeToEvent("PhysicsPreStep", "handleFixedUpdate");
     subscribeToEvent("PostUpdate", "handlePostUpdate");
     subscribeToEvent("PostUpdate", "handlePostUpdate");
+    subscribeToEvent("Points", "handlePoints");
+    subscribeToEvent("Kill", "handleKill");
     subscribeToEvent("KeyDown", "handleKeyDown");
     subscribeToEvent("KeyDown", "handleKeyDown");
     subscribeToEvent("WindowResized", "handleWindowResized");
     subscribeToEvent("WindowResized", "handleWindowResized");
 }
 }
@@ -155,16 +172,29 @@ void startGame()
 {
 {
     // Clear the scene of all existing scripted entities
     // Clear the scene of all existing scripted entities
     array<Entity@> scriptedEntities = gameScene.getScriptedEntities();
     array<Entity@> scriptedEntities = gameScene.getScriptedEntities();
-    for (int i = 0; i < scriptedEntities.length(); ++i)
+    for (uint i = 0; i < scriptedEntities.length(); ++i)
         gameScene.removeEntity(scriptedEntities[i]);
         gameScene.removeEntity(scriptedEntities[i]);
 
 
     Entity@ playerEntity = gameScene.createEntity("Player");
     Entity@ playerEntity = gameScene.createEntity("Player");
-    GameObject@ object = cast<GameObject>(playerEntity.createScriptObject("Scripts/NinjaSnowWar.as", "Ninja"));
-    object.create(Vector3(0, 90, 0), Quaternion());
-    object.health = object.maxHealth = playerHealth;
-    object.side = SIDE_PLAYER;
+    Ninja@ playerNinja = cast<Ninja>(playerEntity.createScriptObject("Scripts/NinjaSnowWar.as", "Ninja"));
+    playerNinja.create(Vector3(0, 90, 0), Quaternion());
+    playerNinja.health = playerNinja.maxHealth = playerHealth;
+    playerNinja.side = SIDE_PLAYER;
+    // Make sure the player can not shoot on first frame by holding the button down
+    playerNinja.controls = playerNinja.prevControls = playerControls;
+
+    resetAI();
 
 
     gameOn = true;
     gameOn = true;
+    score = 0;
+    maxEnemies = initialMaxEnemies;
+    incrementCounter = 0;
+    enemySpawnTimer = 0;
+    powerupSpawnTimer = 0;
+    playerControls.yaw = 0;
+    playerControls.pitch = 0;
+
+    messageText.setText("");
 }
 }
 
 
 void handleUpdate(StringHash eventType, VariantMap& eventData)
 void handleUpdate(StringHash eventType, VariantMap& eventData)
@@ -174,7 +204,7 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
     if (input.getKeyPress(KEY_F2))
     if (input.getKeyPress(KEY_F2))
         engine.setDebugDrawMode(engine.getDebugDrawMode() ^ DEBUGDRAW_PHYSICS);
         engine.setDebugDrawMode(engine.getDebugDrawMode() ^ DEBUGDRAW_PHYSICS);
 
 
-    if ((!console.isVisible()) && (input.getKeyPress('P')))
+    if ((!console.isVisible()) && (input.getKeyPress('P')) && (gameOn))
     {
     {
         paused = !paused;
         paused = !paused;
         if (paused)
         if (paused)
@@ -187,19 +217,121 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
         updateControls();
         updateControls();
 }
 }
 
 
+void handleFixedUpdate(StringHash eventType, VariantMap& eventData)
+{
+    // Check that scene being updated matches (we have only one scene, but for completeness...)
+    if (@eventData["Scene"].getScene() != @gameScene)
+        return;
+
+    float timeStep = eventData["TimeStep"].getFloat();
+
+    // Spawn new objects and check for end/restart of game
+    spawnObjects(timeStep);
+    checkEndAndRestart();
+}
+
 void handlePostUpdate(StringHash eventType, VariantMap& eventData)
 void handlePostUpdate(StringHash eventType, VariantMap& eventData)
 {
 {
     updateCamera();
     updateCamera();
     updateStatus();
     updateStatus();
 }
 }
 
 
-void updateControls()
+void handlePoints(StringHash eventType, VariantMap& eventData)
 {
 {
-    playerControls.set(CTRL_ALL, false);
+    if (eventData["DamageSide"].getInt() == SIDE_PLAYER)
+    {
+        score += eventData["Points"].getInt();
+        if (score > hiscore)
+            hiscore = score;
+    }
+}
 
 
-    Entity@ playerEntity = gameScene.getEntity("Player");
-    if (@playerEntity == null)
+void handleKill(StringHash eventType, VariantMap& eventData)
+{
+    if (eventData["DamageSide"].getInt() == SIDE_PLAYER)
+    {
+        makeAIHarder();
+     
+        // Increment amount of simultaneous enemies after enough kills
+        incrementCounter++;
+        if (incrementCounter >= incrementEach)
+        {
+            incrementCounter = 0;
+            if (maxEnemies < finalMaxEnemies)
+                maxEnemies++;
+        }
+    }
+}
+
+void spawnObjects(float timeStep)
+{
+    // Spawn powerups
+    powerupSpawnTimer += timeStep;
+    if (powerupSpawnTimer >= powerupSpawnRate)
+    {
+        powerupSpawnTimer = 0;
+        //int numPowerups = gameScene.getScriptedEntities("SnowCrate").length() + gameScene.getScriptedEntities("Potion").length();
+        int numPowerups = 0;
+
+        if (numPowerups < maxPowerups)
+        {
+            const float maxOffset = 4000;
+            float xOffset = random(maxOffset * 2.0f) - maxOffset;
+            float zOffset = random(maxOffset * 2.0f) - maxOffset;
+
+            Vector3 position(xOffset, 5000, zOffset);
+            Entity@ crateEntity = gameScene.createEntity();
+            GameObject@ crateObject = cast<GameObject>(crateEntity.createScriptObject("Scripts/NinjaSnowWar.as", "SnowCrate"));
+            crateObject.create(position, Quaternion());
+        }
+    }
+
+    // Spawn enemies
+    enemySpawnTimer += timeStep;
+    if (enemySpawnTimer > enemySpawnRate)
+    {
+        enemySpawnTimer = 0;
+        // Take the player ninja into account
+        int numEnemies = gameScene.getScriptedEntities("Ninja").length() - 1;
+
+        if (numEnemies < maxEnemies)
+        {
+            const float maxOffset = 4000;
+            float offset = random(maxOffset * 2.0) - maxOffset;
+            // Random north/east/south/west direction
+            int dir = randomInt() & 3;
+            dir *= 90;
+            Quaternion q(dir, Vector3(0, 1, 0));
+            Vector3 position(q * Vector3(offset, 1000, 12000));
+
+            Entity@ enemyEntity = gameScene.createEntity();
+            Ninja@ enemyNinja = cast<Ninja>(enemyEntity.createScriptObject("Scripts/NinjaSnowWar.as", "Ninja"));
+            enemyNinja.create(position, q);
+            enemyNinja.side = SIDE_ENEMY;
+            @enemyNinja.controller = AIController();
+            RigidBody@ enemyBody = enemyEntity.getComponent("RigidBody");
+            enemyBody.setLinearVelocity(q * Vector3(0, 1000, -3000));
+        }
+    }
+}
+
+void checkEndAndRestart()
+{
+    if ((gameOn) && (@gameScene.getEntity("Player") == null))
+    {
+        gameOn = false;
+        messageText.setText("Press Fire or Jump to restart!");
         return;
         return;
+    }
+    
+    if ((!gameOn) && (playerControls.isPressed(CTRL_FIRE | CTRL_JUMP, prevPlayerControls)))
+        startGame();
+}
+
+void updateControls()
+{
+    prevPlayerControls = playerControls;
+    playerControls.set(CTRL_ALL, false);
 
 
     if (!console.isVisible())
     if (!console.isVisible())
     {
     {
@@ -226,11 +358,12 @@ void updateControls()
     playerControls.pitch += mouseSensitivity * input.getMouseMoveY();
     playerControls.pitch += mouseSensitivity * input.getMouseMoveY();
     playerControls.pitch = clamp(playerControls.pitch, -60, 60);
     playerControls.pitch = clamp(playerControls.pitch, -60, 60);
 
 
-    GameObject@ object = cast<GameObject>(playerEntity.getScriptObject());
-    object.setControls(playerControls);
-    
-    if ((input.getKeyPress('K')) && (object.health > 0))
-        object.health = object.health - 1;
+    Entity@ playerEntity = gameScene.getEntity("Player");
+    if (@playerEntity != null)
+    {
+        Ninja@ playerNinja = cast<Ninja>(playerEntity.getScriptObject());
+        playerNinja.controls = playerControls;
+    }
 }
 }
 
 
 void updateCamera()
 void updateCamera()
@@ -268,6 +401,9 @@ void updateStatus()
     if (engine.isHeadless())
     if (engine.isHeadless())
         return;
         return;
 
 
+    scoreText.setText("Score " + score);
+    hiscoreText.setText("Hiscore " + hiscore);
+
     Entity@ playerEntity = gameScene.getEntity("Player");
     Entity@ playerEntity = gameScene.getEntity("Player");
     if (@playerEntity == null)
     if (@playerEntity == null)
         return;
         return;

+ 6 - 0
Bin/Data/Scripts/SnowCrate.as

@@ -4,6 +4,7 @@ const int snowcrateHealth = 5;
 const float snowcrateMass = 200;
 const float snowcrateMass = 200;
 const float snowcrateFriction = 0.35;
 const float snowcrateFriction = 0.35;
 const float snowcrateDrawDistance = 15000;
 const float snowcrateDrawDistance = 15000;
+const int snowcratePoints = 250;
 
 
 class SnowCrate : GameObject
 class SnowCrate : GameObject
 {
 {
@@ -47,6 +48,11 @@ class SnowCrate : GameObject
             spawnParticleEffect(body.getPhysicsPosition(), "Particle/SnowExplosionBig.xml", 2);
             spawnParticleEffect(body.getPhysicsPosition(), "Particle/SnowExplosionBig.xml", 2);
             spawnObject(body.getPhysicsPosition(), Quaternion(), "Potion");
             spawnObject(body.getPhysicsPosition(), Quaternion(), "Potion");
             scene.removeEntity(entity);
             scene.removeEntity(entity);
+            
+            VariantMap eventData;
+            eventData["Points"] = snowcratePoints;
+            eventData["DamageSide"] = lastDamageSide;
+            sendEvent("Points", eventData);
         }
         }
     }
     }
 }
 }

+ 25 - 13
Engine/Engine/Engine.cpp

@@ -95,19 +95,8 @@ Engine::Engine(const std::string& windowTitle, const std::string& logFileName, b
     
     
     mCache = new ResourceCache();
     mCache = new ResourceCache();
     
     
-    // Register the inbuilt events as local only
-    static const std::string inbuiltEvents[] = {
-        "Update", "PostUpdate", "PostRenderUpdate", "ClientIdentity", "ClientJoinedScene", "ClientLeftScene", "ClientControls",
-        "ClientDisconnected", "GotSceneInfoList", "JoinedScene", "JoinSceneFailed", "FileTransferCompleted", "FileTransferFailed",
-        "ControlsUpdate", "ControlsPlayback", "MouseButtonDown", "MouseButtonUp", "MouseMove", "KeyDown", "KeyUp", "Char",
-        "PeerConnected", "NetworkPacket", "PeerDisconnected", "PhysicsPreStep", "PhysicsPostStep", "PhysicsCollision",
-        "EntityCollision", "WindowMessage", "WindowResized", "BeginFrame", "EndFrame", "SceneUpdate", "ScenePostUpdate",
-        "AsyncLoadProgress", "AsyncLoadFinished", "TryFocus", "Focused", "Defocused", "Pressed", "Toggled", "SliderChanged",
-        "ViewChanged", "TextChanged", "TextFinished", "ItemSelected", ""
-    };
-    
-    for (unsigned i = 0; inbuiltEvents[i].length(); ++i)
-        registerLocalOnlyEvent(inbuiltEvents[i]);
+    // Register the engine and library events as local only
+    resetLocalOnlyEvents();
     
     
     sInstance = this;
     sInstance = this;
 }
 }
@@ -277,9 +266,14 @@ void Engine::runFrame(Scene* scene, Camera* camera, bool updateScene)
     {
     {
         PROFILE(Engine_RunFrame);
         PROFILE(Engine_RunFrame);
         
         
+        // Get frame timestep / update / render
         float timeStep = getNextTimeStep();
         float timeStep = getNextTimeStep();
         update(timeStep, scene, camera, updateScene);
         update(timeStep, scene, camera, updateScene);
         render();
         render();
+        
+        // Perform script garbage collection outside everything else
+        if (mScriptEngine)
+            mScriptEngine->garbageCollect(false);
     }
     }
     
     
     mProfiler->endFrame();
     mProfiler->endFrame();
@@ -372,6 +366,24 @@ DebugHud* Engine::createDebugHud()
     return mDebugHud;
     return mDebugHud;
 }
 }
 
 
+void Engine::resetLocalOnlyEvents()
+{
+    static const std::string inbuiltEvents[] = {
+        "Update", "PostUpdate", "PostRenderUpdate", "ClientIdentity", "ClientJoinedScene", "ClientLeftScene", "ClientControls",
+        "ClientDisconnected", "GotSceneInfoList", "JoinedScene", "JoinSceneFailed", "FileTransferCompleted", "FileTransferFailed",
+        "ControlsUpdate", "ControlsPlayback", "MouseButtonDown", "MouseButtonUp", "MouseMove", "KeyDown", "KeyUp", "Char",
+        "PeerConnected", "NetworkPacket", "PeerDisconnected", "PhysicsPreStep", "PhysicsPostStep", "PhysicsCollision",
+        "EntityCollision", "WindowMessage", "WindowResized", "BeginFrame", "EndFrame", "SceneUpdate", "ScenePostUpdate",
+        "AsyncLoadProgress", "AsyncLoadFinished", "TryFocus", "Focused", "Defocused", "Pressed", "Toggled", "SliderChanged",
+        "ViewChanged", "TextChanged", "TextFinished", "ItemSelected", ""
+    };
+    
+    // First clear existing registrations, then register the inbuild events
+    removeAllLocalOnlyEvents();
+    for (unsigned i = 0; inbuiltEvents[i].length(); ++i)
+        registerLocalOnlyEvent(inbuiltEvents[i]);
+}
+
 void Engine::setDefaultScene(Scene* scene)
 void Engine::setDefaultScene(Scene* scene)
 {
 {
     mDefaultScene = scene;
     mDefaultScene = scene;

+ 2 - 0
Engine/Engine/Engine.h

@@ -90,6 +90,8 @@ public:
     void removeClient();
     void removeClient();
     //! Remove the server subsystem
     //! Remove the server subsystem
     void removeServer();
     void removeServer();
+    //! Clear previous and register the default local only events
+    void resetLocalOnlyEvents();
     //! Set the default scene. This will always be available as the "scene" global property to scripting
     //! Set the default scene. This will always be available as the "scene" global property to scripting
     void setDefaultScene(Scene* scene);
     void setDefaultScene(Scene* scene);
     //! Set minimum frames per second. If FPS goes lower than this, time will appear to slow down
     //! Set minimum frames per second. If FPS goes lower than this, time will appear to slow down

+ 1 - 0
Engine/Engine/RegisterEngine.cpp

@@ -435,6 +435,7 @@ static void registerEngine(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Engine", "DebugHud@+ createDebugHud()", asMETHOD(Engine, createDebugHud), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "DebugHud@+ createDebugHud()", asMETHOD(Engine, createDebugHud), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void removeClient()", asMETHOD(Engine, removeClient), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void removeClient()", asMETHOD(Engine, removeClient), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void removeServer()", asMETHOD(Engine, removeServer), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void removeServer()", asMETHOD(Engine, removeServer), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Engine", "void resetLocalOnlyEvents()", asMETHOD(Engine, resetLocalOnlyEvents), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void setDefaultScene(Scene@+)", asMETHOD(Engine, setDefaultScene), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void setDefaultScene(Scene@+)", asMETHOD(Engine, setDefaultScene), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void setMinFps(int)", asMETHOD(Engine, setMinFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void setMinFps(int)", asMETHOD(Engine, setMinFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void setMaxFps(int)", asMETHOD(Engine, setMaxFps), asCALL_THISCALL);
     engine->RegisterObjectMethod("Engine", "void setMaxFps(int)", asMETHOD(Engine, setMaxFps), asCALL_THISCALL);

+ 3 - 0
Engine/Engine/RegisterMath.cpp

@@ -64,6 +64,9 @@ static float ATan2(float y, float x)
 
 
 static void registerMathFunctions(asIScriptEngine* engine)
 static void registerMathFunctions(asIScriptEngine* engine)
 {
 {
+    engine->RegisterGlobalProperty("const float M_INFINITY", (void*)&M_INFINITY);
+    engine->RegisterGlobalProperty("const float M_EPSILON", (void*)&M_EPSILON);
+    
     engine->RegisterGlobalFunction("float sin(float)", asFUNCTION(Sin), asCALL_CDECL);
     engine->RegisterGlobalFunction("float sin(float)", asFUNCTION(Sin), asCALL_CDECL);
     engine->RegisterGlobalFunction("float cos(float)", asFUNCTION(Cos), asCALL_CDECL);
     engine->RegisterGlobalFunction("float cos(float)", asFUNCTION(Cos), asCALL_CDECL);
     engine->RegisterGlobalFunction("float tan(float)", asFUNCTION(Tan), asCALL_CDECL);
     engine->RegisterGlobalFunction("float tan(float)", asFUNCTION(Tan), asCALL_CDECL);

+ 6 - 1
Engine/Scene/RemoteEvent.cpp

@@ -92,10 +92,15 @@ void registerLocalOnlyEvent(StringHash eventType)
 
 
 void registerLocalOnlyEvent(const std::string& name)
 void registerLocalOnlyEvent(const std::string& name)
 {
 {
-    registerHash(name);
+    registerHash(name, false);
     localOnlyEvents.insert(StringHash(name));
     localOnlyEvents.insert(StringHash(name));
 }
 }
 
 
+void removeAllLocalOnlyEvents()
+{
+    localOnlyEvents.clear();
+}
+
 bool checkRemoteEvent(StringHash eventType)
 bool checkRemoteEvent(StringHash eventType)
 {
 {
     return localOnlyEvents.find(eventType) == localOnlyEvents.end();
     return localOnlyEvents.find(eventType) == localOnlyEvents.end();

+ 2 - 0
Engine/Scene/RemoteEvent.h

@@ -89,6 +89,8 @@ struct RemoteEvent
 void registerLocalOnlyEvent(StringHash eventType);
 void registerLocalOnlyEvent(StringHash eventType);
 //! Register an event to be only sent locally
 //! Register an event to be only sent locally
 void registerLocalOnlyEvent(const std::string& name);
 void registerLocalOnlyEvent(const std::string& name);
+//! Remove all registered local only events
+void removeAllLocalOnlyEvents();
 //! Check if an event is allowed to be sent remotely
 //! Check if an event is allowed to be sent remotely
 bool checkRemoteEvent(StringHash eventType);
 bool checkRemoteEvent(StringHash eventType);
 //! Return remote event sender connection. Only non-null during the event handling
 //! Return remote event sender connection. Only non-null during the event handling

+ 41 - 41
Engine/Script/RegisterArray.cpp

@@ -179,8 +179,8 @@ CScriptArray::CScriptArray(asUINT length, asIObjectType* ot)
     CreateBuffer(&buffer, length);
     CreateBuffer(&buffer, length);
     
     
     // Notify the GC of the successful creation
     // Notify the GC of the successful creation
-    if (objType->GetFlags() & asOBJ_GC)
-        objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType->GetTypeId());
+    //if (objType->GetFlags() & asOBJ_GC)
+    //    objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType->GetTypeId());
 }
 }
 
 
 CScriptArray::CScriptArray(asUINT length, void *defVal, asIObjectType *ot)
 CScriptArray::CScriptArray(asUINT length, void *defVal, asIObjectType *ot)
@@ -211,8 +211,8 @@ CScriptArray::CScriptArray(asUINT length, void *defVal, asIObjectType *ot)
     CreateBuffer(&buffer, length);
     CreateBuffer(&buffer, length);
     
     
     // Notify the GC of the successful creation
     // Notify the GC of the successful creation
-    if (objType->GetFlags() & asOBJ_GC)
-        objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType->GetTypeId());
+    //if (objType->GetFlags() & asOBJ_GC)
+    //    objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType->GetTypeId());
     
     
     // Initialize the elements with the default value
     // Initialize the elements with the default value
     for (asUINT n = 0; n < GetSize(); ++n)
     for (asUINT n = 0; n < GetSize(); ++n)
@@ -519,27 +519,27 @@ void CScriptArray::CopyBuffer(SArrayBuffer *dst, SArrayBuffer *src)
 }
 }
 
 
 // GC behaviour
 // GC behaviour
-void CScriptArray::EnumReferences(asIScriptEngine* engine)
-{
-    // If the array is holding handles, then we need to notify the GC of them
-    int typeId = objType->GetSubTypeId();
-    if (typeId & asTYPEID_MASK_OBJECT)
-    {
-        void** d = (void**)buffer->data;
-        for (asUINT n = 0; n < buffer->numElements; ++n)
-        {
-            if (d[n])
-                engine->GCEnumCallback(d[n]);
-        }
-    }
-}
+//void CScriptArray::EnumReferences(asIScriptEngine* engine)
+//{
+//    // If the array is holding handles, then we need to notify the GC of them
+//    int typeId = objType->GetSubTypeId();
+//    if (typeId & asTYPEID_MASK_OBJECT)
+//    {
+//        void** d = (void**)buffer->data;
+//        for (asUINT n = 0; n < buffer->numElements; ++n)
+//        {
+//            if (d[n])
+//                engine->GCEnumCallback(d[n]);
+//        }
+//    }
+//}
 
 
 // GC behaviour
 // GC behaviour
-void CScriptArray::ReleaseAllHandles(asIScriptEngine* engine)
-{
-    // Resizing to zero will release everything
-    Resize(0);
-}
+//void CScriptArray::ReleaseAllHandles(asIScriptEngine* engine)
+//{
+//    // Resizing to zero will release everything
+//    Resize(0);
+//}
 
 
 void CScriptArray::AddRef() const
 void CScriptArray::AddRef() const
 {
 {
@@ -557,27 +557,27 @@ void CScriptArray::Release() const
 }
 }
 
 
 // GC behaviour
 // GC behaviour
-int CScriptArray::GetRefCount()
-{
-    return refCount;
-}
+//int CScriptArray::GetRefCount()
+//{
+//    return refCount;
+//}
 
 
 // GC behaviour
 // GC behaviour
-void CScriptArray::SetFlag()
-{
-    gcFlag = true;
-}
+//void CScriptArray::SetFlag()
+//{
+//    gcFlag = true;
+//}
 
 
 // GC behaviour
 // GC behaviour
-bool CScriptArray::GetFlag()
-{
-    return gcFlag;
-}
+//bool CScriptArray::GetFlag()
+//{
+//    return gcFlag;
+//}
 
 
 // Registers the template array type
 // Registers the template array type
 void registerArray(asIScriptEngine* engine)
 void registerArray(asIScriptEngine* engine)
 {
 {
-    engine->RegisterObjectType("array<class T>", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE);
+    engine->RegisterObjectType("array<class T>", 0, asOBJ_REF | asOBJ_TEMPLATE);
     engine->RegisterObjectBehaviour("array<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL);
     engine->RegisterObjectBehaviour("array<T>", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL);
     engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int& in)", asFUNCTIONPR(ScriptArrayFactory, (asIObjectType*), CScriptArray*), asCALL_CDECL);
     engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int& in)", asFUNCTIONPR(ScriptArrayFactory, (asIObjectType*), CScriptArray*), asCALL_CDECL);
     engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int& in, uint)", asFUNCTIONPR(ScriptArrayFactory2, (asIObjectType*, asUINT), CScriptArray*), asCALL_CDECL);
     engine->RegisterObjectBehaviour("array<T>", asBEHAVE_FACTORY, "array<T>@ f(int& in, uint)", asFUNCTIONPR(ScriptArrayFactory2, (asIObjectType*, asUINT), CScriptArray*), asCALL_CDECL);
@@ -594,10 +594,10 @@ void registerArray(asIScriptEngine* engine)
     engine->RegisterObjectMethod("array<T>", "void removeLast()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "void removeLast()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "void resize(uint)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("array<T>", "void resize(uint)", asMETHODPR(CScriptArray, Resize, (asUINT), void), asCALL_THISCALL);
-    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL);
-    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL);
-    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL);
-    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ENUMREFS, "void f(int& in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL);
-    engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASEREFS, "void f(int& in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL);
+    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL);
+    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL);
+    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL);
+    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_ENUMREFS, "void f(int& in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL);
+    //engine->RegisterObjectBehaviour("array<T>", asBEHAVE_RELEASEREFS, "void f(int& in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL);
     engine->RegisterDefaultArrayType("array<T>");
     engine->RegisterDefaultArrayType("array<T>");
 }
 }

+ 9 - 8
Engine/Script/ScriptEngine.cpp

@@ -28,6 +28,7 @@
 #include "RegisterArray.h"
 #include "RegisterArray.h"
 #include "RegisterStdString.h"
 #include "RegisterStdString.h"
 #include "ScriptEngine.h"
 #include "ScriptEngine.h"
+#include "ScriptFile.h"
 #include "StringUtils.h"
 #include "StringUtils.h"
 
 
 #include <angelscript.h>
 #include <angelscript.h>
@@ -128,14 +129,14 @@ void ScriptEngine::garbageCollect(bool fullCycle)
 {
 {
     PROFILE(Script_GarbageCollect);
     PROFILE(Script_GarbageCollect);
     
     
-    // Run either a number of iterations, or a full cycle if requested
-    if (fullCycle)
-        mAngelScriptEngine->GarbageCollect(asGC_FULL_CYCLE);
-    else
-    {
-        for (unsigned i = 0; i < GARBAGE_COLLECT_ITERATIONS; ++i)
-            mAngelScriptEngine->GarbageCollect(asGC_ONE_STEP);
-    }
+    // Unprepare contexts up to the highest used
+    mImmediateContext->Unprepare();
+    unsigned highest = getHighestScriptNestingLevel();
+    for (unsigned i = 0; i < highest; ++i)
+        mScriptFileContexts[i]->Unprepare();
+    
+    // Then actually garbage collect
+    mAngelScriptEngine->GarbageCollect(fullCycle ? asGC_FULL_CYCLE : asGC_ONE_STEP);
 }
 }
 
 
 void ScriptEngine::setLogMode(ScriptLogMode mode)
 void ScriptEngine::setLogMode(ScriptLogMode mode)

+ 0 - 3
Engine/Script/ScriptEngine.h

@@ -40,9 +40,6 @@ enum ScriptLogMode
 //! Maximum function/method nesting level
 //! Maximum function/method nesting level
 static const unsigned MAX_SCRIPT_NESTING_LEVEL = 32;
 static const unsigned MAX_SCRIPT_NESTING_LEVEL = 32;
 
 
-//! Amount of garbage collection iterations when not doing a full cycle
-static const unsigned GARBAGE_COLLECT_ITERATIONS = 5;
-
 //! Utilizes the AngelScript library for executing scripts
 //! Utilizes the AngelScript library for executing scripts
 class ScriptEngine : public RefCounted
 class ScriptEngine : public RefCounted
 {
 {

+ 19 - 7
Engine/Script/ScriptFile.cpp

@@ -37,7 +37,8 @@
 
 
 #include "DebugNew.h"
 #include "DebugNew.h"
 
 
-static int scriptNestingLevel = 0;
+static unsigned scriptNestingLevel = 0;
+static unsigned highestScriptNestingLevel = 0;
 ScriptFile* lastScriptFile = 0;
 ScriptFile* lastScriptFile = 0;
 
 
 ScriptFile::ScriptFile(ScriptEngine* scriptEngine, const std::string& name) :
 ScriptFile::ScriptFile(ScriptEngine* scriptEngine, const std::string& name) :
@@ -59,13 +60,13 @@ ScriptFile::~ScriptFile()
         for (std::vector<ScriptInstance*>::iterator i = instances.begin(); i != instances.end(); ++i)
         for (std::vector<ScriptInstance*>::iterator i = instances.begin(); i != instances.end(); ++i)
             (*i)->releaseObject();
             (*i)->releaseObject();
         
         
+        // Perform a full garbage collection cycle, also clean up contexts which might still refer to the module's functions
+        mScriptEngine->garbageCollect(true);
+        
         // Remove the module
         // Remove the module
         asIScriptEngine* engine = mScriptEngine->getAngelScriptEngine();
         asIScriptEngine* engine = mScriptEngine->getAngelScriptEngine();
         engine->DiscardModule(getName().c_str());
         engine->DiscardModule(getName().c_str());
         mScriptModule = 0;
         mScriptModule = 0;
-        
-        // Perform a full garbage collection cycle
-        mScriptEngine->garbageCollect(true);
     }
     }
     if (lastScriptFile == this)
     if (lastScriptFile == this)
         lastScriptFile = 0;
         lastScriptFile = 0;
@@ -103,12 +104,12 @@ void ScriptFile::load(Deserializer& source, ResourceCache* cache)
     mScriptEngine->setLogMode(LOGMODE_RETAINED);
     mScriptEngine->setLogMode(LOGMODE_RETAINED);
     mScriptEngine->clearLogMessages();
     mScriptEngine->clearLogMessages();
     int result = mScriptModule->Build();
     int result = mScriptModule->Build();
+    std::string errors = mScriptEngine->getLogMessages();
     mScriptEngine->setLogMode(LOGMODE_IMMEDIATE);
     mScriptEngine->setLogMode(LOGMODE_IMMEDIATE);
     if (result < 0)
     if (result < 0)
-    {
-        std::string errors = mScriptEngine->getLogMessages();
         EXCEPTION("Failed to compile script module " + getName() + ":\n" + errors);
         EXCEPTION("Failed to compile script module " + getName() + ":\n" + errors);
-    }
+    if (!errors.empty())
+        LOGWARNING(errors);
     
     
     LOGINFO("Compiled script module " + getName());
     LOGINFO("Compiled script module " + getName());
     mCompiled = true;
     mCompiled = true;
@@ -170,6 +171,8 @@ bool ScriptFile::execute(asIScriptFunction* function, const std::vector<Variant>
     setParameters(context, function, parameters);
     setParameters(context, function, parameters);
     
     
     ++scriptNestingLevel;
     ++scriptNestingLevel;
+    if (scriptNestingLevel > highestScriptNestingLevel)
+        highestScriptNestingLevel = scriptNestingLevel;
     bool success = context->Execute() >= 0;
     bool success = context->Execute() >= 0;
     --scriptNestingLevel;
     --scriptNestingLevel;
     
     
@@ -212,6 +215,8 @@ bool ScriptFile::execute(asIScriptObject* object, asIScriptFunction* method, con
     setParameters(context, method, parameters);
     setParameters(context, method, parameters);
     
     
     ++scriptNestingLevel;
     ++scriptNestingLevel;
+    if (scriptNestingLevel > highestScriptNestingLevel)
+        highestScriptNestingLevel = scriptNestingLevel;
     bool success = context->Execute() >= 0;
     bool success = context->Execute() >= 0;
     --scriptNestingLevel;
     --scriptNestingLevel;
     
     
@@ -530,3 +535,10 @@ unsigned getScriptNestingLevel()
 {
 {
     return scriptNestingLevel;
     return scriptNestingLevel;
 }
 }
+
+unsigned getHighestScriptNestingLevel()
+{
+    unsigned ret = highestScriptNestingLevel;
+    highestScriptNestingLevel = 0;
+    return ret;
+}

+ 2 - 0
Engine/Script/ScriptFile.h

@@ -116,5 +116,7 @@ private:
 ScriptFile* getLastScriptFile();
 ScriptFile* getLastScriptFile();
 //! Get current script execution nesting level
 //! Get current script execution nesting level
 unsigned getScriptNestingLevel();
 unsigned getScriptNestingLevel();
+//! Return highest script execution nesting level last frame, and clear
+unsigned getHighestScriptNestingLevel();
 
 
 #endif // SCRIPT_SCRIPTFILE_H
 #endif // SCRIPT_SCRIPTFILE_H

+ 1 - 1
Examples/NinjaSnowWar/AIController.cpp

@@ -217,7 +217,7 @@ void AIController::control(Ninja* ninja, float time)
     }
     }
     else
     else
     {
     {
-        // If not, walk idly
+        // If no target, walk idly
         ninja->mControls.set(CTRL_ALL, false);
         ninja->mControls.set(CTRL_ALL, false);
         ninja->mControls.set(CTRL_UP, true);
         ninja->mControls.set(CTRL_UP, true);
         ninja->mDirChangeTime -= time;
         ninja->mDirChangeTime -= time;

+ 1 - 1
Examples/NinjaSnowWar/Game.cpp

@@ -918,7 +918,7 @@ void Game::spawnObjects(float timeStep)
             float maxOffset = GameConfig::getReal("Game/EnemySpawnOffset");
             float maxOffset = GameConfig::getReal("Game/EnemySpawnOffset");
             float offset = random(maxOffset * 2.0f) - maxOffset;
             float offset = random(maxOffset * 2.0f) - maxOffset;
             // Random north/east/south/west direction
             // Random north/east/south/west direction
-            int dir = random(4);
+            int dir = rand() & 3;
             dir *= 90;
             dir *= 90;
             Quaternion q((float)dir, Vector3::sUp);
             Quaternion q((float)dir, Vector3::sUp);
             Vector3 position(q * (GameConfig::getVector3("Game/EnemySpawnPosition") + Vector3(offset, 0, 0)));
             Vector3 position(q * (GameConfig::getVector3("Game/EnemySpawnPosition") + Vector3(offset, 0, 0)));

+ 13 - 19
Examples/Urho3D/Application.cpp

@@ -38,10 +38,6 @@
 
 
 Application::Application()
 Application::Application()
 {
 {
-    std::string userDir = getUserDocumentsDirectory();
-    std::string applicationDir = userDir + "Urho3D";
-    
-    createDirectory(applicationDir);
 }
 }
 
 
 Application::~Application()
 Application::~Application()
@@ -56,20 +52,11 @@ Application::~Application()
 
 
 void Application::run()
 void Application::run()
 {
 {
-    init();
+    // Create application directory
+    std::string applicationDir = getUserDocumentsDirectory() + "Urho3D";
+    createDirectory(applicationDir);
     
     
-    while (!mEngine->isExiting())
-    {
-        if (!mScriptFile->execute("void runFrame()"))
-            EXCEPTION("Failed to execute the runFrame() function");
-        
-        // Script garbage collection
-        mEngine->getScriptEngine()->garbageCollect(false);
-    }
-}
-
-void Application::init()
-{
+    // Parse script file name from the command line
     const std::vector<std::string>& arguments = getArguments();
     const std::vector<std::string>& arguments = getArguments();
     std::string scriptFileName;
     std::string scriptFileName;
     if ((arguments.size()) && (arguments[0][0] != '-'))
     if ((arguments.size()) && (arguments[0][0] != '-'))
@@ -78,12 +65,11 @@ void Application::init()
         EXCEPTION("Usage: Urho3D <scriptfile> [options]\n\n"
         EXCEPTION("Usage: Urho3D <scriptfile> [options]\n\n"
             "The script file should implement the functions void start() and void runFrame(). See the readme for options.\n");
             "The script file should implement the functions void start() and void runFrame(). See the readme for options.\n");
     
     
+    // Instantiate the engine
     mEngine = new Engine();
     mEngine = new Engine();
     
     
     // Add resources
     // Add resources
     mCache = mEngine->getResourceCache();
     mCache = mEngine->getResourceCache();
-    if (fileExists("CoreData.pak"))
-        mCache->addPackageFile(new PackageFile("CoreData.pak"));
     if (fileExists("Data.pak"))
     if (fileExists("Data.pak"))
         mCache->addPackageFile(new PackageFile("Data.pak"));
         mCache->addPackageFile(new PackageFile("Data.pak"));
     else
     else
@@ -91,6 +77,7 @@ void Application::init()
     
     
     mCache->addResourcePath(getSystemFontDirectory());
     mCache->addResourcePath(getSystemFontDirectory());
     
     
+    // Initialize engine & scripting
     mEngine->init(arguments);
     mEngine->init(arguments);
     mEngine->createScriptEngine();
     mEngine->createScriptEngine();
     
     
@@ -100,4 +87,11 @@ void Application::init()
         throw Exception(getLog()->getLastMessage(), false);
         throw Exception(getLog()->getLastMessage(), false);
     if (!mScriptFile->execute("void start()"))
     if (!mScriptFile->execute("void start()"))
         EXCEPTION("Failed to execute the start() function");
         EXCEPTION("Failed to execute the start() function");
+    
+    // Run until exited
+    while (!mEngine->isExiting())
+    {
+        if (!mScriptFile->execute("void runFrame()"))
+            EXCEPTION("Failed to execute the runFrame() function");
+    }
 }
 }

+ 7 - 2
Examples/Urho3D/Application.h

@@ -31,19 +31,24 @@ class Engine;
 class ResourceCache;
 class ResourceCache;
 class ScriptFile;
 class ScriptFile;
 
 
+//! Urho3D Shell application
 class Application
 class Application
 {
 {
 public:
 public:
+    //! Construct
     Application();
     Application();
+    //! Destruct
     ~Application();
     ~Application();
     
     
+    //! Run the script-based initialization & main loop
     void run();
     void run();
     
     
 private:
 private:
-    void init();
-    
+    //! Engine
     SharedPtr<Engine> mEngine;
     SharedPtr<Engine> mEngine;
+    //! Resource cache
     SharedPtr<ResourceCache> mCache;
     SharedPtr<ResourceCache> mCache;
+    //! Script file
     SharedPtr<ScriptFile> mScriptFile;
     SharedPtr<ScriptFile> mScriptFile;
 };
 };