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