1 #include <string.h>
2
3 #include "a_pickups.h"
4 #include "gi.h"
5 #include "d_player.h"
6 #include "s_sound.h"
7 #include "i_system.h"
8 #include "r_state.h"
9 #include "p_pspr.h"
10 #include "c_dispatch.h"
11 #include "m_misc.h"
12 #include "gameconfigfile.h"
13 #include "cmdlib.h"
14 #include "templates.h"
15 #include "sbar.h"
16 #include "thingdef/thingdef.h"
17 #include "doomstat.h"
18 #include "g_level.h"
19 #include "d_net.h"
20 #include "farchive.h"
21
22 #define BONUSADD 6
23
24 IMPLEMENT_POINTY_CLASS (AWeapon)
25 DECLARE_POINTER (Ammo1)
26 DECLARE_POINTER (Ammo2)
27 DECLARE_POINTER (SisterWeapon)
28 END_POINTERS
29
30 FString WeaponSection;
31 TArray<FString> KeyConfWeapons;
32 FWeaponSlots *PlayingKeyConf;
33
34 TArray<const PClass *> Weapons_ntoh;
35 TMap<const PClass *, int> Weapons_hton;
36
37 static int STACK_ARGS ntoh_cmp(const void *a, const void *b);
38
39 //===========================================================================
40 //
41 // AWeapon :: Serialize
42 //
43 //===========================================================================
44
Serialize(FArchive & arc)45 void AWeapon::Serialize (FArchive &arc)
46 {
47 Super::Serialize (arc);
48 arc << WeaponFlags
49 << AmmoType1 << AmmoType2
50 << AmmoGive1 << AmmoGive2
51 << MinAmmo1 << MinAmmo2
52 << AmmoUse1 << AmmoUse2
53 << Kickback
54 << YAdjust
55 << UpSound << ReadySound
56 << SisterWeaponType
57 << ProjectileType << AltProjectileType
58 << SelectionOrder
59 << MoveCombatDist
60 << Ammo1 << Ammo2 << SisterWeapon << GivenAsMorphWeapon
61 << bAltFire
62 << ReloadCounter;
63 if (SaveVersion >= 3615) {
64 arc << BobStyle << BobSpeed << BobRangeX << BobRangeY;
65 }
66 arc << FOVScale
67 << Crosshair;
68 if (SaveVersion >= 4203)
69 {
70 arc << MinSelAmmo1 << MinSelAmmo2;
71 }
72 }
73
74 //===========================================================================
75 //
76 // AWeapon :: MarkPrecacheSounds
77 //
78 //===========================================================================
79
MarkPrecacheSounds() const80 void AWeapon::MarkPrecacheSounds() const
81 {
82 Super::MarkPrecacheSounds();
83 UpSound.MarkUsed();
84 ReadySound.MarkUsed();
85 }
86
87 //===========================================================================
88 //
89 // AWeapon :: TryPickup
90 //
91 // If you can't see the weapon when it's active, then you can't pick it up.
92 //
93 //===========================================================================
94
TryPickupRestricted(AActor * & toucher)95 bool AWeapon::TryPickupRestricted (AActor *&toucher)
96 {
97 // Wrong class, but try to pick up for ammo
98 if (ShouldStay())
99 { // Can't pick up weapons for other classes in coop netplay
100 return false;
101 }
102
103 bool gaveSome = (NULL != AddAmmo (toucher, AmmoType1, AmmoGive1));
104 gaveSome |= (NULL != AddAmmo (toucher, AmmoType2, AmmoGive2));
105 if (gaveSome)
106 {
107 GoAwayAndDie ();
108 }
109 return gaveSome;
110 }
111
112
113 //===========================================================================
114 //
115 // AWeapon :: TryPickup
116 //
117 //===========================================================================
118
TryPickup(AActor * & toucher)119 bool AWeapon::TryPickup (AActor *&toucher)
120 {
121 FState * ReadyState = FindState(NAME_Ready);
122 if (ReadyState != NULL &&
123 ReadyState->GetFrame() < sprites[ReadyState->sprite].numframes)
124 {
125 return Super::TryPickup (toucher);
126 }
127 return false;
128 }
129
130 //===========================================================================
131 //
132 // AWeapon :: Use
133 //
134 // Make the player switch to this weapon.
135 //
136 //===========================================================================
137
Use(bool pickup)138 bool AWeapon::Use (bool pickup)
139 {
140 AWeapon *useweap = this;
141
142 // Powered up weapons cannot be used directly.
143 if (WeaponFlags & WIF_POWERED_UP) return false;
144
145 // If the player is powered-up, use the alternate version of the
146 // weapon, if one exists.
147 if (SisterWeapon != NULL &&
148 SisterWeapon->WeaponFlags & WIF_POWERED_UP &&
149 Owner->FindInventory (RUNTIME_CLASS(APowerWeaponLevel2), true))
150 {
151 useweap = SisterWeapon;
152 }
153 if (Owner->player != NULL && Owner->player->ReadyWeapon != useweap)
154 {
155 Owner->player->PendingWeapon = useweap;
156 }
157 // Return false so that the weapon is not removed from the inventory.
158 return false;
159 }
160
161 //===========================================================================
162 //
163 // AWeapon :: Destroy
164 //
165 //===========================================================================
166
Destroy()167 void AWeapon::Destroy()
168 {
169 AWeapon *sister = SisterWeapon;
170
171 if (sister != NULL)
172 {
173 // avoid recursion
174 sister->SisterWeapon = NULL;
175 if (sister != this)
176 { // In case we are our own sister, don't crash.
177 sister->Destroy();
178 }
179 }
180 Super::Destroy();
181 }
182
183 //===========================================================================
184 //
185 // AWeapon :: HandlePickup
186 //
187 // Try to leach ammo from the weapon if you have it already.
188 //
189 //===========================================================================
190
HandlePickup(AInventory * item)191 bool AWeapon::HandlePickup (AInventory *item)
192 {
193 if (item->GetClass() == GetClass())
194 {
195 if (static_cast<AWeapon *>(item)->PickupForAmmo (this))
196 {
197 item->ItemFlags |= IF_PICKUPGOOD;
198 }
199 return true;
200 }
201 if (Inventory != NULL)
202 {
203 return Inventory->HandlePickup (item);
204 }
205 return false;
206 }
207
208 //===========================================================================
209 //
210 // AWeapon :: PickupForAmmo
211 //
212 // The player already has this weapon, so try to pick it up for ammo.
213 //
214 //===========================================================================
215
PickupForAmmo(AWeapon * ownedWeapon)216 bool AWeapon::PickupForAmmo (AWeapon *ownedWeapon)
217 {
218 bool gotstuff = false;
219
220 // Don't take ammo if the weapon sticks around.
221 if (!ShouldStay ())
222 {
223 int oldamount1 = 0;
224 int oldamount2 = 0;
225 if (ownedWeapon->Ammo1 != NULL) oldamount1 = ownedWeapon->Ammo1->Amount;
226 if (ownedWeapon->Ammo2 != NULL) oldamount2 = ownedWeapon->Ammo2->Amount;
227
228 if (AmmoGive1 > 0) gotstuff = AddExistingAmmo (ownedWeapon->Ammo1, AmmoGive1);
229 if (AmmoGive2 > 0) gotstuff |= AddExistingAmmo (ownedWeapon->Ammo2, AmmoGive2);
230
231 AActor *Owner = ownedWeapon->Owner;
232 if (gotstuff && Owner != NULL && Owner->player != NULL)
233 {
234 if (ownedWeapon->Ammo1 != NULL && oldamount1 == 0)
235 {
236 static_cast<APlayerPawn *>(Owner)->CheckWeaponSwitch(ownedWeapon->Ammo1->GetClass());
237 }
238 else if (ownedWeapon->Ammo2 != NULL && oldamount2 == 0)
239 {
240 static_cast<APlayerPawn *>(Owner)->CheckWeaponSwitch(ownedWeapon->Ammo2->GetClass());
241 }
242 }
243 }
244 return gotstuff;
245 }
246
247 //===========================================================================
248 //
249 // AWeapon :: CreateCopy
250 //
251 //===========================================================================
252
CreateCopy(AActor * other)253 AInventory *AWeapon::CreateCopy (AActor *other)
254 {
255 AWeapon *copy = static_cast<AWeapon*>(Super::CreateCopy (other));
256 if (copy != this)
257 {
258 copy->AmmoGive1 = AmmoGive1;
259 copy->AmmoGive2 = AmmoGive2;
260 }
261 return copy;
262 }
263
264 //===========================================================================
265 //
266 // AWeapon :: CreateTossable
267 //
268 // A weapon that's tossed out should contain no ammo, so you can't cheat
269 // by dropping it and then picking it back up.
270 //
271 //===========================================================================
272
CreateTossable()273 AInventory *AWeapon::CreateTossable ()
274 {
275 // Only drop the weapon that is meant to be placed in a level. That is,
276 // only drop the weapon that normally gives you ammo.
277 if (SisterWeapon != NULL &&
278 ((AWeapon*)GetDefault())->AmmoGive1 == 0 &&
279 ((AWeapon*)GetDefault())->AmmoGive2 == 0 &&
280 (((AWeapon*)SisterWeapon->GetDefault())->AmmoGive1 > 0 ||
281 ((AWeapon*)SisterWeapon->GetDefault())->AmmoGive2 > 0))
282 {
283 return SisterWeapon->CreateTossable ();
284 }
285 AWeapon *copy = static_cast<AWeapon *> (Super::CreateTossable ());
286
287 if (copy != NULL)
288 {
289 // If this weapon has a sister, remove it from the inventory too.
290 if (SisterWeapon != NULL)
291 {
292 SisterWeapon->SisterWeapon = NULL;
293 SisterWeapon->Destroy ();
294 }
295 // To avoid exploits, the tossed weapon must not have any ammo.
296 copy->AmmoGive1 = 0;
297 copy->AmmoGive2 = 0;
298 }
299 return copy;
300 }
301
302 //===========================================================================
303 //
304 // AWeapon :: AttachToOwner
305 //
306 //===========================================================================
307
AttachToOwner(AActor * other)308 void AWeapon::AttachToOwner (AActor *other)
309 {
310 Super::AttachToOwner (other);
311
312 Ammo1 = AddAmmo (Owner, AmmoType1, AmmoGive1);
313 Ammo2 = AddAmmo (Owner, AmmoType2, AmmoGive2);
314 SisterWeapon = AddWeapon (SisterWeaponType);
315 if (Owner->player != NULL)
316 {
317 if (!Owner->player->userinfo.GetNeverSwitch() && !(WeaponFlags & WIF_NO_AUTO_SWITCH))
318 {
319 Owner->player->PendingWeapon = this;
320 }
321 if (Owner->player->mo == players[consoleplayer].camera)
322 {
323 StatusBar->ReceivedWeapon (this);
324 }
325 }
326 GivenAsMorphWeapon = false; // will be set explicitly by morphing code
327 }
328
329 //===========================================================================
330 //
331 // AWeapon :: AddAmmo
332 //
333 // Give some ammo to the owner, even if it's just 0.
334 //
335 //===========================================================================
336
AddAmmo(AActor * other,const PClass * ammotype,int amount)337 AAmmo *AWeapon::AddAmmo (AActor *other, const PClass *ammotype, int amount)
338 {
339 AAmmo *ammo;
340
341 if (ammotype == NULL)
342 {
343 return NULL;
344 }
345
346 // [BC] This behavior is from the original Doom. Give 5/2 times as much ammo when
347 // we pick up a weapon in deathmatch.
348 if (( deathmatch ) && ( gameinfo.gametype & GAME_DoomChex ))
349 amount = amount * 5 / 2;
350
351 // extra ammo in baby mode and nightmare mode
352 if (!(this->ItemFlags&IF_IGNORESKILL))
353 {
354 amount = FixedMul(amount, G_SkillProperty(SKILLP_AmmoFactor));
355 }
356 ammo = static_cast<AAmmo *>(other->FindInventory (ammotype));
357 if (ammo == NULL)
358 {
359 ammo = static_cast<AAmmo *>(Spawn (ammotype, 0, 0, 0, NO_REPLACE));
360 ammo->Amount = MIN (amount, ammo->MaxAmount);
361 ammo->AttachToOwner (other);
362 }
363 else if (ammo->Amount < ammo->MaxAmount)
364 {
365 ammo->Amount += amount;
366 if (ammo->Amount > ammo->MaxAmount)
367 {
368 ammo->Amount = ammo->MaxAmount;
369 }
370 }
371 return ammo;
372 }
373
374 //===========================================================================
375 //
376 // AWeapon :: AddExistingAmmo
377 //
378 // Give the owner some more ammo he already has.
379 //
380 //===========================================================================
EXTERN_CVAR(Bool,sv_unlimited_pickup)381 EXTERN_CVAR(Bool, sv_unlimited_pickup)
382
383 bool AWeapon::AddExistingAmmo (AAmmo *ammo, int amount)
384 {
385 if (ammo != NULL && (ammo->Amount < ammo->MaxAmount || sv_unlimited_pickup))
386 {
387 // extra ammo in baby mode and nightmare mode
388 if (!(ItemFlags&IF_IGNORESKILL))
389 {
390 amount = FixedMul(amount, G_SkillProperty(SKILLP_AmmoFactor));
391 }
392 ammo->Amount += amount;
393 if (ammo->Amount > ammo->MaxAmount && !sv_unlimited_pickup)
394 {
395 ammo->Amount = ammo->MaxAmount;
396 }
397 return true;
398 }
399 return false;
400 }
401
402 //===========================================================================
403 //
404 // AWeapon :: AddWeapon
405 //
406 // Give the owner a weapon if they don't have it already.
407 //
408 //===========================================================================
409
AddWeapon(const PClass * weapontype)410 AWeapon *AWeapon::AddWeapon (const PClass *weapontype)
411 {
412 AWeapon *weap;
413
414 if (weapontype == NULL || !weapontype->IsDescendantOf(RUNTIME_CLASS(AWeapon)))
415 {
416 return NULL;
417 }
418 weap = static_cast<AWeapon *>(Owner->FindInventory (weapontype));
419 if (weap == NULL)
420 {
421 weap = static_cast<AWeapon *>(Spawn (weapontype, 0, 0, 0, NO_REPLACE));
422 weap->AttachToOwner (Owner);
423 }
424 return weap;
425 }
426
427 //===========================================================================
428 //
429 // AWeapon :: ShouldStay
430 //
431 //===========================================================================
432
ShouldStay()433 bool AWeapon::ShouldStay ()
434 {
435 if (((multiplayer &&
436 (!deathmatch && !alwaysapplydmflags)) || (dmflags & DF_WEAPONS_STAY)) &&
437 !(flags & MF_DROPPED))
438 {
439 return true;
440 }
441 return false;
442 }
443
444 //===========================================================================
445 //
446 // AWeapon :: CheckAmmo
447 //
448 // Returns true if there is enough ammo to shoot. If not, selects the
449 // next weapon to use.
450 //
451 //===========================================================================
452
CheckAmmo(int fireMode,bool autoSwitch,bool requireAmmo,int ammocount)453 bool AWeapon::CheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo, int ammocount)
454 {
455 int altFire;
456 int count1, count2;
457 int enough, enoughmask;
458 int lAmmoUse1;
459
460 if ((dmflags & DF_INFINITE_AMMO) || (Owner->player->cheats & CF_INFINITEAMMO))
461 {
462 return true;
463 }
464 if (fireMode == EitherFire)
465 {
466 bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false);
467 if (!gotSome && autoSwitch)
468 {
469 barrier_cast<APlayerPawn *>(Owner)->PickNewWeapon (NULL);
470 }
471 return gotSome;
472 }
473 altFire = (fireMode == AltFire);
474 if (!requireAmmo && (WeaponFlags & (WIF_AMMO_OPTIONAL << altFire)))
475 {
476 return true;
477 }
478 count1 = (Ammo1 != NULL) ? Ammo1->Amount : 0;
479 count2 = (Ammo2 != NULL) ? Ammo2->Amount : 0;
480
481 if ((WeaponFlags & WIF_DEHAMMO) && (Ammo1 == NULL))
482 {
483 lAmmoUse1 = 0;
484 }
485 else if (ammocount >= 0 && (WeaponFlags & WIF_DEHAMMO))
486 {
487 lAmmoUse1 = ammocount;
488 }
489 else
490 {
491 lAmmoUse1 = AmmoUse1;
492 }
493
494 enough = (count1 >= lAmmoUse1) | ((count2 >= AmmoUse2) << 1);
495 if (WeaponFlags & (WIF_PRIMARY_USES_BOTH << altFire))
496 {
497 enoughmask = 3;
498 }
499 else
500 {
501 enoughmask = 1 << altFire;
502 }
503 if (altFire && FindState(NAME_AltFire) == NULL)
504 { // If this weapon has no alternate fire, then there is never enough ammo for it
505 enough &= 1;
506 }
507 if (((enough & enoughmask) == enoughmask) || (enough && (WeaponFlags & WIF_AMMO_CHECKBOTH)))
508 {
509 return true;
510 }
511 // out of ammo, pick a weapon to change to
512 if (autoSwitch)
513 {
514 barrier_cast<APlayerPawn *>(Owner)->PickNewWeapon (NULL);
515 }
516 return false;
517 }
518
519 //===========================================================================
520 //
521 // AWeapon :: DepleteAmmo
522 //
523 // Use up some of the weapon's ammo. Returns true if the ammo was successfully
524 // depleted. If checkEnough is false, then the ammo will always be depleted,
525 // even if it drops below zero.
526 //
527 //===========================================================================
528
DepleteAmmo(bool altFire,bool checkEnough,int ammouse)529 bool AWeapon::DepleteAmmo (bool altFire, bool checkEnough, int ammouse)
530 {
531 if (!((dmflags & DF_INFINITE_AMMO) || (Owner->player->cheats & CF_INFINITEAMMO)))
532 {
533 if (checkEnough && !CheckAmmo (altFire ? AltFire : PrimaryFire, false, false, ammouse))
534 {
535 return false;
536 }
537 if (!altFire)
538 {
539 if (Ammo1 != NULL)
540 {
541 if (ammouse >= 0 && (WeaponFlags & WIF_DEHAMMO))
542 {
543 Ammo1->Amount -= ammouse;
544 }
545 else
546 {
547 Ammo1->Amount -= AmmoUse1;
548 }
549 }
550 if ((WeaponFlags & WIF_PRIMARY_USES_BOTH) && Ammo2 != NULL)
551 {
552 Ammo2->Amount -= AmmoUse2;
553 }
554 }
555 else
556 {
557 if (Ammo2 != NULL)
558 {
559 Ammo2->Amount -= AmmoUse2;
560 }
561 if ((WeaponFlags & WIF_ALT_USES_BOTH) && Ammo1 != NULL)
562 {
563 Ammo1->Amount -= AmmoUse1;
564 }
565 }
566 if (Ammo1 != NULL && Ammo1->Amount < 0)
567 Ammo1->Amount = 0;
568 if (Ammo2 != NULL && Ammo2->Amount < 0)
569 Ammo2->Amount = 0;
570 }
571 return true;
572 }
573
574
575 //===========================================================================
576 //
577 // AWeapon :: PostMorphWeapon
578 //
579 // Bring this weapon up after a player unmorphs.
580 //
581 //===========================================================================
582
PostMorphWeapon()583 void AWeapon::PostMorphWeapon ()
584 {
585 if (Owner == NULL)
586 {
587 return;
588 }
589 Owner->player->PendingWeapon = WP_NOCHANGE;
590 Owner->player->ReadyWeapon = this;
591 Owner->player->psprites[ps_weapon].sy = WEAPONBOTTOM;
592 Owner->player->refire = 0;
593 P_SetPsprite (Owner->player, ps_weapon, GetUpState());
594 }
595
596 //===========================================================================
597 //
598 // AWeapon :: EndPowerUp
599 //
600 // The Tome of Power just expired.
601 //
602 //===========================================================================
603
EndPowerup()604 void AWeapon::EndPowerup ()
605 {
606 if (SisterWeapon != NULL && WeaponFlags&WIF_POWERED_UP)
607 {
608 if (GetReadyState() != SisterWeapon->GetReadyState())
609 {
610 if (Owner->player->PendingWeapon == NULL ||
611 Owner->player->PendingWeapon == WP_NOCHANGE)
612 Owner->player->PendingWeapon = SisterWeapon;
613 }
614 else
615 {
616 Owner->player->ReadyWeapon = SisterWeapon;
617 }
618 }
619 }
620
621 //===========================================================================
622 //
623 // AWeapon :: GetUpState
624 //
625 //===========================================================================
626
GetUpState()627 FState *AWeapon::GetUpState ()
628 {
629 return FindState(NAME_Select);
630 }
631
632 //===========================================================================
633 //
634 // AWeapon :: GetDownState
635 //
636 //===========================================================================
637
GetDownState()638 FState *AWeapon::GetDownState ()
639 {
640 return FindState(NAME_Deselect);
641 }
642
643 //===========================================================================
644 //
645 // AWeapon :: GetReadyState
646 //
647 //===========================================================================
648
GetReadyState()649 FState *AWeapon::GetReadyState ()
650 {
651 return FindState(NAME_Ready);
652 }
653
654 //===========================================================================
655 //
656 // AWeapon :: GetAtkState
657 //
658 //===========================================================================
659
GetAtkState(bool hold)660 FState *AWeapon::GetAtkState (bool hold)
661 {
662 FState * state=NULL;
663
664 if (hold) state = FindState(NAME_Hold);
665 if (state == NULL) state = FindState(NAME_Fire);
666 return state;
667 }
668
669 //===========================================================================
670 //
671 // AWeapon :: GetAtkState
672 //
673 //===========================================================================
674
GetAltAtkState(bool hold)675 FState *AWeapon::GetAltAtkState (bool hold)
676 {
677 FState * state=NULL;
678
679 if (hold) state = FindState(NAME_AltHold);
680 if (state == NULL) state = FindState(NAME_AltFire);
681 return state;
682 }
683
684 //===========================================================================
685 //
686 // AWeapon :: GetStateForButtonName
687 //
688 //===========================================================================
689
GetStateForButtonName(FName button)690 FState *AWeapon::GetStateForButtonName (FName button)
691 {
692 return FindState(button);
693 }
694
695
696 /* Weapon giver ***********************************************************/
697
IMPLEMENT_CLASS(AWeaponGiver)698 IMPLEMENT_CLASS(AWeaponGiver)
699
700 void AWeaponGiver::Serialize(FArchive &arc)
701 {
702 Super::Serialize(arc);
703 if (SaveVersion >= 4246)
704 {
705 arc << DropAmmoFactor;
706 }
707 }
708
TryPickup(AActor * & toucher)709 bool AWeaponGiver::TryPickup(AActor *&toucher)
710 {
711 FDropItem *di = GetDropItems();
712 AWeapon *weap;
713
714 if (di != NULL)
715 {
716 const PClass *ti = PClass::FindClass(di->Name);
717 if (ti->IsDescendantOf(RUNTIME_CLASS(AWeapon)))
718 {
719 if (master == NULL)
720 {
721 master = weap = static_cast<AWeapon*>(Spawn(di->Name, 0, 0, 0, NO_REPLACE));
722 if (weap != NULL)
723 {
724 weap->ItemFlags &= ~IF_ALWAYSPICKUP; // use the flag of this item only.
725 weap->flags = (weap->flags & ~MF_DROPPED) | (this->flags & MF_DROPPED);
726
727 // If our ammo gives are non-negative, transfer them to the real weapon.
728 if (AmmoGive1 >= 0) weap->AmmoGive1 = AmmoGive1;
729 if (AmmoGive2 >= 0) weap->AmmoGive2 = AmmoGive2;
730
731 // If DropAmmoFactor is non-negative, modify the given ammo amounts.
732 if (DropAmmoFactor > 0)
733 {
734 weap->AmmoGive1 = FixedMul(weap->AmmoGive1, DropAmmoFactor);
735 weap->AmmoGive2 = FixedMul(weap->AmmoGive2, DropAmmoFactor);
736 }
737 weap->BecomeItem();
738 }
739 else return false;
740 }
741
742 weap = barrier_cast<AWeapon*>(master);
743 bool res = weap->CallTryPickup(toucher);
744 if (res)
745 {
746 GoAwayAndDie();
747 master = NULL;
748 }
749 return res;
750 }
751 }
752 return false;
753 }
754
755 /* Weapon slots ***********************************************************/
756
757 //===========================================================================
758 //
759 // FWeaponSlot :: AddWeapon
760 //
761 // Adds a weapon to the end of the slot if it isn't already in it.
762 //
763 //===========================================================================
764
AddWeapon(const char * type)765 bool FWeaponSlot::AddWeapon(const char *type)
766 {
767 return AddWeapon (PClass::FindClass (type));
768 }
769
AddWeapon(const PClass * type)770 bool FWeaponSlot::AddWeapon(const PClass *type)
771 {
772 unsigned int i;
773
774 if (type == NULL)
775 {
776 return false;
777 }
778
779 if (!type->IsDescendantOf(RUNTIME_CLASS(AWeapon)))
780 {
781 Printf("Can't add non-weapon %s to weapon slots\n", type->TypeName.GetChars());
782 return false;
783 }
784
785 for (i = 0; i < Weapons.Size(); i++)
786 {
787 if (Weapons[i].Type == type)
788 return true; // Already present
789 }
790 WeaponInfo info = { type, -1 };
791 Weapons.Push(info);
792 return true;
793 }
794
795 //===========================================================================
796 //
797 // FWeaponSlot :: AddWeaponList
798 //
799 // Appends all the weapons from the space-delimited list to this slot.
800 // Set clear to true to remove any weapons already in this slot first.
801 //
802 //===========================================================================
803
AddWeaponList(const char * list,bool clear)804 void FWeaponSlot :: AddWeaponList(const char *list, bool clear)
805 {
806 FString copy(list);
807 char *buff = copy.LockBuffer();
808 char *tok;
809
810 if (clear)
811 {
812 Clear();
813 }
814 tok = strtok(buff, " ");
815 while (tok != NULL)
816 {
817 AddWeapon(tok);
818 tok = strtok(NULL, " ");
819 }
820 }
821
822 //===========================================================================
823 //
824 // FWeaponSlot :: LocateWeapon
825 //
826 // Returns the index for the specified weapon in this slot, or -1 if it isn't
827 // in this slot.
828 //
829 //===========================================================================
830
LocateWeapon(const PClass * type)831 int FWeaponSlot::LocateWeapon(const PClass *type)
832 {
833 unsigned int i;
834
835 for (i = 0; i < Weapons.Size(); ++i)
836 {
837 if (Weapons[i].Type == type)
838 {
839 return (int)i;
840 }
841 }
842 return -1;
843 }
844
845 //===========================================================================
846 //
847 // FWeaponSlot :: PickWeapon
848 //
849 // Picks a weapon from this slot. If no weapon is selected in this slot,
850 // or the first weapon in this slot is selected, returns the last weapon.
851 // Otherwise, returns the previous weapon in this slot. This means
852 // precedence is given to the last weapon in the slot, which by convention
853 // is probably the strongest. Does not return weapons you have no ammo
854 // for or which you do not possess.
855 //
856 //===========================================================================
857
PickWeapon(player_t * player,bool checkammo)858 AWeapon *FWeaponSlot::PickWeapon(player_t *player, bool checkammo)
859 {
860 int i, j;
861
862 if (player->mo == NULL)
863 {
864 return NULL;
865 }
866 // Does this slot even have any weapons?
867 if (Weapons.Size() == 0)
868 {
869 return player->ReadyWeapon;
870 }
871 if (player->ReadyWeapon != NULL)
872 {
873 for (i = 0; (unsigned)i < Weapons.Size(); i++)
874 {
875 if (Weapons[i].Type == player->ReadyWeapon->GetClass() ||
876 (player->ReadyWeapon->WeaponFlags & WIF_POWERED_UP &&
877 player->ReadyWeapon->SisterWeapon != NULL &&
878 player->ReadyWeapon->SisterWeapon->GetClass() == Weapons[i].Type))
879 {
880 for (j = (i == 0 ? Weapons.Size() - 1 : i - 1);
881 j != i;
882 j = (j == 0 ? Weapons.Size() - 1 : j - 1))
883 {
884 AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory(Weapons[j].Type));
885
886 if (weap != NULL && weap->IsKindOf(RUNTIME_CLASS(AWeapon)))
887 {
888 if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false))
889 {
890 return weap;
891 }
892 }
893 }
894 }
895 }
896 }
897 for (i = Weapons.Size() - 1; i >= 0; i--)
898 {
899 AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory(Weapons[i].Type));
900
901 if (weap != NULL && weap->IsKindOf(RUNTIME_CLASS(AWeapon)))
902 {
903 if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false))
904 {
905 return weap;
906 }
907 }
908 }
909 return player->ReadyWeapon;
910 }
911
912 //===========================================================================
913 //
914 // FWeaponSlot :: SetInitialPositions
915 //
916 // Fills in the position field for every weapon currently in the slot based
917 // on its position in the slot. These are not scaled to [0,1] so that extra
918 // weapons can use those values to go to the start or end of the slot.
919 //
920 //===========================================================================
921
SetInitialPositions()922 void FWeaponSlot::SetInitialPositions()
923 {
924 unsigned int size = Weapons.Size(), i;
925
926 if (size == 1)
927 {
928 Weapons[0].Position = 0x8000;
929 }
930 else
931 {
932 for (i = 0; i < size; ++i)
933 {
934 Weapons[i].Position = i * 0xFF00 / (size - 1) + 0x80;
935 }
936 }
937 }
938
939 //===========================================================================
940 //
941 // FWeaponSlot :: Sort
942 //
943 // Rearranges the weapons by their position field.
944 //
945 //===========================================================================
946
Sort()947 void FWeaponSlot::Sort()
948 {
949 // This does not use qsort(), because the sort should be stable, and
950 // there is no guarantee that qsort() is stable. This insertion sort
951 // should be fine.
952 int i, j;
953
954 for (i = 1; i < (int)Weapons.Size(); ++i)
955 {
956 fixed_t pos = Weapons[i].Position;
957 const PClass *type = Weapons[i].Type;
958 for (j = i - 1; j >= 0 && Weapons[j].Position > pos; --j)
959 {
960 Weapons[j + 1] = Weapons[j];
961 }
962 Weapons[j + 1].Type = type;
963 Weapons[j + 1].Position = pos;
964 }
965 }
966
967 //===========================================================================
968 //
969 // FWeaponSlots - Copy Constructor
970 //
971 //===========================================================================
972
FWeaponSlots(const FWeaponSlots & other)973 FWeaponSlots::FWeaponSlots(const FWeaponSlots &other)
974 {
975 for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
976 {
977 Slots[i] = other.Slots[i];
978 }
979 }
980
981 //===========================================================================
982 //
983 // FWeaponSlots :: Clear
984 //
985 // Removes all weapons from every slot.
986 //
987 //===========================================================================
988
Clear()989 void FWeaponSlots::Clear()
990 {
991 for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
992 {
993 Slots[i].Clear();
994 }
995 }
996
997 //===========================================================================
998 //
999 // FWeaponSlots :: AddDefaultWeapon
1000 //
1001 // If the weapon already exists in a slot, don't add it. If it doesn't,
1002 // then add it to the specified slot.
1003 //
1004 //===========================================================================
1005
AddDefaultWeapon(int slot,const PClass * type)1006 ESlotDef FWeaponSlots::AddDefaultWeapon (int slot, const PClass *type)
1007 {
1008 int currSlot, index;
1009
1010 if (!LocateWeapon (type, &currSlot, &index))
1011 {
1012 if (slot >= 0 && slot < NUM_WEAPON_SLOTS)
1013 {
1014 bool added = Slots[slot].AddWeapon (type);
1015 return added ? SLOTDEF_Added : SLOTDEF_Full;
1016 }
1017 return SLOTDEF_Full;
1018 }
1019 return SLOTDEF_Exists;
1020 }
1021
1022 //===========================================================================
1023 //
1024 // FWeaponSlots :: LocateWeapon
1025 //
1026 // Returns true if the weapon is in a slot, false otherwise. If the weapon
1027 // is found, it can also optionally return the slot and index for it.
1028 //
1029 //===========================================================================
1030
LocateWeapon(const PClass * type,int * const slot,int * const index)1031 bool FWeaponSlots::LocateWeapon (const PClass *type, int *const slot, int *const index)
1032 {
1033 int i, j;
1034
1035 for (i = 0; i < NUM_WEAPON_SLOTS; i++)
1036 {
1037 j = Slots[i].LocateWeapon(type);
1038 if (j >= 0)
1039 {
1040 if (slot != NULL) *slot = i;
1041 if (index != NULL) *index = j;
1042 return true;
1043 }
1044 }
1045 return false;
1046 }
1047
1048 //===========================================================================
1049 //
1050 // FindMostRecentWeapon
1051 //
1052 // Locates the slot and index for the most recently selected weapon. If the
1053 // player is in the process of switching to a new weapon, that is the most
1054 // recently selected weapon. Otherwise, the current weapon is the most recent
1055 // weapon.
1056 //
1057 //===========================================================================
1058
FindMostRecentWeapon(player_t * player,int * slot,int * index)1059 static bool FindMostRecentWeapon(player_t *player, int *slot, int *index)
1060 {
1061 if (player->PendingWeapon != WP_NOCHANGE)
1062 {
1063 return player->weapons.LocateWeapon(player->PendingWeapon->GetClass(), slot, index);
1064 }
1065 else if (player->ReadyWeapon != NULL)
1066 {
1067 AWeapon *weap = player->ReadyWeapon;
1068 if (!player->weapons.LocateWeapon(weap->GetClass(), slot, index))
1069 {
1070 // If the current weapon wasn't found and is powered up,
1071 // look for its non-powered up version.
1072 if (weap->WeaponFlags & WIF_POWERED_UP && weap->SisterWeaponType != NULL)
1073 {
1074 return player->weapons.LocateWeapon(weap->SisterWeaponType, slot, index);
1075 }
1076 return false;
1077 }
1078 return true;
1079 }
1080 else
1081 {
1082 return false;
1083 }
1084 }
1085
1086 //===========================================================================
1087 //
1088 // FWeaponSlots :: PickNextWeapon
1089 //
1090 // Returns the "next" weapon for this player. If the current weapon is not
1091 // in a slot, then it just returns that weapon, since there's nothing to
1092 // consider it relative to.
1093 //
1094 //===========================================================================
1095
PickNextWeapon(player_t * player)1096 AWeapon *FWeaponSlots::PickNextWeapon(player_t *player)
1097 {
1098 int startslot, startindex;
1099 int slotschecked = 0;
1100
1101 if (player->mo == NULL)
1102 {
1103 return NULL;
1104 }
1105 if (player->ReadyWeapon == NULL || FindMostRecentWeapon(player, &startslot, &startindex))
1106 {
1107 int slot;
1108 int index;
1109
1110 if (player->ReadyWeapon == NULL)
1111 {
1112 startslot = NUM_WEAPON_SLOTS - 1;
1113 startindex = Slots[startslot].Size() - 1;
1114 }
1115
1116 slot = startslot;
1117 index = startindex;
1118 do
1119 {
1120 if (++index >= Slots[slot].Size())
1121 {
1122 index = 0;
1123 slotschecked++;
1124 if (++slot >= NUM_WEAPON_SLOTS)
1125 {
1126 slot = 0;
1127 }
1128 }
1129 const PClass *type = Slots[slot].GetWeapon(index);
1130 AWeapon *weap = static_cast<AWeapon *>(player->mo->FindInventory(type));
1131 if (weap != NULL && weap->CheckAmmo(AWeapon::EitherFire, false))
1132 {
1133 return weap;
1134 }
1135 }
1136 while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
1137 }
1138 return player->ReadyWeapon;
1139 }
1140
1141 //===========================================================================
1142 //
1143 // FWeaponSlots :: PickPrevWeapon
1144 //
1145 // Returns the "previous" weapon for this player. If the current weapon is
1146 // not in a slot, then it just returns that weapon, since there's nothing to
1147 // consider it relative to.
1148 //
1149 //===========================================================================
1150
PickPrevWeapon(player_t * player)1151 AWeapon *FWeaponSlots::PickPrevWeapon (player_t *player)
1152 {
1153 int startslot, startindex;
1154 int slotschecked = 0;
1155
1156 if (player->mo == NULL)
1157 {
1158 return NULL;
1159 }
1160 if (player->ReadyWeapon == NULL || FindMostRecentWeapon (player, &startslot, &startindex))
1161 {
1162 int slot;
1163 int index;
1164
1165 if (player->ReadyWeapon == NULL)
1166 {
1167 startslot = 0;
1168 startindex = 0;
1169 }
1170
1171 slot = startslot;
1172 index = startindex;
1173 do
1174 {
1175 if (--index < 0)
1176 {
1177 slotschecked++;
1178 if (--slot < 0)
1179 {
1180 slot = NUM_WEAPON_SLOTS - 1;
1181 }
1182 index = Slots[slot].Size() - 1;
1183 }
1184 const PClass *type = Slots[slot].GetWeapon(index);
1185 AWeapon *weap = static_cast<AWeapon *>(player->mo->FindInventory(type));
1186 if (weap != NULL && weap->CheckAmmo(AWeapon::EitherFire, false))
1187 {
1188 return weap;
1189 }
1190 }
1191 while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
1192 }
1193 return player->ReadyWeapon;
1194 }
1195
1196 //===========================================================================
1197 //
1198 // FWeaponSlots :: AddExtraWeapons
1199 //
1200 // For every weapon class for the current game, add it to its desired slot
1201 // and position within the slot. Does not first clear the slots.
1202 //
1203 //===========================================================================
1204
AddExtraWeapons()1205 void FWeaponSlots::AddExtraWeapons()
1206 {
1207 unsigned int i;
1208
1209 // Set fractional positions for current weapons.
1210 for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
1211 {
1212 Slots[i].SetInitialPositions();
1213 }
1214
1215 // Append extra weapons to the slots.
1216 for (unsigned int i = 0; i < PClass::m_Types.Size(); ++i)
1217 {
1218 PClass *cls = PClass::m_Types[i];
1219
1220 if (cls->ActorInfo != NULL &&
1221 (cls->ActorInfo->GameFilter == GAME_Any || (cls->ActorInfo->GameFilter & gameinfo.gametype)) &&
1222 cls->ActorInfo->Replacement == NULL && // Replaced weapons don't get slotted.
1223 cls->IsDescendantOf(RUNTIME_CLASS(AWeapon)) &&
1224 !(static_cast<AWeapon*>(GetDefaultByType(cls))->WeaponFlags & WIF_POWERED_UP) &&
1225 !LocateWeapon(cls, NULL, NULL) // Don't duplicate it if it's already present.
1226 )
1227 {
1228 int slot = cls->Meta.GetMetaInt(AWMETA_SlotNumber, -1);
1229 if ((unsigned)slot < NUM_WEAPON_SLOTS)
1230 {
1231 fixed_t position = cls->Meta.GetMetaFixed(AWMETA_SlotPriority, INT_MAX);
1232 FWeaponSlot::WeaponInfo info = { cls, position };
1233 Slots[slot].Weapons.Push(info);
1234 }
1235 }
1236 }
1237
1238 // Now resort every slot to put the new weapons in their proper places.
1239 for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
1240 {
1241 Slots[i].Sort();
1242 }
1243 }
1244
1245 //===========================================================================
1246 //
1247 // FWeaponSlots :: SetFromGameInfo
1248 //
1249 // If neither the player class nor any defined weapon contain a
1250 // slot assignment, use the game's defaults
1251 //
1252 //===========================================================================
1253
SetFromGameInfo()1254 void FWeaponSlots::SetFromGameInfo()
1255 {
1256 unsigned int i;
1257
1258 // Only if all slots are empty
1259 for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
1260 {
1261 if (Slots[i].Size() > 0) return;
1262 }
1263
1264 // Append extra weapons to the slots.
1265 for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
1266 {
1267 for (unsigned j = 0; j < gameinfo.DefaultWeaponSlots[i].Size(); i++)
1268 {
1269 const PClass *cls = PClass::FindClass(gameinfo.DefaultWeaponSlots[i][j]);
1270 if (cls == NULL)
1271 {
1272 Printf("Unknown weapon class '%s' found in default weapon slot assignments\n",
1273 gameinfo.DefaultWeaponSlots[i][j].GetChars());
1274 }
1275 else
1276 {
1277 Slots[i].AddWeapon(cls);
1278 }
1279 }
1280 }
1281 }
1282
1283 //===========================================================================
1284 //
1285 // FWeaponSlots :: StandardSetup
1286 //
1287 // Setup weapons in this order:
1288 // 1. Use slots from player class.
1289 // 2. Add extra weapons that specify their own slots.
1290 // 3. If all slots are empty, use the settings from the gameinfo (compatibility fallback)
1291 //
1292 //===========================================================================
1293
StandardSetup(const PClass * type)1294 void FWeaponSlots::StandardSetup(const PClass *type)
1295 {
1296 SetFromPlayer(type);
1297 AddExtraWeapons();
1298 SetFromGameInfo();
1299 }
1300
1301 //===========================================================================
1302 //
1303 // FWeaponSlots :: LocalSetup
1304 //
1305 // Setup weapons in this order:
1306 // 1. Run KEYCONF weapon commands, affecting slots accordingly.
1307 // 2. Read config slots, overriding current slots. If WeaponSection is set,
1308 // then [<WeaponSection>.<PlayerClass>.Weapons] is tried, followed by
1309 // [<WeaponSection>.Weapons] if that did not exist. If WeaponSection is
1310 // empty, then the slots are read from [<PlayerClass>.Weapons].
1311 //
1312 //===========================================================================
1313
LocalSetup(const PClass * type)1314 void FWeaponSlots::LocalSetup(const PClass *type)
1315 {
1316 P_PlaybackKeyConfWeapons(this);
1317 if (WeaponSection.IsNotEmpty())
1318 {
1319 FString sectionclass(WeaponSection);
1320 sectionclass << '.' << type->TypeName.GetChars();
1321 if (RestoreSlots(GameConfig, sectionclass) == 0)
1322 {
1323 RestoreSlots(GameConfig, WeaponSection);
1324 }
1325 }
1326 else
1327 {
1328 RestoreSlots(GameConfig, type->TypeName.GetChars());
1329 }
1330 }
1331
1332 //===========================================================================
1333 //
1334 // FWeaponSlots :: SendDifferences
1335 //
1336 // Sends the weapon slots from this instance that differ from other's.
1337 //
1338 //===========================================================================
1339
SendDifferences(int playernum,const FWeaponSlots & other)1340 void FWeaponSlots::SendDifferences(int playernum, const FWeaponSlots &other)
1341 {
1342 int i, j;
1343
1344 for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
1345 {
1346 if (other.Slots[i].Size() == Slots[i].Size())
1347 {
1348 for (j = (int)Slots[i].Size(); j-- > 0; )
1349 {
1350 if (other.Slots[i].GetWeapon(j) != Slots[i].GetWeapon(j))
1351 {
1352 break;
1353 }
1354 }
1355 if (j < 0)
1356 { // The two slots are the same.
1357 continue;
1358 }
1359 }
1360 // The slots differ. Send mine.
1361 if (playernum == consoleplayer)
1362 {
1363 Net_WriteByte(DEM_SETSLOT);
1364 }
1365 else
1366 {
1367 Net_WriteByte(DEM_SETSLOTPNUM);
1368 Net_WriteByte(playernum);
1369 }
1370 Net_WriteByte(i);
1371 Net_WriteByte(Slots[i].Size());
1372 for (j = 0; j < Slots[i].Size(); ++j)
1373 {
1374 Net_WriteWeapon(Slots[i].GetWeapon(j));
1375 }
1376 }
1377 }
1378
1379 //===========================================================================
1380 //
1381 // FWeaponSlots :: SetFromPlayer
1382 //
1383 // Sets all weapon slots according to the player class.
1384 //
1385 //===========================================================================
1386
SetFromPlayer(const PClass * type)1387 void FWeaponSlots::SetFromPlayer(const PClass *type)
1388 {
1389 Clear();
1390 for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
1391 {
1392 const char *str = type->Meta.GetMetaString(APMETA_Slot0 + i);
1393 if (str != NULL)
1394 {
1395 Slots[i].AddWeaponList(str, false);
1396 }
1397 }
1398 }
1399
1400 //===========================================================================
1401 //
1402 // FWeaponSlots :: RestoreSlots
1403 //
1404 // Reads slots from a config section. Any slots in the section override
1405 // existing slot settings, while slots not present in the config are
1406 // unaffected. Returns the number of slots read.
1407 //
1408 //===========================================================================
1409
RestoreSlots(FConfigFile * config,const char * section)1410 int FWeaponSlots::RestoreSlots(FConfigFile *config, const char *section)
1411 {
1412 FString section_name(section);
1413 const char *key, *value;
1414 int slotsread = 0;
1415
1416 section_name += ".Weapons";
1417 if (!config->SetSection(section_name))
1418 {
1419 return 0;
1420 }
1421 while (config->NextInSection (key, value))
1422 {
1423 if (strnicmp (key, "Slot[", 5) != 0 ||
1424 key[5] < '0' ||
1425 key[5] > '0'+NUM_WEAPON_SLOTS ||
1426 key[6] != ']' ||
1427 key[7] != 0)
1428 {
1429 continue;
1430 }
1431 Slots[key[5] - '0'].AddWeaponList(value, true);
1432 slotsread++;
1433 }
1434 return slotsread;
1435 }
1436
1437 //===========================================================================
1438 //
1439 // CCMD setslot
1440 //
1441 //===========================================================================
1442
PrintSettings()1443 void FWeaponSlots::PrintSettings()
1444 {
1445 for (int i = 1; i <= NUM_WEAPON_SLOTS; ++i)
1446 {
1447 int slot = i % NUM_WEAPON_SLOTS;
1448 if (Slots[slot].Size() > 0)
1449 {
1450 Printf("Slot[%d]=", slot);
1451 for (int j = 0; j < Slots[slot].Size(); ++j)
1452 {
1453 Printf("%s ", Slots[slot].GetWeapon(j)->TypeName.GetChars());
1454 }
1455 Printf("\n");
1456 }
1457 }
1458 }
1459
CCMD(setslot)1460 CCMD (setslot)
1461 {
1462 int slot;
1463
1464 if (argv.argc() < 2 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
1465 {
1466 Printf("Usage: setslot [slot] [weapons]\nCurrent slot assignments:\n");
1467 if (players[consoleplayer].mo != NULL)
1468 {
1469 FString config(GameConfig->GetConfigPath(false));
1470 Printf(TEXTCOLOR_BLUE "Add the following to " TEXTCOLOR_ORANGE "%s" TEXTCOLOR_BLUE
1471 " to retain these bindings:\n" TEXTCOLOR_NORMAL "[", config.GetChars());
1472 if (WeaponSection.IsNotEmpty())
1473 {
1474 Printf("%s.", WeaponSection.GetChars());
1475 }
1476 Printf("%s.Weapons]\n", players[consoleplayer].mo->GetClass()->TypeName.GetChars());
1477 }
1478 players[consoleplayer].weapons.PrintSettings();
1479 return;
1480 }
1481
1482 if (ParsingKeyConf)
1483 {
1484 KeyConfWeapons.Push(argv.args());
1485 }
1486 else if (PlayingKeyConf != NULL)
1487 {
1488 PlayingKeyConf->Slots[slot].Clear();
1489 for (int i = 2; i < argv.argc(); ++i)
1490 {
1491 PlayingKeyConf->Slots[slot].AddWeapon(argv[i]);
1492 }
1493 }
1494 else
1495 {
1496 if (argv.argc() == 2)
1497 {
1498 Printf ("Slot %d cleared\n", slot);
1499 }
1500
1501 Net_WriteByte(DEM_SETSLOT);
1502 Net_WriteByte(slot);
1503 Net_WriteByte(argv.argc()-2);
1504 for (int i = 2; i < argv.argc(); i++)
1505 {
1506 Net_WriteWeapon(PClass::FindClass(argv[i]));
1507 }
1508 }
1509 }
1510
1511 //===========================================================================
1512 //
1513 // CCMD addslot
1514 //
1515 //===========================================================================
1516
AddSlot(int slot,const PClass * type,bool feedback)1517 void FWeaponSlots::AddSlot(int slot, const PClass *type, bool feedback)
1518 {
1519 if (type != NULL && !Slots[slot].AddWeapon(type) && feedback)
1520 {
1521 Printf ("Could not add %s to slot %d\n", type->TypeName.GetChars(), slot);
1522 }
1523 }
1524
CCMD(addslot)1525 CCMD (addslot)
1526 {
1527 unsigned int slot;
1528
1529 if (argv.argc() != 3 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
1530 {
1531 Printf ("Usage: addslot <slot> <weapon>\n");
1532 return;
1533 }
1534
1535 if (ParsingKeyConf)
1536 {
1537 KeyConfWeapons.Push(argv.args());
1538 }
1539 else if (PlayingKeyConf != NULL)
1540 {
1541 PlayingKeyConf->AddSlot(int(slot), PClass::FindClass(argv[2]), false);
1542 }
1543 else
1544 {
1545 Net_WriteByte(DEM_ADDSLOT);
1546 Net_WriteByte(slot);
1547 Net_WriteWeapon(PClass::FindClass(argv[2]));
1548 }
1549 }
1550
1551 //===========================================================================
1552 //
1553 // CCMD weaponsection
1554 //
1555 //===========================================================================
1556
CCMD(weaponsection)1557 CCMD (weaponsection)
1558 {
1559 if (argv.argc() > 1)
1560 {
1561 WeaponSection = argv[1];
1562 }
1563 }
1564
1565 //===========================================================================
1566 //
1567 // CCMD addslotdefault
1568 //
1569 //===========================================================================
AddSlotDefault(int slot,const PClass * type,bool feedback)1570 void FWeaponSlots::AddSlotDefault(int slot, const PClass *type, bool feedback)
1571 {
1572 if (type != NULL && type->IsDescendantOf(RUNTIME_CLASS(AWeapon)))
1573 {
1574 switch (AddDefaultWeapon(slot, type))
1575 {
1576 case SLOTDEF_Full:
1577 if (feedback)
1578 {
1579 Printf ("Could not add %s to slot %d\n", type->TypeName.GetChars(), slot);
1580 }
1581 break;
1582
1583 default:
1584 case SLOTDEF_Added:
1585 break;
1586
1587 case SLOTDEF_Exists:
1588 break;
1589 }
1590 }
1591 }
1592
CCMD(addslotdefault)1593 CCMD (addslotdefault)
1594 {
1595 const PClass *type;
1596 unsigned int slot;
1597
1598 if (argv.argc() != 3 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
1599 {
1600 Printf ("Usage: addslotdefault <slot> <weapon>\n");
1601 return;
1602 }
1603
1604 type = PClass::FindClass (argv[2]);
1605 if (type == NULL || !type->IsDescendantOf (RUNTIME_CLASS(AWeapon)))
1606 {
1607 Printf ("%s is not a weapon\n", argv[2]);
1608 return;
1609 }
1610
1611 if (ParsingKeyConf)
1612 {
1613 KeyConfWeapons.Push(argv.args());
1614 }
1615 else if (PlayingKeyConf != NULL)
1616 {
1617 PlayingKeyConf->AddSlotDefault(int(slot), PClass::FindClass(argv[2]), false);
1618 }
1619 else
1620 {
1621 Net_WriteByte(DEM_ADDSLOTDEFAULT);
1622 Net_WriteByte(slot);
1623 Net_WriteWeapon(PClass::FindClass(argv[2]));
1624 }
1625 }
1626
1627 //===========================================================================
1628 //
1629 // P_PlaybackKeyConfWeapons
1630 //
1631 // Executes the weapon-related commands from a KEYCONF lump.
1632 //
1633 //===========================================================================
1634
P_PlaybackKeyConfWeapons(FWeaponSlots * slots)1635 void P_PlaybackKeyConfWeapons(FWeaponSlots *slots)
1636 {
1637 PlayingKeyConf = slots;
1638 for (unsigned int i = 0; i < KeyConfWeapons.Size(); ++i)
1639 {
1640 FString cmd(KeyConfWeapons[i]);
1641 AddCommandString(cmd.LockBuffer());
1642 }
1643 PlayingKeyConf = NULL;
1644 }
1645
1646 //===========================================================================
1647 //
1648 // P_SetupWeapons_ntohton
1649 //
1650 // Initializes the ntoh and hton maps for weapon types. To populate the ntoh
1651 // array, weapons are sorted first by game, then lexicographically. Weapons
1652 // from the current game are sorted first, followed by weapons for all other
1653 // games, and within each block, they are sorted by name.
1654 //
1655 //===========================================================================
1656
P_SetupWeapons_ntohton()1657 void P_SetupWeapons_ntohton()
1658 {
1659 unsigned int i;
1660 const PClass *cls;
1661
1662 Weapons_ntoh.Clear();
1663 Weapons_hton.Clear();
1664
1665 cls = NULL;
1666 Weapons_ntoh.Push(cls); // Index 0 is always NULL.
1667 for (i = 0; i < PClass::m_Types.Size(); ++i)
1668 {
1669 PClass *cls = PClass::m_Types[i];
1670
1671 if (cls->ActorInfo != NULL && cls->IsDescendantOf(RUNTIME_CLASS(AWeapon)))
1672 {
1673 Weapons_ntoh.Push(cls);
1674 }
1675 }
1676 qsort(&Weapons_ntoh[1], Weapons_ntoh.Size() - 1, sizeof(Weapons_ntoh[0]), ntoh_cmp);
1677 for (i = 0; i < Weapons_ntoh.Size(); ++i)
1678 {
1679 Weapons_hton[Weapons_ntoh[i]] = i;
1680 }
1681 }
1682
1683 //===========================================================================
1684 //
1685 // ntoh_cmp
1686 //
1687 // Sorting comparison function used by P_SetupWeapons_ntohton().
1688 //
1689 // Weapons that filter for the current game appear first, weapons that filter
1690 // for any game appear second, and weapons that filter for some other game
1691 // appear last. The idea here is to try to keep all the weapons that are
1692 // most likely to be used at the start of the list so that they only need
1693 // one byte to transmit across the network.
1694 //
1695 //===========================================================================
1696
ntoh_cmp(const void * a,const void * b)1697 static int STACK_ARGS ntoh_cmp(const void *a, const void *b)
1698 {
1699 const PClass *c1 = *(const PClass **)a;
1700 const PClass *c2 = *(const PClass **)b;
1701 int g1 = c1->ActorInfo->GameFilter == GAME_Any ? 1 : (c1->ActorInfo->GameFilter & gameinfo.gametype) ? 0 : 2;
1702 int g2 = c2->ActorInfo->GameFilter == GAME_Any ? 1 : (c2->ActorInfo->GameFilter & gameinfo.gametype) ? 0 : 2;
1703 if (g1 != g2)
1704 {
1705 return g1 - g2;
1706 }
1707 return stricmp(c1->TypeName.GetChars(), c2->TypeName.GetChars());
1708 }
1709
1710 //===========================================================================
1711 //
1712 // P_WriteDemoWeaponsChunk
1713 //
1714 // Store the list of weapons so that adding new ones does not automatically
1715 // break demos.
1716 //
1717 //===========================================================================
1718
P_WriteDemoWeaponsChunk(BYTE ** demo)1719 void P_WriteDemoWeaponsChunk(BYTE **demo)
1720 {
1721 WriteWord(Weapons_ntoh.Size(), demo);
1722 for (unsigned int i = 1; i < Weapons_ntoh.Size(); ++i)
1723 {
1724 WriteString(Weapons_ntoh[i]->TypeName.GetChars(), demo);
1725 }
1726 }
1727
1728 //===========================================================================
1729 //
1730 // P_ReadDemoWeaponsChunk
1731 //
1732 // Restore the list of weapons that was current at the time the demo was
1733 // recorded.
1734 //
1735 //===========================================================================
1736
P_ReadDemoWeaponsChunk(BYTE ** demo)1737 void P_ReadDemoWeaponsChunk(BYTE **demo)
1738 {
1739 int count, i;
1740 const PClass *type;
1741 const char *s;
1742
1743 count = ReadWord(demo);
1744 Weapons_ntoh.Resize(count);
1745 Weapons_hton.Clear(count);
1746
1747 Weapons_ntoh[0] = type = NULL;
1748 Weapons_hton[type] = 0;
1749
1750 for (i = 1; i < count; ++i)
1751 {
1752 s = ReadStringConst(demo);
1753 type = PClass::FindClass(s);
1754 // If a demo was recorded with a weapon that is no longer present,
1755 // should we report it?
1756 Weapons_ntoh[i] = type;
1757 if (type != NULL)
1758 {
1759 Weapons_hton[type] = i;
1760 }
1761 }
1762 }
1763
1764 //===========================================================================
1765 //
1766 // Net_WriteWeapon
1767 //
1768 //===========================================================================
1769
Net_WriteWeapon(const PClass * type)1770 void Net_WriteWeapon(const PClass *type)
1771 {
1772 int index, *index_p;
1773
1774 index_p = Weapons_hton.CheckKey(type);
1775 if (index_p == NULL)
1776 {
1777 index = 0;
1778 }
1779 else
1780 {
1781 index = *index_p;
1782 }
1783 // 32767 weapons better be enough for anybody.
1784 assert(index >= 0 && index <= 32767);
1785 if (index < 128)
1786 {
1787 Net_WriteByte(index);
1788 }
1789 else
1790 {
1791 Net_WriteByte(0x80 | index);
1792 Net_WriteByte(index >> 7);
1793 }
1794 }
1795
1796 //===========================================================================
1797 //
1798 // Net_ReadWeapon
1799 //
1800 //===========================================================================
1801
Net_ReadWeapon(BYTE ** stream)1802 const PClass *Net_ReadWeapon(BYTE **stream)
1803 {
1804 int index;
1805
1806 index = ReadByte(stream);
1807 if (index & 0x80)
1808 {
1809 index = (index & 0x7F) | (ReadByte(stream) << 7);
1810 }
1811 if ((unsigned)index >= Weapons_ntoh.Size())
1812 {
1813 return NULL;
1814 }
1815 return Weapons_ntoh[index];
1816 }
1817
1818 //===========================================================================
1819 //
1820 // A_ZoomFactor
1821 //
1822 //===========================================================================
1823
DEFINE_ACTION_FUNCTION_PARAMS(AWeapon,A_ZoomFactor)1824 DEFINE_ACTION_FUNCTION_PARAMS(AWeapon, A_ZoomFactor)
1825 {
1826 ACTION_PARAM_START(2);
1827 ACTION_PARAM_FLOAT(zoom, 0);
1828 ACTION_PARAM_INT(flags, 1);
1829
1830 if (self->player != NULL && self->player->ReadyWeapon != NULL)
1831 {
1832 zoom = 1 / clamp(zoom, 0.1f, 50.f);
1833 if (flags & 1)
1834 { // Make the zoom instant.
1835 self->player->FOV = self->player->DesiredFOV * zoom;
1836 }
1837 if (flags & 2)
1838 { // Disable pitch/yaw scaling.
1839 zoom = -zoom;
1840 }
1841 self->player->ReadyWeapon->FOVScale = zoom;
1842 }
1843 }
1844
1845 //===========================================================================
1846 //
1847 // A_SetCrosshair
1848 //
1849 //===========================================================================
1850
DEFINE_ACTION_FUNCTION_PARAMS(AWeapon,A_SetCrosshair)1851 DEFINE_ACTION_FUNCTION_PARAMS(AWeapon, A_SetCrosshair)
1852 {
1853 ACTION_PARAM_START(1);
1854 ACTION_PARAM_INT(xhair, 0);
1855
1856 if (self->player != NULL && self->player->ReadyWeapon != NULL)
1857 {
1858 self->player->ReadyWeapon->Crosshair = xhair;
1859 }
1860 }
1861