From 5cf54580e62ab112de3b80fedff924decfa89654 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sun, 6 Apr 2025 16:09:14 -0500 Subject: [PATCH] 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 --- Engine/source/T3D/shapeBase.cpp | 8 + Engine/source/T3D/shapeBase.h | 2 + Engine/source/T3D/vehicles/vehicle.cpp | 2 + .../game/data/DamageModel/DamageModel.module | 11 + .../game/data/DamageModel/DamageModel.tscript | 47 ++ .../guis/damageGuiOverlay.asset.taml | 1 + .../DamageModel/guis/damageGuiOverlay.gui | 285 ++++++++ .../data/DamageModel/images/crosshair.png | Bin 0 -> 144 bytes .../DamageModel/images/crosshair_blue.png | Bin 0 -> 134 bytes .../images/crosshair_blue_image.asset.taml | 3 + .../data/DamageModel/images/damageBottom.png | Bin 0 -> 1898 bytes .../images/damageBottom_image.asset.taml | 3 + .../data/DamageModel/images/damageFront.png | Bin 0 -> 1914 bytes .../images/damageFront_image.asset.taml | 3 + .../data/DamageModel/images/damageLeft.png | Bin 0 -> 1888 bytes .../images/damageLeft_image.asset.taml | 3 + .../data/DamageModel/images/damageRight.png | Bin 0 -> 1837 bytes .../images/damageRight_image.asset.taml | 3 + .../data/DamageModel/images/damageTop.png | Bin 0 -> 1914 bytes .../images/damageTop_image.asset.taml | 3 + .../scripts/client/playGui.tscript | 53 ++ .../DamageModel/scripts/server/player.tscript | 42 ++ .../scripts/server/projectile.tscript | 46 ++ .../scripts/server/radiusDamage.tscript | 73 ++ .../scripts/server/shapeBase.tscript | 285 ++++++++ .../scripts/server/utility.tscript | 34 + .../DamageModel/scripts/server/weapon.tscript | 645 ++++++++++++++++++ 27 files changed, 1552 insertions(+) create mode 100644 Templates/BaseGame/game/data/DamageModel/DamageModel.module create mode 100644 Templates/BaseGame/game/data/DamageModel/DamageModel.tscript create mode 100644 Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.asset.taml create mode 100644 Templates/BaseGame/game/data/DamageModel/guis/damageGuiOverlay.gui create mode 100644 Templates/BaseGame/game/data/DamageModel/images/crosshair.png create mode 100644 Templates/BaseGame/game/data/DamageModel/images/crosshair_blue.png create mode 100644 Templates/BaseGame/game/data/DamageModel/images/crosshair_blue_image.asset.taml create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageBottom.png create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageBottom_image.asset.taml create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageFront.png create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageFront_image.asset.taml create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageLeft.png create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageLeft_image.asset.taml create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageRight.png create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageRight_image.asset.taml create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageTop.png create mode 100644 Templates/BaseGame/game/data/DamageModel/images/damageTop_image.asset.taml create mode 100644 Templates/BaseGame/game/data/DamageModel/scripts/client/playGui.tscript create mode 100644 Templates/BaseGame/game/data/DamageModel/scripts/server/player.tscript create mode 100644 Templates/BaseGame/game/data/DamageModel/scripts/server/projectile.tscript create mode 100644 Templates/BaseGame/game/data/DamageModel/scripts/server/radiusDamage.tscript create mode 100644 Templates/BaseGame/game/data/DamageModel/scripts/server/shapeBase.tscript create mode 100644 Templates/BaseGame/game/data/DamageModel/scripts/server/utility.tscript create mode 100644 Templates/BaseGame/game/data/DamageModel/scripts/server/weapon.tscript diff --git a/Engine/source/T3D/shapeBase.cpp b/Engine/source/T3D/shapeBase.cpp index 968e8fb9b..7f2389bd8 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 706f54b72..594b709bd 100644 --- a/Engine/source/T3D/shapeBase.h +++ b/Engine/source/T3D/shapeBase.h @@ -579,6 +579,8 @@ public: F32 density; F32 maxEnergy; F32 maxDamage; + F32 mCollisionMul; + F32 mImpactMul; F32 repairRate; ///< Rate per tick. F32 disabledLevel; diff --git a/Engine/source/T3D/vehicles/vehicle.cpp b/Engine/source/T3D/vehicles/vehicle.cpp index cfbd9a876..a0ba43208 100644 --- a/Engine/source/T3D/vehicles/vehicle.cpp +++ b/Engine/source/T3D/vehicles/vehicle.cpp @@ -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 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..7e29f7d9e --- /dev/null +++ b/Templates/BaseGame/game/data/DamageModel/DamageModel.tscript @@ -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) +{ +} 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 0000000000000000000000000000000000000000..06bcf5c6ca7dcd8481c5660c7fc875dd303464e9 GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j+1|*LJg}1B{;D1osdy8@szBYwCIG4*or`-fKIy=Ke83- pnwCvmC@_EH`j4KABv=I!7}iW+?Rl-@XAjiP;OXk;vd$@?2>{A;EA9XQ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d5b0485d7ad5ebd7ba72f05901210a275312e90c GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^>>$j+1|*LJg}1rL4Grq=Drhv!Qa&;Uf*~0#p9*AO0_0BRSz` f&z6nC3I`Z69e7L?*M3t3>S6G7^>bP0l+XkKiajbo literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..883935ba8a837f92e61c7d0ecaf72b7c426a9fa3 GIT binary patch literal 1898 zcmV-w2bK7VP)toKuthQ$2^-KHXK-)z#J2O-rR9pT8=VDJ_-C zQo7R(e*e2t`5F2f_;XOD!q@l&2l~eWlm^wQ+yw1`_M%_ylz)Z(0{-Ye{0&rnS`O_fR7Z=}8H6+(EJ09*p%=iL z;2tofXnl?T)Sm&-8rD3z{3b_7x=1L#U?@))0ie#!SUU<;TM?8+er5aI-|4gnt5jRR}IB+!<_?nILt!~yifS$`;) zMtL*z9P|+MIWV$8uZ?Q6-~fVDu#2F3flGqDU@qk`DaeyWGvXah$^mr%j6)}$0H5hG zf}NCm!sTyLq1DnXggO<0z6w2|HvruP?S@8dpcaM};BV4e|9*7ftu*T!H(CoO7NC34 zg)2FLU^~EK1pY2Kuf+!oDNhJ*pgj75%m*XyJE2R!7nF7Rw^N=S%fk}rK@z^(g+~$i z`7{G4{98)U{Ze-k8IJ|?k@;@uchLRN)zI5Y&6u)vKE(#+LAQakD8biIje;)Wu(I)^ z=>0{e?f^XmeLWfd6RA7{8hIi`SAeVaPs^_>2>k%K=W8HnC729iw;<<=p)p6k)1fbc zc!z~>p^xyzh0l~ zTYC}Y%T^rucpHi_6eXDjHh{yRx+U~9<)bI%ZDktxQ<2vS@DaEGKH>QqsJlLq z%~xLaxOUG`6q7MBCaxn$m_|5ac<($U*AkSWe zJ^+RT&hW%1IM4?Upg4ov6#$Q+I2(bfp>7wcT&qKVWJMcSV;5SeJMXws=F+Y4v@(4^4`8*i{mCZEc*TC424gQD?S{I(e=scr!sF^AU*l&D;sA>1{R5*xH}DoB zy#m*y&Qu;SG3``iFhgSi!Wdj33k{@R6TC5|@-p}k98CuG49{I=J(*_vo1u}t*C2h4 zYHQ>GO6kmH=GizD*O`AMsP08;434-=P{EzufWgJm8UyVyNzIReyDfTA=Xkyy zYBJWnl)dh)y++Z*p9Kf9<$yC(n(fD;z-}383>^Gs8LHr>rMgeO4H!$91qy!{i9UK5 z=Mk_BybTWyfs??y8g%i!#mPZ$W~O&GxXg;6UbgBRZ>uK)l56AdVinjbZolQinPg{ zkx8^~4%DmHbl(lw85p%MdObhz+}m}%XKxwsvQ}q#x9%dCmzfsEi<*W5$vbd=+W}M! zB)TN zgPIv>gkBFu17;h|zjt=myYWI+@7a5bvfFiLSGp_Wxr=b^n?@6(Ax*&n28V&fHsIRV zfufgSW}5@Ztwt}fFz?_eXVhdI9B_IKK6e1Rq;S?f#cv&)?|`lSgdRzCP+OOETDQ9! z&gLGSc7WRk+v5Mlum|e-36I)N?s{n8>{AT*rqHrzM6NjCOv&K!8Y>5pmz^<`8px0|~VCq3QNL^a=2i1YCw{ej=UAsHLJrw2Myk3fN3hzyD z`%Z)9tlkDp)s4ZPD=mx0=8gjlGUEwmCTbvB9e8Q*`M=s!F`#Wm>T!SGJcrA5r}}-D z?-T>7KX*)z1_uZ7#DU}m=n2$}JoPu)8XO1?{09eWF#y5H;6QMofE@TgV^F~EMwfyE k!GWuBKw}WxDK7{90mfO3Ix@<;ApigX07*qoM6N<$f^#^AVE_OC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ff759fc7c3c7c704eb204a46770c8f8594dc01fa GIT binary patch literal 1914 zcmV-=2Zi{FP)Px#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy8FWQhbW?9;ba!ELWdKlNX>N2bPDNB8b~7$DE;u(kfL#Cp2GdDI zK~#8N?VX8_R7DVnhlS;E*Toe@5Kw{#hZqh|@B$J+jDm_7HKO4#azr#JLFG`COSx|Z z5d;C@k5s>J(qlG;-7GsZJMSoxU#jQrOuz1`uIlRQ>ZVeOf2HNXKb6X@(sn*N9vmnm z2UPmrP)2@4pMwL1=RjXdAI6~Y;*SoNl>-A&{Qv)^th|eU2M03Gfxj!2A)pPVZG#Sl z4il86?O;efo$&Xmo_6ZAzFPgh%Xf;p-ce8c;U_Rw# z(6!KSz)zHqLH`72(v1EW^u`^+-`q^Uzeb%WtsCs5yaBob`U;pvd5mqcZK;j6wXGDw ziB1Lw8p(k)tE%XuP@=n`_d#cX#o#kOtArPNDE9TF>{p-5@_Mio%mokd%syxw>{A$pMh=#!EB_pDrUcIF zD#{qjk3g4#jg${TRdAugseme^N_~>Q?S_61{Sf*Zc#87lU?LdNDDEWX9iQr+1ziMI z^1cnwUC`q|Z{ye0Mvali;U<4S4!#0hBm5=QBQCnHyl7QMgWyyEL0cz z9m-q4=`^FNK+f`h)(C6^t0=z=eN+X?T4!lf2kn~-UeX|eFDU;EPNlx+HQeC2UdeXw z9-rqx^%6#Fut3BUvL@JQPyHNVU^~m}20QybM0pX|NcjZR+1;7_95}|aAEB=2U0+Xx z=6aS?!Rf>NWE6H%J_7V$TvA-;`B|_TEa9`>iw2=Hyo373UDVJ*4)joZXZh(s7kCG# zt)F$5kMQ|hpa<|gc(8>{9+X$pM+1gW1NpHP9vlQNAF9g+p6yBA!EE^fTpH!t2o4Hk z<0aYP0E5-F?KlSW6G`c}K@HA1z;%cB@%K&6czOe%v~I6`2V=nt@MRs?1B@r=5nbi^ z-ee5sLMMO@4Qn!``Dt&K*CK&@~ zd*cEh0GA6#lQFo==Uw0}Pzxls#{ditw3Gu3Mgvz97^KgDRg@1wb@AOZ{{gIZb^~tZ z#mv=DX=xzgna04GUt{1lrk=qS$_7T)Lk);d2IfSBLG34R<7;(tAPr6fdd~Ll&2Is2 z&FQx8=b6Dh19-0eW9`}ie(NSb{N@t}wE7GCO|WI|m$GX^_oe zz%8fzeNuYY_0CZ5^W49U3O+W61Ignu`|)k~?J~u>XC||A4 z_+T{99W~)*KQO3w7&zOT6yq{rSoyfqnjR;axU4d!vZf{zu5x{^0ay*5<9T;sI-JvvAOmv-W+M-cvzd_^XT37x1TO-&9m6BYseA{$ z*-7y`lj48eL-8G4y7}%;cMf{P;)Q*3AT1B3r{#f%qKp$5jP?A49)a105iE>x)aN?i zBd=~VIphBV%)j3YRvKt>kOlv;%>iWONkn%C-hdjL(Q7kSU;xnsq>|3+=51`HyZ{=@f?|og@7`$n5xi0315jN7-b$nJs|R!_d#Q%5;1q*Lg=au?u*w1C zZBo}HWWE@8g_Fg22eN%VM*Z20?ND67X1oHEOv^?~>HBWWw4V;0l0h996 zi)iV896*3mfhYAYBE(bRP2lxZaY8S~$!$5n`0)QZ@Z{kFP&?Y~El#F=$Nd9&7&i(E zC0_{4*>}&^2)_q;yiF!%{X7V<#cgDq7#o-dz634_j2kTnkpkXg+4T8(1m#V;E1;*7 z8|O)}hr@bjS!&sA8)L>i5icn979uYpxd1hXU|cp^T=G^SAn(HWf2{GM2-j~0aXiKw zfxeQDje&Mwj}dssqsd|WQh?Aa+NTb*&aD0JU!k^ 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 0000000000000000000000000000000000000000..9562db1dd826b1f10491bc8e01157ca0111dad5d GIT binary patch literal 1888 zcma)-`#;kQ1INF!*`19@2s5SQq&0^kBA3nFbI`?oBvQi3%4H72WW{kg$0L_K)nTsZ z(ZtD}VJ@8#QFy{k4aapDW|1DR=MQ*Z&rhHC=f}_Im-ho#7m6HG4G92%oV^{%?I3Xg z00Q990~&rtyLS+f5G!XZ0I1DE?FGmlbX6MpD$PA2m=+yy=Q`jT5E@P+xR5}A&+w!Ry}j0N&iBC{=jfBK`RQVGe3LF#1L z8a&lUzCiZ#U%-|B!tdoQij5jxsUPa#8wA+7%*~FRi%KwzUVSqe>hAvaibjRJ2doPG zA!*U_?o(6O|6PZYItmNtluOd$H>RDPcOSwT@kZnK8SOL>wt=ygJqML=XFMkZ4Dp zv@rdhVLeKVa)zVMBY0N>uEVw(T-PZu0$~RsY29B4ADwo9blj^YcV`4w4w^`(du5qC3esaQ<;TPmWGtG2MFYZmu6HOG5J>!EGN zH8#!Y$Dn!cuSOd?tN+Sgg-AMKRF6WSL_J1zSS-Nh|7NS%riEFU=d!nbKfY&;p%<#N z{h%<1%>>0#f1rG7qOvVwXZs3{j|mP++$}E_2{61ch|MtM$P8{7qJrNzWp}V)xK^M; z$Gj{p^Tf(059KE!9}A=7-m9yM+f+akQghP}3Gfg`w}SL?&~Ggb-iW5QX_RgdDt{as zbJkRI7(xoQ5T#OeoblL1f$3J@hoZwNBK*q9&{0^dp}B~~S*m)}-lJyo ze1H}{q6QK}{Y{9wCul5hb ze?HW>@6X&r-u7+=V;hnv0vckYGbdrHT;gAjV$k3+{ETJeM8NvIQq1B^==`nYN7uOT zibZ#`>5B@6T)n^fgU3xotr6UtEc1JGG$D^O-JK@Q{$@{aWCCLh0wf>2>YxHt8VJk< zn$)Ofz|LH{nb}9a;oJtWU`jKEORBxhHJCm(SWpzqH+@^@J|G<4iAm*enI@x z2Nt;4B%fPw{~HBFu!Rld18fMyHH2o}QHhcQR%#iY!#viTteSI)SQgYIYgcQaAyaGo zNtZpCyMtau{l%WGHKI* z{AvERn=bTYzLvb?pK>aM5ia<{8Ae!;2+>>C0#x?VdXv@b>qcZPm#x@6p_(2w`r6zZ zHw27eNOd|u$|fOudKcGHZt0&uSW4R*Rguv z49qEE0S*XLOe4iE<5@g&W-nNq&7$v`hr`IDnuUIUG^KpoYe5)A| z_f_ClOpqD27bxOex#1U8fZ9JxdQ+C*SG}#d#Qrf}moTar|KwRQe>!Oo(tj#2J%*~C za!KqLG(TNx6ZSB;itq0T+3ylyP-Pg`; zcxSr&CvQOr*dxw~LHmJ|Fa6>66$B{Q{_<#SWz)nTl}wf=wy47EQU{00D924ND#}?v zVxNB>XzCRpk>T)`0D{xWnJ%OLfn4-}RAn;TYi?n5U{FBSe_>f*@C_0_{<4)9!WrsG z`=thBvGV%oIZ)-5)^O^@-dvwwS7msYeZmch=ZkQa`^j0ePq7 zS|e4JGoOBWW_CX*{Rk=#Xw!Q46Q(2mv0l9n^@7mizL(q`acX4OKQ`1j$HIaifEGeq zo^Y!sw{X#3wsXQ3rh9gg)h_9~XfxQe>q!I;!dM=wh&&snCuVh9_a&@ z@-Ilwzv&}R*L#k>!F?`rbqvYHZ8bfKlSol5+R>n@d^Gt(#5N(VgJ!S$t?Focd% zOUqeH^CF8g3W95~#zuLtorVWq?!GLdIVRBf<>i&RY3DT72iBTynb}lQzH%n4x`Cb! zztOKFzBQ&!&j3pw1@!1Y=ly?*`LBTXUneC37*3AGzuy&BA1D&AC%ceptf|TW0Xxle Ap8x;= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3a9a626cc82f5468a7a2f4d213fdd7c906a64e38 GIT binary patch literal 1837 zcmajg`#;l*9|!P{VZ$0*)@n>vXEVpTn^JD6Y1;w7o4A-ulM8i!|TWQ)j)qwn9^}2003ZkFI>=} zNC1!l6u^gRvYtviM2_g<=K=t2*P#0`@`tw;)%_whI3B}^)k4q`wt282i{y#oGBHrGHDGV5NC@dB@yZGt=V*_@7_T-~v@Kv~{? zFr|vGJs=1~z*X|8*0#^Ze3C}75-HMLP81Rr@|VX5@32w^-VOLuy)H{Aw=n8(&|wM;DKK+wQt|)iJ{}o%W<~e6YhjMCKAolLYB=3pAEOA#Y?ZtQ z6U^p&71w=b$|I^NmDp(p73tSNw@8qp5)!6&E`n0;tp&Y;vMyC`A0ozn|9g7%qAfSX=PvI93|RO`op7$U5y}|5*H;fih$t99DyTU86j*6KRN6_ z>O+tP>5Wnb$aFbLTqrBmcsRTER@J^*vzEqIvMaTu93~1R*Dx4?h z8x_Kmj*Nu#0Uz}{dIpxuk~IsN`jPtm_sf!7Cnb2Ajj6Te*7jb1obWBs1)Yu=@q2S3 zUZHjDp)O)34|-utsq3idO8@fB8xQ>QdET|bXX5Q=VbO*n^_z;Z4Y6}iId6X^&7p)W z1mu@uscFlt$ssM!eUp9VT@$l25!f~GkVMza%F*<32PY%FVE+Ok)qNc~j%E+NiOHOLQ4qn(kS%K|Hif%;NHm6?UK92R*r}ULa~-VzkZl`A=oC zoVi}|yO$`+Y2wDG)hozcMS4%{X)lxWVfqr>TMJet@>5qZe3);SIA``fwgyr*>gC#N zEOz;A+Q*DE7==K^m`5r2?eQjucyrU=%Trl&D2j~NSn=lm^I-F)G0-hb&>SV&GxOpZLP79 zx7YyVJ|=wwg(Kj#4D71=KVGor_s}wJR!H_Zjw{FQ3VK7{2!<8!ai^;cRoa`!io8 zaJdfD1-Hx7TQRi(XFnJsEmyl{cF24f+Zjle_cYWgh;qHcv5}0I{GMYle4r1q(N4TG zI=;>DBsETb&6&@QCB3fY+if!LwVHleL!Y5f3a#g4G;KDKmJ3jTdPNb@!(t=ao za(O#+d}hzX@}McX4j6g&?bt;r_Vi%dZ5?CCSl-{VL4+L0+T6bG 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 0000000000000000000000000000000000000000..ff759fc7c3c7c704eb204a46770c8f8594dc01fa GIT binary patch literal 1914 zcmV-=2Zi{FP)Px#1ZP1_K>z@;j|==^1poj5AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy8FWQhbW?9;ba!ELWdKlNX>N2bPDNB8b~7$DE;u(kfL#Cp2GdDI zK~#8N?VX8_R7DVnhlS;E*Toe@5Kw{#hZqh|@B$J+jDm_7HKO4#azr#JLFG`COSx|Z z5d;C@k5s>J(qlG;-7GsZJMSoxU#jQrOuz1`uIlRQ>ZVeOf2HNXKb6X@(sn*N9vmnm z2UPmrP)2@4pMwL1=RjXdAI6~Y;*SoNl>-A&{Qv)^th|eU2M03Gfxj!2A)pPVZG#Sl z4il86?O;efo$&Xmo_6ZAzFPgh%Xf;p-ce8c;U_Rw# z(6!KSz)zHqLH`72(v1EW^u`^+-`q^Uzeb%WtsCs5yaBob`U;pvd5mqcZK;j6wXGDw ziB1Lw8p(k)tE%XuP@=n`_d#cX#o#kOtArPNDE9TF>{p-5@_Mio%mokd%syxw>{A$pMh=#!EB_pDrUcIF zD#{qjk3g4#jg${TRdAugseme^N_~>Q?S_61{Sf*Zc#87lU?LdNDDEWX9iQr+1ziMI z^1cnwUC`q|Z{ye0Mvali;U<4S4!#0hBm5=QBQCnHyl7QMgWyyEL0cz z9m-q4=`^FNK+f`h)(C6^t0=z=eN+X?T4!lf2kn~-UeX|eFDU;EPNlx+HQeC2UdeXw z9-rqx^%6#Fut3BUvL@JQPyHNVU^~m}20QybM0pX|NcjZR+1;7_95}|aAEB=2U0+Xx z=6aS?!Rf>NWE6H%J_7V$TvA-;`B|_TEa9`>iw2=Hyo373UDVJ*4)joZXZh(s7kCG# zt)F$5kMQ|hpa<|gc(8>{9+X$pM+1gW1NpHP9vlQNAF9g+p6yBA!EE^fTpH!t2o4Hk z<0aYP0E5-F?KlSW6G`c}K@HA1z;%cB@%K&6czOe%v~I6`2V=nt@MRs?1B@r=5nbi^ z-ee5sLMMO@4Qn!``Dt&K*CK&@~ zd*cEh0GA6#lQFo==Uw0}Pzxls#{ditw3Gu3Mgvz97^KgDRg@1wb@AOZ{{gIZb^~tZ z#mv=DX=xzgna04GUt{1lrk=qS$_7T)Lk);d2IfSBLG34R<7;(tAPr6fdd~Ll&2Is2 z&FQx8=b6Dh19-0eW9`}ie(NSb{N@t}wE7GCO|WI|m$GX^_oe zz%8fzeNuYY_0CZ5^W49U3O+W61Ignu`|)k~?J~u>XC||A4 z_+T{99W~)*KQO3w7&zOT6yq{rSoyfqnjR;axU4d!vZf{zu5x{^0ay*5<9T;sI-JvvAOmv-W+M-cvzd_^XT37x1TO-&9m6BYseA{$ z*-7y`lj48eL-8G4y7}%;cMf{P;)Q*3AT1B3r{#f%qKp$5jP?A49)a105iE>x)aN?i zBd=~VIphBV%)j3YRvKt>kOlv;%>iWONkn%C-hdjL(Q7kSU;xnsq>|3+=51`HyZ{=@f?|og@7`$n5xi0315jN7-b$nJs|R!_d#Q%5;1q*Lg=au?u*w1C zZBo}HWWE@8g_Fg22eN%VM*Z20?ND67X1oHEOv^?~>HBWWw4V;0l0h996 zi)iV896*3mfhYAYBE(bRP2lxZaY8S~$!$5n`0)QZ@Z{kFP&?Y~El#F=$Nd9&7&i(E zC0_{4*>}&^2)_q;yiF!%{X7V<#cgDq7#o-dz634_j2kTnkpkXg+4T8(1m#V;E1;*7 z8|O)}hr@bjS!&sA8)L>i5icn979uYpxd1hXU|cp^T=G^SAn(HWf2{GM2-j~0aXiKw zfxeQDje&Mwj}dssqsd|WQh?Aa+NTb*&aD0JU!k^ 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..817ae85c2 --- /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(%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(); + } +} \ 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..b1aa67448 --- /dev/null +++ b/Templates/BaseGame/game/data/DamageModel/scripts/server/shapeBase.tscript @@ -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); +} \ 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/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]); + } +}