Browse Source

Script API fixes and additions.
Changed ScriptInstance to hold a weak pointer to the ScriptFile to avoid circular references.
Fixed missing RigidBody pointers in entity collision event.
More objects for scripted NinjaSnowWar.

Lasse Öörni 15 years ago
parent
commit
7cc3d7d842

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

@@ -1,7 +0,0 @@
-const int CTRL_UP = 1;
-const int CTRL_DOWN = 2;
-const int CTRL_LEFT = 4;
-const int CTRL_RIGHT = 8;
-const int CTRL_FIRE = 16;
-const int CTRL_JUMP = 32;
-const int CTRL_ALL = 63;

+ 122 - 58
Bin/Data/Scripts/GameObject.as

@@ -1,4 +1,14 @@
-#include "Scripts/Controls.as"
+const int CTRL_UP = 1;
+const int CTRL_DOWN = 2;
+const int CTRL_LEFT = 4;
+const int CTRL_RIGHT = 8;
+const int CTRL_FIRE = 16;
+const int CTRL_JUMP = 32;
+const int CTRL_ALL = 63;
+
+const int SIDE_NEUTRAL = 0;
+const int SIDE_PLAYER = 1;
+const int SIDE_ENEMY = 2;
 
 class GameObject : ScriptObject
 {
@@ -7,12 +17,20 @@ class GameObject : ScriptObject
     bool onGround;
     bool isSliding;
     float duration;
+    int health;
+    int maxHealth;
+    int side;
+    int lastDamageSide;
 
     GameObject()
     {
         onGround = false;
         isSliding = false;
-        duration = 0;
+        duration = -1; // Infinite
+        health = 0;
+        maxHealth = 0;
+        side = SIDE_NEUTRAL;
+        lastDamageSide = SIDE_NEUTRAL;
     }
 
     void create(const Vector3&in position, const Quaternion&in rotation)
@@ -27,7 +45,7 @@ class GameObject : ScriptObject
     void updateFixed(float timeStep)
     {
         // Disappear when duration expired
-        if (duration > 0)
+        if (duration >= 0)
         {
             duration -= timeStep;
             if (duration <= 0)
@@ -35,17 +53,110 @@ class GameObject : ScriptObject
         }
     }
 
+    bool damage(GameObject@ origin, int amount)
+    {
+        if ((origin.side == side) || (health == 0))
+            return false;
+
+        lastDamageSide = origin.side;
+        health -= amount;
+        if (health < 0)
+            health = 0;
+        return true;
+    }
+
+    bool heal(int amount)
+    {
+        // By default do not heal
+        return false;
+    }
+
+    void playSound(const string&in soundName)
+    {
+        RigidBody@ body = entity.getComponent("RigidBody");
+        if (@body == null)
+            return;
+
+        // Create the sound channel
+        PositionalChannel@ channel = entity.createComponent("PositionalChannel", entity.getUniqueComponentName());
+        body.addChild(channel);
+        channel.setAutoRemove(true);
+        channel.setDistanceAttenuation(200, 5000, 1);
+        Sound@ sound = cache.getResource("Sound", soundName);
+        channel.play(sound, sound.getFrequency());
+    }
+    
+    Entity@ spawnObject(const Vector3&in position, const Quaternion&in rotation, const string&in className)
+    {
+        Entity@ newEntity = scene.createEntity();
+
+        // Create the ScriptInstance with specified class
+        ScriptInstance@ instance = newEntity.createComponent("ScriptInstance");
+        instance.setScriptClass(cache.getResource("ScriptFile", "Scripts/NinjaSnowWar.as"), className);
+        GameObject@ object = cast<GameObject>(instance.getScriptObject());
+        if (@object != null)
+            object.create(position, rotation);
+
+        return newEntity;
+    }
+
+    Entity@ spawnParticleEffect(const Vector3&in position, const string&in effectName, float duration)
+    {
+        Entity@ newEntity = scene.createEntity();
+
+        // Create the particle emitter
+        ParticleEmitter@ emitter = newEntity.createComponent("ParticleEmitter");
+        emitter.loadParameters(cache.getResource("XMLFile", effectName));
+        emitter.setPosition(position);
+
+        // Create a GameObject for managing the effect lifetime
+        ScriptInstance@ instance = newEntity.createComponent("ScriptInstance");
+        instance.setScriptClass(cache.getResource("ScriptFile", "Scripts/NinjaSnowWar.as"), "GameObject");
+        GameObject@ object = cast<GameObject>(instance.getScriptObject());
+        if (@object != null)
+            object.duration = duration;
+
+        return newEntity;
+    }
+
+    Entity@ spawnSound(const Vector3&in position, const string&in soundName, float duration)
+    {
+        Entity@ newEntity = scene.createEntity();
+
+        // Create a GameObject for managing the effect lifetime
+        ScriptInstance@ instance = newEntity.createComponent("ScriptInstance");
+        instance.setScriptClass(cache.getResource("ScriptFile", "Scripts/NinjaSnowWar.as"), "GameObject");
+        GameObject@ object = cast<GameObject>(instance.getScriptObject());
+        if (@object != null)
+            object.duration = duration;
+
+        // Create the sound channel
+        PositionalChannel@ channel = newEntity.createComponent("PositionalChannel", entity.getUniqueComponentName());
+        channel.setPosition(position);
+        channel.setAutoRemove(true);
+        channel.setDistanceAttenuation(200, 5000, 1);
+        Sound@ sound = cache.getResource("Sound", soundName);
+        channel.play(sound, sound.getFrequency());
+
+        return newEntity;
+    }
+
     void handleEntityCollision(StringHash eventType, VariantMap& eventData)
     {
         Entity@ otherEntity = eventData["OtherEntity"].getEntity();
-        // If the other entity does not have a ScriptInstance component, it's static world geometry
-        if (!otherEntity.hasComponent("ScriptInstance"))
-            handleWorldCollision(eventData);
-        else
-            handleObjectCollision(otherEntity, eventData);
+        RigidBody@ otherBody = eventData["OtherBody"].getRigidBody();
+
+        // If the other rigid body belongs to static geometry, perform world collision
+        if (otherBody.getCollisionGroup() == 2)
+            worldCollision(eventData);
+
+        // If the other entity is scripted, perform object-to-object collision
+        GameObject@ otherObject = cast<GameObject>(otherEntity.getScriptObject());
+        if (@otherObject != null)
+            objectCollision(otherEntity, otherObject, eventData);
     }
 
-    void handleWorldCollision(VariantMap& eventData)
+    void worldCollision(VariantMap& eventData)
     {
         RigidBody@ body = entity.getComponent("RigidBody");
 
@@ -76,8 +187,8 @@ class GameObject : ScriptObject
         if (onGround == true)
             isSliding = false;
     }
-    
-    void handleObjectCollision(Entity@ otherEntity, VariantMap& eventData)
+
+    void objectCollision(Entity@ otherEntity, GameObject@ otherObject, VariantMap& eventData)
     {
     }
 
@@ -96,51 +207,4 @@ class GameObject : ScriptObject
             isSliding = false;
         }
     }
