1 /*
2 #include "actor.h"
3 #include "p_enemy.h"
4 #include "a_action.h"
5 #include "m_random.h"
6 #include "p_local.h"
7 #include "a_doomglobal.h"
8 #include "s_sound.h"
9 #include "r_data/r_translate.h"
10 #include "thingdef/thingdef.h"
11 #include "g_level.h"
12 */
13
14 #define MARINE_PAIN_CHANCE 160
15
16 static FRandom pr_m_refire ("SMarineRefire");
17 static FRandom pr_m_punch ("SMarinePunch");
18 static FRandom pr_m_gunshot ("SMarineGunshot");
19 static FRandom pr_m_saw ("SMarineSaw");
20 static FRandom pr_m_fireshotgun2 ("SMarineFireSSG");
21
IMPLEMENT_CLASS(AScriptedMarine)22 IMPLEMENT_CLASS (AScriptedMarine)
23
24 void AScriptedMarine::Serialize (FArchive &arc)
25 {
26 Super::Serialize (arc);
27
28 if (arc.IsStoring ())
29 {
30 arc.WriteSprite (SpriteOverride);
31 }
32 else
33 {
34 SpriteOverride = arc.ReadSprite ();
35 }
36 arc << CurrentWeapon;
37 }
38
Activate(AActor * activator)39 void AScriptedMarine::Activate (AActor *activator)
40 {
41 if (flags2 & MF2_DORMANT)
42 {
43 flags2 &= ~MF2_DORMANT;
44 tics = 1;
45 }
46 }
47
Deactivate(AActor * activator)48 void AScriptedMarine::Deactivate (AActor *activator)
49 {
50 if (!(flags2 & MF2_DORMANT))
51 {
52 flags2 |= MF2_DORMANT;
53 tics = -1;
54 }
55 }
56
GetWeaponStates(int weap,FState * & melee,FState * & missile)57 bool AScriptedMarine::GetWeaponStates(int weap, FState *&melee, FState *&missile)
58 {
59 static ENamedName WeaponNames[] =
60 {
61 NAME_None,
62 NAME_Fist,
63 NAME_Berserk,
64 NAME_Chainsaw,
65 NAME_Pistol,
66 NAME_Shotgun,
67 NAME_SSG,
68 NAME_Chaingun,
69 NAME_Rocket,
70 NAME_Plasma,
71 NAME_Railgun,
72 NAME_BFG
73 };
74
75 if (weap < WEAPON_Dummy || weap > WEAPON_BFG) weap = WEAPON_Dummy;
76
77 melee = FindState(NAME_Melee, WeaponNames[weap], true);
78 missile = FindState(NAME_Missile, WeaponNames[weap], true);
79
80 return melee != NULL || missile != NULL;
81 }
82
BeginPlay()83 void AScriptedMarine::BeginPlay ()
84 {
85 Super::BeginPlay ();
86
87 // Set the current weapon
88 for(int i=WEAPON_Dummy; i<=WEAPON_BFG; i++)
89 {
90 FState *melee, *missile;
91 if (GetWeaponStates(i, melee, missile))
92 {
93 if (melee == MeleeState && missile == MissileState)
94 {
95 CurrentWeapon = i;
96 }
97 }
98 }
99 }
100
Tick()101 void AScriptedMarine::Tick ()
102 {
103 Super::Tick ();
104
105 // Override the standard sprite, if desired
106 if (SpriteOverride != 0 && sprite == SpawnState->sprite)
107 {
108 sprite = SpriteOverride;
109 }
110
111 if (special1 != 0)
112 {
113 if (CurrentWeapon == WEAPON_SuperShotgun)
114 { // Play SSG reload sounds
115 int ticks = level.maptime - special1;
116 if (ticks < 47)
117 {
118 switch (ticks)
119 {
120 case 14:
121 S_Sound (this, CHAN_WEAPON, "weapons/sshoto", 1, ATTN_NORM);
122 break;
123 case 28:
124 S_Sound (this, CHAN_WEAPON, "weapons/sshotl", 1, ATTN_NORM);
125 break;
126 case 41:
127 S_Sound (this, CHAN_WEAPON, "weapons/sshotc", 1, ATTN_NORM);
128 break;
129 }
130 }
131 else
132 {
133 special1 = 0;
134 }
135 }
136 else
137 { // Wait for a long refire time
138 if (level.maptime >= special1)
139 {
140 special1 = 0;
141 }
142 else
143 {
144 flags |= MF_JUSTATTACKED;
145 }
146 }
147 }
148 }
149
150 //============================================================================
151 //
152 // A_M_Refire
153 //
154 //============================================================================
155
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_M_Refire)156 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Refire)
157 {
158 ACTION_PARAM_START(1);
159 ACTION_PARAM_BOOL(ignoremissile, 0);
160
161 if (self->target == NULL || self->target->health <= 0)
162 {
163 if (self->MissileState && pr_m_refire() < 160)
164 { // Look for a new target most of the time
165 if (P_LookForPlayers (self, true, NULL) && P_CheckMissileRange (self))
166 { // Found somebody new and in range, so don't stop shooting
167 return;
168 }
169 }
170 self->SetState (self->state + 1);
171 return;
172 }
173 if (((ignoremissile || self->MissileState == NULL) && !self->CheckMeleeRange ()) ||
174 !P_CheckSight (self, self->target) ||
175 pr_m_refire() < 4) // Small chance of stopping even when target not dead
176 {
177 self->SetState (self->state + 1);
178 }
179 }
180
181 //============================================================================
182 //
183 // A_M_SawRefire
184 //
185 //============================================================================
186
DEFINE_ACTION_FUNCTION(AActor,A_M_SawRefire)187 DEFINE_ACTION_FUNCTION(AActor, A_M_SawRefire)
188 {
189 if (self->target == NULL || self->target->health <= 0)
190 {
191 self->SetState (self->state + 1);
192 return;
193 }
194 if (!self->CheckMeleeRange ())
195 {
196 self->SetState (self->state + 1);
197 }
198 }
199
200 //============================================================================
201 //
202 // A_MarineNoise
203 //
204 //============================================================================
205
DEFINE_ACTION_FUNCTION(AActor,A_MarineNoise)206 DEFINE_ACTION_FUNCTION(AActor, A_MarineNoise)
207 {
208 if (static_cast<AScriptedMarine *>(self)->CurrentWeapon == AScriptedMarine::WEAPON_Chainsaw)
209 {
210 S_Sound (self, CHAN_WEAPON, "weapons/sawidle", 1, ATTN_NORM);
211 }
212 }
213
214 //============================================================================
215 //
216 // A_MarineChase
217 //
218 //============================================================================
219
DEFINE_ACTION_FUNCTION(AActor,A_MarineChase)220 DEFINE_ACTION_FUNCTION(AActor, A_MarineChase)
221 {
222 CALL_ACTION(A_MarineNoise, self);
223 A_Chase (self);
224 }
225
226 //============================================================================
227 //
228 // A_MarineLook
229 //
230 //============================================================================
231
DEFINE_ACTION_FUNCTION(AActor,A_MarineLook)232 DEFINE_ACTION_FUNCTION(AActor, A_MarineLook)
233 {
234 CALL_ACTION(A_MarineNoise, self);
235 CALL_ACTION(A_Look, self);
236 }
237
238 //============================================================================
239 //
240 // A_M_Saw
241 //
242 //============================================================================
243
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_M_Saw)244 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Saw)
245 {
246 ACTION_PARAM_START(4);
247 ACTION_PARAM_SOUND(fullsound, 0);
248 ACTION_PARAM_SOUND(hitsound, 1);
249 ACTION_PARAM_INT(damage, 2);
250 ACTION_PARAM_CLASS(pufftype, 3);
251
252 if (self->target == NULL)
253 return;
254
255 if (pufftype == NULL) pufftype = PClass::FindClass(NAME_BulletPuff);
256 if (damage == 0) damage = 2;
257
258 A_FaceTarget (self);
259 if (self->CheckMeleeRange ())
260 {
261 angle_t angle;
262 AActor *linetarget;
263
264 damage *= (pr_m_saw()%10+1);
265 angle = self->angle + (pr_m_saw.Random2() << 18);
266
267 P_LineAttack (self, angle, MELEERANGE+1,
268 P_AimLineAttack (self, angle, MELEERANGE+1, &linetarget), damage,
269 NAME_Melee, pufftype, false, &linetarget);
270
271 if (!linetarget)
272 {
273 S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM);
274 return;
275 }
276 S_Sound (self, CHAN_WEAPON, hitsound, 1, ATTN_NORM);
277
278 // turn to face target
279 angle = self->AngleTo(linetarget);
280 if (angle - self->angle > ANG180)
281 {
282 if (angle - self->angle < (angle_t)(-ANG90/20))
283 self->angle = angle + ANG90/21;
284 else
285 self->angle -= ANG90/20;
286 }
287 else
288 {
289 if (angle - self->angle > ANG90/20)
290 self->angle = angle - ANG90/21;
291 else
292 self->angle += ANG90/20;
293 }
294 }
295 else
296 {
297 S_Sound (self, CHAN_WEAPON, fullsound, 1, ATTN_NORM);
298 }
299 //A_Chase (self);
300 }
301
302 //============================================================================
303 //
304 // A_M_Punch
305 //
306 //============================================================================
307
MarinePunch(AActor * self,int damagemul)308 static void MarinePunch(AActor *self, int damagemul)
309 {
310 angle_t angle;
311 int damage;
312 int pitch;
313 AActor *linetarget;
314
315 if (self->target == NULL)
316 return;
317
318 damage = ((pr_m_punch()%10+1) << 1) * damagemul;
319
320 A_FaceTarget (self);
321 angle = self->angle + (pr_m_punch.Random2() << 18);
322 pitch = P_AimLineAttack (self, angle, MELEERANGE, &linetarget);
323 P_LineAttack (self, angle, MELEERANGE, pitch, damage, NAME_Melee, NAME_BulletPuff, true, &linetarget);
324
325 // turn to face target
326 if (linetarget)
327 {
328 S_Sound (self, CHAN_WEAPON, "*fist", 1, ATTN_NORM);
329 self->angle = self->AngleTo(linetarget);
330
331 }
332 }
333
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_M_Punch)334 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_Punch)
335 {
336 ACTION_PARAM_START(1);
337 ACTION_PARAM_INT(mult, 0);
338
339 MarinePunch(self, mult);
340 }
341
342 //============================================================================
343 //
344 // P_GunShot2
345 //
346 //============================================================================
347
P_GunShot2(AActor * mo,bool accurate,int pitch,const PClass * pufftype)348 void P_GunShot2 (AActor *mo, bool accurate, int pitch, const PClass *pufftype)
349 {
350 angle_t angle;
351 int damage;
352
353 damage = 5*(pr_m_gunshot()%3+1);
354 angle = mo->angle;
355
356 if (!accurate)
357 {
358 angle += pr_m_gunshot.Random2 () << 18;
359 }
360
361 P_LineAttack (mo, angle, MISSILERANGE, pitch, damage, NAME_Hitscan, pufftype);
362 }
363
364 //============================================================================
365 //
366 // A_M_FirePistol
367 //
368 //============================================================================
369
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_M_FirePistol)370 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_FirePistol)
371 {
372 if (self->target == NULL)
373 return;
374
375 ACTION_PARAM_START(1);
376 ACTION_PARAM_BOOL(accurate, 0);
377
378 S_Sound (self, CHAN_WEAPON, "weapons/pistol", 1, ATTN_NORM);
379 A_FaceTarget (self);
380 P_GunShot2 (self, accurate, P_AimLineAttack (self, self->angle, MISSILERANGE),
381 PClass::FindClass(NAME_BulletPuff));
382 }
383
384 //============================================================================
385 //
386 // A_M_FireShotgun
387 //
388 //============================================================================
389
DEFINE_ACTION_FUNCTION(AActor,A_M_FireShotgun)390 DEFINE_ACTION_FUNCTION(AActor, A_M_FireShotgun)
391 {
392 int pitch;
393
394 if (self->target == NULL)
395 return;
396
397 S_Sound (self, CHAN_WEAPON, "weapons/shotgf", 1, ATTN_NORM);
398 A_FaceTarget (self);
399 pitch = P_AimLineAttack (self, self->angle, MISSILERANGE);
400 for (int i = 0; i < 7; ++i)
401 {
402 P_GunShot2 (self, false, pitch, PClass::FindClass(NAME_BulletPuff));
403 }
404 self->special1 = level.maptime + 27;
405 }
406
407 //============================================================================
408 //
409 // A_M_CheckAttack
410 //
411 //============================================================================
412
DEFINE_ACTION_FUNCTION(AActor,A_M_CheckAttack)413 DEFINE_ACTION_FUNCTION(AActor, A_M_CheckAttack)
414 {
415 if (self->special1 != 0 || self->target == NULL)
416 {
417 self->SetState (self->FindState("SkipAttack"));
418 }
419 else
420 {
421 A_FaceTarget (self);
422 }
423 }
424
425 //============================================================================
426 //
427 // A_M_FireShotgun2
428 //
429 //============================================================================
430
DEFINE_ACTION_FUNCTION(AActor,A_M_FireShotgun2)431 DEFINE_ACTION_FUNCTION(AActor, A_M_FireShotgun2)
432 {
433 int pitch;
434
435 if (self->target == NULL)
436 return;
437
438 S_Sound (self, CHAN_WEAPON, "weapons/sshotf", 1, ATTN_NORM);
439 A_FaceTarget (self);
440 pitch = P_AimLineAttack (self, self->angle, MISSILERANGE);
441 for (int i = 0; i < 20; ++i)
442 {
443 int damage = 5*(pr_m_fireshotgun2()%3+1);
444 angle_t angle = self->angle + (pr_m_fireshotgun2.Random2() << 19);
445
446 P_LineAttack (self, angle, MISSILERANGE,
447 pitch + (pr_m_fireshotgun2.Random2() * 332063), damage,
448 NAME_Hitscan, NAME_BulletPuff);
449 }
450 self->special1 = level.maptime;
451 }
452
453 //============================================================================
454 //
455 // A_M_FireCGun
456 //
457 //============================================================================
458
DEFINE_ACTION_FUNCTION_PARAMS(AActor,A_M_FireCGun)459 DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_M_FireCGun)
460 {
461 if (self->target == NULL)
462 return;
463
464 ACTION_PARAM_START(1);
465 ACTION_PARAM_BOOL(accurate, 0);
466
467 S_Sound (self, CHAN_WEAPON, "weapons/chngun", 1, ATTN_NORM);
468 A_FaceTarget (self);
469 P_GunShot2 (self, accurate, P_AimLineAttack (self, self->angle, MISSILERANGE),
470 PClass::FindClass(NAME_BulletPuff));
471 }
472
473 //============================================================================
474 //
475 // A_M_FireMissile
476 //
477 // Giving a marine a rocket launcher is probably a bad idea unless you pump
478 // up his health, because he's just as likely to kill himself as he is to
479 // kill anything else with it.
480 //
481 //============================================================================
482
DEFINE_ACTION_FUNCTION(AActor,A_M_FireMissile)483 DEFINE_ACTION_FUNCTION(AActor, A_M_FireMissile)
484 {
485 if (self->target == NULL)
486 return;
487
488 if (self->CheckMeleeRange ())
489 { // If too close, punch it
490 MarinePunch(self, 1);
491 }
492 else
493 {
494 A_FaceTarget (self);
495 P_SpawnMissile (self, self->target, PClass::FindClass("Rocket"));
496 }
497 }
498
499 //============================================================================
500 //
501 // A_M_FireRailgun
502 //
503 //============================================================================
504
DEFINE_ACTION_FUNCTION(AActor,A_M_FireRailgun)505 DEFINE_ACTION_FUNCTION(AActor, A_M_FireRailgun)
506 {
507 if (self->target == NULL)
508 return;
509
510 CALL_ACTION(A_MonsterRail, self);
511 self->special1 = level.maptime + 50;
512 }
513
514 //============================================================================
515 //
516 // A_M_FirePlasma
517 //
518 //============================================================================
519
DEFINE_ACTION_FUNCTION(AActor,A_M_FirePlasma)520 DEFINE_ACTION_FUNCTION(AActor, A_M_FirePlasma)
521 {
522 if (self->target == NULL)
523 return;
524
525 A_FaceTarget (self);
526 P_SpawnMissile (self, self->target, PClass::FindClass("PlasmaBall"));
527 self->special1 = level.maptime + 20;
528 }
529
530 //============================================================================
531 //
532 // A_M_BFGsound
533 //
534 //============================================================================
535
DEFINE_ACTION_FUNCTION(AActor,A_M_BFGsound)536 DEFINE_ACTION_FUNCTION(AActor, A_M_BFGsound)
537 {
538 if (self->target == NULL)
539 return;
540
541 if (self->special1 != 0)
542 {
543 self->SetState (self->SeeState);
544 }
545 else
546 {
547 A_FaceTarget (self);
548 S_Sound (self, CHAN_WEAPON, "weapons/bfgf", 1, ATTN_NORM);
549 // Don't interrupt the firing sequence
550 self->PainChance = 0;
551 }
552 }
553
554 //============================================================================
555 //
556 // A_M_FireBFG
557 //
558 //============================================================================
559
DEFINE_ACTION_FUNCTION(AActor,A_M_FireBFG)560 DEFINE_ACTION_FUNCTION(AActor, A_M_FireBFG)
561 {
562 if (self->target == NULL)
563 return;
564
565 A_FaceTarget (self);
566 P_SpawnMissile (self, self->target, PClass::FindClass("BFGBall"));
567 self->special1 = level.maptime + 30;
568 self->PainChance = MARINE_PAIN_CHANCE;
569 }
570
571 //---------------------------------------------------------------------------
572
SetWeapon(EMarineWeapon type)573 void AScriptedMarine::SetWeapon (EMarineWeapon type)
574 {
575 if (GetWeaponStates(type, MeleeState, MissileState))
576 {
577 static const char *classes[] = {
578 "ScriptedMarine",
579 "MarineFist",
580 "MarineBerserk",
581 "MarineChainsaw",
582 "MarinePistol",
583 "MarineShotgun",
584 "MarineSSG",
585 "MarineChaingun",
586 "MarineRocket",
587 "MarinePlasma",
588 "MarineRailgun",
589 "MarineBFG"
590 };
591
592 const PClass *cls = PClass::FindClass(classes[type]);
593 if (cls != NULL)
594 DecalGenerator = GetDefaultByType(cls)->DecalGenerator;
595 else
596 DecalGenerator = NULL;
597 }
598 }
599
SetSprite(const PClass * source)600 void AScriptedMarine::SetSprite (const PClass *source)
601 {
602 if (source == NULL || source->ActorInfo == NULL)
603 { // A valid actor class wasn't passed, so use the standard sprite
604 SpriteOverride = sprite = GetClass()->ActorInfo->OwnedStates[0].sprite;
605 // Copy the standard scaling
606 scaleX = GetDefault()->scaleX;
607 scaleY = GetDefault()->scaleY;
608 }
609 else
610 { // Use the same sprite and scaling the passed class spawns with
611 SpriteOverride = sprite = GetDefaultByType (source)->SpawnState->sprite;
612 scaleX = GetDefaultByType(source)->scaleX;
613 scaleY = GetDefaultByType(source)->scaleY;
614 }
615 }
616