convert line ends
[canaan.git] / prj / cam / src / deepc / weapon / dpcplgun.cpp
blob882be1cf993a9bb379731507116048a24f64cc7b
1 /*
2 @Copyright Looking Glass Studios, Inc.
3 1996,1997,1998,1999,2000 Unpublished Work.
4 */
6 ////////////////////////////////////////
7 // Player Gun Animation
8 //
10 #include <dpcpgapi.h>
12 #include <cfgdbg.h>
13 #include <config.h>
14 #include <schema.h>
16 #include <dpcplayr.h>
17 #include <dpcplcst.h>
18 #include <gunflash.h>
19 #include <gunprop.h>
20 #include <gunapi.h>
21 #include <dpcganpr.h>
22 #include <gunanim.h>
23 #include <dpcparam.h>
24 #include <dpctrcst.h>
26 #include <stdlib.h>
28 #include <appagg.h>
29 #include <aggmemb.h>
31 #include <matrix.h>
32 #include <matrixs.h>
33 #include <fix.h>
34 #include <mprintf.h>
35 #include <mouse.h>
37 #include <plyrmode.h> // for gun wobble
39 #include <iobjsys.h>
40 #include <traitman.h>
41 #include <mnamprop.h>
42 #include <osysbase.h>
43 #include <objpos.h>
44 #include <playrobj.h>
45 #include <rand.h>
46 #include <rendprop.h>
47 #include <portal.h>
48 #include <physapi.h>
49 #include <esnd.h>
50 #include <ctagset.h>
51 #include <scrnmode.h>
53 #include <dpcplayr.h>
54 #include <dpcplayr.h>
55 #include <dpcctrl.h>
56 #include <dpcobjst.h>
57 #include <dpcparam.h>
58 #include <gunproj.h>
59 #include <dpcprop.h>
60 #include <gunvhot.h>
62 #include <dpcgame.h>
63 #include <dpcinv.h>
64 #include <dpccam.h> // for zoom
66 // delayed burst
67 #include <autolink.h>
68 #include <dmgmodel.h>
69 #include <linkman.h>
70 #include <relation.h>
72 // network joy
73 #include <netman.h>
74 #include <ghostapi.h>
76 extern "C"
78 #include <camera.h>
81 // Include these absolutely last
82 #include <dbmem.h>
83 #include <initguid.h>
84 DEFINE_LG_GUID(IID_IPlayerGun, 0x18d);
86 // where should I really get these from?
87 #define min(a,b) (((a) < (b)) ? (a) : (b))
88 #define max(a,b) (((a) > (b)) ? (a) : (b))
90 // basic gun states
91 enum ePlayerGunState
93 kPlayerGunOff, kPlayerGunLowered, kPlayerGunRaising, kPlayerGunRaised,
94 kPlayerGunLowering, kPlayerGunPoweringUp, kPlayerGunFiring, kPlayerGunPoweringDown,
95 kPlayerGunResetting, kPlayerGunStartingLoad, kPlayerGunFinishingLoad, kPlayerGunLoading,
96 kPlayerGunDelaying
98 enum ePlayerGunSwingState
100 kPlayerGunNotSwing, kPlayerGunSwingStart, kPlayerGunSwingLeft, kPlayerGunSwingRight,
102 enum ePlayerGunFlags
104 kPlayerGunTriggerPulled = 0x0001, kPlayerGunTriggerReleased = 0x0002, kPlayerGunOverloadStarted = 0x0004
107 // is this right?
108 const float kRandMax = 32767;
110 class cPlayerGun:
111 public cCTDelegating<IPlayerGun>,
112 public cCTAggregateMemberControl<kCTU_Default>
114 public:
115 cPlayerGun(IUnknown* pOuter):
116 m_lastFireTime(0),
117 m_flags(0)
119 MI_INIT_AGGREGATION_1(pOuter, IPlayerGun, kPriorityNormal, NULL);
122 STDMETHOD(Init)(THIS)
124 m_handID = NULL;
125 m_state = kPlayerGunOff;
126 m_gunID = OBJ_NULL;
127 return S_OK;
130 STDMETHOD(GameInit)(THIS)
132 m_pObjSys = AppGetObj(IObjectSystem);
133 return S_OK;
135 STDMETHOD(GameTerm)(THIS)
137 SafeRelease(m_pObjSys);
138 m_pObjSys = NULL;
139 return S_OK;
141 STDMETHOD(Set)(THIS_ ObjID gunID);
142 STDMETHOD(SetRaised)(THIS_ ObjID gunID);
143 STDMETHOD_(ObjID, Get)(THIS)
145 return m_gunID;
147 STDMETHOD_(ObjID, GetHand)(THIS)
149 return m_handID;
151 STDMETHOD(Off)(THIS);
152 STDMETHOD(Frame)(tSimTime deltaTime);
153 STDMETHOD(Raise)(THIS);
154 STDMETHOD(Lower)(THIS);
155 STDMETHOD(Unequip) (THIS_ ObjID oldGun, ObjID newGun, BOOL spew, BOOL sednToInv);
156 STDMETHOD(ReleaseTrigger)(THIS);
157 STDMETHOD(PullTrigger)(THIS);
158 STDMETHOD(Load)(void);
159 STDMETHOD_(BOOL, IsReloading)(THIS) const
161 return((m_state == kPlayerGunStartingLoad) ||
162 (m_state == kPlayerGunLoading) ||
163 (m_state == kPlayerGunFinishingLoad));
165 STDMETHOD_(BOOL, IsTriggerPulled)(THIS) const
167 return(m_flags&kPlayerGunTriggerPulled);
169 STDMETHOD_ (BOOL, MatchesCurrentAmmo)(THIS_ ObjID clipID)
171 if (m_gunID == OBJ_NULL)
172 return FALSE;
173 return(GetProjectile(m_gunID) == GetProjectileFromClip(clipID, m_gunID));
177 private:
178 ObjID m_handID; // ID of the object we are using to represent the gun in front of the player
179 ObjID m_gunID; // Actual gun object
180 ePlayerGunState m_state;
181 int m_flags;
182 ePlayerGunSwingState m_swingState;
183 tSimTime m_swingStartTime;
184 tSimTime m_lastFireTime;
185 tSimTime m_stateChangeTime; // time at which we last changed state
186 tSimTime m_burstNum; // how many shots we've fired in current burst
187 mxs_ang m_heading;
188 mxs_ang m_pitch;
189 mxs_ang m_pitchTarget; // where we want pitch to be
190 mxs_ang m_pitchRate; // rate at which we're going there
191 float m_bob;
192 float m_bobTarget; // where we want bob to be
193 float m_kickBack; // current kick pack offset
194 IObjectSystem *m_pObjSys;
195 sGunAnimsState m_animState; // anim state
197 // cached property data
198 sGunAnim *m_pGunAnim; // anim controls from property
199 sPlayerGunDesc *m_pPlayerGunDesc;
200 eWeaponSkills m_weaponType;
201 ObjID m_unequippingGun;
202 ObjID m_nextGun;
203 BOOL m_unequippingSpew;
204 BOOL m_unequippingSendToInv;
206 // internal functions
207 void Create(void);
208 void SetState(ePlayerGunState state);
209 void UpdateState(void);
210 void SetControls(void);
211 void SetSwingState(ePlayerGunSwingState state);
212 void UpdateObjPos(void);
213 void IntegrateControls(tSimTime delta);
214 void Fire(void);
216 void Animate(void);
217 BOOL Animating(void);
218 void SetJoint(int jointNum, float jointPos);
219 void StartJointAnimation(const sGunJointAnim &jointAnim);
220 void StartAnimation(void);
221 void EndAnimation(void);
222 BOOL AnimateJoint(const sGunJointAnim &jointAnim);
223 BOOL AnimateParameter(float *pValue, sGunAnimState *pState, const sGunParamAnim &params);
225 mxs_ang CalcKickAngle(mxs_ang theta, int flags);
226 void BoundAbs(mxs_ang &pTheta, mxs_ang bound);
227 void BoundAbs(float &pTheta, float bound);
228 void ApplyKick(mxs_ang pitch, mxs_ang heading, sGunKick *pGunKick, float pct);
229 void ApplyJolt(mxs_ang pitch, mxs_ang heading, float joltBack, float pct);
230 mxs_ang CalcRandAngle(void) const;
231 BOOL TestForBreakage(void);
232 void StartFiringSequence(void);
233 void LaunchProjectile(mxs_ang pitch, mxs_ang heading);
235 void LinkProjectile(ObjID projID);
236 void ExplodeProjectile(void);
238 BOOL ShouldWobble(void);
241 //////////////////////////////////////////
242 // Decide if gun should wobble
244 BOOL cPlayerGun::ShouldWobble(void)
246 if (((g_pPlayerMode->GetMode() == kPM_Stand) || (g_pPlayerMode->GetMode() == kPM_Crouch)) && PhysObjOnGround(PlayerObject()))
248 mxs_vector velocity;
249 PhysGetVelocity(PlayerObject(), &velocity);
250 mxs_real velocity_mag = mx_mag_vec(&velocity);
252 return(velocity_mag>GetGunAnimParams()->m_wobbleSpeed);
254 return FALSE;
257 ////////////////////////////////////////
258 // Set the current gun
260 STDMETHODIMP cPlayerGun::Set(ObjID gunID)
262 Off();
264 // interpret Set to OBJ_NULL as Off
265 if (gunID == OBJ_NULL)
266 return S_OK;
268 m_gunID = gunID;
269 m_pGunAnim = NULL;
270 if (!PlayerGunDescGet(gunID, &m_pPlayerGunDesc))
272 Warning(("No player gun description for obj %d\n", gunID));
273 Off();
274 return E_FAIL;
276 #if 0
277 if (!g_pWeaponTypeProperty->Get(gunID, (int*)&m_weaponType))
279 Warning(("No weapon type for obj %d\n", gunID));
280 // default to conventional
281 m_weaponType = kWeaponConventional;
283 #endif
284 // For DC, all weapons "conventional"
285 m_weaponType = kWeaponConventional;
286 Create();
287 Raise();
288 UpdateObjPos();
289 // play reload sound (== activate sound)
290 cTagSet eventTags("Event Reload");
291 int schemaHandle = ESndPlay(&eventTags, gunID, OBJ_NULL);
293 // tell ghost we are using the gun.... (ack, why do we have to do this twice?)
294 GhostSetWeapon(PlayerObject(),gunID,FALSE);
296 return S_OK;
299 ////////////////////////////////////////
300 // Set the current gun
302 STDMETHODIMP cPlayerGun::SetRaised(ObjID gunID)
304 Off();
306 // interpret Set to OBJ_NULL as Off
307 if (gunID == OBJ_NULL)
308 return S_OK;
310 m_gunID = gunID;
311 m_pGunAnim = NULL;
312 if (!PlayerGunDescGet(gunID, &m_pPlayerGunDesc))
314 Warning(("No player gun description for obj %d\n", gunID));
315 Off();
316 return E_FAIL;
318 #if 0
319 if (!g_pWeaponTypeProperty->Get(gunID, (int*)&m_weaponType))
321 Warning(("No weapon type for obj %d\n", gunID));
322 // default to conventional
323 m_weaponType = kWeaponConventional;
325 #endif
326 // For DC, all weapons "conventional"
327 m_weaponType = kWeaponConventional;
328 Create();
329 m_pitch = GetGunAnimParams()->m_raisedPitch;
330 SetState(kPlayerGunRaised);
331 UpdateObjPos();
333 // tell ghost we are using the gun.... (ack, why do we have to do this twice?)
334 GhostSetWeapon(PlayerObject(),gunID,FALSE);
336 return S_OK;
339 ////////////////////////////////////////
340 // Raise the gun
342 void cPlayerGun::Create(void)
344 Label *pHandModelName;
345 float zeroJoints[MAX_REND_JOINTS] = {0, 0, 0, 0, 0, 0};
346 sGunAnimParams *pAnimParams = GetGunAnimParams();
348 Assert_(m_handID==OBJ_NULL);
349 Assert_(m_gunID!=OBJ_NULL);
351 // create the gun model object
352 m_handID = m_pObjSys->BeginCreate(ROOT_ARCHETYPE, kObjectConcrete);
353 m_pObjSys->NameObject(m_handID, "Player Gun");
354 if (PlayerGunDescGetHandModel(m_gunID, &pHandModelName))
355 ObjSetModelName(m_handID, (char*)pHandModelName);
356 m_pObjSys->SetObjTransience(m_handID, TRUE);
357 ObjSetHasRefs(m_handID, FALSE);
358 ObjSetJointPos(m_handID, zeroJoints);
359 m_pObjSys->EndCreate(m_handID);
361 m_flags = 0;
362 m_animState.m_flags = 0;
363 m_bobTarget = 0;
364 m_bob = 0;
365 m_heading = 0;
366 m_pitch = pAnimParams->m_loweredPitch;
367 m_pitchTarget = pAnimParams->m_raisedPitch;
368 m_pitchRate = pAnimParams->m_raisePitchRate;
369 m_kickBack = 0;
372 ////////////////////////////////////////
373 // Raise the gun
375 STDMETHODIMP cPlayerGun::Raise(void)
377 SetState(kPlayerGunRaising);
378 return S_OK;
381 ////////////////////////////////////////
383 STDMETHODIMP cPlayerGun::Lower(void)
385 SetState(kPlayerGunLowering);
386 return S_OK;
389 ////////////////////////////////////////
391 STDMETHODIMP cPlayerGun::Unequip(ObjID oldGun, ObjID newGun, BOOL spew, BOOL sendToInv)
393 // @TODO: make this work
394 m_unequippingGun = oldGun;
395 m_nextGun = newGun;
396 m_unequippingSpew = spew;
397 m_unequippingSendToInv = sendToInv; // FALSE if from drag/drop
398 Lower();
399 return S_OK;
402 ////////////////////////////////////////
404 void cPlayerGun::SetSwingState(ePlayerGunSwingState state)
406 m_swingState = state;
407 m_swingStartTime = GetSimTime();
410 ////////////////////////////////////////
412 void cPlayerGun::SetState(ePlayerGunState state)
414 switch (m_state)
416 case kPlayerGunRaised:
417 SetSwingState(kPlayerGunNotSwing);
418 break;
421 m_state = state;
422 m_stateChangeTime = GetSimTime();
424 switch (m_state)
426 case kPlayerGunLowered:
427 Off();
428 if (m_unequippingGun)
430 AutoAppIPtr(DPCPlayer);
431 pDPCPlayer->Equip(PlayerObject(), kEquipWeapon,
432 m_nextGun,m_unequippingSpew);
433 if (m_unequippingSendToInv) {
434 DPCInvAddObj(PlayerObject(), m_unequippingGun, 1);
436 m_unequippingGun = m_nextGun = OBJ_NULL;
438 break;
439 case kPlayerGunRaised:
440 m_burstNum = 0;
443 float zoom = BaseGunDescGetZoom(m_gunID);
444 ZoomTarget(zoom, 2.0);
447 break;
448 case kPlayerGunPoweringUp:
449 case kPlayerGunPoweringDown:
450 // Post firing animation
451 if (g_gunAnimPostProperty->Get(m_gunID, &m_pGunAnim))
452 StartAnimation();
453 break;
454 case kPlayerGunResetting:
455 // Post firing animation
456 if (g_gunAnimPostProperty->Get(m_gunID, &m_pGunAnim))
457 StartAnimation();
458 break;
459 case kPlayerGunStartingLoad:
460 m_pitchTarget = m_pPlayerGunDesc->m_reloadPitch;
461 m_pitchRate = m_pPlayerGunDesc->m_reloadRate;
462 break;
463 case kPlayerGunFinishingLoad:
464 m_pitchTarget = GetGunAnimParams()->m_raisedPitch;
465 m_pitchRate = m_pPlayerGunDesc->m_reloadRate;
466 break;
470 ////////////////////////////////////////
472 inline void cPlayerGun::SetJoint(int jointNum, float jointPos)
474 float *pJoints;
476 // this is stupid
477 pJoints = ObjJointPos(m_handID);
478 pJoints[jointNum] = jointPos;
481 ////////////////////////////////////////
483 void cPlayerGun::StartJointAnimation(const sGunJointAnim &jointAnim)
485 SetJoint(jointAnim.m_num, jointAnim.m_params.m_target2);
488 ////////////////////////////////////////
490 inline BOOL cPlayerGun::Animating(void)
492 return(m_animState.m_flags)&kGunAnimating;
495 ////////////////////////////////////////
497 void cPlayerGun::StartAnimation(void)
499 Assert_(m_pGunAnim != NULL);
500 m_animState.m_startTime = GetSimTime();
501 StartJointAnimation(m_pGunAnim->m_jointAnim);
502 m_animState.m_flags = kGunAnimating;
503 m_animState.m_jointAnimState.m_flags = 0;
506 void cPlayerGun::EndAnimation(void)
508 m_animState.m_flags &= ~kGunAnimating;
509 m_pGunAnim = NULL;
512 ////////////////////////////////////////
513 // Integrate a given parameter value towards a target, specified in the anim params
514 // Sets values in the gun joint state structure
515 BOOL cPlayerGun::AnimateParameter(float *pValue, sGunAnimState *pState, const sGunParamAnim &params)
517 float elapsedTime;
518 float target;
519 float rate;
520 float value;
522 // up/down anim
523 if ((pState->m_flags)&kGunAnimReversed)
525 rate = params.m_rate2/1000;
526 target = params.m_target2;
527 elapsedTime = GetSimTime()-pState->m_reverseTime;
528 value = params.m_target1+rate*elapsedTime;
530 else
532 rate = params.m_rate1/1000;
533 target = params.m_target1;
534 elapsedTime = GetSimTime()-m_animState.m_startTime;
535 value = params.m_target2+rate*elapsedTime;
537 if (((rate<0) && (value<=target)) || ((rate>0) && (value>=target)))
539 *pValue = target;
540 // check if we want to go to target 2 or not
541 if (!(params.m_flags&kGunAnimTarget2) || ((pState->m_flags)&kGunAnimReversed))
542 return TRUE;
543 else
545 pState->m_flags |= kGunAnimReversed;
546 pState->m_reverseTime = GetSimTime();
548 return FALSE;
550 *pValue = value;
551 return FALSE;
554 ////////////////////////////////////////
555 // Returns TRUE when animation completed
557 BOOL cPlayerGun::AnimateJoint(const sGunJointAnim &jointAnim)
559 float *pJoints;
561 pJoints = ObjJointPos(m_handID);
562 if (AnimateParameter(&pJoints[0], &m_animState.m_jointAnimState, m_pGunAnim->m_jointAnim.m_params))
563 return TRUE;
564 return FALSE;
567 ////////////////////////////////////////
568 // Enact animation & detect end
570 void cPlayerGun::Animate(void)
572 Assert_(m_pGunAnim != NULL);
573 // currently we only have one animation to do!
574 if (AnimateJoint(m_pGunAnim->m_jointAnim))
575 EndAnimation();
578 ////////////////////////////////////////
580 mxs_ang cPlayerGun::CalcKickAngle(mxs_ang theta, int flags)
582 AutoAppIPtr(DPCPlayer);
583 // adjust for agility
584 // MAX_STAT_VAL better be more than 1...
585 theta = mxs_ang(float(theta)*(float(MAX_STAT_VAL-pDPCPlayer->GetStat(kStatAgility))/float(MAX_STAT_VAL-1)));
586 if ((flags&kPlayerGunKickUp) && (flags&kPlayerGunKickDown))
587 if (Rand()>(kRandMax/2))
588 flags = kPlayerGunKickUp;
589 else
590 flags = kPlayerGunKickDown;
591 if (flags&kPlayerGunKickUp)
592 return theta*(0.5+float(Rand())/(2*kRandMax));
593 else if (flags&kPlayerGunKickDown)
594 return theta*(-0.5-float(Rand())/(2*kRandMax));
595 else
596 return 0;
599 ////////////////////////////////////////
601 void cPlayerGun::ApplyJolt(mxs_ang pitch, mxs_ang heading, float joltBack, float pct)
603 mxs_angvec ang;
604 mxs_vector vel;
605 mxs_vector pos;
606 mxs_matrix mat;
608 // Apply pitch jolt
609 PhysGetSubModRotation(PlayerObject(), PLAYER_HEAD, &ang);
610 ang.ty += pitch*pct;
611 // Apply heading jolt
612 ang.tz += heading*pct;
613 PosPropLock++;
614 PhysicsListenerLock = TRUE;
615 PhysSetSubModRotation(PlayerObject(), PLAYER_HEAD, &ang);
616 PhysicsListenerLock = FALSE;
617 PosPropLock--;
619 // Apply jolt back
620 PhysGetVelocity(PlayerObject(), &vel);
621 CameraGetLocation(PlayerCamera(), &pos, &ang);
622 mx_ang2mat(&mat, &ang);
623 mx_scale_addeq_vec(&vel, &mat.vec[0], joltBack);
624 PhysSetVelocity(PlayerObject(), &vel);
627 ////////////////////////////////////////
629 void cPlayerGun::BoundAbs(mxs_ang &theta, mxs_ang bound)
631 if (bound<0)
632 theta = max(theta, bound);
633 else
634 theta = min(theta, bound);
637 ////////////////////////////////////////
639 void cPlayerGun::BoundAbs(float &theta, float bound)
641 if (bound<0)
642 theta = max(theta, bound);
643 else
644 theta = min(theta, bound);
647 ////////////////////////////////////////
649 void cPlayerGun::ApplyKick(mxs_ang pitch, mxs_ang heading, sGunKick *pGunKick, float pct)
651 m_pitch += pitch*pct;
652 BoundAbs(m_pitch, pGunKick->m_kickPitchMax);
653 m_pitchRate = pGunKick->m_kickAngularReturnRate;
654 m_heading += heading*pct;
655 m_kickBack += pGunKick->m_kickBack*pct;
656 BoundAbs(m_kickBack, pGunKick->m_kickBackMax);
657 UpdateObjPos();
660 ////////////////////////////////////////
662 inline mxs_ang cPlayerGun::CalcRandAngle(void) const
664 AutoAppIPtr(DPCPlayer);
665 mxs_ang randAngle = GetSkillParams()->m_inaccuracy*(MAX_SKILL_VAL-pDPCPlayer->GetWeaponSkill(m_weaponType));
666 // adjust for traits
668 if ((m_pPlayerGunDesc->m_handedness == kPlayerGunTwoHanded) &&
669 pDPCPlayer->HasTrait(PlayerObject(), kTraitRifleman))
671 ConfigSpew("PlayerShotSpew", ("Rifleman: Adjusting inaccuracy from %x to %x\n", randAngle, mxs_ang(randAngle*kTraitRiflemanKickFrac)));
672 randAngle *= kTraitRiflemanKickFrac;
674 else if ((m_pPlayerGunDesc->m_handedness == kPlayerGunOneHanded) &&
675 pDPCPlayer->HasTrait(PlayerObject(), kTraitOneHand))
677 ConfigSpew("PlayerShotSpew", ("OneHanded: Adjusting inaccuracy from %x to %x\n", randAngle, mxs_ang(randAngle*kTraitOneHandKickFrac)));
678 randAngle *= kTraitOneHandKickFrac;
681 return randAngle;
684 /////////////////////////////////////////////////////////
685 // Check to see if gun breaks. TRUE iff gun breaks.
686 // Should only be called with state normal.
689 BOOL cPlayerGun::TestForBreakage(void)
691 sGunReliability *pGunReliability;
692 BOOL gunBreaks = FALSE;
694 if (ObjGetObjState(m_gunID) != kObjStateNormal)
695 return FALSE;
696 if (g_pGunReliabilityProperty->Get(m_gunID, &pGunReliability))
698 AutoAppIPtr(DPCPlayer);
699 float condition = GunGetCondition(m_gunID);
700 if (condition < pGunReliability->m_threshBreak)
702 float minBreak = pGunReliability->m_minBreak/100;
703 float maxBreak = pGunReliability->m_maxBreak/100;
704 int weapSkill = pDPCPlayer->GetWeaponSkill(m_weaponType);
705 float breakChance = (minBreak+(1-(condition-pGunReliability->m_threshBreak)/100)*(maxBreak-minBreak))*(1-(GetSkillParams()->m_breakModifier*weapSkill));
707 if ((float(Rand())/kRandMax)<breakChance)
709 gunBreaks = TRUE;
710 ObjSetObjState(m_gunID, kObjStateBroken);
711 #if 0
712 // Spoken "broken" alert
713 SchemaPlay((Label *)"bb04",NULL);
714 #endif
715 cTagSet tagSet("Event Break");
716 ESndPlay(&tagSet, m_gunID, OBJ_NULL);
719 float degradeRate = pGunReliability->m_degradeRate;
720 float degradeMod;
721 if (config_get_float("gun_degrade_rate", &degradeMod))
723 degradeRate *= degradeMod;
726 // maintenance affecting degredation rate removed in favor
727 // of the new "wrench" system. -- Xemu, 2/15/99
728 int maintSkill = pDPCPlayer->GetTechSkill(kTechMaintenance);
729 if (maintSkill>0)
730 degradeRate *= (1.0-(1.0/(7.0-float(maintSkill))));
732 if (condition>degradeRate)
734 ConfigSpew("DegradeSpew", ("Gun %d degrades by %g to %g\n", m_gunID, degradeRate, condition-degradeRate));
735 condition -= degradeRate;
737 else
739 ConfigSpew("DegradeSpew", ("Gun %d degrades to zero\n", m_gunID));
740 condition = 0;
743 GunSetCondition(m_gunID, condition);
745 // Implement degrading of gun's special powers
746 float degradeRate2 = pGunReliability->m_silenceDegradeRate;
747 if (degradeRate2)
749 float silenceVal = GunGetSilencing(m_gunID);
750 if (silenceVal>degradeRate2)
752 ConfigSpew("DegradeSpew", ("Gun %d alt-degrades by %g to %g\n", m_gunID, degradeRate2, silenceVal-degradeRate2));
753 silenceVal -= degradeRate2;
755 else
757 ConfigSpew("DegradeSpew", ("Gun %d alt-degrades to zero\n", m_gunID));
758 silenceVal = 0;
760 GunSetSilencing(m_gunID, silenceVal);
764 return gunBreaks;
767 ////////////////////////////////////////
769 void cPlayerGun::LaunchProjectile(mxs_ang pitch, mxs_ang heading)
771 ObjID projID;
772 ObjID launchedID;
773 sBaseGunDesc *pBaseGunDesc;
775 if (!BaseGunDescGet(m_gunID, &pBaseGunDesc))
777 Warning(("No base gun description for obj %d\n", m_gunID));
778 return;
781 // find projectile
782 // @TODO: cache proj ID
783 if ((projID = GetProjectile(m_gunID)) != OBJ_NULL)
785 // calc modifiers
786 mxs_ang randAngle = CalcRandAngle();
787 AutoAppIPtr(DPCPlayer);
789 mxs_ang cursor_pitch, cursor_heading;
790 cursor_pitch = cursor_heading = 0;
791 if (DPC_mouse)
793 float dx, dy, MCH, MCP;
794 short mpx, mpy;
795 sScrnMode scm;
797 mouse_get_xy(&mpx, &mpy);
798 ScrnModeGet(&scm);
800 dx = -1.0 * (float)(mpx - (scm.w / 2)) / (float)(scm.w / 2);
801 dy = -1.0 * (float)(mpy - (scm.h / 2)) / (float)(scm.h / 2);
803 MCH = 10000;
804 MCP = 7000;
805 config_get_float("cursor_heading",&MCH);
806 config_get_float("cursor_pitch",&MCP);
808 cursor_heading = (mxs_ang)(dx * MCH);
809 cursor_pitch = (mxs_ang)(dy * MCP);
810 //mprintf("cursor deviation = %x, %x\n",cursor_heading, cursor_pitch);
813 // stim intensity
814 float stimModifier = pBaseGunDesc->m_stimModifier;
815 // weapon skill
816 stimModifier *= pDPCPlayer->GetStimMultiplier(m_gunID);
817 // Lucky shot trait
818 if (pDPCPlayer->HasTrait(PlayerObject(), kTraitSharpshooter))
819 stimModifier *= GetTraitParams()->m_lethalMult;
821 // Launch projectiles
822 if (GunStateGetAmmo(m_gunID)>=pBaseGunDesc->m_ammoUsage)
824 sLaunchParams launchParams = g_defaultLaunchParams;
826 ConfigSpew("PlayerShotSpew", ("Launching projectiles, objID %d\n", projID));
827 // setup valid loc (fall back proj start location) as player head position...
828 launchParams.flags = kLaunchTellAI|kLaunchLocOverride|kLaunchPitchOverride;
829 // setup remainder of launch parameters
830 launchParams.speedMult = pBaseGunDesc->m_speedModifier;
831 launchParams.intensityMult = stimModifier;
832 // VHotGetOffset(&launchParams.offset, m_handID, 0);
833 // mx_addeq_vec(&launchParams.offset, &m_pPlayerGunDesc->m_fireOffset);
834 launchParams.loc = PlayerCamera()->pos;
835 launchParams.pitch = PlayerCamera()->ang.ty-pitch - cursor_pitch;
836 launchParams.heading = heading + cursor_heading;
837 launchParams.error = randAngle;
838 // actually launch the damn things
839 launchedID = GunLaunchProjectile(PlayerObject(), projID, &launchParams);
840 // dec ammo
841 GunStateSetAmmo(m_gunID, GunStateGetAmmo(m_gunID)-pBaseGunDesc->m_ammoUsage);
842 #if 0
843 // Spoken out-of-ammo alert
844 if (GunStateGetAmmo(m_gunID) == 0)
846 SchemaPlay((Label *)"bb08",NULL);
848 #endif
850 // link the projectile if we are delayed burst
851 if ((m_pPlayerGunDesc->m_flags)&kPlayerGunDelayedExplosion)
852 LinkProjectile(launchedID);
854 // play firing sound
855 cTagSet tagSet("Event Shoot");
856 // @TODO: make this the real weapon mode
857 tagSet.Add(cTag("WeaponMode", int(0)));
859 float condition = GunGetSilencing(m_gunID);
860 tagSet.Add(cTag("DegradeValue", int(condition/10)));
861 // Don't network the sound for all players
862 sSchemaCallParams params = g_sDefaultSchemaCallParams;
863 params.flags |= SCH_NO_NETWORK;
864 ObjPos *pHandPos = ObjPosGet(m_handID);
865 AutoAppIPtr(NetManager);
866 #if 0
867 if (pNetManager->Networking())
868 ESndPlayLoc(&tagSet, m_gunID, projID, &(pHandPos->loc.vec), &params);
869 else
870 ESndPlay(&tagSet, m_gunID, projID);
871 #endif
872 // DPCHACK Porges -- this sort of abuses the sound card but is
873 // here so gun fire (as opposed to bullet impact) will propagate
874 // to AIs. Probably wants another routine to prop the sound w/o
875 // playing it out loud or something.
876 ESndPlayLoc(&tagSet, m_gunID, projID, &(pHandPos->loc.vec), &params);
879 // update state
880 ++m_burstNum;
881 if ((m_flags&kPlayerGunTriggerPulled) &&
882 ((m_burstNum<pBaseGunDesc->m_burst) || (pBaseGunDesc->m_burst<0)) &&
883 (GunStateGetAmmo(m_gunID)>=pBaseGunDesc->m_ammoUsage))
884 SetState(kPlayerGunResetting);
885 else
886 SetState(kPlayerGunPoweringDown);
889 ///////////////////////////////////////////
891 void cPlayerGun::Fire(void)
893 ITraitManager *pTraitMan = AppGetObj(ITraitManager);
894 cMxsVector fireOffset;
895 sGunKick *pGunKick;
896 GunKickGetSafe(m_gunID, &pGunKick);
898 mxs_ang pitch = CalcKickAngle(pGunKick->m_kickPitch, m_pPlayerGunDesc->m_flags);
899 mxs_ang heading = CalcKickAngle(pGunKick->m_kickHeading, (m_pPlayerGunDesc->m_flags) >> 2);
900 mxs_ang joltPitch = CalcKickAngle(pGunKick->m_joltPitch, (m_pPlayerGunDesc->m_flags) >> 4);
901 mxs_ang joltHeading = CalcKickAngle(pGunKick->m_joltHeading, (m_pPlayerGunDesc->m_flags) >> 6);
903 // pre-shot kickback
904 ApplyKick(pitch, heading, pGunKick, 1);
905 // pre-shot jolt
906 ApplyJolt(-joltPitch, joltHeading, pGunKick->m_joltBack, 1);
908 // shoot
909 // note - kick disabled for actual shot effect for the moment (probably for all time)
910 // so just pass in zero for pitch & heading adj
911 LaunchProjectile(0, 0);
913 // create gun flash(es)
914 AutoAppIPtr(TraitManager);
915 CreateGunFlashes(pTraitManager->GetArchetype(m_gunID), m_handID);
918 post-shot no longer used...
919 // post-shot kickback
920 ApplyKick(pitch, heading, pGunKick, 1-pGunKick->m_preKickPct);
921 // post-shot jolt
922 ApplyJolt(-joltPitch, joltHeading, pGunKick->m_joltBack, 1-pGunKick->m_preKickPct);
925 m_lastFireTime = GetSimTime();
927 SafeRelease(pTraitMan);
928 GhostNotify(PlayerObject(),kGhostStFiring);
931 ////////////////////////////////////////
933 void cPlayerGun::UpdateState(void)
935 sGunAnimParams *pAnimParams = GetGunAnimParams();
937 switch (m_state)
939 case kPlayerGunPoweringUp:
940 SetState(kPlayerGunFiring);
941 UpdateState();
942 break;
943 case kPlayerGunFiring:
944 if (ObjGetObjState(m_gunID) == kObjStateNormal)
946 if (!TestForBreakage())
947 Fire();
949 else
950 SetState(kPlayerGunPoweringDown);
951 break;
952 case kPlayerGunPoweringDown:
954 sBaseGunDesc *pBaseGunDesc;
955 BaseGunDescGetSafe(m_gunID, &pBaseGunDesc);
956 if (GetSimTime()>=(m_lastFireTime+pBaseGunDesc->m_shotInterval))
959 cTagSet eventTags("Event Reset");
960 int schemaHandle = ESndPlay(&eventTags, m_gunID, OBJ_NULL);
962 SetState(kPlayerGunRaised);
965 break;
966 case kPlayerGunResetting:
968 // @TODO: query anims here?
969 sBaseGunDesc *pBaseGunDesc;
970 BaseGunDescGetSafe(m_gunID, &pBaseGunDesc);
971 if (GetSimTime()>=(m_lastFireTime+pBaseGunDesc->m_burstInterval))
972 SetState(kPlayerGunFiring);
974 break;
975 case kPlayerGunRaising:
976 if (m_pitch == pAnimParams->m_raisedPitch)
977 SetState(kPlayerGunRaised);
978 break;
979 case kPlayerGunLowering:
980 if (m_pitch == pAnimParams->m_loweredPitch)
981 SetState(kPlayerGunLowered);
982 break;
983 case kPlayerGunRaised:
984 if ((m_swingState == kPlayerGunNotSwing) && ShouldWobble())
985 SetSwingState(kPlayerGunSwingStart);
986 else if (!ShouldWobble())
987 SetSwingState(kPlayerGunNotSwing);
988 break;
989 case kPlayerGunFinishingLoad:
990 if (m_pitch == pAnimParams->m_raisedPitch)
991 SetState(kPlayerGunRaised);
992 break;
993 case kPlayerGunStartingLoad:
994 if (m_pitch == m_pPlayerGunDesc->m_reloadPitch)
995 SetState(kPlayerGunLoading);
996 break;
997 case kPlayerGunLoading:
999 sBaseGunDesc *pBaseGunDesc;
1000 BaseGunDescGetSafe(m_gunID, &pBaseGunDesc);
1001 if (GetSimTime()>(m_stateChangeTime+pBaseGunDesc->m_reloadTime))
1003 GunLoadSound(m_gunID);
1004 SetState(kPlayerGunFinishingLoad);
1007 break;
1011 ////////////////////////////////////////
1013 float Integrate(float val, float targ, float rate, tSimTime delta)
1015 float diff = val-targ;
1016 float move = rate*delta/1000;
1017 if (diff<0)
1019 if (diff<-1*move)
1020 return val+move;
1022 else
1024 if (diff>move)
1025 return val-move;
1027 return targ;
1030 ////////////////////////////////////////
1032 mxs_ang IntegrateAng(mxs_ang val, mxs_ang targ, mxs_ang rate, tSimTime delta)
1034 if (val != targ)
1036 mxs_ang move = fix_mul(rate, fix_from_float(float(delta)/1000.0));
1037 mxs_ang diff = targ-val;
1038 if (diff<MX_ANG_PI)
1040 if (move>diff)
1041 return targ;
1042 else
1043 return val+move;
1045 else
1047 if (move>mxs_ang(-diff))
1048 return targ;
1049 else
1050 return val-move;
1053 return targ;
1056 ////////////////////////////////////////
1058 void cPlayerGun::SetControls(void)
1060 sGunAnimParams *pAnimParams = GetGunAnimParams();
1062 switch (m_state)
1064 case kPlayerGunRaised:
1065 if (m_swingState != kPlayerGunNotSwing)
1067 int delta = GetSimTime()-m_swingStartTime;
1068 switch (m_swingState)
1070 case kPlayerGunSwingStart:
1071 if (delta>(pAnimParams->m_swingPeriod)/2)
1072 SetSwingState(kPlayerGunSwingRight);
1073 delta = GetSimTime()-m_swingStartTime;
1074 break;
1075 case kPlayerGunSwingLeft:
1076 if (delta>pAnimParams->m_swingPeriod)
1077 SetSwingState(kPlayerGunSwingRight);
1078 delta = GetSimTime()-m_swingStartTime;
1079 break;
1080 case kPlayerGunSwingRight:
1081 if (delta>pAnimParams->m_swingPeriod)
1082 SetSwingState(kPlayerGunSwingLeft);
1083 delta = GetSimTime()-m_swingStartTime;
1084 break;
1086 switch (m_swingState)
1088 case kPlayerGunSwingStart:
1089 m_heading = fix_mul(pAnimParams->m_swingAmplitude, fix_from_float(float(delta)/(float(pAnimParams->m_swingPeriod))));
1090 break;
1091 case kPlayerGunSwingLeft:
1092 m_heading = fix_mul(pAnimParams->m_swingAmplitude, fix_from_float((float(delta)-float((pAnimParams->m_swingPeriod)/2))/float(pAnimParams->m_swingPeriod)));
1093 break;
1094 case kPlayerGunSwingRight:
1095 m_heading = fix_mul(pAnimParams->m_swingAmplitude, fix_from_float((float((pAnimParams->m_swingPeriod)/2)-float(delta))/float(pAnimParams->m_swingPeriod)));
1096 break;
1098 if (m_bob==m_bobTarget)
1100 m_bobTarget = (float(Rand())/float(RAND_MAX))*pAnimParams->m_bobAmplitude;
1101 ConfigSpew("BobSpew", ("Setting bob target to %g\n", m_bobTarget));
1104 break;
1105 case kPlayerGunRaising:
1106 m_pitchTarget = pAnimParams->m_raisedPitch;
1107 m_pitchRate = pAnimParams->m_raisePitchRate;
1108 break;
1109 case kPlayerGunLowering:
1110 m_pitchTarget = pAnimParams->m_loweredPitch;
1111 m_pitchRate = pAnimParams->m_raisePitchRate;
1112 break;
1116 ////////////////////////////////////////
1118 void cPlayerGun::IntegrateControls(tSimTime delta)
1120 sGunKick *pGunKick;
1121 sGunAnimParams *pAnimParams = GetGunAnimParams();
1123 // Heading
1124 m_heading = IntegrateAng(m_heading, 0, pAnimParams->m_swingReturn, delta);
1126 // Pitch
1127 m_pitch = IntegrateAng(m_pitch, m_pitchTarget, m_pitchRate, delta);
1129 // Bob
1130 ConfigSpew("BobSpew", ("Bob target is %g, delta %d, rate %g\n", m_bob, delta, pAnimParams->m_bobRate));
1131 ConfigSpew("BobSpew", ("Setting bob from %g", m_bob));
1132 if (m_bob<m_bobTarget)
1133 m_bob = min(m_bob+pAnimParams->m_bobRate*delta/1000, m_bobTarget);
1134 else if (m_bob>m_bobTarget)
1135 m_bob = max(m_bob-pAnimParams->m_bobRate*delta/1000, m_bobTarget);
1136 ConfigSpew("BobSpew", (" to %g\n", m_bob));
1138 // Kickback
1139 GunKickGetSafe(m_gunID, &pGunKick);
1140 m_kickBack = Integrate(m_kickBack, 0, pGunKick->m_kickBackReturnRate, delta);
1143 ////////////////////////////////////////
1145 void cPlayerGun::UpdateObjPos(void)
1147 mxs_vector camPos;
1148 mxs_angvec camFacing;
1149 mxs_matrix m1, m2;
1150 mxs_vector offset, kickBackVec;
1152 if (m_handID == OBJ_NULL)
1153 return;
1155 // Get base location/facing
1156 CameraGetLocation(PlayerCamera(), &camPos, &camFacing);
1157 // turn player facing into matrix
1158 mx_ang2mat(&m1, &camFacing);
1160 // apply heading adjustment
1161 mx_rot_z_mat(&m2, &m1, m_heading);
1162 // apply pitch adjustment
1163 mx_rot_y_mat(&m1, &m2, -m_pitch);
1165 // calc facing vector
1166 mx_mat2ang(&camFacing, &m1);
1168 // calc offset
1169 mx_copy_vec(&offset, &(m_pPlayerGunDesc->m_posOffset));
1170 kickBackVec.x = m_kickBack;
1171 kickBackVec.y = 0;
1172 kickBackVec.z = 0;
1173 mx_addeq_vec(&offset, &kickBackVec);
1174 mx_mat_muleq_vec(&m1, &offset);
1175 mx_addeq_vec(&camPos, &offset);
1177 // bob
1178 camPos.z += m_bob;
1180 // update actual obj pos
1181 ObjPosUpdate(m_handID, &camPos, &camFacing);
1184 ////////////////////////////////////////
1186 STDMETHODIMP cPlayerGun::Off(void)
1188 // destroy old model
1189 if (m_handID != OBJ_NULL)
1191 m_pObjSys->Destroy(m_handID);
1192 m_handID = OBJ_NULL;
1193 SetState(kPlayerGunOff);
1195 m_gunID = OBJ_NULL;
1196 m_flags = 0;
1197 GhostSetWeapon(PlayerObject(),m_gunID,FALSE);
1198 return S_OK;
1201 ////////////////////////////////////////
1203 STDMETHODIMP cPlayerGun::Frame(tSimTime timeDelta)
1205 if (m_state != kPlayerGunOff)
1207 if (Animating())
1208 Animate();
1210 if (!Animating())
1211 UpdateState();
1213 // UpdateState can kill the model
1214 if (m_handID == OBJ_NULL)
1215 return S_OK;
1217 SetControls();
1218 IntegrateControls(timeDelta);
1219 UpdateObjPos();
1221 return S_OK;
1224 ////////////////////////////////////////
1226 inline void cPlayerGun::StartFiringSequence(void)
1228 SetState(kPlayerGunPoweringUp);
1229 UpdateState();
1232 ////////////////////////////////////////
1234 // LinkProjectile: used by delayed blast weapons.
1235 // We are over-loading the projectile link here, but what the hell.
1236 // No one else should be linking projectiles to the player... or this will cause trouble
1238 void cPlayerGun::LinkProjectile(ObjID projID)
1240 AutoAppIPtr(LinkManager);
1242 pLinkManager->Add(PlayerObject(), projID, g_pProjectileLinks->GetID());
1245 ////////////////////////////////////////
1247 // Explode all linked projectiles.
1249 void cPlayerGun::ExplodeProjectile(void)
1251 cAutoLinkQuery query(g_pProjectileLinks, PlayerObject(), LINKOBJ_WILDCARD);
1252 AutoAppIPtr(DamageModel);
1253 AutoAppIPtr(LinkManager);
1254 ObjID projID;
1256 while (!query->Done())
1258 projID = query.GetDest();
1259 pLinkManager->Remove(query->ID());
1260 pDamageModel->SlayObject(projID, PlayerObject());
1261 query->Next();
1265 ////////////////////////////////////////
1267 STDMETHODIMP cPlayerGun::PullTrigger(void)
1269 Assert_(!(m_flags&kPlayerGunTriggerPulled));
1270 m_flags |= kPlayerGunTriggerPulled;
1272 switch (m_state)
1274 case kPlayerGunRaised:
1275 if (ObjGetObjState(m_gunID) == kObjStateNormal)
1277 sBaseGunDesc *pBaseGunDesc;
1278 BaseGunDescGetSafe(m_gunID, &pBaseGunDesc);
1279 if (GunStateGetAmmo(m_gunID)>=pBaseGunDesc->m_ammoUsage)
1281 StartFiringSequence();
1282 return S_OK;
1284 else
1286 // play out of ammo sound
1287 ESndPlay(&cTagSet("Event OutofAmmo"), m_gunID, OBJ_NULL);
1290 else
1291 // @TODO: play broken noise here
1293 ESndPlay(&cTagSet("Event Broken"), m_gunID, OBJ_NULL);
1295 break;
1296 default:
1297 return E_FAIL;
1299 return S_OK;
1302 ////////////////////////////////////////
1304 STDMETHODIMP cPlayerGun::ReleaseTrigger(void)
1306 m_flags &= ~kPlayerGunTriggerPulled;
1307 if ((m_pPlayerGunDesc->m_flags)&kPlayerGunDelayedExplosion)
1309 ExplodeProjectile();
1311 return S_OK;
1314 ////////////////////////////////////////
1316 STDMETHODIMP cPlayerGun::Load(void)
1318 SetState(kPlayerGunStartingLoad);
1319 return S_OK;
1322 ////////////////////////////////////////
1324 void DPCPlayerGunCreate(void)
1326 AutoAppIPtr(Unknown);
1327 new cPlayerGun(pUnknown);