-
-    Entity@ spawnObject(const string&in className, const Vector3&in position, const Quaternion&in rotation)
-    {
-        Entity@ newEntity = scene.createEntity();
-
-        // Create the ScriptInstance with specified class
-        ScriptInstance@ instance = newEntity.createComponent("ScriptInstance");
-        instance.setScriptClass(cache.getResource("ScriptFile", "Scripts/NinjaSnowWar.as"), className);
-        GameObject@ object = cast<GameObject>(instance.getScriptObject());
-        if (@object != null)
-            object.create(position, rotation);
-
-        return newEntity;
-    }
-
-    Entity@ spawnParticleEffect(const string&in effectName, float duration, const Vector3&in position, const Quaternion&in rotation)
-    {
-        Entity@ newEntity = scene.createEntity();
-        
-        // Create the particle emitter
-        ParticleEmitter@ emitter = newEntity.createComponent("ParticleEmitter");
-        emitter.loadParameters(cache.getResource("XMLFile", effectName));
-        emitter.setPosition(position);
-        emitter.setRotation(rotation);
-
-        // Create a GameObject for managing the effect lifetime
-        ScriptInstance@ instance = newEntity.createComponent("ScriptInstance");
-        instance.setScriptClass(cache.getResource("ScriptFile", "Scripts/NinjaSnowWar.as"), "GameObject");
-        GameObject@ object = cast<GameObject>(instance.getScriptObject());
-        if (@object != null)
-            object.duration = duration;
-        
-        return newEntity;
-    }
-
-    void playSound(const string&in soundName)
-    {
-        RigidBody@ body = entity.getComponent("RigidBody");
-        if (@body == null)
-            return;
-        PositionalChannel@ channel = entity.createComponent("PositionalChannel", entity.getUniqueComponentName());
-        body.addChild(channel);
-        channel.setAutoRemove(true);
-        channel.setDistanceAttenuation(200, 5000, 1);
-        Sound@ sound = cache.getResource("Sound", soundName);
-        channel.play(sound, sound.getFrequency());
-    }
 }

+ 34 - 0
Bin/Data/Scripts/LightFlash.as

@@ -0,0 +1,34 @@
+#include "Scripts/GameObject.as"
+
+class LightFlash : GameObject
+{
+    LightFlash()
+    {
+    }
+    
+    void create(const Vector3&in position, const Quaternion&in rotation)
+    {
+        Light@ light = entity.createComponent("Light");
+        light.setLightType(LIGHT_POINT);
+        light.setRange(500.0);
+        light.setColor(Color(2.0, 2.0, 2.0));
+        light.setCastShadows(true);
+        light.setShadowResolution(0.25);
+        light.setRampTexture(cache.getResource("Texture2D", "Textures/RampWide.png"));
+        light.setPosition(position);
+        duration = 0.1;
+    }
+
+    void updateFixed(float timeStep)
+    {
+        Light@ light = entity.getComponent("Light");
+        light.setColor(light.getColor() * max(1.0 - timeStep * 10.0, 0.0));
+
+        if (duration > 0)
+        {
+            duration -= timeStep;
+            if (duration <= 0)
+                scene.removeEntity(entity);
+        }
+    }
+}

