qzdoom/src/g_inventory/a_weapons.cpp

1590 lines
40 KiB
C++
Raw Normal View History

/*
** a_weapons.cpp
** Implements weapon handling
**
**---------------------------------------------------------------------------
** Copyright 2000-2016 Randy Heit
** Copyright 2006-2016 Cheistoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
2016-03-01 15:47:10 +00:00
#include <string.h>
#include "a_pickups.h"
#include "gi.h"
#include "d_player.h"
#include "s_sound.h"
#include "i_system.h"
#include "r_state.h"
#include "p_pspr.h"
#include "c_dispatch.h"
#include "m_misc.h"
#include "gameconfigfile.h"
#include "cmdlib.h"
#include "templates.h"
#include "sbar.h"
#include "doomstat.h"
#include "g_level.h"
#include "d_net.h"
#include "serializer.h"
#include "thingdef.h"
#include "virtual.h"
2017-01-18 13:18:17 +00:00
2016-03-01 15:47:10 +00:00
extern FFlagDef WeaponFlagDefs[];
IMPLEMENT_CLASS(AWeapon, false, true)
IMPLEMENT_POINTERS_START(AWeapon)
IMPLEMENT_POINTER(Ammo1)
IMPLEMENT_POINTER(Ammo2)
IMPLEMENT_POINTER(SisterWeapon)
IMPLEMENT_POINTERS_END
2016-03-01 15:47:10 +00:00
DEFINE_FIELD(AWeapon, WeaponFlags)
DEFINE_FIELD(AWeapon, AmmoType1)
DEFINE_FIELD(AWeapon, AmmoType2)
DEFINE_FIELD(AWeapon, AmmoGive1)
DEFINE_FIELD(AWeapon, AmmoGive2)
DEFINE_FIELD(AWeapon, MinAmmo1)
DEFINE_FIELD(AWeapon, MinAmmo2)
DEFINE_FIELD(AWeapon, AmmoUse1)
DEFINE_FIELD(AWeapon, AmmoUse2)
DEFINE_FIELD(AWeapon, Kickback)
DEFINE_FIELD(AWeapon, YAdjust)
DEFINE_FIELD(AWeapon, UpSound)
DEFINE_FIELD(AWeapon, ReadySound)
DEFINE_FIELD(AWeapon, SisterWeaponType)
DEFINE_FIELD(AWeapon, ProjectileType)
DEFINE_FIELD(AWeapon, AltProjectileType)
DEFINE_FIELD(AWeapon, SelectionOrder)
DEFINE_FIELD(AWeapon, MinSelAmmo1)
DEFINE_FIELD(AWeapon, MinSelAmmo2)
DEFINE_FIELD(AWeapon, MoveCombatDist)
DEFINE_FIELD(AWeapon, ReloadCounter)
DEFINE_FIELD(AWeapon, BobStyle)
DEFINE_FIELD(AWeapon, BobSpeed)
DEFINE_FIELD(AWeapon, BobRangeX)
DEFINE_FIELD(AWeapon, BobRangeY)
DEFINE_FIELD(AWeapon, Ammo1)
DEFINE_FIELD(AWeapon, Ammo2)
DEFINE_FIELD(AWeapon, SisterWeapon)
DEFINE_FIELD(AWeapon, FOVScale)
DEFINE_FIELD(AWeapon, Crosshair)
DEFINE_FIELD(AWeapon, GivenAsMorphWeapon)
DEFINE_FIELD(AWeapon, bAltFire)
DEFINE_FIELD(AWeapon, SlotNumber)
DEFINE_FIELD_BIT(AWeapon, WeaponFlags, bDehAmmo, WIF_DEHAMMO)
//===========================================================================
//
//
//
//===========================================================================
2016-03-01 15:47:10 +00:00
FString WeaponSection;
TArray<FString> KeyConfWeapons;
FWeaponSlots *PlayingKeyConf;
TArray<PClassActor *> Weapons_ntoh;
TMap<PClassActor *, int> Weapons_hton;
2016-03-01 15:47:10 +00:00
static int ntoh_cmp(const void *a, const void *b);
2016-03-01 15:47:10 +00:00
//===========================================================================
//
//
//
//===========================================================================
void AWeapon::Finalize(FStateDefinitions &statedef)
{
Super::Finalize(statedef);
FState *ready = FindState(NAME_Ready);
FState *select = FindState(NAME_Select);
FState *deselect = FindState(NAME_Deselect);
FState *fire = FindState(NAME_Fire);
auto TypeName = GetClass()->TypeName;
// Consider any weapon without any valid state abstract and don't output a warning
// This is for creating base classes for weapon groups that only set up some properties.
if (ready || select || deselect || fire)
{
if (!ready)
{
I_Error("Weapon %s doesn't define a ready state.", TypeName.GetChars());
}
if (!select)
{
I_Error("Weapon %s doesn't define a select state.", TypeName.GetChars());
}
if (!deselect)
{
I_Error("Weapon %s doesn't define a deselect state.", TypeName.GetChars());
}
if (!fire)
{
I_Error("Weapon %s doesn't define a fire state.", TypeName.GetChars());
}
}
}
2016-03-01 15:47:10 +00:00
//===========================================================================
//
// AWeapon :: Serialize
//
//===========================================================================
void AWeapon::Serialize(FSerializer &arc)
2016-03-01 15:47:10 +00:00
{
Super::Serialize (arc);
auto def = (AWeapon*)GetDefault();
arc("weaponflags", WeaponFlags, def->WeaponFlags)
("ammogive1", AmmoGive1, def->AmmoGive1)
("ammogive2", AmmoGive2, def->AmmoGive2)
("minammo1", MinAmmo1, def->MinAmmo1)
("minammo2", MinAmmo2, def->MinAmmo2)
("ammouse1", AmmoUse1, def->AmmoUse1)
("ammouse2", AmmoUse2, def->AmmoUse2)
("kickback", Kickback, Kickback)
("yadjust", YAdjust, def->YAdjust)
("upsound", UpSound, def->UpSound)
("readysound", ReadySound, def->ReadySound)
("selectionorder", SelectionOrder, def->SelectionOrder)
("ammo1", Ammo1)
("ammo2", Ammo2)
("sisterweapon", SisterWeapon)
("givenasmorphweapon", GivenAsMorphWeapon, def->GivenAsMorphWeapon)
("altfire", bAltFire, def->bAltFire)
("reloadcounter", ReloadCounter, def->ReloadCounter)
("bobstyle", BobStyle, def->BobStyle)
("bobspeed", BobSpeed, def->BobSpeed)
("bobrangex", BobRangeX, def->BobRangeX)
("bobrangey", BobRangeY, def->BobRangeY)
("fovscale", FOVScale, def->FOVScale)
("crosshair", Crosshair, def->Crosshair)
("minselammo1", MinSelAmmo1, def->MinSelAmmo1)
("minselammo2", MinSelAmmo2, def->MinSelAmmo2);
/* these can never change
("ammotype1", AmmoType1, def->AmmoType1)
("ammotype2", AmmoType2, def->AmmoType2)
("sisterweapontype", SisterWeaponType, def->SisterWeaponType)
("projectiletype", ProjectileType, def->ProjectileType)
("altprojectiletype", AltProjectileType, def->AltProjectileType)
("movecombatdist", MoveCombatDist, def->MoveCombatDist)
*/
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
// AWeapon :: MarkPrecacheSounds
//
//===========================================================================
void AWeapon::MarkPrecacheSounds() const
{
Super::MarkPrecacheSounds();
UpSound.MarkUsed();
ReadySound.MarkUsed();
}
//===========================================================================
//
// AWeapon :: CheckAmmo
//
// Returns true if there is enough ammo to shoot. If not, selects the
// next weapon to use.
//
//===========================================================================
bool AWeapon::CheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo, int ammocount)
{
int altFire;
int count1, count2;
int enough, enoughmask;
int lAmmoUse1;
if ((dmflags & DF_INFINITE_AMMO) || (Owner->FindInventory (PClass::FindActor(NAME_PowerInfiniteAmmo), true) != nullptr))
2016-03-01 15:47:10 +00:00
{
return true;
}
if (fireMode == EitherFire)
{
bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false);
if (!gotSome && autoSwitch)
{
barrier_cast<APlayerPawn *>(Owner)->PickNewWeapon (nullptr);
2016-03-01 15:47:10 +00:00
}
return gotSome;
}
altFire = (fireMode == AltFire);
if (!requireAmmo && (WeaponFlags & (WIF_AMMO_OPTIONAL << altFire)))
{
return true;
}
count1 = (Ammo1 != nullptr) ? Ammo1->Amount : 0;
count2 = (Ammo2 != nullptr) ? Ammo2->Amount : 0;
2016-03-01 15:47:10 +00:00
if ((WeaponFlags & WIF_DEHAMMO) && (Ammo1 == nullptr))
2016-03-01 15:47:10 +00:00
{
lAmmoUse1 = 0;
}
else if (ammocount >= 0 && (WeaponFlags & WIF_DEHAMMO))
{
lAmmoUse1 = ammocount;
}
else
{
lAmmoUse1 = AmmoUse1;
}
enough = (count1 >= lAmmoUse1) | ((count2 >= AmmoUse2) << 1);
if (WeaponFlags & (WIF_PRIMARY_USES_BOTH << altFire))
{
enoughmask = 3;
}
else
{
enoughmask = 1 << altFire;
}
if (altFire && FindState(NAME_AltFire) == nullptr)
2016-03-01 15:47:10 +00:00
{ // If this weapon has no alternate fire, then there is never enough ammo for it
enough &= 1;
}
if (((enough & enoughmask) == enoughmask) || (enough && (WeaponFlags & WIF_AMMO_CHECKBOTH)))
{
return true;
}
// out of ammo, pick a weapon to change to
if (autoSwitch)
{
barrier_cast<APlayerPawn *>(Owner)->PickNewWeapon (nullptr);
2016-03-01 15:47:10 +00:00
}
return false;
}
2016-11-26 20:39:20 +00:00
DEFINE_ACTION_FUNCTION(AWeapon, CheckAmmo)
{
PARAM_SELF_PROLOGUE(AWeapon);
PARAM_INT(mode);
PARAM_BOOL(autoswitch);
PARAM_BOOL_DEF(require);
PARAM_INT_DEF(ammocnt);
ACTION_RETURN_BOOL(self->CheckAmmo(mode, autoswitch, require, ammocnt));
}
2016-03-01 15:47:10 +00:00
//===========================================================================
//
// AWeapon :: DepleteAmmo
//
// Use up some of the weapon's ammo. Returns true if the ammo was successfully
// depleted. If checkEnough is false, then the ammo will always be depleted,
// even if it drops below zero.
//
//===========================================================================
bool AWeapon::DepleteAmmo (bool altFire, bool checkEnough, int ammouse)
{
if (!((dmflags & DF_INFINITE_AMMO) || (Owner->FindInventory (PClass::FindActor(NAME_PowerInfiniteAmmo), true) != nullptr)))
2016-03-01 15:47:10 +00:00
{
if (checkEnough && !CheckAmmo (altFire ? AltFire : PrimaryFire, false, false, ammouse))
{
return false;
}
if (!altFire)
{
if (Ammo1 != nullptr)
2016-03-01 15:47:10 +00:00
{
if (ammouse >= 0 && (WeaponFlags & WIF_DEHAMMO))
{
Ammo1->Amount -= ammouse;
}
else
{
Ammo1->Amount -= AmmoUse1;
}
}
if ((WeaponFlags & WIF_PRIMARY_USES_BOTH) && Ammo2 != nullptr)
2016-03-01 15:47:10 +00:00
{
Ammo2->Amount -= AmmoUse2;
}
}
else
{
if (Ammo2 != nullptr)
2016-03-01 15:47:10 +00:00
{
Ammo2->Amount -= AmmoUse2;
}
if ((WeaponFlags & WIF_ALT_USES_BOTH) && Ammo1 != nullptr)
2016-03-01 15:47:10 +00:00
{
Ammo1->Amount -= AmmoUse1;
}
}
if (Ammo1 != nullptr && Ammo1->Amount < 0)
2016-03-01 15:47:10 +00:00
Ammo1->Amount = 0;
if (Ammo2 != nullptr && Ammo2->Amount < 0)
2016-03-01 15:47:10 +00:00
Ammo2->Amount = 0;
}
return true;
}
DEFINE_ACTION_FUNCTION(AWeapon, DepleteAmmo)
{
PARAM_SELF_PROLOGUE(AWeapon);
PARAM_BOOL(altfire);
PARAM_BOOL_DEF(checkenough);
PARAM_INT_DEF(ammouse);
ACTION_RETURN_BOOL(self->DepleteAmmo(altfire, checkenough, ammouse));
}
2016-03-01 15:47:10 +00:00
//===========================================================================
//
// AWeapon :: PostMorphWeapon
//
// Bring this weapon up after a player unmorphs.
//
//===========================================================================
void AWeapon::PostMorphWeapon ()
{
DPSprite *pspr;
if (Owner == nullptr)
2016-03-01 15:47:10 +00:00
{
return;
}
Owner->player->PendingWeapon = WP_NOCHANGE;
Owner->player->ReadyWeapon = this;
Owner->player->refire = 0;
pspr = Owner->player->GetPSprite(PSP_WEAPON);
pspr->y = WEAPONBOTTOM;
pspr->ResetInterpolation();
pspr->SetState(GetUpState());
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
// AWeapon :: GetUpState
//
//===========================================================================
FState *AWeapon::GetUpState ()
{
IFVIRTUAL(AWeapon, GetUpState)
{
VMValue params[1] = { (DObject*)this };
VMReturn ret;
FState *retval;
ret.PointerAt((void**)&retval);
GlobalVMStack.Call(func, params, 1, &ret, 1, nullptr);
return retval;
}
2016-11-26 21:25:49 +00:00
return nullptr;
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
// AWeapon :: GetDownState
//
//===========================================================================
FState *AWeapon::GetDownState ()
{
IFVIRTUAL(AWeapon, GetDownState)
{
VMValue params[1] = { (DObject*)this };
VMReturn ret;
FState *retval;
ret.PointerAt((void**)&retval);
GlobalVMStack.Call(func, params, 1, &ret, 1, nullptr);
return retval;
}
2016-11-26 21:25:49 +00:00
return nullptr;
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
// AWeapon :: GetReadyState
//
//===========================================================================
FState *AWeapon::GetReadyState ()
{
IFVIRTUAL(AWeapon, GetReadyState)
{
VMValue params[1] = { (DObject*)this };
VMReturn ret;
FState *retval;
ret.PointerAt((void**)&retval);
GlobalVMStack.Call(func, params, 1, &ret, 1, nullptr);
return retval;
}
2016-11-26 21:25:49 +00:00
return nullptr;
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
// AWeapon :: GetAtkState
//
//===========================================================================
FState *AWeapon::GetAtkState (bool hold)
{
IFVIRTUAL(AWeapon, GetAtkState)
{
VMValue params[2] = { (DObject*)this, hold };
VMReturn ret;
FState *retval;
ret.PointerAt((void**)&retval);
GlobalVMStack.Call(func, params, 2, &ret, 1, nullptr);
return retval;
}
2016-11-26 21:25:49 +00:00
return nullptr;
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
// AWeapon :: GetAtkState
//
//===========================================================================
FState *AWeapon::GetAltAtkState (bool hold)
{
IFVIRTUAL(AWeapon, GetAltAtkState)
{
VMValue params[2] = { (DObject*)this, hold };
VMReturn ret;
FState *retval;
ret.PointerAt((void**)&retval);
GlobalVMStack.Call(func, params, 2, &ret, 1, nullptr);
return retval;
}
2016-11-26 21:25:49 +00:00
return nullptr;
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
// AWeapon :: GetStateForButtonName
//
//===========================================================================
FState *AWeapon::GetStateForButtonName (FName button)
{
return FindState(button);
}
/* Weapon slots ***********************************************************/
//===========================================================================
//
// FWeaponSlot :: AddWeapon
//
// Adds a weapon to the end of the slot if it isn't already in it.
//
//===========================================================================
bool FWeaponSlot::AddWeapon(const char *type)
{
return AddWeapon(static_cast<PClassActor *>(PClass::FindClass(type)));
2016-03-01 15:47:10 +00:00
}
bool FWeaponSlot::AddWeapon(PClassActor *type)
2016-03-01 15:47:10 +00:00
{
unsigned int i;
if (type == nullptr)
2016-03-01 15:47:10 +00:00
{
return false;
}
if (!type->IsDescendantOf(NAME_Weapon))
2016-03-01 15:47:10 +00:00
{
Printf("Can't add non-weapon %s to weapon slots\n", type->TypeName.GetChars());
return false;
}
for (i = 0; i < Weapons.Size(); i++)
{
if (Weapons[i].Type == type)
return true; // Already present
}
WeaponInfo info = { type, -1 };
Weapons.Push(info);
return true;
}
//===========================================================================
//
// FWeaponSlot :: AddWeaponList
//
// Appends all the weapons from the space-delimited list to this slot.
// Set clear to true to remove any weapons already in this slot first.
//
//===========================================================================
void FWeaponSlot :: AddWeaponList(const char *list, bool clear)
{
FString copy(list);
char *buff = copy.LockBuffer();
char *tok;
if (clear)
{
Clear();
}
tok = strtok(buff, " ");
while (tok != nullptr)
2016-03-01 15:47:10 +00:00
{
AddWeapon(tok);
tok = strtok(nullptr, " ");
2016-03-01 15:47:10 +00:00
}
}
//===========================================================================
//
// FWeaponSlot :: LocateWeapon
//
// Returns the index for the specified weapon in this slot, or -1 if it isn't
// in this slot.
//
//===========================================================================
int FWeaponSlot::LocateWeapon(PClassActor *type)
2016-03-01 15:47:10 +00:00
{
unsigned int i;
for (i = 0; i < Weapons.Size(); ++i)
{
if (Weapons[i].Type == type)
{
return (int)i;
}
}
return -1;
}
//===========================================================================
//
// FWeaponSlot :: PickWeapon
//
// Picks a weapon from this slot. If no weapon is selected in this slot,
// or the first weapon in this slot is selected, returns the last weapon.
// Otherwise, returns the previous weapon in this slot. This means
// precedence is given to the last weapon in the slot, which by convention
// is probably the strongest. Does not return weapons you have no ammo
// for or which you do not possess.
//
//===========================================================================
AWeapon *FWeaponSlot::PickWeapon(player_t *player, bool checkammo)
{
int i, j;
if (player->mo == nullptr)
2016-03-01 15:47:10 +00:00
{
return nullptr;
2016-03-01 15:47:10 +00:00
}
// Does this slot even have any weapons?
if (Weapons.Size() == 0)
{
return player->ReadyWeapon;
}
if (player->ReadyWeapon != nullptr)
2016-03-01 15:47:10 +00:00
{
for (i = 0; (unsigned)i < Weapons.Size(); i++)
{
if (Weapons[i].Type == player->ReadyWeapon->GetClass() ||
(player->ReadyWeapon->WeaponFlags & WIF_POWERED_UP &&
player->ReadyWeapon->SisterWeapon != nullptr &&
2016-03-01 15:47:10 +00:00
player->ReadyWeapon->SisterWeapon->GetClass() == Weapons[i].Type))
{
for (j = (i == 0 ? Weapons.Size() - 1 : i - 1);
j != i;
j = (j == 0 ? Weapons.Size() - 1 : j - 1))
{
AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory(Weapons[j].Type));
if (weap != nullptr && weap->IsKindOf(NAME_Weapon))
2016-03-01 15:47:10 +00:00
{
if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false))
{
return weap;
}
}
}
}
}
}
for (i = Weapons.Size() - 1; i >= 0; i--)
{
AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory(Weapons[i].Type));
if (weap != nullptr && weap->IsKindOf(NAME_Weapon))
2016-03-01 15:47:10 +00:00
{
if (!checkammo || weap->CheckAmmo(AWeapon::EitherFire, false))
{
return weap;
}
}
}
return player->ReadyWeapon;
}
//===========================================================================
//
// FWeaponSlot :: SetInitialPositions
//
// Fills in the position field for every weapon currently in the slot based
// on its position in the slot. These are not scaled to [0,1] so that extra
// weapons can use those values to go to the start or end of the slot.
//
//===========================================================================
void FWeaponSlot::SetInitialPositions()
{
unsigned int size = Weapons.Size(), i;
if (size == 1)
{
Weapons[0].Position = 0x8000;
}
else
{
for (i = 0; i < size; ++i)
{
Weapons[i].Position = i * 0xFF00 / (size - 1) + 0x80;
}
}
}
//===========================================================================
//
// FWeaponSlot :: Sort
//
// Rearranges the weapons by their position field.
//
//===========================================================================
void FWeaponSlot::Sort()
{
// This does not use qsort(), because the sort should be stable, and
// there is no guarantee that qsort() is stable. This insertion sort
// should be fine.
int i, j;
for (i = 1; i < (int)Weapons.Size(); ++i)
{
2016-03-23 19:45:48 +00:00
int pos = Weapons[i].Position;
PClassActor *type = Weapons[i].Type;
2016-03-01 15:47:10 +00:00
for (j = i - 1; j >= 0 && Weapons[j].Position > pos; --j)
{
Weapons[j + 1] = Weapons[j];
}
Weapons[j + 1].Type = type;
Weapons[j + 1].Position = pos;
}
}
//===========================================================================
//
// FWeaponSlots - Copy Constructor
//
//===========================================================================
FWeaponSlots::FWeaponSlots(const FWeaponSlots &other)
{
for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
{
Slots[i] = other.Slots[i];
}
}
//===========================================================================
//
// FWeaponSlots :: Clear
//
// Removes all weapons from every slot.
//
//===========================================================================
void FWeaponSlots::Clear()
{
for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
{
Slots[i].Clear();
}
}
//===========================================================================
//
// FWeaponSlots :: AddDefaultWeapon
//
// If the weapon already exists in a slot, don't add it. If it doesn't,
// then add it to the specified slot.
//
//===========================================================================
ESlotDef FWeaponSlots::AddDefaultWeapon (int slot, PClassActor *type)
2016-03-01 15:47:10 +00:00
{
int currSlot, index;
if (!LocateWeapon (type, &currSlot, &index))
{
if (slot >= 0 && slot < NUM_WEAPON_SLOTS)
{
bool added = Slots[slot].AddWeapon (type);
return added ? SLOTDEF_Added : SLOTDEF_Full;
}
return SLOTDEF_Full;
}
return SLOTDEF_Exists;
}
//===========================================================================
//
// FWeaponSlots :: LocateWeapon
//
// Returns true if the weapon is in a slot, false otherwise. If the weapon
// is found, it can also optionally return the slot and index for it.
//
//===========================================================================
bool FWeaponSlots::LocateWeapon (PClassActor *type, int *const slot, int *const index)
2016-03-01 15:47:10 +00:00
{
int i, j;
for (i = 0; i < NUM_WEAPON_SLOTS; i++)
{
j = Slots[i].LocateWeapon(type);
if (j >= 0)
{
if (slot != nullptr) *slot = i;
if (index != nullptr) *index = j;
2016-03-01 15:47:10 +00:00
return true;
}
}
return false;
}
DEFINE_ACTION_FUNCTION(FWeaponSlots, LocateWeapon)
{
PARAM_SELF_STRUCT_PROLOGUE(FWeaponSlots);
PARAM_CLASS(weap, AWeapon);
int slot = 0, index = 0;
bool retv = self->LocateWeapon(weap, &slot, &index);
if (numret >= 1) ret[0].SetInt(retv);
if (numret >= 2) ret[1].SetInt(slot);
if (numret >= 3) ret[2].SetInt(index);
return MIN(numret, 3);
}
2016-03-01 15:47:10 +00:00
//===========================================================================
//
// FindMostRecentWeapon
//
// Locates the slot and index for the most recently selected weapon. If the
// player is in the process of switching to a new weapon, that is the most
// recently selected weapon. Otherwise, the current weapon is the most recent
// weapon.
//
//===========================================================================
static bool FindMostRecentWeapon(player_t *player, int *slot, int *index)
{
if (player->PendingWeapon != WP_NOCHANGE)
{
return player->weapons.LocateWeapon(player->PendingWeapon->GetClass(), slot, index);
}
else if (player->ReadyWeapon != nullptr)
2016-03-01 15:47:10 +00:00
{
AWeapon *weap = player->ReadyWeapon;
if (!player->weapons.LocateWeapon(weap->GetClass(), slot, index))
{
// If the current weapon wasn't found and is powered up,
// look for its non-powered up version.
if (weap->WeaponFlags & WIF_POWERED_UP && weap->SisterWeaponType != nullptr)
2016-03-01 15:47:10 +00:00
{
return player->weapons.LocateWeapon(weap->SisterWeaponType, slot, index);
}
return false;
}
return true;
}
else
{
return false;
}
}
//===========================================================================
//
// FWeaponSlots :: PickNextWeapon
//
// Returns the "next" weapon for this player. If the current weapon is not
// in a slot, then it just returns that weapon, since there's nothing to
// consider it relative to.
//
//===========================================================================
AWeapon *FWeaponSlots::PickNextWeapon(player_t *player)
{
int startslot, startindex;
int slotschecked = 0;
if (player->mo == nullptr)
2016-03-01 15:47:10 +00:00
{
return nullptr;
2016-03-01 15:47:10 +00:00
}
if (player->ReadyWeapon == nullptr || FindMostRecentWeapon(player, &startslot, &startindex))
2016-03-01 15:47:10 +00:00
{
int slot;
int index;
if (player->ReadyWeapon == nullptr)
2016-03-01 15:47:10 +00:00
{
startslot = NUM_WEAPON_SLOTS - 1;
startindex = Slots[startslot].Size() - 1;
}
slot = startslot;
index = startindex;
do
{
if (++index >= Slots[slot].Size())
{
index = 0;
slotschecked++;
if (++slot >= NUM_WEAPON_SLOTS)
{
slot = 0;
}
}
PClassActor *type = Slots[slot].GetWeapon(index);
2016-03-01 15:47:10 +00:00
AWeapon *weap = static_cast<AWeapon *>(player->mo->FindInventory(type));
if (weap != nullptr && weap->CheckAmmo(AWeapon::EitherFire, false))
2016-03-01 15:47:10 +00:00
{
return weap;
}
}
while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
}
return player->ReadyWeapon;
}
//===========================================================================
//
// FWeaponSlots :: PickPrevWeapon
//
// Returns the "previous" weapon for this player. If the current weapon is
// not in a slot, then it just returns that weapon, since there's nothing to
// consider it relative to.
//
//===========================================================================
AWeapon *FWeaponSlots::PickPrevWeapon (player_t *player)
{
int startslot, startindex;
int slotschecked = 0;
if (player->mo == nullptr)
2016-03-01 15:47:10 +00:00
{
return nullptr;
2016-03-01 15:47:10 +00:00
}
if (player->ReadyWeapon == nullptr || FindMostRecentWeapon (player, &startslot, &startindex))
2016-03-01 15:47:10 +00:00
{
int slot;
int index;
if (player->ReadyWeapon == nullptr)
2016-03-01 15:47:10 +00:00
{
startslot = 0;
startindex = 0;
}
slot = startslot;
index = startindex;
do
{
if (--index < 0)
{
slotschecked++;
if (--slot < 0)
{
slot = NUM_WEAPON_SLOTS - 1;
}
index = Slots[slot].Size() - 1;
}
PClassActor *type = Slots[slot].GetWeapon(index);
2016-03-01 15:47:10 +00:00
AWeapon *weap = static_cast<AWeapon *>(player->mo->FindInventory(type));
if (weap != nullptr && weap->CheckAmmo(AWeapon::EitherFire, false))
2016-03-01 15:47:10 +00:00
{
return weap;
}
}
while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
}
return player->ReadyWeapon;
}
//===========================================================================
//
// FWeaponSlots :: AddExtraWeapons
//
// For every weapon class for the current game, add it to its desired slot
// and position within the slot. Does not first clear the slots.
//
//===========================================================================
void FWeaponSlots::AddExtraWeapons()
{
unsigned int i;
// Set fractional positions for current weapons.
for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
{
Slots[i].SetInitialPositions();
}
// Append extra weapons to the slots.
for (unsigned int i = 0; i < PClassActor::AllActorClasses.Size(); ++i)
{
PClassActor *cls = PClassActor::AllActorClasses[i];
2016-03-01 15:47:10 +00:00
if (!cls->IsDescendantOf(NAME_Weapon))
2016-03-01 15:47:10 +00:00
{
continue;
}
auto weapdef = ((AWeapon*)GetDefaultByType(cls));
if ((cls->GameFilter == GAME_Any || (cls->GameFilter & gameinfo.gametype)) &&
cls->ActorInfo()->Replacement == nullptr && // Replaced weapons don't get slotted.
!(weapdef->WeaponFlags & WIF_POWERED_UP) &&
!LocateWeapon(cls, nullptr, nullptr) // Don't duplicate it if it's already present.
2016-03-01 15:47:10 +00:00
)
{
int slot = weapdef->SlotNumber;
2016-03-01 15:47:10 +00:00
if ((unsigned)slot < NUM_WEAPON_SLOTS)
{
FWeaponSlot::WeaponInfo info = { cls, weapdef->SlotPriority };
2016-03-01 15:47:10 +00:00
Slots[slot].Weapons.Push(info);
}
}
}
// Now resort every slot to put the new weapons in their proper places.
for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
{
Slots[i].Sort();
}
}
//===========================================================================
//
// FWeaponSlots :: SetFromGameInfo
//
// If neither the player class nor any defined weapon contain a
// slot assignment, use the game's defaults
//
//===========================================================================
void FWeaponSlots::SetFromGameInfo()
{
unsigned int i;
// Only if all slots are empty
for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
{
if (Slots[i].Size() > 0) return;
}
// Append extra weapons to the slots.
for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
{
for (unsigned j = 0; j < gameinfo.DefaultWeaponSlots[i].Size(); j++)
2016-03-01 15:47:10 +00:00
{
PClassActor *cls = PClass::FindActor(gameinfo.DefaultWeaponSlots[i][j]);
if (cls == nullptr)
2016-03-01 15:47:10 +00:00
{
Printf("Unknown weapon class '%s' found in default weapon slot assignments\n",
gameinfo.DefaultWeaponSlots[i][j].GetChars());
}
else
{
Slots[i].AddWeapon(cls);
}
}
}
}
//===========================================================================
//
// FWeaponSlots :: StandardSetup
//
// Setup weapons in this order:
// 1. Use slots from player class.
// 2. Add extra weapons that specify their own slots.
// 3. If all slots are empty, use the settings from the gameinfo (compatibility fallback)
//
//===========================================================================
void FWeaponSlots::StandardSetup(PClassActor *type)
2016-03-01 15:47:10 +00:00
{
SetFromPlayer(type);
AddExtraWeapons();
SetFromGameInfo();
}
//===========================================================================
//
// FWeaponSlots :: LocalSetup
//
// Setup weapons in this order:
// 1. Run KEYCONF weapon commands, affecting slots accordingly.
// 2. Read config slots, overriding current slots. If WeaponSection is set,
// then [<WeaponSection>.<PlayerClass>.Weapons] is tried, followed by
// [<WeaponSection>.Weapons] if that did not exist. If WeaponSection is
// empty, then the slots are read from [<PlayerClass>.Weapons].
//
//===========================================================================
void FWeaponSlots::LocalSetup(PClassActor *type)
{
P_PlaybackKeyConfWeapons(this);
if (WeaponSection.IsNotEmpty())
{
FString sectionclass(WeaponSection);
sectionclass << '.' << type->TypeName.GetChars();
if (RestoreSlots(GameConfig, sectionclass) == 0)
{
RestoreSlots(GameConfig, WeaponSection);
}
}
else
{
RestoreSlots(GameConfig, type->TypeName.GetChars());
}
}
//===========================================================================
//
// FWeaponSlots :: SendDifferences
//
// Sends the weapon slots from this instance that differ from other's.
//
//===========================================================================
void FWeaponSlots::SendDifferences(int playernum, const FWeaponSlots &other)
{
int i, j;
for (i = 0; i < NUM_WEAPON_SLOTS; ++i)
{
if (other.Slots[i].Size() == Slots[i].Size())
{
for (j = (int)Slots[i].Size(); j-- > 0; )
{
if (other.Slots[i].GetWeapon(j) != Slots[i].GetWeapon(j))
{
break;
}
}
if (j < 0)
{ // The two slots are the same.
continue;
}
}
// The slots differ. Send mine.
if (playernum == consoleplayer)
{
Net_WriteByte(DEM_SETSLOT);
}
else
{
Net_WriteByte(DEM_SETSLOTPNUM);
Net_WriteByte(playernum);
}
Net_WriteByte(i);
Net_WriteByte(Slots[i].Size());
for (j = 0; j < Slots[i].Size(); ++j)
{
Net_WriteWeapon(Slots[i].GetWeapon(j));
}
}
}
//===========================================================================
//
// FWeaponSlots :: SetFromPlayer
//
// Sets all weapon slots according to the player class.
//
//===========================================================================
void FWeaponSlots::SetFromPlayer(PClassActor *type)
2016-03-01 15:47:10 +00:00
{
Clear();
auto Slot = ((APlayerPawn*)GetDefaultByType(type))->Slot;
2016-03-01 15:47:10 +00:00
for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
{
if (Slot[i] != NAME_None)
2016-03-01 15:47:10 +00:00
{
Slots[i].AddWeaponList(Slot[i], false);
2016-03-01 15:47:10 +00:00
}
}
}
//===========================================================================
//
// FWeaponSlots :: RestoreSlots
//
// Reads slots from a config section. Any slots in the section override
// existing slot settings, while slots not present in the config are
// unaffected. Returns the number of slots read.
//
//===========================================================================
int FWeaponSlots::RestoreSlots(FConfigFile *config, const char *section)
{
FString section_name(section);
const char *key, *value;
int slotsread = 0;
section_name += ".Weapons";
if (!config->SetSection(section_name))
{
return 0;
}
while (config->NextInSection (key, value))
{
if (strnicmp (key, "Slot[", 5) != 0 ||
key[5] < '0' ||
key[5] > '0'+NUM_WEAPON_SLOTS ||
key[6] != ']' ||
key[7] != 0)
{
continue;
}
Slots[key[5] - '0'].AddWeaponList(value, true);
slotsread++;
}
return slotsread;
}
//===========================================================================
//
// CCMD setslot
//
//===========================================================================
void FWeaponSlots::PrintSettings()
{
for (int i = 1; i <= NUM_WEAPON_SLOTS; ++i)
{
int slot = i % NUM_WEAPON_SLOTS;
if (Slots[slot].Size() > 0)
{
Printf("Slot[%d]=", slot);
for (int j = 0; j < Slots[slot].Size(); ++j)
{
Printf("%s ", Slots[slot].GetWeapon(j)->TypeName.GetChars());
}
Printf("\n");
}
}
}
CCMD (setslot)
{
int slot;
if (argv.argc() < 2 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
{
Printf("Usage: setslot [slot] [weapons]\nCurrent slot assignments:\n");
if (players[consoleplayer].mo != nullptr)
2016-03-01 15:47:10 +00:00
{
FString config(GameConfig->GetConfigPath(false));
Printf(TEXTCOLOR_BLUE "Add the following to " TEXTCOLOR_ORANGE "%s" TEXTCOLOR_BLUE
" to retain these bindings:\n" TEXTCOLOR_NORMAL "[", config.GetChars());
if (WeaponSection.IsNotEmpty())
{
Printf("%s.", WeaponSection.GetChars());
}
Printf("%s.Weapons]\n", players[consoleplayer].mo->GetClass()->TypeName.GetChars());
}
players[consoleplayer].weapons.PrintSettings();
return;
}
if (ParsingKeyConf)
{
KeyConfWeapons.Push(argv.args());
}
else if (PlayingKeyConf != nullptr)
2016-03-01 15:47:10 +00:00
{
PlayingKeyConf->Slots[slot].Clear();
for (int i = 2; i < argv.argc(); ++i)
{
PlayingKeyConf->Slots[slot].AddWeapon(argv[i]);
}
}
else
{
if (argv.argc() == 2)
{
Printf ("Slot %d cleared\n", slot);
}
Net_WriteByte(DEM_SETSLOT);
Net_WriteByte(slot);
Net_WriteByte(argv.argc()-2);
for (int i = 2; i < argv.argc(); i++)
{
Net_WriteWeapon(dyn_cast<PClassActor>(PClass::FindClass(argv[i])));
2016-03-01 15:47:10 +00:00
}
}
}
//===========================================================================
//
// CCMD addslot
//
//===========================================================================
void FWeaponSlots::AddSlot(int slot, PClassActor *type, bool feedback)
2016-03-01 15:47:10 +00:00
{
if (type != nullptr && !Slots[slot].AddWeapon(type) && feedback)
2016-03-01 15:47:10 +00:00
{
Printf ("Could not add %s to slot %d\n", type->TypeName.GetChars(), slot);
}
}
CCMD (addslot)
{
unsigned int slot;
if (argv.argc() != 3 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
{
Printf ("Usage: addslot <slot> <weapon>\n");
return;
}
PClassActor *type= dyn_cast<PClassActor>(PClass::FindClass(argv[2]));
if (type == nullptr)
2016-03-01 15:47:10 +00:00
{
Printf("%s is not a weapon\n", argv[2]);
return;
}
if (ParsingKeyConf)
{
KeyConfWeapons.Push(argv.args());
}
else if (PlayingKeyConf != nullptr)
2016-03-01 15:47:10 +00:00
{
PlayingKeyConf->AddSlot(int(slot), type, false);
}
else
{
Net_WriteByte(DEM_ADDSLOT);
Net_WriteByte(slot);
Net_WriteWeapon(type);
}
}
//===========================================================================
//
// CCMD weaponsection
//
//===========================================================================
CCMD (weaponsection)
{
if (argv.argc() > 1)
{
WeaponSection = argv[1];
}
}
//===========================================================================
//
// CCMD addslotdefault
//
//===========================================================================
void FWeaponSlots::AddSlotDefault(int slot, PClassActor *type, bool feedback)
2016-03-01 15:47:10 +00:00
{
if (type != nullptr && type->IsDescendantOf(NAME_Weapon))
2016-03-01 15:47:10 +00:00
{
switch (AddDefaultWeapon(slot, type))
{
case SLOTDEF_Full:
if (feedback)
{
Printf ("Could not add %s to slot %d\n", type->TypeName.GetChars(), slot);
}
break;
default:
case SLOTDEF_Added:
break;
case SLOTDEF_Exists:
break;
}
}
}
CCMD (addslotdefault)
{
PClassActor *type;
2016-03-01 15:47:10 +00:00
unsigned int slot;
if (argv.argc() != 3 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
{
Printf ("Usage: addslotdefault <slot> <weapon>\n");
return;
}
type = dyn_cast<PClassActor>(PClass::FindClass(argv[2]));
if (type == nullptr)
2016-03-01 15:47:10 +00:00
{
Printf ("%s is not a weapon\n", argv[2]);
return;
}
if (ParsingKeyConf)
{
KeyConfWeapons.Push(argv.args());
}
else if (PlayingKeyConf != nullptr)
2016-03-01 15:47:10 +00:00
{
PlayingKeyConf->AddSlotDefault(int(slot), type, false);
}
else
{
Net_WriteByte(DEM_ADDSLOTDEFAULT);
Net_WriteByte(slot);
Net_WriteWeapon(type);
}
}
//===========================================================================
//
// P_PlaybackKeyConfWeapons
//
// Executes the weapon-related commands from a KEYCONF lump.
//
//===========================================================================
void P_PlaybackKeyConfWeapons(FWeaponSlots *slots)
{
PlayingKeyConf = slots;
for (unsigned int i = 0; i < KeyConfWeapons.Size(); ++i)
{
FString cmd(KeyConfWeapons[i]);
AddCommandString(cmd.LockBuffer());
}
PlayingKeyConf = nullptr;
2016-03-01 15:47:10 +00:00
}
//===========================================================================
//
// P_SetupWeapons_ntohton
//
// Initializes the ntoh and hton maps for weapon types. To populate the ntoh
// array, weapons are sorted first by game, then lexicographically. Weapons
// from the current game are sorted first, followed by weapons for all other
// games, and within each block, they are sorted by name.
//
//===========================================================================
void P_SetupWeapons_ntohton()
{
unsigned int i;
PClassActor *cls;
2016-03-01 15:47:10 +00:00
Weapons_ntoh.Clear();
Weapons_hton.Clear();
cls = nullptr;
Weapons_ntoh.Push(cls); // Index 0 is always nullptr.
2016-03-01 15:47:10 +00:00
for (i = 0; i < PClassActor::AllActorClasses.Size(); ++i)
{
PClassActor *cls = PClassActor::AllActorClasses[i];
if (cls->IsDescendantOf(NAME_Weapon))
2016-03-01 15:47:10 +00:00
{
Weapons_ntoh.Push(static_cast<PClassActor *>(cls));
2016-03-01 15:47:10 +00:00
}
}
qsort(&Weapons_ntoh[1], Weapons_ntoh.Size() - 1, sizeof(Weapons_ntoh[0]), ntoh_cmp);
for (i = 0; i < Weapons_ntoh.Size(); ++i)
{
Weapons_hton[Weapons_ntoh[i]] = i;
}
}
//===========================================================================
//
// ntoh_cmp
//
// Sorting comparison function used by P_SetupWeapons_ntohton().
//
// Weapons that filter for the current game appear first, weapons that filter
// for any game appear second, and weapons that filter for some other game
// appear last. The idea here is to try to keep all the weapons that are
// most likely to be used at the start of the list so that they only need
// one byte to transmit across the network.
//
//===========================================================================
static int ntoh_cmp(const void *a, const void *b)
2016-03-01 15:47:10 +00:00
{
PClassActor *c1 = *(PClassActor **)a;
PClassActor *c2 = *(PClassActor **)b;
2016-03-01 15:47:10 +00:00
int g1 = c1->GameFilter == GAME_Any ? 1 : (c1->GameFilter & gameinfo.gametype) ? 0 : 2;
int g2 = c2->GameFilter == GAME_Any ? 1 : (c2->GameFilter & gameinfo.gametype) ? 0 : 2;
if (g1 != g2)
{
return g1 - g2;
}
return stricmp(c1->TypeName.GetChars(), c2->TypeName.GetChars());
}
//===========================================================================
//
// P_WriteDemoWeaponsChunk
//
// Store the list of weapons so that adding new ones does not automatically
// break demos.
//
//===========================================================================
void P_WriteDemoWeaponsChunk(uint8_t **demo)
2016-03-01 15:47:10 +00:00
{
WriteWord(Weapons_ntoh.Size(), demo);
for (unsigned int i = 1; i < Weapons_ntoh.Size(); ++i)
{
WriteString(Weapons_ntoh[i]->TypeName.GetChars(), demo);
}
}
//===========================================================================
//
// P_ReadDemoWeaponsChunk
//
// Restore the list of weapons that was current at the time the demo was
// recorded.
//
//===========================================================================
void P_ReadDemoWeaponsChunk(uint8_t **demo)
2016-03-01 15:47:10 +00:00
{
int count, i;
PClassActor *type;
2016-03-01 15:47:10 +00:00
const char *s;
count = ReadWord(demo);
Weapons_ntoh.Resize(count);
Weapons_hton.Clear(count);
Weapons_ntoh[0] = type = nullptr;
2016-03-01 15:47:10 +00:00
Weapons_hton[type] = 0;
for (i = 1; i < count; ++i)
{
s = ReadStringConst(demo);
type = dyn_cast<PClassActor>(PClass::FindClass(s));
2016-03-01 15:47:10 +00:00
// If a demo was recorded with a weapon that is no longer present,
// should we report it?
Weapons_ntoh[i] = type;
if (type != nullptr)
2016-03-01 15:47:10 +00:00
{
Weapons_hton[type] = i;
}
}
}
//===========================================================================
//
// Net_WriteWeapon
//
//===========================================================================
void Net_WriteWeapon(PClassActor *type)
2016-03-01 15:47:10 +00:00
{
int index, *index_p;
index_p = Weapons_hton.CheckKey(type);
if (index_p == nullptr)
2016-03-01 15:47:10 +00:00
{
index = 0;
}
else
{
index = *index_p;
}
// 32767 weapons better be enough for anybody.
assert(index >= 0 && index <= 32767);
if (index < 128)
{
Net_WriteByte(index);
}
else
{
Net_WriteByte(0x80 | index);
Net_WriteByte(index >> 7);
}
}
//===========================================================================
//
// Net_ReadWeapon
//
//===========================================================================
PClassActor *Net_ReadWeapon(uint8_t **stream)
2016-03-01 15:47:10 +00:00
{
int index;
index = ReadByte(stream);
if (index & 0x80)
{
index = (index & 0x7F) | (ReadByte(stream) << 7);
}
if ((unsigned)index >= Weapons_ntoh.Size())
{
return nullptr;
2016-03-01 15:47:10 +00:00
}
return Weapons_ntoh[index];
}