1 /*
2 #include "actor.h"
3 #include "info.h"
4 #include "a_pickups.h"
5 #include "a_artifacts.h"
6 #include "gstrings.h"
7 #include "p_local.h"
8 #include "s_sound.h"
9 #include "m_random.h"
10 #include "a_action.h"
11 #include "a_hexenglobal.h"
12 #include "w_wad.h"
13 #include "thingdef/thingdef.h"
14 #include "g_level.h"
15 */
16
17 EXTERN_CVAR(Bool, sv_unlimited_pickup)
18
19 static FRandom pr_poisonbag ("PoisonBag");
20 static FRandom pr_poisoncloud ("PoisonCloud");
21 static FRandom pr_poisoncloudd ("PoisonCloudDamage");
22
23 DECLARE_ACTION(A_CheckThrowBomb)
24
25 // Poison Bag Artifact (Flechette) ------------------------------------------
26
27 class AArtiPoisonBag : public AInventory
28 {
29 DECLARE_CLASS (AArtiPoisonBag, AInventory)
30 public:
31 bool HandlePickup (AInventory *item);
32 AInventory *CreateCopy (AActor *other);
33 void BeginPlay ();
34 };
35
36 IMPLEMENT_CLASS (AArtiPoisonBag)
37
38 // Poison Bag 1 (The Cleric's) ----------------------------------------------
39
40 class AArtiPoisonBag1 : public AArtiPoisonBag
41 {
42 DECLARE_CLASS (AArtiPoisonBag1, AArtiPoisonBag)
43 public:
44 bool Use (bool pickup);
45 };
46
IMPLEMENT_CLASS(AArtiPoisonBag1)47 IMPLEMENT_CLASS (AArtiPoisonBag1)
48
49 bool AArtiPoisonBag1::Use (bool pickup)
50 {
51 angle_t angle = Owner->angle >> ANGLETOFINESHIFT;
52 AActor *mo;
53
54 mo = Spawn ("PoisonBag", Owner->Vec3Offset(
55 16*finecosine[angle],
56 24*finesine[angle],
57 -Owner->floorclip+8*FRACUNIT), ALLOW_REPLACE);
58 if (mo)
59 {
60 mo->target = Owner;
61 return true;
62 }
63 return false;
64 }
65
66 // Poison Bag 2 (The Mage's) ------------------------------------------------
67
68 class AArtiPoisonBag2 : public AArtiPoisonBag
69 {
70 DECLARE_CLASS (AArtiPoisonBag2, AArtiPoisonBag)
71 public:
72 bool Use (bool pickup);
73 };
74
IMPLEMENT_CLASS(AArtiPoisonBag2)75 IMPLEMENT_CLASS (AArtiPoisonBag2)
76
77 bool AArtiPoisonBag2::Use (bool pickup)
78 {
79 angle_t angle = Owner->angle >> ANGLETOFINESHIFT;
80 AActor *mo;
81
82 mo = Spawn ("FireBomb", Owner->Vec3Offset(
83 16*finecosine[angle],
84 24*finesine[angle],
85 -Owner->floorclip+8*FRACUNIT), ALLOW_REPLACE);
86 if (mo)
87 {
88 mo->target = Owner;
89 return true;
90 }
91 return false;
92 }
93
94 // Poison Bag 3 (The Fighter's) ---------------------------------------------
95
96 class AArtiPoisonBag3 : public AArtiPoisonBag
97 {
98 DECLARE_CLASS (AArtiPoisonBag3, AArtiPoisonBag)
99 public:
100 bool Use (bool pickup);
101 };
102
IMPLEMENT_CLASS(AArtiPoisonBag3)103 IMPLEMENT_CLASS (AArtiPoisonBag3)
104
105 bool AArtiPoisonBag3::Use (bool pickup)
106 {
107 AActor *mo;
108
109 mo = Spawn("ThrowingBomb", Owner->PosPlusZ(-Owner->floorclip+35*FRACUNIT + (Owner->player? Owner->player->crouchoffset : 0)), ALLOW_REPLACE);
110 if (mo)
111 {
112 mo->angle = Owner->angle + (((pr_poisonbag()&7) - 4) << 24);
113
114 /* Original flight code from Hexen
115 * mo->momz = 4*FRACUNIT+((player->lookdir)<<(FRACBITS-4));
116 * mo->z += player->lookdir<<(FRACBITS-4);
117 * P_ThrustMobj(mo, mo->angle, mo->info->speed);
118 * mo->momx += player->mo->momx>>1;
119 * mo->momy += player->mo->momy>>1;
120 */
121
122 // When looking straight ahead, it uses a z velocity of 4 while the xy velocity
123 // is as set by the projectile. To accommodate this with a proper trajectory, we
124 // aim the projectile ~20 degrees higher than we're looking at and increase the
125 // speed we fire at accordingly.
126 angle_t orgpitch = angle_t(-Owner->pitch) >> ANGLETOFINESHIFT;
127 angle_t modpitch = angle_t(0xDC00000 - Owner->pitch) >> ANGLETOFINESHIFT;
128 angle_t angle = mo->angle >> ANGLETOFINESHIFT;
129 fixed_t speed = fixed_t(sqrt((double)mo->Speed*mo->Speed + (4.0*65536*4*65536)));
130 fixed_t xyscale = FixedMul(speed, finecosine[modpitch]);
131
132 mo->velz = FixedMul(speed, finesine[modpitch]);
133 mo->velx = FixedMul(xyscale, finecosine[angle]) + (Owner->velx >> 1);
134 mo->vely = FixedMul(xyscale, finesine[angle]) + (Owner->vely >> 1);
135 mo->AddZ(FixedMul(mo->Speed, finesine[orgpitch]));
136
137 mo->target = Owner;
138 mo->tics -= pr_poisonbag()&3;
139 P_CheckMissileSpawn(mo, Owner->radius);
140 return true;
141 }
142 return false;
143 }
144
145 // Poison Bag 4 (Generic Giver) ----------------------------------------------
146
147 class AArtiPoisonBagGiver : public AArtiPoisonBag
148 {
149 DECLARE_CLASS (AArtiPoisonBagGiver, AArtiPoisonBag)
150 public:
151 bool Use (bool pickup);
152 };
153
IMPLEMENT_CLASS(AArtiPoisonBagGiver)154 IMPLEMENT_CLASS (AArtiPoisonBagGiver)
155
156 bool AArtiPoisonBagGiver::Use (bool pickup)
157 {
158 const PClass *MissileType = PClass::FindClass((ENamedName) this->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None));
159 if (MissileType != NULL)
160 {
161 AActor *mo = Spawn (MissileType, Owner->Pos(), ALLOW_REPLACE);
162 if (mo != NULL)
163 {
164 if (mo->IsKindOf(RUNTIME_CLASS(AInventory)))
165 {
166 AInventory *inv = static_cast<AInventory *>(mo);
167 if (inv->CallTryPickup(Owner))
168 return true;
169 }
170 mo->Destroy(); // Destroy if not inventory or couldn't be picked up
171 }
172 }
173 return false;
174 }
175
176 // Poison Bag 5 (Generic Thrower) ----------------------------------------------
177
178 class AArtiPoisonBagShooter : public AArtiPoisonBag
179 {
180 DECLARE_CLASS (AArtiPoisonBagShooter, AArtiPoisonBag)
181 public:
182 bool Use (bool pickup);
183 };
184
IMPLEMENT_CLASS(AArtiPoisonBagShooter)185 IMPLEMENT_CLASS (AArtiPoisonBagShooter)
186
187 bool AArtiPoisonBagShooter::Use (bool pickup)
188 {
189 const PClass *MissileType = PClass::FindClass((ENamedName) this->GetClass()->Meta.GetMetaInt (ACMETA_MissileName, NAME_None));
190 if (MissileType != NULL)
191 {
192 AActor *mo = P_SpawnPlayerMissile(Owner, MissileType);
193 if (mo != NULL)
194 {
195 // automatic handling of seeker missiles
196 if (mo->flags2 & MF2_SEEKERMISSILE)
197 {
198 mo->tracer = Owner->target;
199 }
200 return true;
201 }
202 }
203 return false;
204 }
205
206 //============================================================================
207 //
208 // GetFlechetteType
209 //
210 //============================================================================
211
GetFlechetteType(AActor * other)212 const PClass *GetFlechetteType(AActor *other)
213 {
214 const PClass *spawntype = NULL;
215 if (other->IsKindOf(RUNTIME_CLASS(APlayerPawn)))
216 {
217 spawntype = static_cast<APlayerPawn*>(other)->FlechetteType;
218 }
219 if (spawntype == NULL)
220 {
221 // default fallback if nothing valid defined.
222 spawntype = RUNTIME_CLASS(AArtiPoisonBag3);
223 }
224 return spawntype;
225 }
226
227 //============================================================================
228 //
229 // AArtiPoisonBag :: HandlePickup
230 //
231 //============================================================================
232
HandlePickup(AInventory * item)233 bool AArtiPoisonBag::HandlePickup (AInventory *item)
234 {
235 // Only do special handling when picking up the base class
236 if (item->GetClass() != RUNTIME_CLASS(AArtiPoisonBag))
237 {
238 return Super::HandlePickup (item);
239 }
240
241 if (GetClass() == GetFlechetteType(Owner))
242 {
243 if (Amount < MaxAmount || sv_unlimited_pickup)
244 {
245 Amount += item->Amount;
246 if (Amount > MaxAmount && !sv_unlimited_pickup)
247 {
248 Amount = MaxAmount;
249 }
250 item->ItemFlags |= IF_PICKUPGOOD;
251 }
252 return true;
253 }
254 if (Inventory != NULL)
255 {
256 return Inventory->HandlePickup (item);
257 }
258 return false;
259 }
260
261 //============================================================================
262 //
263 // AArtiPoisonBag :: CreateCopy
264 //
265 //============================================================================
266
CreateCopy(AActor * other)267 AInventory *AArtiPoisonBag::CreateCopy (AActor *other)
268 {
269 // Only the base class gets special handling
270 if (GetClass() != RUNTIME_CLASS(AArtiPoisonBag))
271 {
272 return Super::CreateCopy (other);
273 }
274
275 AInventory *copy;
276
277 const PClass *spawntype = GetFlechetteType(other);
278 copy = static_cast<AInventory *>(Spawn (spawntype, 0, 0, 0, NO_REPLACE));
279 copy->Amount = Amount;
280 copy->MaxAmount = MaxAmount;
281 GoAwayAndDie ();
282 return copy;
283 }
284
285 //============================================================================
286 //
287 // AArtiPoisonBag :: BeginPlay
288 //
289 //============================================================================
290
BeginPlay()291 void AArtiPoisonBag::BeginPlay ()
292 {
293 Super::BeginPlay ();
294 // If a subclass's specific icon is not defined, let it use the base class's.
295 if (!Icon.isValid())
296 {
297 AInventory *defbag;
298 // Why doesn't this work?
299 //defbag = GetDefault<AArtiPoisonBag>();
300 defbag = (AInventory *)GetDefaultByType (RUNTIME_CLASS(AArtiPoisonBag));
301 Icon = defbag->Icon;
302 }
303 }
304
305 // Poison Cloud -------------------------------------------------------------
306
307 class APoisonCloud : public AActor
308 {
309 DECLARE_CLASS (APoisonCloud, AActor)
310 public:
311 int DoSpecialDamage (AActor *target, int damage, FName damagetype);
312 void BeginPlay ();
313 };
314
IMPLEMENT_CLASS(APoisonCloud)315 IMPLEMENT_CLASS (APoisonCloud)
316
317 void APoisonCloud::BeginPlay ()
318 {
319 velx = 1; // missile objects must move to impact other objects
320 special1 = 24+(pr_poisoncloud()&7);
321 special2 = 0;
322 }
323
DoSpecialDamage(AActor * victim,int damage,FName damagetype)324 int APoisonCloud::DoSpecialDamage (AActor *victim, int damage, FName damagetype)
325 {
326 if (victim->player)
327 {
328 bool mate = (target != NULL && victim->player != target->player && victim->IsTeammate (target));
329 bool dopoison;
330
331 if (!mate)
332 {
333 dopoison = victim->player->poisoncount < 4;
334 }
335 else
336 {
337 dopoison = victim->player->poisoncount < (int)(4.f * level.teamdamage);
338 }
339
340 if (dopoison)
341 {
342 int damage = 15 + (pr_poisoncloudd()&15);
343 if (mate)
344 {
345 damage = (int)((float)damage * level.teamdamage);
346 }
347 // Handle passive damage modifiers (e.g. PowerProtection)
348 if (victim->Inventory != NULL)
349 {
350 victim->Inventory->ModifyDamage(damage, damagetype, damage, true);
351 }
352 // Modify with damage factors
353 damage = FixedMul(damage, victim->DamageFactor);
354 if (damage > 0)
355 {
356 damage = DamageTypeDefinition::ApplyMobjDamageFactor(damage, damagetype, victim->GetClass()->ActorInfo->DamageFactors);
357 }
358 if (damage > 0)
359 {
360 P_PoisonDamage (victim->player, this,
361 15+(pr_poisoncloudd()&15), false); // Don't play painsound
362
363 // If successful, play the poison sound.
364 if (P_PoisonPlayer (victim->player, this, this->target, 50))
365 S_Sound (victim, CHAN_VOICE, "*poison", 1, ATTN_NORM);
366 }
367 }
368 return -1;
369 }
370 else if (!(victim->flags3 & MF3_ISMONSTER))
371 { // only damage monsters/players with the poison cloud
372 return -1;
373 }
374 return damage;
375 }
376
377 //===========================================================================
378 //
379 // A_PoisonBagInit
380 //
381 //===========================================================================
382
DEFINE_ACTION_FUNCTION(AActor,A_PoisonBagInit)383 DEFINE_ACTION_FUNCTION(AActor, A_PoisonBagInit)
384 {
385 AActor *mo;
386
387 mo = Spawn<APoisonCloud> (self->PosPlusZ(28*FRACUNIT), ALLOW_REPLACE);
388 if (mo)
389 {
390 mo->target = self->target;
391 }
392 }
393
394 //===========================================================================
395 //
396 // A_PoisonBagCheck
397 //
398 //===========================================================================
399
DEFINE_ACTION_FUNCTION(AActor,A_PoisonBagCheck)400 DEFINE_ACTION_FUNCTION(AActor, A_PoisonBagCheck)
401 {
402 if (--self->special1 <= 0)
403 {
404 self->SetState (self->FindState ("Death"));
405 }
406 else
407 {
408 return;
409 }
410 }
411
412 //===========================================================================
413 //
414 // A_PoisonBagDamage
415 //
416 //===========================================================================
417
DEFINE_ACTION_FUNCTION(AActor,A_PoisonBagDamage)418 DEFINE_ACTION_FUNCTION(AActor, A_PoisonBagDamage)
419 {
420 int bobIndex;
421
422 P_RadiusAttack (self, self->target, 4, 40, self->DamageType, RADF_HURTSOURCE);
423 bobIndex = self->special2;
424 self->AddZ(finesine[bobIndex << BOBTOFINESHIFT] >> 1);
425 self->special2 = (bobIndex + 1) & 63;
426 }
427
428 //===========================================================================
429 //
430 // A_CheckThrowBomb
431 //
432 //===========================================================================
433
DEFINE_ACTION_FUNCTION(AActor,A_CheckThrowBomb)434 DEFINE_ACTION_FUNCTION(AActor, A_CheckThrowBomb)
435 {
436 if (--self->health <= 0)
437 {
438 self->SetState (self->FindState(NAME_Death));
439 }
440 }
441
442 //===========================================================================
443 //
444 // A_CheckThrowBomb2
445 //
446 //===========================================================================
447
DEFINE_ACTION_FUNCTION(AActor,A_CheckThrowBomb2)448 DEFINE_ACTION_FUNCTION(AActor, A_CheckThrowBomb2)
449 {
450 // [RH] Check using actual velocity, although the velz < 2 check still stands
451 //if (abs(self->velx) < FRACUNIT*3/2 && abs(self->vely) < FRACUNIT*3/2
452 // && self->velz < 2*FRACUNIT)
453 if (self->velz < 2*FRACUNIT &&
454 TMulScale32 (self->velx, self->velx, self->vely, self->vely, self->velz, self->velz)
455 < (3*3)/(2*2))
456 {
457 self->SetState (self->SpawnState + 6);
458 self->SetZ(self->floorz);
459 self->velz = 0;
460 self->BounceFlags = BOUNCE_None;
461 self->flags &= ~MF_MISSILE;
462 }
463 CALL_ACTION(A_CheckThrowBomb, self);
464 }
465