+ 86 - 3
Bin/Data/Scripts/Ninja.as

@@ -13,6 +13,7 @@ const Vector3 ninjaThrowVelocity(0, 425, 2000);
 const Vector3 ninjaThrowPosition(0, 20, 100);
 const float ninjaThrowDelay = 0.1;
 const float ninjaDrawDistance = 15000;
+const float ninjaCorpseDuration = 3;
 
 class Ninja : GameObject
 {
@@ -29,6 +30,7 @@ class Ninja : GameObject
 
     Ninja()
     {
+        health = maxHealth = 2;
         okToJump = false;
         smoke = false;
         onGround = false;
@@ -93,6 +95,12 @@ class Ninja : GameObject
 
     void updateFixed(float timeStep)
     {
+        if (health <= 0)
+        {
+            deathUpdate(timeStep);
+            return;
+        }
+
         RigidBody@ body = entity.getComponent("RigidBody");
         AnimationController@ controller = entity.getComponent("AnimationController");
 
@@ -229,10 +237,12 @@ class Ninja : GameObject
             controller.setFade("Models/Ninja_Attack1.ani", 0.0, 0.5);
             controller.setPriority("Models/Ninja_Attack1.ani", 1);
 
-            Entity@ snowball = spawnObject("SnowBall", body.getPhysicsPosition() + vel * timeStep + q * ninjaThrowPosition, getAim());
+            Entity@ snowball = spawnObject(body.getPhysicsPosition() + vel * timeStep + q * ninjaThrowPosition, getAim(), "SnowBall");
             RigidBody@ snowballBody = snowball.getComponent("RigidBody");
             snowballBody.setLinearVelocity(projectileVel);
-            
+            GameObject@ snowballObject = cast<GameObject>(snowball.getScriptObject());
+            snowballObject.side = side;
+
             playSound("Sounds/NutThrow.wav");
 
             throwTime = ninjaThrowDelay;
@@ -242,6 +252,79 @@ class Ninja : GameObject
         
         resetWorldCollision();
     }
-    
 
+    void deathUpdate(float timeStep)
+    {
+        RigidBody@ body = entity.getComponent("RigidBody");
+        AnimationController@ controller = entity.getComponent("AnimationController");
+
+        Vector3 vel = body.getLinearVelocity();
+
+        // Overall damping to cap maximum speed
+        body.applyForce(Vector3(-ninjaDampingForce * vel.x, 0, -ninjaDampingForce * vel.z));
+
+        // Collide only to world geometry
+        body.setCollisionMask(2);
+
+        // Pick death animation on first death update
+        if (deathDir == 0)
+        {
+            if (random(1.0) < 0.5)
+                deathDir = -1;
+            else
+                deathDir = 1;
+
+            playSound("Sounds/SmallExplosion.wav");
+        }
+
+        deathTime += timeStep;
+        AnimatedModel@ model = entity.getComponent("AnimatedModel");
+
+        // Move the model node to center the corpse mostly within the physics cylinder
+        // (because of the animation)
+        if (deathDir < 0)
+        {
+            // Backward death
+            controller.removeAnimations(ANIM_ATTACK, 0.1);
+            controller.setAnimation("Models/Ninja_Death1.ani", ANIM_MOVE, false, false, 0.5, 1.0, 0.2, 0.0, true);
+            if ((deathTime >= 0.3) && (deathTime < 0.8))
+                model.translate(Vector3(0, 0, 425 * timeStep));
+        }
+        else if (deathDir > 0)
+        {
+            // Forward death
+            controller.removeAnimations(ANIM_ATTACK, 0.1);
+            controller.setAnimation("Models/Ninja_Death2.ani", ANIM_MOVE, false, false, 0.5, 1.0, 0.2, 0.0, true);
+            if ((deathTime >= 0.4) && (deathTime < 0.8))
+                model.translate(Vector3(0, 0, -425 * timeStep));
+        }
+
+        // Create smokecloud just before vanishing
+        if ((deathTime > (ninjaCorpseDuration - 1)) && (!smoke))
+        {
+            spawnParticleEffect(body.getPhysicsPosition() + Vector3(0, -40, 0), "Particle/Smoke.xml", 8);
+            smoke = true;
+        }
+
+        if (deathTime > ninjaCorpseDuration)
+        {
+            spawnObject(body.getPhysicsPosition() + Vector3(0, -50, 0), Quaternion(), "LightFlash");
+            spawnSound(body.getPhysicsPosition() + Vector3(0, -50, 0), "Sounds/BigExplosion.wav", 2);
+            scene.removeEntity(entity);
+        }
+    }
+
+    bool heal(int amount)
+    {
+        if (health == maxHealth)
+            return false;
+
+        health += amount;
+        if (health > maxHealth)
+            health = maxHealth;
+        // If player, play the "powerup" sound
+        if (side == SIDE_PLAYER)
+            playSound("Sounds/Powerup.wav");
+        return true;
+    }
 }

+ 44 - 13
Bin/Data/Scripts/NinjaSnowWar.as

@@ -1,7 +1,16 @@
 // Partial remake of NinjaSnowWar in script
 
+#include "Scripts/LightFlash.as"
 #include "Scripts/Ninja.as"
+#include "Scripts/Potion.as"
 #include "Scripts/SnowBall.as"
+#include "Scripts/SnowCrate.as"
+
+const float mouseSensitivity = 0.125;
+const float cameraMinDist = 25;
+const float cameraMaxDist = 500;
+const float cameraSafetyDist = 30;
+const int playerHealth = 20;
 
 Scene@ gameScene;
 Camera@ gameCamera;
@@ -12,11 +21,8 @@ BorderImage@ healthBar;
 BorderImage@ sight;
 
 Controls playerControls;
-float mouseSensitivity = 0.125;
-float cameraMinDist = 25;
-float cameraMaxDist = 500;
-float cameraSafetyDist = 30;
 bool paused = false;
+bool gameOn = false;
 
 void start()
 {
@@ -25,7 +31,7 @@ void start()
     initScene();
     createCamera();
     createOverlays();
-    spawnPlayer();
+    startGame();
 
     subscribeToEvent("Update", "handleUpdate");
     subscribeToEvent("PostUpdate", "handlePostUpdate");
@@ -145,13 +151,22 @@ void createOverlays()
     uiRoot.addChild(healthBorder);
 }
 
-void spawnPlayer()
+void startGame()
 {
+    // Clear the scene of all existing scripted entities
+    array<Entity@> scriptedEntities = gameScene.getScriptedEntities();
+    for (int i = 0; i < scriptedEntities.length(); ++i)
+        gameScene.removeEntity(scriptedEntities[i]);
+
     Entity@ playerEntity = gameScene.createEntity("Player");
     ScriptInstance@ instance = playerEntity.createComponent("ScriptInstance");
     instance.setScriptClass(cache.getResource("ScriptFile", "Scripts/NinjaSnowWar.as"), "Ninja");
-    Ninja@ object = cast<Ninja>(instance.getScriptObject());
+    GameObject@ object = cast<GameObject>(instance.getScriptObject());
     object.create(Vector3(0, 90, 0), Quaternion());
+    object.health = object.maxHealth = playerHealth;
+    object.side = SIDE_PLAYER;
+
+    gameOn = true;
 }
 
 void handleUpdate(StringHash eventType, VariantMap& eventData)
@@ -177,12 +192,17 @@ void handleUpdate(StringHash eventType, VariantMap& eventData)
 void handlePostUpdate(StringHash eventType, VariantMap& eventData)
 {
     updateCamera();
+    updateStatus();
 }
 
 void updateControls()
 {
     playerControls.set(CTRL_ALL, false);
 
+    Entity@ playerEntity = gameScene.getEntity("Player");
+    if (@playerEntity == null)
+        return;
+
     if (!console.isVisible())
     {
         if (input.getKeyDown('W'))
@@ -208,13 +228,11 @@ void updateControls()
     playerControls.pitch += mouseSensitivity * input.getMouseMoveY();
     playerControls.pitch = clamp(playerControls.pitch, -60, 60);
 
-    Entity@ playerEntity = gameScene.getEntity("Player");
-    if (@playerEntity == null)
-        return;
-
-    ScriptInstance@ instance = playerEntity.getComponent("ScriptInstance");
-    Ninja@ object = cast<Ninja>(instance.getScriptObject());
+    GameObject@ object = cast<GameObject>(playerEntity.getScriptObject());
     object.setControls(playerControls);
+    
+    if ((input.getKeyPress('K')) && (object.health > 0))
+        object.health = object.health - 1;
 }
 
 void updateCamera()
@@ -247,6 +265,19 @@ void updateCamera()
     audio.setListenerRotation(dir);
 }
 
+void updateStatus()
+{
+    if (engine.isHeadless())
+        return;
+
+    Entity@ playerEntity = gameScene.getEntity("Player");
+    if (@playerEntity == null)
+        return;
+
+    GameObject@ object = cast<GameObject>(playerEntity.getScriptObject());
+    healthBar.setWidth(116 * object.health / playerHealth);
+}
+
 void handleKeyDown(StringHash eventType, VariantMap& eventData)
 {
     // Check for toggling the console

+ 57 - 0
Bin/Data/Scripts/Potion.as

@@ -0,0 +1,57 @@
+#include "Scripts/GameObject.as"
+
+const int potionHealAmount = 5;
+const float potionMass = 10;
+const float potionFriction = 0.5;
+const float potionDrawDistance = 15000;
+
+class Potion : GameObject
+{
+    int healAmount;
+
+    Potion()
+    {
+        healAmount = potionHealAmount;
+    }
+
+    void start()
+    {
+        subscribeToEvent("EntityCollision", "handleEntityCollision");
+    }
+    
+    void create(const Vector3&in position, const Quaternion&in rotation)
+    {
+        // Create model
+        StaticModel@ model = entity.createComponent("StaticModel");
+        model.setModel(cache.getResource("Model", "Models/Potion.mdl"));
+        model.setMaterial(cache.getResource("Material", "Materials/Potion.xml"));
+        model.setDrawDistance(potionDrawDistance);
+        model.setCastShadows(true);
+
+        // Create body
+        RigidBody@ body = entity.createComponent("RigidBody");
+        body.setPosition(position);
+        body.setRotation(rotation);
+        body.setMode(PHYS_DYNAMIC);
+        body.setMass(potionMass);
+        body.setFriction(potionFriction);
+        body.setCollisionShape(cache.getResource("CollisionShape", "Physics/Potion.xml"));
+        body.setCollisionGroup(1);
+        body.setCollisionMask(3);
+        body.addChild(model);
+    }
+    
+    void objectCollision(Entity@ otherEntity, GameObject@ otherObject, VariantMap& eventData)
+    {
+        if (healAmount > 0)
+        {
+            if (otherObject.heal(healAmount))
+            {
+                // Note: this is called during a physics world callback. Can not remove the potion at this point,
+                // but instead set it to remove itself during the next update
+                healAmount = 0;
+                duration = 0;
+            }
+        }
+    }
+}

+ 30 - 4
Bin/Data/Scripts/SnowBall.as

@@ -6,13 +6,18 @@ const float snowballDampingForce = 20;
 const float snowballMinHitSpeed = 100;
 const float snowballDuration = 5;
 const float snowballGroundHitDuration = 1;
+const float snowballObjectHitDuration = 0;
 const float snowballDrawDistance = 7500;
+const int snowballDamage = 1;
 
 class SnowBall : GameObject
 {
+    int hitDamage;
+
     SnowBall()
     {
         duration = snowballDuration;
+        hitDamage = snowballDamage;
     }
 
     void start()
@@ -51,23 +56,44 @@ class SnowBall : GameObject
             body.applyForce(Vector3(-snowballDampingForce * vel.x, 0, -snowballDampingForce * vel.z));
             
         // Disappear when duration expired
-        if (duration > 0)
+        if (duration >= 0)
         {
             duration -= timeStep;
             if (duration <= 0)
             {
-                spawnParticleEffect("Particle/SnowExplosion.xml", 1, body.getPhysicsPosition(), Quaternion());
+                spawnParticleEffect(body.getPhysicsPosition(), "Particle/SnowExplosion.xml", 1);
                 scene.removeEntity(entity);
             }
         }
     }
     
-    void handleWorldCollision(VariantMap& eventData)
+    void worldCollision(VariantMap& eventData)
     {
-        GameObject::handleWorldCollision(eventData);
+        GameObject::worldCollision(eventData);
         
         // If hit the ground, disappear after a short while
         if (duration > snowballGroundHitDuration)
             duration = snowballGroundHitDuration;
     }
+
+    void objectCollision(Entity@ otherEntity, GameObject@ otherObject, VariantMap& eventData)
+    {
+        if (hitDamage > 0)
+        {
+            RigidBody@ body = entity.getComponent("RigidBody");
+            if ((body.getLinearVelocity().getLength() >= snowballMinHitSpeed))
+            {
+                if (side != otherObject.side)
+                {
+                    otherObject.damage(this, hitDamage);
+                    // Create a temp entity for the hit sound
+                    spawnSound(body.getPhysicsPosition(), "Sounds/PlayerFistHit.wav", 0.2);
+                }
+
+                hitDamage = 0;
+            }
+        }
+        if (duration > snowballObjectHitDuration)
+            duration = snowballObjectHitDuration;
+    }
 }

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

@@ -0,0 +1,52 @@
+#include "Scripts/GameObject.as"
+
+const int snowcrateHealth = 5;
+const float snowcrateMass = 200;
+const float snowcrateFriction = 0.35;
+const float snowcrateDrawDistance = 15000;
+
+class SnowCrate : GameObject
+{
+    SnowCrate()
+    {
+        health = maxHealth = snowcrateHealth;
+    }
+    
+    void start()
+    {
+        subscribeToEvent("EntityCollision", "handleEntityCollision");
+    }
+    
+    void create(const Vector3&in position, const Quaternion&in rotation)
+    {
+        // Create model
+        StaticModel@ model = entity.createComponent("StaticModel");
+        model.setModel(cache.getResource("Model", "Models/SnowCrate.mdl"));
+        model.setMaterial(cache.getResource("Material", "Materials/SnowCrate.xml"));
+        model.setDrawDistance(snowcrateDrawDistance);
+        model.setCastShadows(true);
+
+        // Create body
+        RigidBody@ body = entity.createComponent("RigidBody");
+        body.setPosition(position);
+        body.setRotation(rotation);
+        body.setMode(PHYS_DYNAMIC);
+        body.setMass(snowcrateMass);
+        body.setFriction(snowcrateFriction);
+        body.setCollisionShape(cache.getResource("CollisionShape", "Physics/SnowCrate.xml"));
+        body.setCollisionGroup(2);
+        body.setCollisionMask(3);
+        body.addChild(model);
+    }
+    
+    void updateFixed(float timeStep)
+    {
+        if (health <= 0)
+        {
+            RigidBody@ body = entity.getComponent("RigidBody");
+            spawnParticleEffect(body.getPhysicsPosition(), "Particle/SnowExplosionBig.xml", 2);
+            spawnObject(body.getPhysicsPosition(), Quaternion(), "Potion");
+            scene.removeEntity(entity);
+        }
+    }
+}

+ 3 - 1
Engine/Engine/RegisterCommon.cpp

@@ -65,8 +65,10 @@ static void registerColor(asIScriptEngine* engine)
     engine->RegisterObjectBehaviour("Color", asBEHAVE_CONSTRUCT, "void f(float, float, float, float)", asFUNCTION(ConstructColorRGBA), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectBehaviour("Color", asBEHAVE_CONSTRUCT, "void f(float, float, float)", asFUNCTION(ConstructColorRGB), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Color", "Color &opAssign(const Color& in)", asMETHOD(Color, operator =), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Color", "Color &opAddAssign(const Color& in)", asMETHOD(Color, operator +=), asCALL_THISCALL);
     engine->RegisterObjectMethod("Color", "bool opEquals(const Color& in) const", asMETHOD(Color, operator ==), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Color", "Color &opMul(float) const", asMETHOD(Color, operator *), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Color", "Color opMul(float) const", asMETHOD(Color, operator *), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Color", "Color opAdd(const Color& in) const", asMETHOD(Color, operator +), asCALL_THISCALL);
     engine->RegisterObjectMethod("Color", "Vector3 getRGB() const", asMETHOD(Color, getRGB), asCALL_THISCALL);
     engine->RegisterObjectMethod("Color", "Color lerp(const Color& in, float) const", asMETHOD(Color, lerp), asCALL_THISCALL);
     engine->RegisterObjectMethod("Color", "string toString() const", asFUNCTIONPR(toString, (const Color&), std::string), asCALL_CDECL_OBJLAST);

+ 1 - 1
Engine/Engine/RegisterMath.cpp

@@ -80,7 +80,7 @@ static void registerMathFunctions(asIScriptEngine* engine)
     engine->RegisterGlobalFunction("int randomInt(int)", asFUNCTIONPR(random, (int), int), asCALL_CDECL);
     engine->RegisterGlobalFunction("void setRandomSeed(int)", asFUNCTION(srand), asCALL_CDECL);
     engine->RegisterGlobalFunction("float min(float, float)", asFUNCTIONPR(min, (float, float), float), asCALL_CDECL);
-    engine->RegisterGlobalFunction("float max(float, float)", asFUNCTIONPR(min, (float, float), float), asCALL_CDECL);
+    engine->RegisterGlobalFunction("float max(float, float)", asFUNCTIONPR(max, (float, float), float), asCALL_CDECL);
     engine->RegisterGlobalFunction("float clamp(float, float, float)", asFUNCTIONPR(clamp, (float, float, float), float), asCALL_CDECL);
     engine->RegisterGlobalFunction("float lerp(float, float, float)", asFUNCTIONPR(lerp, (float, float, float), float), asCALL_CDECL);
 }

+ 55 - 3
Engine/Engine/RegisterScene.cpp

@@ -183,6 +183,37 @@ static CScriptArray* EntityGetComponents(Entity* ptr)
     return sharedPtrVectorToHandleArray<Component>(components, "array<Component@>");
 }
 
+static asIScriptObject* EntityGetScriptObject(Entity* ptr)
+{
+    const std::vector<SharedPtr<Component> >& components = ptr->getComponents();
+    for (std::vector<SharedPtr<Component> >::const_iterator i = components.begin(); i != components.end(); ++i)
+    {
+        if ((*i)->getType() == ScriptInstance::getTypeStatic())
+        {
+            ScriptInstance* instance = static_cast<ScriptInstance*>(i->getPtr());
+            asIScriptObject* object = instance->getScriptObject();
+            if (object)
+                return object;
+        }
+    }
+    return 0;
+}
+
+static asIScriptObject* EntityGetScriptObjectWithClass(const std::string& className, Entity* ptr)
+{
+    const std::vector<SharedPtr<Component> >& components = ptr->getComponents();
+    for (std::vector<SharedPtr<Component> >::const_iterator i = components.begin(); i != components.end(); ++i)
+    {
+        if ((*i)->getType() == ScriptInstance::getTypeStatic())
+        {
+            ScriptInstance* instance = static_cast<ScriptInstance*>(i->getPtr());
+            if (instance->getClassName() == className)
+                return instance->getScriptObject();
+        }
+    }
+    return 0;
+}
+
 static Entity* GetEntity()
 {
     return getScriptContextEntity();
@@ -195,8 +226,8 @@ static void SendEntityEvent(Entity* entity, const std::string& eventType, Varian
 
 static void registerEntity(asIScriptEngine* engine)
 {
+    engine->RegisterInterface("ScriptObject");
     engine->RegisterGlobalProperty("const uint LOCAL_ENTITY", (void*)&LOCAL_ENTITY);
-    
     engine->RegisterObjectType("Scene", 0, asOBJ_REF);
     engine->RegisterObjectBehaviour("Entity", asBEHAVE_ADDREF, "void f()", asMETHOD(Entity, addRef), asCALL_THISCALL);
     engine->RegisterObjectBehaviour("Entity", asBEHAVE_RELEASE, "void f()", asMETHOD(Entity, releaseRef), asCALL_THISCALL);
@@ -230,6 +261,8 @@ static void registerEntity(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Entity", "bool hasComponent(const string& in, const string& in) const", asFUNCTION(EntityHasComponentWithName), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Entity", "Component@+ getComponent(const string& in) const", asFUNCTION(EntityGetComponent), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Entity", "Component@+ getComponent(const string& in, const string& in) const", asFUNCTION(EntityGetComponentWithName), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Entity", "ScriptObject@+ getScriptObject() const", asFUNCTION(EntityGetScriptObject), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Entity", "ScriptObject@+ getScriptObject(const string& in) const", asFUNCTION(EntityGetScriptObjectWithClass), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Entity", "uint getNumComponents() const", asMETHOD(Entity, getNumComponents), asCALL_THISCALL);
     engine->RegisterObjectMethod("Entity", "array<Component@>@ getComponents() const", asFUNCTION(EntityGetComponents), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Entity", "bool isAuthority() const", asMETHOD(Entity, isAuthority), asCALL_THISCALL);
@@ -367,6 +400,11 @@ static void SceneLoadAsyncXML(File* file, Scene* ptr)
     }
 }
 
+static void SceneRemoveAllEntitiesDefault(Scene* ptr)
+{
+    ptr->removeAllEntities();
+}
+
 static CScriptArray* SceneGetEntities(Scene* ptr)
 {
     const std::map<EntityID, SharedPtr<Entity> >& entities = ptr->getEntities();
@@ -376,7 +414,19 @@ static CScriptArray* SceneGetEntities(Scene* ptr)
     return vectorToHandleArray<Entity>(result, "array<Entity@>");
 }
 
-static CScriptArray* SceneGetEntitiesWithClass(const std::string& className, Scene* ptr)
+static CScriptArray* SceneGetScriptedEntities(Scene* ptr)
+{
+    const std::map<EntityID, SharedPtr<Entity> >& entities = ptr->getEntities();
+    std::vector<Entity*> result;
+    for (std::map<EntityID, SharedPtr<Entity> >::const_iterator i = entities.begin(); i != entities.end(); ++i)
+    {
+        if (i->second->hasComponent<ScriptInstance>())
+            result.push_back(i->second);
+    }
+    return vectorToHandleArray<Entity>(result, "array<Entity@>");
+}
+
+static CScriptArray* SceneGetScriptedEntitiesWithClass(const std::string& className, Scene* ptr)
 {
     const std::map<EntityID, SharedPtr<Entity> >& entities = ptr->getEntities();
     std::vector<Entity*> result;
@@ -448,6 +498,7 @@ static void registerScene(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Scene", "void removeEntity(uint)", asMETHODPR(Scene, removeEntity, (EntityID), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void removeEntity(Entity@+)", asMETHODPR(Scene, removeEntity, (Entity*), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void removeEntity(const string& in)", asMETHODPR(Scene, removeEntity, (const std::string&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Scene", "void removeAllEntities()", asFUNCTION(SceneRemoveAllEntitiesDefault), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "void removeAllEntities(uint8)", asMETHOD(Scene, removeAllEntities), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void setTransientPredictionTime(float)", asMETHOD(Scene, setTransientPredictionTime), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "void setInterpolationConstant(float)", asMETHOD(Scene, setInterpolationConstant), asCALL_THISCALL);
@@ -460,7 +511,8 @@ static void registerScene(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Scene", "Entity@+ getEntity(const string& in) const", asMETHODPR(Scene, getEntity, (const std::string&) const, Entity*), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "uint getNumEntities() const", asMETHOD(Scene, getNumEntities), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "array<Entity@>@ getEntities() const", asFUNCTION(SceneGetEntities), asCALL_CDECL_OBJLAST);
-    engine->RegisterObjectMethod("Scene", "array<Entity@>@ getEntities(const string& in) const", asFUNCTION(SceneGetEntitiesWithClass), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "array<Entity@>@ getScriptedEntities() const", asFUNCTION(SceneGetScriptedEntities), asCALL_CDECL_OBJLAST);
+    engine->RegisterObjectMethod("Scene", "array<Entity@>@ getScriptedEntities(const string& in) const", asFUNCTION(SceneGetScriptedEntitiesWithClass), asCALL_CDECL_OBJLAST);
     engine->RegisterObjectMethod("Scene", "Vector3 getEntityPosition(Entity@+) const", asMETHOD(Scene, getEntityPosition), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "float getTransientPredictionTime() const", asMETHOD(Scene, getTransientPredictionTime), asCALL_THISCALL);
     engine->RegisterObjectMethod("Scene", "float getInterpolationConstant() const" ,asMETHOD(Scene, getInterpolationConstant), asCALL_THISCALL);

+ 0 - 1
Engine/Engine/RegisterScript.cpp

@@ -75,7 +75,6 @@ static ScriptInstance* GetSelf()
 static void registerScriptInstance(asIScriptEngine* engine)
 {
     registerComponent<ScriptInstance>(engine, "ScriptInstance");
-    engine->RegisterInterface("ScriptObject");
     engine->RegisterObjectMethod("ScriptInstance", "bool setScriptClass(ScriptFile@+, const string& in)", asMETHOD(ScriptInstance, setScriptClass), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "void setEnabled(bool)", asMETHOD(ScriptInstance, setEnabled), asCALL_THISCALL);
     engine->RegisterObjectMethod("ScriptInstance", "bool execute(const string& in, const array<Variant>@+)", asFUNCTION(ScriptInstanceExecute), asCALL_CDECL_OBJLAST);

+ 4 - 4
Engine/Physics/PhysicsWorld.cpp

@@ -591,16 +591,16 @@ void PhysicsWorld::nearCallback(void *userData, dGeomID geomA, dGeomID geomB)
     
     if (entityA)
     {
-        entityCollisionData[EntityCollision::P_BODY] = (void*)bodyA;
-        entityCollisionData[EntityCollision::P_OTHERBODY] = (void*)bodyB;
+        entityCollisionData[EntityCollision::P_BODY] = (void*)rigidBodyA;
+        entityCollisionData[EntityCollision::P_OTHERBODY] = (void*)rigidBodyB;
         entityCollisionData[EntityCollision::P_OTHERENTITY] = (void*)entityB;
         entityCollisionData[EntityCollision::P_CONTACTS] = contactsA.getBuffer();
         sendEvent(entityA, EVENT_ENTITYCOLLISION, entityCollisionData);
     }
     if (entityB)
     {
-        entityCollisionData[EntityCollision::P_BODY] = (void*)bodyB;
-        entityCollisionData[EntityCollision::P_OTHERBODY] = (void*)bodyA;
+        entityCollisionData[EntityCollision::P_BODY] = (void*)rigidBodyB;
+        entityCollisionData[EntityCollision::P_OTHERBODY] = (void*)rigidBodyA;
         entityCollisionData[EntityCollision::P_OTHERENTITY] = (void*)entityA;
         entityCollisionData[EntityCollision::P_CONTACTS] = contactsB.getBuffer();
         sendEvent(entityB, EVENT_ENTITYCOLLISION, entityCollisionData);

+ 4 - 4
Engine/Script/ScriptInstance.cpp

@@ -343,20 +343,20 @@ void ScriptInstance::releaseObject()
 {
     if (mScriptObject)
     {
-        if (mMethods[METHOD_STOP])
+        if ((mScriptFile) && (mMethods[METHOD_STOP]))
             mScriptFile->execute(mScriptObject, mMethods[METHOD_STOP]);
         
         removeAllEventHandlers();
-        clearMethods();
-        // Unsubscribe also the update events
         unsubscribeFromAllEvents();
+        clearMethods();
         
         mScriptObject->Release();
         mScriptObject = 0;
         
         objectToInstance.erase((void*)mScriptObject);
         
-        mScriptFile->removeScriptInstance(this);
+        if (mScriptFile)
+            mScriptFile->removeScriptInstance(this);
     }
 }
 

+ 1 - 1
Engine/Script/ScriptInstance.h

@@ -140,7 +140,7 @@ private:
     //! Script engine
     SharedPtr<ScriptEngine> mScriptEngine;
     //! Script file
-    SharedPtr<ScriptFile> mScriptFile;
+    WeakPtr<ScriptFile> mScriptFile;
     //! Script object
     asIScriptObject* mScriptObject;
     //! Class name