diff --git a/Engine/source/T3D/shapeBase.cpp b/Engine/source/T3D/shapeBase.cpp
index e164c8394..b3d2e1db1 100644
--- a/Engine/source/T3D/shapeBase.cpp
+++ b/Engine/source/T3D/shapeBase.cpp
@@ -172,6 +172,8 @@ ShapeBaseData::ShapeBaseData()
density( 1.0f ),
maxEnergy( 0.0f ),
maxDamage( 1.0f ),
+ mCollisionMul(0.0f),
+ mImpactMul(0.0f),
repairRate( 0.0033f ),
disabledLevel( 1.0f ),
destroyedLevel( 1.0f ),
@@ -229,6 +231,8 @@ ShapeBaseData::ShapeBaseData(const ShapeBaseData& other, bool temp_clone) : Game
density = other.density;
maxEnergy = other.maxEnergy;
maxDamage = other.maxDamage;
+ mCollisionMul = other.mCollisionMul;
+ mImpactMul = other.mImpactMul;
repairRate = other.repairRate;
disabledLevel = other.disabledLevel;
destroyedLevel = other.destroyedLevel;
@@ -585,6 +589,10 @@ void ShapeBaseData::initPersistFields()
addField( "isInvincible", TypeBool, Offset(isInvincible, ShapeBaseData),
"Invincible flag; when invincible, the object cannot be damaged or "
"repaired." );
+ addFieldV("collisionMul", TypeRangedF32, Offset(mCollisionMul, ShapeBaseData), &CommonValidators::PositiveFloat,
+ "collision damage multiplier");
+ addFieldV("impactMul", TypeRangedF32, Offset(mImpactMul, ShapeBaseData), &CommonValidators::PositiveFloat,
+ "impact damage multiplier");
endGroup( "Damage/Energy" );
addGroup( "Camera", "The settings used by the shape when it is the camera." );
diff --git a/Engine/source/T3D/shapeBase.h b/Engine/source/T3D/shapeBase.h
index 131992fa9..b34e544bf 100644
--- a/Engine/source/T3D/shapeBase.h
+++ b/Engine/source/T3D/shapeBase.h
@@ -584,6 +584,8 @@ public:
F32 density;
F32 maxEnergy;
F32 maxDamage;
+ F32 mCollisionMul;
+ F32 mImpactMul;
F32 repairRate; ///< Rate per tick.
F32 disabledLevel;
diff --git a/Templates/BaseGame/game/data/DamageModel/DamageModel.module b/Templates/BaseGame/game/data/DamageModel/DamageModel.module
new file mode 100644
index 000000000..0bce1ed70
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/DamageModel.module
@@ -0,0 +1,11 @@
+
+
+
diff --git a/Templates/BaseGame/game/data/DamageModel/DamageModel.tscript b/Templates/BaseGame/game/data/DamageModel/DamageModel.tscript
new file mode 100644
index 000000000..3268a5b9e
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/DamageModel.tscript
@@ -0,0 +1,48 @@
+function DamageModel::onCreate(%this)
+{
+}
+
+function DamageModel::onDestroy(%this)
+{
+}
+
+//This is called when the server is initially set up by the game application
+function DamageModel::initServer(%this)
+{
+}
+
+//This is called when the server is created for an actual game/map to be played
+function DamageModel::onCreateGameServer(%this)
+{
+ %this.registerDatablock("./scripts/managedData/managedParticleData");
+ %this.registerDatablock("./scripts/managedData/managedParticleEmitterData");
+ %this.queueExec("./scripts/server/utility");
+ %this.queueExec("./scripts/server/radiusDamage");
+ %this.queueExec("./scripts/server/projectile");
+ %this.queueExec("./scripts/server/weapon");
+ %this.queueExec("./scripts/server/shapeBase");
+ %this.queueExec("./scripts/server/vehicle");
+ %this.queueExec("./scripts/server/player");
+}
+
+//This is called when the server is shut down due to the game/map being exited
+function DamageModel::onDestroyGameServer(%this)
+{
+}
+
+//This is called when the client is initially set up by the game application
+function DamageModel::initClient(%this)
+{
+ %this.queueExec("./guis/damageGuiOverlay.gui");
+ %this.queueExec("./scripts/client/playGui");
+}
+
+//This is called when a client connects to a server
+function DamageModel::onCreateClientConnection(%this)
+{
+}
+
+//This is called when a client disconnects from a server
+function DamageModel::onDestroyClientConnection(%this)
+{
+}
diff --git a/Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.asset.taml b/Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.asset.taml
new file mode 100644
index 000000000..ab0935549
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.asset.taml
@@ -0,0 +1 @@
+
diff --git a/Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.gui b/Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.gui
new file mode 100644
index 000000000..2c8ee1fe6
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.gui
@@ -0,0 +1,285 @@
+//--- OBJECT WRITE BEGIN ---
+$guiContent = new GuiContainer(DamageGuiOverlay) {
+ isContainer = "1";
+ Profile = "GuiContentProfile";
+ HorizSizing = "relative";
+ VertSizing = "relative";
+ position = "0 0";
+ Extent = "1024 768";
+ MinExtent = "8 8";
+ canSave = "1";
+ Visible = "1";
+ tooltipprofile = "GuiToolTipProfile";
+ hovertime = "1000";
+ canSaveDynamicFields = "1";
+ Enabled = "1";
+ helpTag = "0";
+ noCursor = "1";
+ new GuiShapeNameHud() {
+ fillColor = "0 0 0 0.25";
+ frameColor = "0 1 0 1";
+ textColor = "0 1 0 1";
+ showFill = "0";
+ showFrame = "0";
+ verticalOffset = "0.2";
+ distanceFade = "0.1";
+ isContainer = "0";
+ Profile = "GuiModelessDialogProfile";
+ HorizSizing = "width";
+ VertSizing = "height";
+ position = "0 0";
+ Extent = "1024 768";
+ MinExtent = "8 8";
+ canSave = "1";
+ Visible = "1";
+ tooltipprofile = "GuiToolTipProfile";
+ hovertime = "1000";
+ canSaveDynamicFields = "0";
+ };
+ new GuiCrossHairHud(Reticle) {
+ damageFillColor = "0 1 0 1";
+ damageFrameColor = "1 0.6 0 1";
+ damageRect = "50 4";
+ damageOffset = "0 10";
+ bitmapAsset = "FPSEquipment:blank_image";
+ wrap = "0";
+ isContainer = "0";
+ Profile = "GuiModelessDialogProfile";
+ HorizSizing = "center";
+ VertSizing = "center";
+ position = "496 368";
+ Extent = "32 32";
+ MinExtent = "8 8";
+ canSave = "1";
+ Visible = "1";
+ tooltipprofile = "GuiToolTipProfile";
+ hovertime = "1000";
+ canSaveDynamicFields = "0";
+ };
+ new GuiCrossHairHud(ZoomReticle) {
+ damageFillColor = "0 1 0 1";
+ damageFrameColor = "1 0.6 0 1";
+ damageRect = "50 4";
+ damageOffset = "0 10";
+ bitmapAsset = "DamageModel:bino_image";
+ wrap = "0";
+ isContainer = "0";
+ Profile = "GuiModelessDialogProfile";
+ HorizSizing = "width";
+ VertSizing = "height";
+ position = "0 0";
+ Extent = "1024 768";
+ MinExtent = "8 8";
+ canSave = "1";
+ Visible = "0";
+ tooltipprofile = "GuiToolTipProfile";
+ hovertime = "1000";
+ canSaveDynamicFields = "0";
+ };
+ new GuiBitmapBorderCtrl(WeaponHUD) {
+ isContainer = "0";
+ Profile = "ChatHudBorderProfile";
+ HorizSizing = "right";
+ VertSizing = "top";
+ position = "78 693";
+ Extent = "124 72";
+ MinExtent = "8 8";
+ canSave = "1";
+ Visible = "1";
+ tooltipprofile = "GuiToolTipProfile";
+ hovertime = "1000";
+ canSaveDynamicFields = "0";
+
+ new GuiBitmapCtrl() {
+ bitmap = "UI:hudfill_image";
+ wrap = "0";
+ isContainer = "0";
+ Profile = "GuiDefaultProfile";
+ HorizSizing = "width";
+ VertSizing = "height";
+ position = "8 8";
+ Extent = "108 56";
+ MinExtent = "8 8";
+ canSave = "1";
+ Visible = "1";
+ tooltipprofile = "GuiToolTipProfile";
+ hovertime = "1000";
+ canSaveDynamicFields = "0";
+ };
+ new GuiBitmapCtrl(PreviewImage) {
+ bitmapAsset = "UI:hudfill_image";
+ wrap = "0";
+ isContainer = "0";
+ Profile = "GuiDefaultProfile";
+ HorizSizing = "width";
+ VertSizing = "height";
+ position = "8 8";
+ Extent = "108 56";
+ MinExtent = "8 2";
+ canSave = "1";
+ Visible = "1";
+ tooltipprofile = "GuiToolTipProfile";
+ hovertime = "1000";
+ canSaveDynamicFields = "0";
+ };
+ new GuiTextCtrl(AmmoAmount) {
+ maxLength = "255";
+ Margin = "0 0 0 0";
+ Padding = "0 0 0 0";
+ AnchorTop = "0";
+ AnchorBottom = "0";
+ AnchorLeft = "0";
+ AnchorRight = "0";
+ isContainer = "0";
+ Profile = "HudTextItalicProfile";
+ HorizSizing = "right";
+ VertSizing = "top";
+ position = "40 8";
+ Extent = "120 16";
+ MinExtent = "8 8";
+ canSave = "1";
+ Visible = "1";
+ tooltipprofile = "GuiToolTipProfile";
+ hovertime = "1000";
+ canSaveDynamicFields = "0";
+ };
+ };
+ new GuiHealthTextHud() {
+ fillColor = "0 0 0 0.65";
+ frameColor = "0 0 0 1";
+ textColor = "1 1 1 1";
+ warningColor = "1 0 0 1";
+ showFill = "1";
+ showFrame = "1";
+ showTrueValue = "0";
+ showEnergy = "0";
+ warnThreshold = "25";
+ pulseThreshold = "15";
+ pulseRate = "750";
+ position = "5 693";
+ extent = "72 72";
+ minExtent = "8 2";
+ horizSizing = "right";
+ vertSizing = "top";
+ profile = "GuiBigTextProfile";
+ visible = "1";
+ active = "1";
+ tooltipProfile = "GuiToolTipProfile";
+ hovertime = "1000";
+ isContainer = "0";
+ canSave = "1";
+ canSaveDynamicFields = "0";
+ };
+ new GuiControl(DamageHUD) {
+ position = "384 256";
+ extent = "256 256";
+ minExtent = "8 2";
+ horizSizing = "center";
+ vertSizing = "center";
+ profile = "GuiDefaultProfile";
+ visible = "1";
+ active = "1";
+ tooltipProfile = "GuiToolTipProfile";
+ hovertime = "1000";
+ isContainer = "1";
+ canSave = "1";
+ canSaveDynamicFields = "0";
+
+ new GuiBitmapCtrl(DamageFront) {
+ bitmapAsset = "DamageModel:damageFront_image";
+ wrap = "0";
+ position = "0 0";
+ extent = "256 32";
+ minExtent = "8 2";
+ horizSizing = "right";
+ vertSizing = "bottom";
+ profile = "GuiDefaultProfile";
+ visible = "0";
+ active = "1";
+ tooltipProfile = "GuiToolTipProfile";
+ hovertime = "1000";
+ isContainer = "0";
+ internalName = "Damage[Front]";
+ hidden = "1";
+ canSave = "1";
+ canSaveDynamicFields = "0";
+ };
+ new GuiBitmapCtrl(DamageTop) {
+ bitmapAsset = "DamageModel:damageTop_image";
+ wrap = "0";
+ position = "0 0";
+ extent = "256 32";
+ minExtent = "8 2";
+ horizSizing = "right";
+ vertSizing = "bottom";
+ profile = "GuiDefaultProfile";
+ visible = "0";
+ active = "1";
+ tooltipProfile = "GuiToolTipProfile";
+ hovertime = "1000";
+ isContainer = "0";
+ internalName = "Damage[Top]";
+ hidden = "1";
+ canSave = "1";
+ canSaveDynamicFields = "0";
+ };
+ new GuiBitmapCtrl(DamageBottom) {
+ bitmapAsset = "DamageModel:damageBottom_image";
+ wrap = "0";
+ position = "0 224";
+ extent = "256 32";
+ minExtent = "8 2";
+ horizSizing = "right";
+ vertSizing = "bottom";
+ profile = "GuiDefaultProfile";
+ visible = "0";
+ active = "1";
+ tooltipProfile = "GuiToolTipProfile";
+ hovertime = "1000";
+ isContainer = "0";
+ internalName = "Damage[Bottom]";
+ hidden = "1";
+ canSave = "1";
+ canSaveDynamicFields = "0";
+ };
+ new GuiBitmapCtrl(DamageLeft) {
+ bitmapAsset = "DamageModel:damageLeft_image";
+ wrap = "0";
+ position = "0 0";
+ extent = "32 256";
+ minExtent = "8 2";
+ horizSizing = "right";
+ vertSizing = "bottom";
+ profile = "GuiDefaultProfile";
+ visible = "0";
+ active = "1";
+ tooltipProfile = "GuiToolTipProfile";
+ hovertime = "1000";
+ isContainer = "0";
+ internalName = "Damage[Left]";
+ hidden = "1";
+ canSave = "1";
+ canSaveDynamicFields = "0";
+ };
+ new GuiBitmapCtrl(DamageRight) {
+ bitmapAsset = "DamageModel:damageRight_image";
+ wrap = "0";
+ position = "224 0";
+ extent = "32 256";
+ minExtent = "8 2";
+ horizSizing = "right";
+ vertSizing = "bottom";
+ profile = "GuiDefaultProfile";
+ visible = "0";
+ active = "1";
+ tooltipProfile = "GuiToolTipProfile";
+ hovertime = "1000";
+ isContainer = "0";
+ internalName = "Damage[Right]";
+ hidden = "1";
+ canSave = "1";
+ canSaveDynamicFields = "0";
+ };
+ };
+};
+//--- OBJECT WRITE END ---
diff --git a/Templates/BaseGame/game/data/DamageModel/images/crosshair.png b/Templates/BaseGame/game/data/DamageModel/images/crosshair.png
new file mode 100644
index 000000000..06bcf5c6c
Binary files /dev/null and b/Templates/BaseGame/game/data/DamageModel/images/crosshair.png differ
diff --git a/Templates/BaseGame/game/data/DamageModel/images/crosshair_blue.png b/Templates/BaseGame/game/data/DamageModel/images/crosshair_blue.png
new file mode 100644
index 000000000..d5b0485d7
Binary files /dev/null and b/Templates/BaseGame/game/data/DamageModel/images/crosshair_blue.png differ
diff --git a/Templates/BaseGame/game/data/DamageModel/images/crosshair_blue_image.asset.taml b/Templates/BaseGame/game/data/DamageModel/images/crosshair_blue_image.asset.taml
new file mode 100644
index 000000000..0962854e3
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/images/crosshair_blue_image.asset.taml
@@ -0,0 +1,3 @@
+
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageBottom.png b/Templates/BaseGame/game/data/DamageModel/images/damageBottom.png
new file mode 100644
index 000000000..883935ba8
Binary files /dev/null and b/Templates/BaseGame/game/data/DamageModel/images/damageBottom.png differ
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageBottom_image.asset.taml b/Templates/BaseGame/game/data/DamageModel/images/damageBottom_image.asset.taml
new file mode 100644
index 000000000..044292120
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/images/damageBottom_image.asset.taml
@@ -0,0 +1,3 @@
+
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageFront.png b/Templates/BaseGame/game/data/DamageModel/images/damageFront.png
new file mode 100644
index 000000000..ff759fc7c
Binary files /dev/null and b/Templates/BaseGame/game/data/DamageModel/images/damageFront.png differ
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageFront_image.asset.taml b/Templates/BaseGame/game/data/DamageModel/images/damageFront_image.asset.taml
new file mode 100644
index 000000000..3b6d5836f
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/images/damageFront_image.asset.taml
@@ -0,0 +1,3 @@
+
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageLeft.png b/Templates/BaseGame/game/data/DamageModel/images/damageLeft.png
new file mode 100644
index 000000000..9562db1dd
Binary files /dev/null and b/Templates/BaseGame/game/data/DamageModel/images/damageLeft.png differ
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageLeft_image.asset.taml b/Templates/BaseGame/game/data/DamageModel/images/damageLeft_image.asset.taml
new file mode 100644
index 000000000..66e90be28
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/images/damageLeft_image.asset.taml
@@ -0,0 +1,3 @@
+
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageRight.png b/Templates/BaseGame/game/data/DamageModel/images/damageRight.png
new file mode 100644
index 000000000..3a9a626cc
Binary files /dev/null and b/Templates/BaseGame/game/data/DamageModel/images/damageRight.png differ
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageRight_image.asset.taml b/Templates/BaseGame/game/data/DamageModel/images/damageRight_image.asset.taml
new file mode 100644
index 000000000..b9d0b382d
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/images/damageRight_image.asset.taml
@@ -0,0 +1,3 @@
+
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageTop.png b/Templates/BaseGame/game/data/DamageModel/images/damageTop.png
new file mode 100644
index 000000000..ff759fc7c
Binary files /dev/null and b/Templates/BaseGame/game/data/DamageModel/images/damageTop.png differ
diff --git a/Templates/BaseGame/game/data/DamageModel/images/damageTop_image.asset.taml b/Templates/BaseGame/game/data/DamageModel/images/damageTop_image.asset.taml
new file mode 100644
index 000000000..52eb13ffb
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/images/damageTop_image.asset.taml
@@ -0,0 +1,3 @@
+
diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/client/playGui.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/client/playGui.tscript
new file mode 100644
index 000000000..77f533cce
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/scripts/client/playGui.tscript
@@ -0,0 +1,53 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// PlayGui is the main TSControl through which the game is viewed.
+// The PlayGui also contains the hud controls.
+//-----------------------------------------------------------------------------
+
+function DamageModel::Playgui_onWake(%this)
+{
+ Canvas.pushDialog(DamageGuiOverlay);
+}
+
+function DamageModel::Playgui_onSleep(%this)
+{
+ Canvas.popDialog(DamageGuiOverlay);
+}
+
+function DamageModel::Playgui_clearHud( %this )
+{
+ Canvas.popDialog(DamageGuiOverlay);
+}
+
+function clientCmdSetDamageDirection(%direction)
+{
+ %ctrl = DamageHUD.findObjectByInternalName("damage[" @ %direction@"]");
+ if (isObject(%ctrl))
+ {
+ // Show the indicator, and schedule an event to hide it again
+ cancelAll(%ctrl);
+ %ctrl.setVisible(true);
+ %ctrl.schedule(1500, setVisible, false);
+ }
+}
\ No newline at end of file
diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/server/player.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/server/player.tscript
new file mode 100644
index 000000000..b874c9f5c
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/scripts/server/player.tscript
@@ -0,0 +1,42 @@
+function PlayerData::damage(%this, %obj, %sourceObject, %position, %damage, %damageType)
+{
+ if (!isObject(%obj) || %obj.getDamageState() !$= "Enabled" || !%damage)
+ return;
+
+ %rootObj = %obj;
+ if (%obj.healthFromMount)
+ %rootObj = findRootObject(%obj);
+
+ %rootObj.applyDamage(%damage);
+ %this.onDamage(%rootObj, %damage);
+
+ %this.setDamageDirection(%rootObj, %sourceObject, %position);
+
+ // Deal with client callbacks here because we don't have this
+ // information in the onDamage or onDisable methods
+ %client = %rootObj.client;
+ %sourceClient = %sourceObject ? %sourceObject.client : 0;
+
+ %location = "Body";
+ if (isObject(%client))
+ {
+ if (%rootObj.getDamageState() !$= "Enabled")
+ {
+ callGamemodeFunction("onDeath", %client, %sourceObject, %sourceClient, %damageType, %location);
+ }
+ }
+}
+
+function PlayerData::onDamage(%this, %obj, %delta)
+{
+ Parent::onDamage(%this, %obj, %delta);
+
+ // This method is invoked by the ShapeBase code whenever the
+ // object's damage level changes.
+ if (%delta > 0 && %obj.getDamageState() !$= "Destroyed")
+ {
+ // If the pain is excessive, let's hear about it.
+ if (%delta > 10)
+ %obj.playPain();
+ }
+}
\ No newline at end of file
diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/server/projectile.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/server/projectile.tscript
new file mode 100644
index 000000000..1af454f61
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/scripts/server/projectile.tscript
@@ -0,0 +1,46 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+// "Universal" script methods for projectile damage handling. You can easily
+// override these support functions with an equivalent namespace method if your
+// weapon needs a unique solution for applying damage.
+
+function ProjectileData::onCollision(%data, %proj, %col, %fade, %pos, %normal)
+{
+ //echo("ProjectileData::onCollision("@%data.getName()@", "@%proj@", "@%col.getClassName()@", "@%fade@", "@%pos@", "@%normal@")");
+
+ // Apply damage to the object all shape base objects
+ if (%data.directDamage > 0)
+ {
+ if (%col.getType() & ($TypeMasks::ShapeBaseObjectType))
+ %col.damage(%proj, %pos, %data.directDamage, %data.damageType);
+ }
+}
+
+function ProjectileData::onExplode(%data, %proj, %position, %mod)
+{
+ //echo("ProjectileData::onExplode("@%data.getName()@", "@%proj@", "@%position@", "@%mod@")");
+
+ // Damage objects within the projectiles damage radius
+ if (%data.damageRadius > 0)
+ radiusDamage(%proj, %position, %data.damageRadius, %data.radiusDamage, %data.damageType, %data.areaImpulse);
+}
diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/server/radiusDamage.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/server/radiusDamage.tscript
new file mode 100644
index 000000000..91255f25c
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/scripts/server/radiusDamage.tscript
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+// Support function which applies damage to objects within the radius of
+// some effect, usually an explosion. This function will also optionally
+// apply an impulse to each object.
+
+function radiusDamage(%sourceObject, %position, %radius, %damage, %damageType, %impulse)
+{
+ // Use the container system to iterate through all the objects
+ // within our explosion radius. We'll apply damage to all ShapeBase
+ // objects.
+ InitContainerRadiusSearch(%position, %radius, $TypeMasks::ShapeBaseObjectType | $TypeMasks::DynamicShapeObjectType);
+
+ %halfRadius = %radius / 2;
+ while ((%targetObject = containerSearchNext()) != 0)
+ {
+ // Calculate how much exposure the current object has to
+ // the explosive force. The object types listed are objects
+ // that will block an explosion. If the object is totally blocked,
+ // then no damage is applied.
+ %coverage = calcExplosionCoverage(%position, %targetObject,
+ $TypeMasks::InteriorObjectType |
+ $TypeMasks::TerrainObjectType |
+ $TypeMasks::ForceFieldObjectType |
+ $TypeMasks::StaticShapeObjectType |
+ $TypeMasks::VehicleObjectType);
+ if (%coverage == 0)
+ continue;
+
+ // Radius distance subtracts out the length of smallest bounding
+ // box axis to return an appriximate distance to the edge of the
+ // object's bounds, as opposed to the distance to it's center.
+ %dist = containerSearchCurrRadiusDist();
+
+ // Calculate a distance scale for the damage and the impulse.
+ // Full damage is applied to anything less than half the radius away,
+ // linear scale from there.
+ %distScale = (%dist < %halfRadius)? 1.0 : 1.0 - ((%dist - %halfRadius) / %halfRadius);
+ %distScale = mClamp(%distScale,0.0,1.0);
+
+ // Apply the damage
+ %targetObject.damage(%sourceObject, %position, %damage * %coverage * %distScale, %damageType);
+
+ // Apply the impulse
+ if (%impulse)
+ {
+ %impulseVec = VectorSub(%targetObject.getWorldBoxCenter(), %position);
+ %impulseVec = VectorNormalize(%impulseVec);
+ %impulseVec = VectorScale(%impulseVec, %impulse * %distScale);
+ %targetObject.applyImpulse(%position, %impulseVec);
+ }
+ }
+}
diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/server/shapeBase.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/server/shapeBase.tscript
new file mode 100644
index 000000000..2a1b01fae
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/scripts/server/shapeBase.tscript
@@ -0,0 +1,289 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+// This file contains ShapeBase methods used by all the derived classes
+$DeathDuration = 10000;
+$CorpseTimeoutValue = 20000;
+//-----------------------------------------------------------------------------
+// ShapeBase object
+//-----------------------------------------------------------------------------
+
+// A raycast helper function to keep from having to duplicate code everytime
+// that a raycast is needed.
+// %this = the object doing the cast, usually a player
+// %range = range to search
+// %mask = what to look for
+
+function ShapeBase::doRaycast(%this, %range, %mask)
+{
+ // get the eye vector and eye transform of the player
+ %eyeVec = %this.getEyeVector();
+ %eyeTrans = %this.getEyeTransform();
+
+ // extract the position of the player's camera from the eye transform (first 3 words)
+ %eyePos = getWord(%eyeTrans, 0) SPC getWord(%eyeTrans, 1) SPC getWord(%eyeTrans, 2);
+
+ // normalize the eye vector
+ %nEyeVec = VectorNormalize(%eyeVec);
+
+ // scale (lengthen) the normalized eye vector according to the search range
+ %scEyeVec = VectorScale(%nEyeVec, %range);
+
+ // add the scaled & normalized eye vector to the position of the camera
+ %eyeEnd = VectorAdd(%eyePos, %scEyeVec);
+
+ // see if anything gets hit
+ %searchResult = containerRayCast(%eyePos, %eyeEnd, %mask, %this);
+
+ return %searchResult;
+}
+
+//-----------------------------------------------------------------------------
+
+function ShapeBase::damage(%this, %sourceObject, %position, %damage, %damageType)
+{
+ // All damage applied by one object to another should go through this method.
+ // This function is provided to allow objects some chance of overriding or
+ // processing damage values and types. As opposed to having weapons call
+ // ShapeBase::applyDamage directly. Damage is redirected to the datablock,
+ // this is standard procedure for many built in callbacks.
+
+ if (isObject(%this))
+ %this.getDataBlock().damage(%this, %sourceObject, %position, %damage, %damageType);
+}
+
+//-----------------------------------------------------------------------------
+
+function ShapeBase::setDamageDt(%this, %damageAmount, %damageType)
+{
+ // This function is used to apply damage over time. The damage is applied
+ // at a fixed rate (50 ms). Damage could be applied over time using the
+ // built in ShapBase C++ repair functions (using a neg. repair), but this
+ // has the advantage of going through the normal script channels.
+
+ if (%this.getState() !$= "Dead")
+ {
+ %this.damage(0, "0 0 0", %damageAmount, %damageType);
+ %this.damageSchedule = %this.schedule(50, "setDamageDt", %damageAmount, %damageType);
+ }
+ else
+ %this.damageSchedule = "";
+}
+
+function ShapeBase::clearDamageDt(%this)
+{
+ if (%this.damageSchedule !$= "")
+ {
+ cancel(%this.damageSchedule);
+ %this.damageSchedule = "";
+ }
+}
+
+function GameBase::damage(%this, %sourceObject, %position, %damage, %damageType)
+{
+ // All damage applied by one object to another should go through this method.
+ // This function is provided to allow objects some chance of overriding or
+ // processing damage values and types. As opposed to having weapons call
+ // ShapeBase::applyDamage directly. Damage is redirected to the datablock,
+ // this is standard procedure for many built in callbacks.
+
+ %datablock = %this.getDataBlock();
+ if ( isObject( %datablock ) )
+ %datablock.damage(%this, %sourceObject, %position, %damage, %damageType);
+}
+
+//-----------------------------------------------------------------------------
+// ShapeBase datablock
+//-----------------------------------------------------------------------------
+
+function GameBaseData::damage(%this, %obj, %source, %position, %amount, %damageType)
+{
+ // Ignore damage by default. This empty method is here to
+ // avoid console warnings.
+}
+
+function ShapeBaseData::onAdd(%this, %obj)
+{
+ %obj.setDamageState("Enabled");
+}
+
+function ShapeBaseData::setDamageDirection(%this, %obj, %sourceObject, %damagePos)
+{
+ %client = (%obj.client) ? %obj.client : %obj.getControllingClient();
+ if (!%client) return;
+
+ if (%damagePos $= "" && isObject(%sourceObject))
+ {
+ if (%sourceObject.isField(initialPosition))
+ {
+ // Projectiles have this field set to the muzzle point of
+ // the firing weapon at the time the projectile was created.
+ // This gives a damage direction towards the firing object,
+ // turret, vehicle, etc. Bullets and weapon fired grenades
+ // are examples of projectiles.
+ %damagePos = %sourceObject.initialPosition;
+ }
+ else
+ {
+ // Other objects that cause damage, such as mines, use their own
+ // location as the damage position. This gives a damage direction
+ // towards the explosive origin rather than the person that lay the
+ // explosives.
+ %damagePos = %sourceObject.getPosition();
+ }
+ }
+
+ // Rotate damage vector into object space
+ %damageVec = VectorSub(%damagePos, %obj.getWorldBoxCenter());
+ %damageVec = VectorNormalize(%damageVec);
+ %damageVec = MatrixMulVector(%client.getCameraObject().getInverseTransform(), %damageVec);
+
+ // Determine largest component of damage vector to get direction
+ %vecComponents = -%damageVec.x SPC %damageVec.x SPC -%damageVec.y SPC %damageVec.y SPC -%damageVec.z SPC %damageVec.z;
+ %vecDirections = "Left" SPC "Right" SPC "Bottom" SPC "Front" SPC "Bottom" SPC "Top";
+
+ %max = -1;
+ for (%i = 0; %i < 6; %i++)
+ {
+ %value = getWord(%vecComponents, %i);
+ if (%value > %max)
+ {
+ %max = %value;
+ %damageDir = getWord(%vecDirections, %i);
+ }
+ }
+ commandToClient(%client, 'setDamageDirection', %damageDir);
+}
+
+
+function ShapeBaseData::onCollision(%this, %obj, %collObj, %vec, %len )
+{
+ if (!isObject(%obj) || %obj.getDamageState() $= "Destroyed")
+ return;
+
+ //echo(%this SPC %obj SPC %collObj SPC %vec SPC %len );
+ %dmgPos = VectorSub(%obj.getPosition(), %vec);
+ %dmgAmt = %len/%this.minImpactSpeed * %this.collisionMul;
+
+ %this.damage(%obj, %collObj, %dmgPos, %dmgAmt, "impact");
+}
+
+function ShapeBaseData::onImpact(%this, %obj, %collObj, %vec, %len )
+{
+ if (!isObject(%obj) || %obj.getDamageState() $= "Destroyed")
+ return;
+
+ //echo(%this SPC %obj SPC %collObj SPC %vec SPC %len );
+ %dmgPos = VectorSub(%obj.getPosition(), %vec);
+ %dmgAmt = %len/%this.minImpactSpeed * %this.impactMul;
+
+ %this.damage(%obj, %collObj, %dmgPos, %dmgAmt, "impact");
+}
+
+//----------------------------------------------------------------------------
+
+function ShapeBaseData::damage(%this, %obj, %sourceObject, %position, %damage, %damageType)
+{
+ if (!isObject(%obj) || %obj.getDamageState() $= "Destroyed" || !%damage)
+ return;
+
+ %rootObj = %obj;
+ if (%obj.healthFromMount)
+ %rootObj = findRootObject(%obj);
+
+ %rootObj.applyDamage(%damage);
+ %this.onDamage(%rootObj, %damage);
+
+ %this.setDamageDirection(%obj, %sourceObject, %position);
+
+ // Deal with client callbacks here because we don't have this
+ // information in the onDamage or onDisable methods
+ %client = %rootObj.client;
+ %sourceClient = %sourceObject ? %sourceObject.client : 0;
+
+ if (isObject(%client))
+ {
+ if (%obj.getDamageState() $= "Destroyed")
+ {
+ callGamemodeFunction("onDeath", %client, %sourceObject, %sourceClient, %damageType, "");
+ }
+ }
+}
+
+function ShapeBaseData::onDamage(%this, %obj, %delta)
+{
+ // This method is invoked by the ShapeBase code whenever the
+ // object's damage level changes.
+ if (%delta > 0 && %obj.getDamageState() !$= "Destroyed")
+ {
+ // Apply a damage flash
+ %obj.setDamageFlash(1);
+
+ //total raw damage allowed to be stored
+ if (%this.maxDamage> 1.0 && %obj.getDamageLevel() >= %this.maxDamage)
+ %obj.setDamageState("Destroyed");
+ //damage before we are considered destroyed (can animate via "damage" thread)
+ else if (%this.destroyedLevel> 1.0 && %obj.getDamageLevel() >= %this.destroyedLevel)
+ %obj.setDamageState("Destroyed");
+ //optional additional disabled level
+ else if (%this.disabledLevel> 1.0 && %obj.getDamageLevel() >= %this.disabledLevel)
+ %obj.setDamageState("Disabled");
+ }
+}
+
+function ShapeBaseData::onDisabled(%this, %obj, %state)
+{
+ // Release the weapon triggers
+ for (%slot = 0; %slot<4; %slot++)
+ {
+ if (%obj.getMountedImage(%slot))
+ %obj.setImageTrigger(%slot, false);
+ }
+}
+
+function ShapeBaseData::onDestroyed(%this, %obj, %state)
+{
+ // Release the weapon triggers
+ for (%slot = 0; %slot<4; %slot++)
+ {
+ if (%obj.getMountedImage(%slot))
+ %obj.setImageTrigger(%slot, false);
+ }
+
+ if (%obj.client)
+ {
+ %obj.client.player = 0;
+ %obj.client.schedule($DeathDuration, "spawnControlObject");
+ }
+ // Schedule corpse removal. Just keeping the place clean.
+ %obj.schedule($CorpseTimeoutValue - 1000, "startFade", 1000, 0, true);
+ %obj.schedule($CorpseTimeoutValue, "delete");
+ %obj.schedule($CorpseTimeoutValue,"blowUp");
+}
+
+function ShapeBaseData::onRemove(%this, %obj)
+{
+ if (isMethod(Parent, "onRemove"))
+ Parent::onRemove(%this, %obj);
+
+ deleteMountchain(%obj);
+}
\ No newline at end of file
diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/server/utility.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/server/utility.tscript
new file mode 100644
index 000000000..c74f8ee50
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/scripts/server/utility.tscript
@@ -0,0 +1,34 @@
+function findRootObject(%obj)
+{ if (!isObject(%obj)) return -1;
+ %ret = %obj;
+ if (isObject(%obj.getObjectMount()))
+ %ret = findRootObject(%obj.getObjectMount());
+ return %ret;
+}
+
+function deleteMountchain(%obj)
+{
+ if (!isObject(%obj)) return;
+ %count = %obj.getMountedObjectCount();
+ for (%i=%count; %i>=0; %i--)
+ {
+ if (isObject(%obj.getMountedObject(%i)))
+ deleteMountchain(%obj.getMountedObject(%i));
+ }
+ if (%obj.isMounted())
+ %obj.delete();
+}
+
+
+function setMountChainDamage(%obj,%damagePercent)
+{
+ if (!isObject(%obj)) return;
+ %count = %obj.getMountedObjectCount();
+ for (%i=0; %i<%count; %i++)
+ {
+ if (isObject(%obj.getMountedObject(%i)))
+ setMountChainDamage(%obj.getMountedObject(%i),%damagePercent);
+ }
+
+ %obj.setDamageLevel(%obj.getMaxDamage()*%damagePercent);
+}
\ No newline at end of file
diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/server/vehicle.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/server/vehicle.tscript
new file mode 100644
index 000000000..fec8c9ecd
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/scripts/server/vehicle.tscript
@@ -0,0 +1,134 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+// Parenting is in place for WheeledVehicleData to VehicleData. This should
+// make it easier for people to simply drop in new (generic) vehicles. All that
+// the user needs to create is a set of datablocks for the new wheeled vehicle
+// to use. This means that no (or little) scripting should be necessary.
+
+// Special, or unique vehicles however will still require some scripting. They
+// may need to override the onAdd() function in order to mount weapons,
+// differing tires/springs, etc., almost everything else is taken care of in the
+// WheeledVehicleData and VehicleData methods. This helps us by not having to
+// duplicate the same code for every new vehicle.
+
+// In theory this would work for HoverVehicles and FlyingVehicles also, but
+// hasn't been tested or fully implemented for those classes -- yet.
+
+function VehicleData::onAdd(%this, %obj)
+{
+ %obj.setRechargeRate(%this.rechargeRate);
+ %obj.setEnergyLevel(%this.MaxEnergy);
+ %obj.setRepairRate(0);
+
+ if (%obj.mountable || %obj.mountable $= "")
+ %this.isMountable(%obj, true);
+ else
+ %this.isMountable(%obj, false);
+
+ if (%this.nameTag !$= "")
+ %obj.setShapeName(%this.nameTag);
+}
+
+function VehicleData::onDestroyed(%this, %obj, %state)
+{
+ //echo("\c4VehicleData::onRemove("@ %this.getName() @", "@ %obj.getClassName() @")");
+ // if there are passengers/driver, kick them out
+ if (!%this.killPassengers)
+ {
+ for(%i = 0; %i < %obj.getMountedObjectCount(); %i++)
+ {
+ if (%obj.getMountedObject(%i))
+ {
+ %passenger = %obj.getMountedObject(%i);
+ if (%passenger.isMemberOfClass("Player"))
+ %passenger.getDataBlock().doDismount(%passenger, true);
+ }
+ }
+ }
+ Parent::onDestroyed(%this, %obj, %state);
+}
+
+// ----------------------------------------------------------------------------
+// Vehicle player mounting and dismounting
+// ----------------------------------------------------------------------------
+
+function VehicleData::isMountable(%this, %obj, %val)
+{
+ %obj.mountable = %val;
+}
+
+function VehicleData::mountPlayer(%this, %vehicle, %player)
+{
+ //echo("\c4VehicleData::mountPlayer("@ %this.getName() @", "@ %vehicle @", "@ %player.client.nameBase @")");
+
+ if (isObject(%vehicle) && %vehicle.getDamageState() !$= "Destroyed")
+ {
+ %player.startFade(1000, 0, true);
+ %this.schedule(1000, "setMountVehicle", %vehicle, %player);
+ %player.schedule(1500, "startFade", 1000, 0, false);
+ }
+}
+
+function VehicleData::setMountVehicle(%this, %vehicle, %player)
+{
+ //echo("\c4VehicleData::setMountVehicle("@ %this.getName() @", "@ %vehicle @", "@ %player.client.nameBase @")");
+
+ if (isObject(%vehicle) && %vehicle.getDamageState() !$= "Destroyed")
+ {
+ %node = %this.findEmptySeat(%vehicle, %player);
+ if (%node >= 0)
+ {
+ //echo("\c4Mount Node: "@ %node);
+ %vehicle.mountObject(%player, %node);
+ //%player.playAudio(0, MountVehicleSound);
+ %player.mVehicle = %vehicle;
+ }
+ }
+}
+
+function VehicleData::findEmptySeat(%this, %vehicle, %player)
+{
+ //echo("\c4This vehicle has "@ %this.numMountPoints @" mount points.");
+
+ for (%i = 0; %i < %this.numMountPoints; %i++)
+ {
+ %node = %vehicle.getMountNodeObject(%i);
+ if (%node == 0)
+ return %i;
+ }
+ return -1;
+}
+
+function VehicleData::switchSeats(%this, %vehicle, %player)
+{
+ for (%i = 0; %i < %this.numMountPoints; %i++)
+ {
+ %node = %vehicle.getMountNodeObject(%i);
+ if (%node == %player || %node > 0)
+ continue;
+
+ if (%node == 0)
+ return %i;
+ }
+ return -1;
+}
diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/server/weapon.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/server/weapon.tscript
new file mode 100644
index 000000000..8cb476614
--- /dev/null
+++ b/Templates/BaseGame/game/data/DamageModel/scripts/server/weapon.tscript
@@ -0,0 +1,645 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+// ----------------------------------------------------------------------------
+// This file contains Weapon and Ammo Class/"namespace" helper methods as well
+// as hooks into the inventory system. These functions are not attached to a
+// specific C++ class or datablock, but define a set of methods which are part
+// of dynamic namespaces "class". The Items include these namespaces into their
+// scope using the ItemData and ItemImageData "className" variable.
+// ----------------------------------------------------------------------------
+
+// All ShapeBase images are mounted into one of 8 slots on a shape. This weapon
+// system assumes all primary weapons are mounted into this specified slot:
+$WeaponSlot = 0;
+
+//-----------------------------------------------------------------------------
+// Weapon Class
+//-----------------------------------------------------------------------------
+
+function Weapon::onUse(%data, %obj)
+{
+ // Default behavior for all weapons is to mount it into the object's weapon
+ // slot, which is currently assumed to be slot 0
+ if (%obj.getMountedImage($WeaponSlot) != %data.image.getId())
+ {
+ serverPlay3D(WeaponUseSound, %obj.getTransform());
+
+ %obj.mountImage(%data.image, $WeaponSlot);
+ if (%obj.client)
+ {
+ if (%data.description !$= "")
+ messageClient(%obj.client, 'MsgWeaponUsed', '\c0%1 selected.', %data.description);
+ else
+ messageClient(%obj.client, 'MsgWeaponUsed', '\c0Weapon selected');
+ }
+
+ // If this is a Player class object then allow the weapon to modify allowed poses
+ if (%obj.isInNamespaceHierarchy("Player"))
+ {
+ // Start by allowing everything
+ %obj.allowAllPoses();
+
+ // Now see what isn't allowed by the weapon
+
+ %image = %data.image;
+
+ if (%image.jumpingDisallowed)
+ %obj.allowJumping(false);
+
+ if (%image.jetJumpingDisallowed)
+ %obj.allowJetJumping(false);
+
+ if (%image.sprintDisallowed)
+ %obj.allowSprinting(false);
+
+ if (%image.crouchDisallowed)
+ %obj.allowCrouching(false);
+
+ if (%image.proneDisallowed)
+ %obj.allowProne(false);
+
+ if (%image.swimmingDisallowed)
+ %obj.allowSwimming(false);
+ }
+ }
+}
+
+function Weapon::onPickup(%this, %obj, %shape, %amount)
+{
+ // The parent Item method performs the actual pickup.
+ // For player's we automatically use the weapon if the
+ // player does not already have one in hand.
+ if (Parent::onPickup(%this, %obj, %shape, %amount))
+ {
+ serverPlay3D(WeaponPickupSound, %shape.getTransform());
+ if (%shape.getClassName() $= "Player" && %shape.getMountedImage($WeaponSlot) == 0)
+ %shape.use(%this);
+ }
+}
+
+function Weapon::onInventory(%this, %obj, %amount)
+{
+ // Weapon inventory has changed, make sure there are no weapons
+ // of this type mounted if there are none left in inventory.
+ if (!%amount && (%slot = %obj.getMountSlot(%this.image)) != -1)
+ %obj.unmountImage(%slot);
+}
+
+//-----------------------------------------------------------------------------
+// Weapon Image Class
+//-----------------------------------------------------------------------------
+
+function WeaponImage::onMount(%this, %obj, %slot)
+{
+ // Images assume a false ammo state on load. We need to
+ // set the state according to the current inventory.
+ if(%this.isField("clip"))
+ {
+ // Use the clip system for this weapon. Check if the player already has
+ // some ammo in a clip.
+ if (%obj.getInventory(%this.ammo))
+ {
+ %obj.setImageAmmo(%slot, true);
+ %currentAmmo = %obj.getInventory(%this.ammo);
+ }
+ else if(%obj.getInventory(%this.clip) > 0)
+ {
+ // Fill the weapon up from the first clip
+ %obj.setInventory(%this.ammo, %this.ammo.maxInventory);
+ %obj.setImageAmmo(%slot, true);
+
+ // Add any spare ammo that may be "in the player's pocket"
+ %currentAmmo = %this.ammo.maxInventory;
+ %amountInClips += %obj.getFieldValue( "remaining" @ %this.ammo.getName());
+ }
+ else
+ {
+ %currentAmmo = 0 + %obj.getFieldValue( "remaining" @ %this.ammo.getName());
+ }
+
+ %amountInClips = %obj.getInventory(%this.clip);
+ %amountInClips *= %this.ammo.maxInventory;
+
+ if (%obj.client !$= "" && !%obj.isAiControlled)
+ %obj.client.RefreshWeaponHud(%currentAmmo, %this.item.previewImage, %this.item.reticle, %this.item.zoomReticle, %amountInClips);
+ }
+ else if(%this.ammo !$= "")
+ {
+ // Use the ammo pool system for this weapon
+ if (%obj.getInventory(%this.ammo))
+ {
+ %obj.setImageAmmo(%slot, true);
+ %currentAmmo = %obj.getInventory(%this.ammo);
+ }
+ else
+ %currentAmmo = 0;
+
+ if (%obj.client !$= "" && !%obj.isAiControlled)
+ %obj.client.RefreshWeaponHud( 1, %this.item.previewImage, %this.item.reticle, %this.item.zoomReticle, %currentAmmo );
+ }
+}
+
+function WeaponImage::onUnmount(%this, %obj, %slot)
+{
+ if (%obj.client !$= "" && !%obj.isAiControlled)
+ %obj.client.RefreshWeaponHud(0, "", "");
+}
+
+// ----------------------------------------------------------------------------
+// A "generic" weaponimage onFire handler for most weapons. Can be overridden
+// with an appropriate namespace method for any weapon that requires a custom
+// firing solution.
+
+// projectileSpread is a dynamic property declared in the weaponImage datablock
+// for those weapons in which bullet skew is desired. Must be greater than 0,
+// otherwise the projectile goes straight ahead as normal. lower values give
+// greater accuracy, higher values increase the spread pattern.
+// ----------------------------------------------------------------------------
+
+function WeaponImage::onFire(%this, %obj, %slot)
+{
+ //echo("\c4WeaponImage::onFire( "@%this.getName()@", "@%obj.client.nameBase@", "@%slot@" )");
+
+ // Make sure we have valid data
+ if (!isObject(%this.projectile))
+ {
+ error("WeaponImage::onFire() - Invalid projectile datablock");
+ return;
+ }
+
+ // Decrement inventory ammo. The image's ammo state is updated
+ // automatically by the ammo inventory hooks.
+ if ( !%this.infiniteAmmo )
+ %obj.decInventory(%this.ammo, 1);
+
+ // Get the player's velocity, we'll then add it to that of the projectile
+ %objectVelocity = %obj.getVelocity();
+
+ %numProjectiles = %this.projectileNum;
+ if (%numProjectiles == 0)
+ %numProjectiles = 1;
+
+ for (%i = 0; %i < %numProjectiles; %i++)
+ {
+ if (%this.projectileSpread)
+ {
+ // We'll need to "skew" this projectile a little bit. We start by
+ // getting the straight ahead aiming point of the gun
+ %vec = %obj.getMuzzleVector(%slot);
+
+ // Then we'll create a spread matrix by randomly generating x, y, and z
+ // points in a circle
+ %matrix = "";
+ for(%j = 0; %j < 3; %j++)
+ %matrix = %matrix @ (getRandom() - 0.5) * 2 * 3.1415926 * %this.projectileSpread @ " ";
+ %mat = MatrixCreateFromEuler(%matrix);
+
+ // Which we'll use to alter the projectile's initial vector with
+ %muzzleVector = MatrixMulVector(%mat, %vec);
+ %muzzleVector = VectorScale(VectorNormalize(%muzzleVector), %this.projectileSpread *2);
+ %muzzleVector = VectorAdd(%muzzleVector, %vec);
+ }
+ else
+ {
+ // Weapon projectile doesn't have a spread factor so we fire it using
+ // the straight ahead aiming point of the gun
+ %muzzleVector = %obj.getMuzzleVector(%slot);
+ }
+
+ // Add player's velocity
+ %muzzleVelocity = VectorAdd(
+ VectorScale(%muzzleVector, %this.projectile.muzzleVelocity),
+ VectorScale(%objectVelocity, %this.projectile.velInheritFactor));
+
+ // Create the projectile object
+ %p = new (%this.projectileType)()
+ {
+ dataBlock = %this.projectile;
+ initialVelocity = %muzzleVelocity;
+ initialPosition = %obj.getMuzzlePoint(%slot);
+ sourceObject = %obj;
+ sourceSlot = %slot;
+ client = %obj.client;
+ sourceClass = %obj.getClassName();
+ };
+ MissionCleanup.add(%p);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// A "generic" weaponimage onAltFire handler for most weapons. Can be
+// overridden with an appropriate namespace method for any weapon that requires
+// a custom firing solution.
+// ----------------------------------------------------------------------------
+
+function WeaponImage::onAltFire(%this, %obj, %slot)
+{
+ //echo("\c4WeaponImage::onAltFire("@%this.getName()@", "@%obj.client.nameBase@", "@%slot@")");
+
+ // Decrement inventory ammo. The image's ammo state is updated
+ // automatically by the ammo inventory hooks.
+ %obj.decInventory(%this.ammo, 1);
+
+ // Get the player's velocity, we'll then add it to that of the projectile
+ %objectVelocity = %obj.getVelocity();
+
+ %numProjectiles = %this.altProjectileNum;
+ if (%numProjectiles == 0)
+ %numProjectiles = 1;
+
+ for (%i = 0; %i < %numProjectiles; %i++)
+ {
+ if (%this.altProjectileSpread)
+ {
+ // We'll need to "skew" this projectile a little bit. We start by
+ // getting the straight ahead aiming point of the gun
+ %vec = %obj.getMuzzleVector(%slot);
+
+ // Then we'll create a spread matrix by randomly generating x, y, and z
+ // points in a circle
+ %matrix = "";
+ for(%i = 0; %i < 3; %i++)
+ %matrix = %matrix @ (getRandom() - 0.5) * 2 * 3.1415926 * %this.altProjectileSpread @ " ";
+ %mat = MatrixCreateFromEuler(%matrix);
+
+ // Which we'll use to alter the projectile's initial vector with
+ %muzzleVector = MatrixMulVector(%mat, %vec);
+ }
+ else
+ {
+ // Weapon projectile doesn't have a spread factor so we fire it using
+ // the straight ahead aiming point of the gun.
+ %muzzleVector = %obj.getMuzzleVector(%slot);
+ }
+
+ // Add player's velocity
+ %muzzleVelocity = VectorAdd(
+ VectorScale(%muzzleVector, %this.altProjectile.muzzleVelocity),
+ VectorScale(%objectVelocity, %this.altProjectile.velInheritFactor));
+
+ // Create the projectile object
+ %p = new (%this.projectileType)()
+ {
+ dataBlock = %this.altProjectile;
+ initialVelocity = %muzzleVelocity;
+ initialPosition = %obj.getMuzzlePoint(%slot);
+ sourceObject = %obj;
+ sourceSlot = %slot;
+ client = %obj.client;
+ };
+ MissionCleanup.add(%p);
+ }
+}
+
+// ----------------------------------------------------------------------------
+// A "generic" weaponimage onWetFire handler for most weapons. Can be
+// overridden with an appropriate namespace method for any weapon that requires
+// a custom firing solution.
+// ----------------------------------------------------------------------------
+
+function WeaponImage::onWetFire(%this, %obj, %slot)
+{
+ //echo("\c4WeaponImage::onWetFire("@%this.getName()@", "@%obj.client.nameBase@", "@%slot@")");
+
+ // Decrement inventory ammo. The image's ammo state is updated
+ // automatically by the ammo inventory hooks.
+ %obj.decInventory(%this.ammo, 1);
+
+ // Get the player's velocity, we'll then add it to that of the projectile
+ %objectVelocity = %obj.getVelocity();
+
+ %numProjectiles = %this.projectileNum;
+ if (%numProjectiles == 0)
+ %numProjectiles = 1;
+
+ for (%i = 0; %i < %numProjectiles; %i++)
+ {
+ if (%this.wetProjectileSpread)
+ {
+ // We'll need to "skew" this projectile a little bit. We start by
+ // getting the straight ahead aiming point of the gun
+ %vec = %obj.getMuzzleVector(%slot);
+
+ // Then we'll create a spread matrix by randomly generating x, y, and z
+ // points in a circle
+ %matrix = "";
+ for(%j = 0; %j < 3; %j++)
+ %matrix = %matrix @ (getRandom() - 0.5) * 2 * 3.1415926 * %this.wetProjectileSpread @ " ";
+ %mat = MatrixCreateFromEuler(%matrix);
+
+ // Which we'll use to alter the projectile's initial vector with
+ %muzzleVector = MatrixMulVector(%mat, %vec);
+ }
+ else
+ {
+ // Weapon projectile doesn't have a spread factor so we fire it using
+ // the straight ahead aiming point of the gun.
+ %muzzleVector = %obj.getMuzzleVector(%slot);
+ }
+
+ // Add player's velocity
+ %muzzleVelocity = VectorAdd(
+ VectorScale(%muzzleVector, %this.wetProjectile.muzzleVelocity),
+ VectorScale(%objectVelocity, %this.wetProjectile.velInheritFactor));
+
+ // Create the projectile object
+ %p = new (%this.projectileType)()
+ {
+ dataBlock = %this.wetProjectile;
+ initialVelocity = %muzzleVelocity;
+ initialPosition = %obj.getMuzzlePoint(%slot);
+ sourceObject = %obj;
+ sourceSlot = %slot;
+ client = %obj.client;
+ };
+ MissionCleanup.add(%p);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Clip Management
+//-----------------------------------------------------------------------------
+
+function WeaponImage::onClipEmpty(%this, %obj, %slot)
+{
+ //echo("WeaponImage::onClipEmpty: " SPC %this SPC %obj SPC %slot);
+
+ // Attempt to automatically reload. Schedule this so it occurs
+ // outside of the current state that called this method
+ %this.schedule(0, "reloadAmmoClip", %obj, %slot);
+}
+
+function WeaponImage::reloadAmmoClip(%this, %obj, %slot)
+{
+ //echo("WeaponImage::reloadAmmoClip: " SPC %this SPC %obj SPC %slot);
+
+ // Make sure we're indeed the currect image on the given slot
+ if (%this != %obj.getMountedImage(%slot))
+ return;
+
+ if ( %this.isField("clip") )
+ {
+ if (%obj.getInventory(%this.clip) > 0)
+ {
+ %obj.decInventory(%this.clip, 1);
+ %obj.setInventory(%this.ammo, %this.ammo.maxInventory);
+ %obj.setImageAmmo(%slot, true);
+ }
+ else
+ {
+ %amountInPocket = %obj.getFieldValue( "remaining" @ %this.ammo.getName());
+ if ( %amountInPocket )
+ {
+ %obj.setFieldValue( "remaining" @ %this.ammo.getName(), 0);
+ %obj.setInventory( %this.ammo, %amountInPocket );
+ %obj.setImageAmmo( %slot, true );
+ }
+ }
+
+ }
+}
+
+function WeaponImage::clearAmmoClip( %this, %obj, %slot )
+{
+ //echo("WeaponImage::clearAmmoClip: " SPC %this SPC %obj SPC %slot);
+
+ // if we're not empty put the remaining bullets from the current clip
+ // in to the player's "pocket".
+
+ if ( %this.isField( "clip" ) )
+ {
+ // Commenting out this line will use a "hard clip" system, where
+ // A player will lose any ammo currently in the gun when reloading.
+ %pocketAmount = %this.stashSpareAmmo( %obj );
+
+ if ( %obj.getInventory( %this.clip ) > 0 || %pocketAmount != 0 )
+ %obj.setImageAmmo(%slot, false);
+ }
+}
+function WeaponImage::stashSpareAmmo( %this, %player )
+{
+ // If the amount in our pocket plus what we are about to add from the clip
+ // Is over a clip, add a clip to inventory and keep the remainder
+ // on the player
+ if (%player.getInventory( %this.ammo ) < %this.ammo.maxInventory )
+ {
+ %nameOfAmmoField = "remaining" @ %this.ammo.getName();
+
+ %amountInPocket = %player.getFieldValue( %nameOfAmmoField );
+
+ %amountInGun = %player.getInventory( %this.ammo );
+
+ %combinedAmmo = %amountInGun + %amountInPocket;
+
+ // Give the player another clip if the amount in our pocket + the
+ // Amount in our gun is over the size of a clip.
+ if ( %combinedAmmo >= %this.ammo.maxInventory )
+ {
+ %player.setFieldValue( %nameOfAmmoField, %combinedAmmo - %this.ammo.maxInventory );
+ %player.incInventory( %this.clip, 1 );
+ }
+ else if ( %player.getInventory(%this.clip) > 0 )// Only put it back in our pocket if we have clips.
+ %player.setFieldValue( %nameOfAmmoField, %combinedAmmo );
+
+ return %player.getFieldValue( %nameOfAmmoField );
+
+ }
+
+ return 0;
+
+}
+
+//-----------------------------------------------------------------------------
+// Clip Class
+//-----------------------------------------------------------------------------
+
+function AmmoClip::onPickup(%this, %obj, %shape, %amount)
+{
+ // The parent Item method performs the actual pickup.
+ if (Parent::onPickup(%this, %obj, %shape, %amount))
+ serverPlay3D(AmmoPickupSound, %shape.getTransform());
+
+ // The clip inventory state has changed, we need to update the
+ // current mounted image using this clip to reflect the new state.
+ if ((%image = %shape.getMountedImage($WeaponSlot)) > 0)
+ {
+ // Check if this weapon uses the clip we just picked up and if
+ // there is no ammo.
+ if (%image.isField("clip") && %image.clip.getId() == %this.getId())
+ {
+ %outOfAmmo = !%shape.getImageAmmo($WeaponSlot);
+
+ %currentAmmo = %shape.getInventory(%image.ammo);
+
+ if ( isObject( %image.clip ) )
+ %amountInClips = %shape.getInventory(%image.clip);
+
+ %amountInClips *= %image.ammo.maxInventory;
+ %amountInClips += %obj.getFieldValue( "remaining" @ %this.ammo.getName() );
+
+ %shape.client.setAmmoAmountHud(%currentAmmo, %amountInClips );
+
+ if (%outOfAmmo)
+ {
+ %image.onClipEmpty(%shape, $WeaponSlot);
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Ammmo Class
+//-----------------------------------------------------------------------------
+
+function Ammo::onPickup(%this, %obj, %shape, %amount)
+{
+ // The parent Item method performs the actual pickup.
+ if (Parent::onPickup(%this, %obj, %shape, %amount))
+ serverPlay3D(AmmoPickupSound, %shape.getTransform());
+}
+
+function Ammo::onInventory(%this, %obj, %amount)
+{
+ // The ammo inventory state has changed, we need to update any
+ // mounted images using this ammo to reflect the new state.
+ for (%i = 0; %i < 8; %i++)
+ {
+ if ((%image = %obj.getMountedImage(%i)) > 0)
+ if (isObject(%image.ammo) && %image.ammo.getId() == %this.getId())
+ {
+ %obj.setImageAmmo(%i, %amount != 0);
+ %currentAmmo = %obj.getInventory(%this);
+
+ if (%obj.getClassname() $= "Player")
+ {
+ if ( isObject( %this.clip ) )
+ {
+ %amountInClips = %obj.getInventory(%this.clip);
+ %amountInClips *= %this.maxInventory;
+ %amountInClips += %obj.getFieldValue( "remaining" @ %this.getName() );
+ }
+ else //Is a single fire weapon, like the grenade launcher.
+ {
+ %amountInClips = %currentAmmo;
+ %currentAmmo = 1;
+ }
+
+ if (%obj.client !$= "" && !%obj.isAiControlled)
+ %obj.client.setAmmoAmountHud(%currentAmmo, %amountInClips);
+ }
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Weapon cycling
+// ----------------------------------------------------------------------------
+
+function ShapeBase::clearWeaponCycle(%this)
+{
+ %this.totalCycledWeapons = 0;
+}
+
+function ShapeBase::addToWeaponCycle(%this, %weapon)
+{
+ %this.cycleWeapon[%this.totalCycledWeapons++ - 1] = %weapon;
+}
+
+function ShapeBase::cycleWeapon(%this, %direction)
+{
+ // Can't cycle what we don't have
+ if (%this.totalCycledWeapons == 0)
+ return;
+
+ // Find out the index of the current weapon, if any (not all
+ // available weapons may be part of the cycle)
+ %currentIndex = -1;
+ if (%this.getMountedImage($WeaponSlot) != 0)
+ {
+ %curWeapon = %this.getMountedImage($WeaponSlot).item.getName();
+ for (%i=0; %i<%this.totalCycledWeapons; %i++)
+ {
+ if (%this.cycleWeapon[%i] $= %curWeapon)
+ {
+ %currentIndex = %i;
+ break;
+ }
+ }
+ }
+
+ // Get the next weapon index
+ %nextIndex = 0;
+ %dir = 1;
+ if (%currentIndex != -1)
+ {
+ if (%direction $= "prev")
+ {
+ %dir = -1;
+ %nextIndex = %currentIndex - 1;
+ if (%nextIndex < 0)
+ {
+ // Wrap around to the end
+ %nextIndex = %this.totalCycledWeapons - 1;
+ }
+ }
+ else
+ {
+ %nextIndex = %currentIndex + 1;
+ if (%nextIndex >= %this.totalCycledWeapons)
+ {
+ // Wrap back to the beginning
+ %nextIndex = 0;
+ }
+ }
+ }
+
+ // We now need to check if the next index is a valid weapon. If not,
+ // then continue to cycle to the next weapon, in the appropriate direction,
+ // until one is found. If nothing is found, then do nothing.
+ %found = false;
+ for (%i=0; %i<%this.totalCycledWeapons; %i++)
+ {
+ %weapon = %this.cycleWeapon[%nextIndex];
+ if (%weapon !$= "" && %this.hasInventory(%weapon) && %this.hasAmmo(%weapon))
+ {
+ // We've found out weapon
+ %found = true;
+ break;
+ }
+
+ %nextIndex = %nextIndex + %dir;
+ if (%nextIndex < 0)
+ {
+ %nextIndex = %this.totalCycledWeapons - 1;
+ }
+ else if (%nextIndex >= %this.totalCycledWeapons)
+ {
+ %nextIndex = 0;
+ }
+ }
+
+ if (%found)
+ {
+ %this.use(%this.cycleWeapon[%nextIndex]);
+ }
+}