Generic Damagemodel
included in root/data/ as a module so theres a generic folks can riff off of additionally, includes a physcs based damage option inspector exposed augment for velicity based collisions, as well as utilities for applying damage to the object a given thing is mounted to further. also fixes a lack of vehicles being able to use thier mvTriggerCount4 and 5 for the additional 2 mountpoints allowed
|
|
@ -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." );
|
||||
|
|
|
|||
|
|
@ -579,6 +579,8 @@ public:
|
|||
F32 density;
|
||||
F32 maxEnergy;
|
||||
F32 maxDamage;
|
||||
F32 mCollisionMul;
|
||||
F32 mImpactMul;
|
||||
F32 repairRate; ///< Rate per tick.
|
||||
|
||||
F32 disabledLevel;
|
||||
|
|
|
|||
|
|
@ -726,6 +726,8 @@ void Vehicle::updateMove(const Move* move)
|
|||
if (mDamageState == Enabled) {
|
||||
setImageTriggerState(0,move->trigger[0]);
|
||||
setImageTriggerState(1,move->trigger[1]);
|
||||
setImageTriggerState(2, move->trigger[4]);
|
||||
setImageTriggerState(3, move->trigger[5]);
|
||||
}
|
||||
|
||||
// Throttle
|
||||
|
|
|
|||
11
Templates/BaseGame/game/data/DamageModel/DamageModel.module
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<ModuleDefinition
|
||||
ModuleId="DamageModel"
|
||||
VersionId="1"
|
||||
Group="Game"
|
||||
scriptFile="DamageModel.tscript"
|
||||
CreateFunction="onCreate"
|
||||
DestroyFunction="onDestroy">
|
||||
<DeclaredAssets
|
||||
Extension="asset.taml"
|
||||
Recurse="true"/>
|
||||
</ModuleDefinition>
|
||||
47
Templates/BaseGame/game/data/DamageModel/DamageModel.tscript
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
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/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)
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
<GUIAsset canSave="true" canSaveDynamicFields="true" AssetName="damageGuiOverlay" scriptFile="@assetFile=damageGuiOverlay.gui" GUIFile="@assetFile=damageGuiOverlay.gui" VersionId="1"/>
|
||||
|
|
@ -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 ---
|
||||
BIN
Templates/BaseGame/game/data/DamageModel/images/crosshair.png
Normal file
|
After Width: | Height: | Size: 144 B |
|
After Width: | Height: | Size: 134 B |
|
|
@ -0,0 +1,3 @@
|
|||
<ImageAsset
|
||||
AssetName="crosshair_blue_image"
|
||||
imageFile="@assetFile=crosshair_blue.png"/>
|
||||
BIN
Templates/BaseGame/game/data/DamageModel/images/damageBottom.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<ImageAsset
|
||||
AssetName="damageBottom_image"
|
||||
imageFile="@assetFile=damageBottom.png"/>
|
||||
BIN
Templates/BaseGame/game/data/DamageModel/images/damageFront.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<ImageAsset
|
||||
AssetName="damageFront_image"
|
||||
imageFile="@assetFile=damageFront.png"/>
|
||||
BIN
Templates/BaseGame/game/data/DamageModel/images/damageLeft.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<ImageAsset
|
||||
AssetName="damageLeft_image"
|
||||
imageFile="@assetFile=damageLeft.png"/>
|
||||
BIN
Templates/BaseGame/game/data/DamageModel/images/damageRight.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<ImageAsset
|
||||
AssetName="damageRight_image"
|
||||
imageFile="@assetFile=damageRight.png"/>
|
||||
BIN
Templates/BaseGame/game/data/DamageModel/images/damageTop.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<ImageAsset
|
||||
AssetName="damageTop_image"
|
||||
imageFile="@assetFile=damageTop.png"/>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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(%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;
|
||||
|
||||
%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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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)
|
||||
{
|
||||
if (!%obj.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(%obj.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(%obj.client, 'setDamageDirection', %damageDir);
|
||||
}
|
||||
|
||||
|
||||
function ShapeBaseData::onCollision(%this, %obj, %collObj, %vec, %len )
|
||||
{
|
||||
if ((!isObject(%obj) || %obj.getDamageState() !$= "Enabled"))
|
||||
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() !$= "Enabled"))
|
||||
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() !$= "Enabled" || !%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 (%rootObj.getDamageState() !$= "Enabled")
|
||||
{
|
||||
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() $= "Enabled")
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
Parent::onRemove(%this, %obj);
|
||||
deleteMountchain(%obj);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||