2006-02-24 04:48:15 +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"
|
|
|
|
|
|
|
|
#define BONUSADD 6
|
|
|
|
|
|
|
|
FState AWeapon::States[] =
|
|
|
|
{
|
|
|
|
S_NORMAL (SHTG, 'E', 0, A_Light0 , NULL)
|
|
|
|
};
|
|
|
|
|
|
|
|
IMPLEMENT_POINTY_CLASS (AWeapon)
|
|
|
|
DECLARE_POINTER (Ammo1)
|
|
|
|
DECLARE_POINTER (Ammo2)
|
|
|
|
DECLARE_POINTER (SisterWeapon)
|
|
|
|
END_POINTERS
|
|
|
|
|
|
|
|
BEGIN_DEFAULTS (AWeapon, Any, -1, 0)
|
|
|
|
PROP_Inventory_PickupSound ("misc/w_pkup")
|
|
|
|
END_DEFAULTS
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: Serialize
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
void AWeapon::Serialize (FArchive &arc)
|
|
|
|
{
|
|
|
|
Super::Serialize (arc);
|
|
|
|
arc << WeaponFlags
|
|
|
|
<< AmmoType1 << AmmoType2
|
|
|
|
<< AmmoGive1 << AmmoGive2
|
|
|
|
<< MinAmmo1 << MinAmmo2
|
|
|
|
<< AmmoUse1 << AmmoUse2
|
|
|
|
<< Kickback
|
|
|
|
<< YAdjust
|
|
|
|
<< AR_SOUNDW(UpSound) << AR_SOUNDW(ReadySound)
|
|
|
|
<< SisterWeaponType
|
|
|
|
<< ProjectileType << AltProjectileType
|
|
|
|
<< SelectionOrder
|
|
|
|
<< MoveCombatDist
|
|
|
|
<< Ammo1 << Ammo2 << SisterWeapon
|
|
|
|
<< bAltFire;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: TryPickup
|
|
|
|
//
|
|
|
|
// If you can't see the weapon when it's active, then you can't pick it up.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
bool AWeapon::TryPickup (AActor *toucher)
|
|
|
|
{
|
2006-10-31 14:53:21 +00:00
|
|
|
FState * ReadyState = FindState(NAME_Ready);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (ReadyState != NULL &&
|
|
|
|
ReadyState->GetFrame() < sprites[ReadyState->sprite.index].numframes)
|
|
|
|
{
|
|
|
|
return Super::TryPickup (toucher);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: Use
|
|
|
|
//
|
|
|
|
// Make the player switch to this weapon.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
bool AWeapon::Use (bool pickup)
|
|
|
|
{
|
|
|
|
AWeapon *useweap = this;
|
|
|
|
|
2006-04-16 13:29:50 +00:00
|
|
|
// Powered up weapons cannot be used directly.
|
|
|
|
if (WeaponFlags & WIF_POWERED_UP) return false;
|
|
|
|
|
2006-02-24 04:48:15 +00:00
|
|
|
// If the player is powered-up, use the alternate version of the
|
|
|
|
// weapon, if one exists.
|
|
|
|
if (SisterWeapon != NULL &&
|
|
|
|
SisterWeapon->WeaponFlags & WIF_POWERED_UP &&
|
|
|
|
Owner->FindInventory (RUNTIME_CLASS(APowerWeaponLevel2)))
|
|
|
|
{
|
|
|
|
useweap = SisterWeapon;
|
|
|
|
}
|
|
|
|
if (Owner->player != NULL && Owner->player->ReadyWeapon != useweap)
|
|
|
|
{
|
|
|
|
Owner->player->PendingWeapon = useweap;
|
|
|
|
}
|
|
|
|
// Return false so that the weapon is not removed from the inventory.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: HandlePickup
|
|
|
|
//
|
|
|
|
// Try to leach ammo from the weapon if you have it already.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
bool AWeapon::HandlePickup (AInventory *item)
|
|
|
|
{
|
|
|
|
if (item->GetClass() == GetClass())
|
|
|
|
{
|
|
|
|
if (static_cast<AWeapon *>(item)->PickupForAmmo (this))
|
|
|
|
{
|
|
|
|
item->ItemFlags |= IF_PICKUPGOOD;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (Inventory != NULL)
|
|
|
|
{
|
|
|
|
return Inventory->HandlePickup (item);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: PickupForAmmo
|
|
|
|
//
|
|
|
|
// The player already has this weapon, so try to pick it up for ammo.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
bool AWeapon::PickupForAmmo (AWeapon *ownedWeapon)
|
|
|
|
{
|
|
|
|
bool gotstuff = false;
|
|
|
|
|
|
|
|
// Don't take ammo if the weapon sticks around.
|
|
|
|
if (!ShouldStay ())
|
|
|
|
{
|
2006-07-03 09:07:56 +00:00
|
|
|
if (AmmoGive1 > 0) gotstuff = AddExistingAmmo (ownedWeapon->Ammo1, AmmoGive1);
|
|
|
|
if (AmmoGive2 > 0) gotstuff |= AddExistingAmmo (ownedWeapon->Ammo2, AmmoGive2);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
return gotstuff;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: CreateCopy
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
AInventory *AWeapon::CreateCopy (AActor *other)
|
|
|
|
{
|
|
|
|
AWeapon *copy = static_cast<AWeapon*>(Super::CreateCopy (other));
|
|
|
|
if (copy != this)
|
|
|
|
{
|
|
|
|
copy->AmmoGive1 = AmmoGive1;
|
|
|
|
copy->AmmoGive2 = AmmoGive2;
|
|
|
|
}
|
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: CreateTossable
|
|
|
|
//
|
|
|
|
// A weapon that's tossed out should contain no ammo, so you can't cheat
|
|
|
|
// by dropping it and then picking it back up.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
AInventory *AWeapon::CreateTossable ()
|
|
|
|
{
|
|
|
|
// Only drop the weapon that is meant to be placed in a level. That is,
|
|
|
|
// only drop the weapon that normally gives you ammo.
|
|
|
|
if (SisterWeapon != NULL &&
|
|
|
|
((AWeapon*)GetDefault())->AmmoGive1 == 0 &&
|
|
|
|
((AWeapon*)GetDefault())->AmmoGive2 == 0 &&
|
|
|
|
(((AWeapon*)SisterWeapon->GetDefault())->AmmoGive1 > 0 ||
|
|
|
|
((AWeapon*)SisterWeapon->GetDefault())->AmmoGive2 > 0))
|
|
|
|
{
|
|
|
|
return SisterWeapon->CreateTossable ();
|
|
|
|
}
|
|
|
|
AWeapon *copy = static_cast<AWeapon *> (Super::CreateTossable ());
|
|
|
|
|
|
|
|
if (copy != NULL)
|
|
|
|
{
|
|
|
|
// If this weapon has a sister, remove it from the inventory too.
|
|
|
|
if (SisterWeapon != NULL)
|
|
|
|
{
|
|
|
|
SisterWeapon->Destroy ();
|
|
|
|
}
|
2006-09-09 01:14:13 +00:00
|
|
|
// To avoid exploits, the tossed weapon must not have any ammo.
|
2006-02-24 04:48:15 +00:00
|
|
|
copy->AmmoGive1 = 0;
|
|
|
|
copy->AmmoGive2 = 0;
|
|
|
|
}
|
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: AttachToOwner
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
void AWeapon::AttachToOwner (AActor *other)
|
|
|
|
{
|
|
|
|
Super::AttachToOwner (other);
|
|
|
|
|
|
|
|
Ammo1 = AddAmmo (Owner, AmmoType1, AmmoGive1);
|
|
|
|
Ammo2 = AddAmmo (Owner, AmmoType2, AmmoGive2);
|
|
|
|
SisterWeapon = AddWeapon (SisterWeaponType);
|
|
|
|
if (Owner->player != NULL)
|
|
|
|
{
|
2007-01-22 22:15:50 +00:00
|
|
|
if (!Owner->player->userinfo.neverswitch && !(WeaponFlags & WIF_NO_AUTO_SWITCH))
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
Owner->player->PendingWeapon = this;
|
|
|
|
}
|
|
|
|
if (Owner->player->mo == players[consoleplayer].camera)
|
|
|
|
{
|
|
|
|
StatusBar->ReceivedWeapon (this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: AddAmmo
|
|
|
|
//
|
|
|
|
// Give some ammo to the owner, even if it's just 0.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
AAmmo *AWeapon::AddAmmo (AActor *other, const PClass *ammotype, int amount)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
AAmmo *ammo;
|
|
|
|
|
|
|
|
if (ammotype == NULL)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
2006-09-09 01:14:13 +00:00
|
|
|
// extra ammo in baby mode and nightmare mode
|
|
|
|
if (gameskill == sk_baby || (gameskill == sk_nightmare && gameinfo.gametype != GAME_Strife))
|
|
|
|
{
|
|
|
|
if (gameinfo.gametype & (GAME_Doom|GAME_Strife))
|
|
|
|
amount += amount;
|
|
|
|
else
|
|
|
|
amount += amount >> 1;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
ammo = static_cast<AAmmo *>(other->FindInventory (ammotype));
|
|
|
|
if (ammo == NULL)
|
|
|
|
{
|
2006-07-16 09:10:45 +00:00
|
|
|
ammo = static_cast<AAmmo *>(Spawn (ammotype, 0, 0, 0, NO_REPLACE));
|
2006-02-24 04:48:15 +00:00
|
|
|
ammo->Amount = MIN (amount, ammo->MaxAmount);
|
|
|
|
ammo->AttachToOwner (other);
|
|
|
|
}
|
|
|
|
else if (ammo->Amount < ammo->MaxAmount)
|
|
|
|
{
|
|
|
|
ammo->Amount += amount;
|
|
|
|
if (ammo->Amount > ammo->MaxAmount)
|
|
|
|
{
|
|
|
|
ammo->Amount = ammo->MaxAmount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ammo;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: AddExistingAmmo
|
|
|
|
//
|
|
|
|
// Give the owner some more ammo he already has.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
bool AWeapon::AddExistingAmmo (AAmmo *ammo, int amount)
|
|
|
|
{
|
|
|
|
if (ammo != NULL && ammo->Amount < ammo->MaxAmount)
|
|
|
|
{
|
2006-09-09 01:14:13 +00:00
|
|
|
// extra ammo in baby mode and nightmare mode
|
|
|
|
if (gameskill == sk_baby || (gameskill == sk_nightmare && gameinfo.gametype != GAME_Strife))
|
|
|
|
{
|
|
|
|
if (gameinfo.gametype & (GAME_Doom|GAME_Strife))
|
|
|
|
amount += amount;
|
|
|
|
else
|
|
|
|
amount += amount >> 1;
|
|
|
|
}
|
2006-02-24 04:48:15 +00:00
|
|
|
ammo->Amount += amount;
|
|
|
|
if (ammo->Amount > ammo->MaxAmount)
|
|
|
|
{
|
|
|
|
ammo->Amount = ammo->MaxAmount;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: AddWeapon
|
|
|
|
//
|
|
|
|
// Give the owner a weapon if they don't have it already.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
AWeapon *AWeapon::AddWeapon (const PClass *weapontype)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
AWeapon *weap;
|
|
|
|
|
|
|
|
if (weapontype == NULL)
|
|
|
|
{
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
weap = static_cast<AWeapon *>(Owner->FindInventory (weapontype));
|
|
|
|
if (weap == NULL)
|
|
|
|
{
|
2006-07-16 09:10:45 +00:00
|
|
|
weap = static_cast<AWeapon *>(Spawn (weapontype, 0, 0, 0, NO_REPLACE));
|
2006-02-24 04:48:15 +00:00
|
|
|
weap->AttachToOwner (Owner);
|
|
|
|
}
|
|
|
|
return weap;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: ShouldStay
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
bool AWeapon::ShouldStay ()
|
|
|
|
{
|
|
|
|
if (((multiplayer &&
|
|
|
|
(!deathmatch && !alwaysapplydmflags)) || (dmflags & DF_WEAPONS_STAY)) &&
|
|
|
|
!(flags & MF_DROPPED))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// 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 altFire;
|
|
|
|
int count1, count2;
|
|
|
|
int enough, enoughmask;
|
|
|
|
|
|
|
|
if (dmflags & DF_INFINITE_AMMO)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (fireMode == EitherFire)
|
|
|
|
{
|
|
|
|
bool gotSome = CheckAmmo (PrimaryFire, false) || CheckAmmo (AltFire, false);
|
|
|
|
if (!gotSome && autoSwitch)
|
|
|
|
{
|
|
|
|
static_cast<APlayerPawn *> (Owner)->PickNewWeapon (NULL);
|
|
|
|
}
|
|
|
|
return gotSome;
|
|
|
|
}
|
|
|
|
altFire = (fireMode == AltFire);
|
|
|
|
if (!requireAmmo && (WeaponFlags & (WIF_AMMO_OPTIONAL << altFire)))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
count1 = (Ammo1 != NULL) ? Ammo1->Amount : 0;
|
|
|
|
count2 = (Ammo2 != NULL) ? Ammo2->Amount : 0;
|
|
|
|
|
|
|
|
enough = (count1 >= AmmoUse1) | ((count2 >= AmmoUse2) << 1);
|
|
|
|
if (WeaponFlags & (WIF_PRIMARY_USES_BOTH << altFire))
|
|
|
|
{
|
|
|
|
enoughmask = 3;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
enoughmask = 1 << altFire;
|
|
|
|
}
|
2006-10-31 14:53:21 +00:00
|
|
|
if (altFire && FindState(NAME_AltFire) == NULL)
|
2006-02-24 04:48:15 +00:00
|
|
|
{ // If this weapon has no alternate fire, then there is never enough ammo for it
|
|
|
|
enough &= 1;
|
|
|
|
}
|
|
|
|
if ((enough & enoughmask) == enoughmask)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// out of ammo, pick a weapon to change to
|
|
|
|
if (autoSwitch)
|
|
|
|
{
|
|
|
|
static_cast<APlayerPawn *> (Owner)->PickNewWeapon (NULL);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// 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)
|
|
|
|
{
|
|
|
|
if (!(dmflags & DF_INFINITE_AMMO))
|
|
|
|
{
|
|
|
|
if (checkEnough && !CheckAmmo (altFire ? AltFire : PrimaryFire, false))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!altFire)
|
|
|
|
{
|
|
|
|
if (Ammo1 != NULL)
|
|
|
|
{
|
|
|
|
Ammo1->Amount -= AmmoUse1;
|
|
|
|
}
|
|
|
|
if ((WeaponFlags & WIF_PRIMARY_USES_BOTH) && Ammo2 != NULL)
|
|
|
|
{
|
|
|
|
Ammo2->Amount -= AmmoUse2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (Ammo2 != NULL)
|
|
|
|
{
|
|
|
|
Ammo2->Amount -= AmmoUse2;
|
|
|
|
}
|
|
|
|
if ((WeaponFlags & WIF_ALT_USES_BOTH) && Ammo1 != NULL)
|
|
|
|
{
|
|
|
|
Ammo1->Amount -= AmmoUse1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (Ammo1 != NULL && Ammo1->Amount < 0)
|
|
|
|
Ammo1->Amount = 0;
|
|
|
|
if (Ammo2 != NULL && Ammo2->Amount < 0)
|
|
|
|
Ammo2->Amount = 0;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: PostMorphWeapon
|
|
|
|
//
|
|
|
|
// Bring this weapon up after a player unmorphs.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
void AWeapon::PostMorphWeapon ()
|
|
|
|
{
|
|
|
|
Owner->player->PendingWeapon = WP_NOCHANGE;
|
|
|
|
Owner->player->ReadyWeapon = this;
|
|
|
|
Owner->player->psprites[ps_weapon].sy = WEAPONBOTTOM;
|
2006-10-31 14:53:21 +00:00
|
|
|
P_SetPsprite (Owner->player, ps_weapon, GetUpState());
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: EndPowerUp
|
|
|
|
//
|
|
|
|
// The Tome of Power just expired.
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
void AWeapon::EndPowerup ()
|
|
|
|
{
|
2006-05-07 23:55:17 +00:00
|
|
|
if (SisterWeapon != NULL && WeaponFlags&WIF_POWERED_UP)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2006-10-31 14:53:21 +00:00
|
|
|
if (GetReadyState() != SisterWeapon->GetReadyState())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2007-04-29 08:44:32 +00:00
|
|
|
if (Owner->player->PendingWeapon != NULL)
|
|
|
|
Owner->player->PendingWeapon = SisterWeapon;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Owner->player->ReadyWeapon = SisterWeapon;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: GetUpState
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
FState *AWeapon::GetUpState ()
|
|
|
|
{
|
2006-10-31 14:53:21 +00:00
|
|
|
return FindState(NAME_Select);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: GetDownState
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
FState *AWeapon::GetDownState ()
|
|
|
|
{
|
2006-10-31 14:53:21 +00:00
|
|
|
return FindState(NAME_Deselect);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: GetReadyState
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
|
|
|
FState *AWeapon::GetReadyState ()
|
|
|
|
{
|
2006-10-31 14:53:21 +00:00
|
|
|
return FindState(NAME_Ready);
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
|
|
|
// AWeapon :: GetAtkState
|
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2006-10-31 14:53:21 +00:00
|
|
|
FState *AWeapon::GetAtkState (bool hold)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2006-10-31 14:53:21 +00:00
|
|
|
FState * state=NULL;
|
|
|
|
|
|
|
|
if (hold) state = FindState(NAME_Hold);
|
|
|
|
if (state == NULL) state = FindState(NAME_Fire);
|
|
|
|
return state;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//===========================================================================
|
|
|
|
//
|
2006-10-31 14:53:21 +00:00
|
|
|
// AWeapon :: GetAtkState
|
2006-02-24 04:48:15 +00:00
|
|
|
//
|
|
|
|
//===========================================================================
|
|
|
|
|
2006-10-31 14:53:21 +00:00
|
|
|
FState *AWeapon::GetAltAtkState (bool hold)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
2006-10-31 14:53:21 +00:00
|
|
|
FState * state=NULL;
|
|
|
|
|
|
|
|
if (hold) state = FindState(NAME_AltHold);
|
|
|
|
if (state == NULL) state = FindState(NAME_AltFire);
|
|
|
|
return state;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Weapon slots ***********************************************************/
|
|
|
|
|
|
|
|
FWeaponSlots LocalWeapons;
|
|
|
|
|
|
|
|
FWeaponSlot::FWeaponSlot ()
|
|
|
|
{
|
|
|
|
Clear ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FWeaponSlot::Clear ()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < MAX_WEAPONS_PER_SLOT; i++)
|
|
|
|
{
|
|
|
|
Weapons[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FWeaponSlot::AddWeapon (const char *type)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
return AddWeapon (PClass::FindClass (type));
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
bool FWeaponSlot::AddWeapon (const PClass *type)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_WEAPONS_PER_SLOT; i++)
|
|
|
|
{
|
|
|
|
if (Weapons[i] == type)
|
|
|
|
return true; // Already present
|
|
|
|
if (Weapons[i] == NULL)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (i == MAX_WEAPONS_PER_SLOT)
|
|
|
|
{ // This slot is full
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Weapons[i] = type;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
AWeapon *FWeaponSlot::PickWeapon (player_t *player)
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
if (player->ReadyWeapon != NULL)
|
|
|
|
{
|
|
|
|
for (i = 0; i < MAX_WEAPONS_PER_SLOT; i++)
|
|
|
|
{
|
|
|
|
if (Weapons[i] == player->ReadyWeapon->GetClass() ||
|
|
|
|
(player->ReadyWeapon->WeaponFlags & WIF_POWERED_UP &&
|
|
|
|
player->ReadyWeapon->SisterWeapon != NULL &&
|
|
|
|
player->ReadyWeapon->SisterWeapon->GetClass() == Weapons[i]))
|
|
|
|
{
|
|
|
|
for (j = (unsigned)(i - 1) % MAX_WEAPONS_PER_SLOT;
|
|
|
|
j != i;
|
|
|
|
j = (unsigned)(j - 1) % MAX_WEAPONS_PER_SLOT)
|
|
|
|
{
|
|
|
|
AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory (Weapons[j]));
|
|
|
|
|
|
|
|
if (weap != NULL && weap->CheckAmmo (AWeapon::EitherFire, false))
|
|
|
|
{
|
|
|
|
return weap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (i = MAX_WEAPONS_PER_SLOT - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory (Weapons[i]));
|
|
|
|
|
|
|
|
if (weap != NULL && weap->CheckAmmo (AWeapon::EitherFire, false))
|
|
|
|
{
|
|
|
|
return weap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return player->ReadyWeapon;
|
|
|
|
}
|
|
|
|
|
|
|
|
void FWeaponSlots::Clear ()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
|
|
|
|
{
|
|
|
|
Slots[i].Clear ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the weapon already exists in a slot, don't add it. If it doesn't,
|
|
|
|
// then add it to the specified slot. False is returned if the weapon was
|
|
|
|
// not in a slot and could not be added. True is returned otherwise.
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
ESlotDef FWeaponSlots::AddDefaultWeapon (int slot, const PClass *type)
|
2006-02-24 04:48:15 +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;
|
|
|
|
}
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
bool FWeaponSlots::LocateWeapon (const PClass *type, int *const slot, int *const index)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
for (i = 0; i < NUM_WEAPON_SLOTS; i++)
|
|
|
|
{
|
|
|
|
for (j = 0; j < MAX_WEAPONS_PER_SLOT; j++)
|
|
|
|
{
|
|
|
|
if (Slots[i].Weapons[j] == type)
|
|
|
|
{
|
|
|
|
*slot = i;
|
|
|
|
*index = j;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (Slots[i].Weapons[j] == NULL)
|
|
|
|
{ // No more weapons in this slot, so try the next
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool FindMostRecentWeapon (player_s *player, int *slot, int *index)
|
|
|
|
{
|
|
|
|
if (player->PendingWeapon != WP_NOCHANGE)
|
|
|
|
{
|
|
|
|
if (player->psprites[ps_weapon].state != NULL &&
|
|
|
|
player->psprites[ps_weapon].state->GetAction() == A_Raise)
|
|
|
|
{
|
|
|
|
if (LocalWeapons.LocateWeapon (player->PendingWeapon->GetClass(), slot, index))
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return LocalWeapons.LocateWeapon (player->PendingWeapon->GetClass(), slot, index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (player->ReadyWeapon != NULL)
|
|
|
|
{
|
|
|
|
AWeapon *weap = player->ReadyWeapon;
|
|
|
|
if (!LocalWeapons.LocateWeapon (weap->GetClass(), slot, index))
|
|
|
|
{
|
|
|
|
if (weap->WeaponFlags & WIF_POWERED_UP && weap->SisterWeaponType != NULL)
|
|
|
|
{
|
|
|
|
return LocalWeapons.LocateWeapon (weap->SisterWeaponType, slot, index);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
AWeapon *PickNextWeapon (player_s *player)
|
|
|
|
{
|
|
|
|
int startslot, startindex;
|
|
|
|
|
|
|
|
if (player->ReadyWeapon == NULL || FindMostRecentWeapon (player, &startslot, &startindex))
|
|
|
|
{
|
|
|
|
int start;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (player->ReadyWeapon == NULL)
|
|
|
|
{
|
|
|
|
startslot = NUM_WEAPON_SLOTS - 1;
|
|
|
|
startindex = MAX_WEAPONS_PER_SLOT - 1;
|
|
|
|
}
|
|
|
|
start = startslot * MAX_WEAPONS_PER_SLOT + startindex;
|
|
|
|
|
|
|
|
for (i = 1; i < NUM_WEAPON_SLOTS * MAX_WEAPONS_PER_SLOT + 1; i++)
|
|
|
|
{
|
|
|
|
int slot = (unsigned)((start + i) / MAX_WEAPONS_PER_SLOT) % NUM_WEAPON_SLOTS;
|
|
|
|
int index = (unsigned)(start + i) % MAX_WEAPONS_PER_SLOT;
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *type = LocalWeapons.Slots[slot].Weapons[index];
|
2006-02-24 04:48:15 +00:00
|
|
|
AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory (type));
|
|
|
|
|
|
|
|
if (weap != NULL && weap->CheckAmmo (AWeapon::EitherFire, false))
|
|
|
|
{
|
|
|
|
return weap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return player->ReadyWeapon;
|
|
|
|
}
|
|
|
|
|
|
|
|
AWeapon *PickPrevWeapon (player_s *player)
|
|
|
|
{
|
|
|
|
int startslot, startindex;
|
|
|
|
|
|
|
|
if (player->ReadyWeapon == NULL || FindMostRecentWeapon (player, &startslot, &startindex))
|
|
|
|
{
|
|
|
|
int start;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (player->ReadyWeapon == NULL)
|
|
|
|
{
|
|
|
|
startslot = 0;
|
|
|
|
startindex = 0;
|
|
|
|
}
|
|
|
|
start = startslot * MAX_WEAPONS_PER_SLOT + startindex;
|
|
|
|
|
|
|
|
for (i = 1; i < NUM_WEAPON_SLOTS * MAX_WEAPONS_PER_SLOT + 1; i++)
|
|
|
|
{
|
|
|
|
int slot = start - i;
|
|
|
|
if (slot < 0)
|
|
|
|
slot += NUM_WEAPON_SLOTS * MAX_WEAPONS_PER_SLOT;
|
|
|
|
int index = slot % MAX_WEAPONS_PER_SLOT;
|
|
|
|
slot /= MAX_WEAPONS_PER_SLOT;
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *type = LocalWeapons.Slots[slot].Weapons[index];
|
2006-02-24 04:48:15 +00:00
|
|
|
AWeapon *weap = static_cast<AWeapon *> (player->mo->FindInventory (type));
|
|
|
|
|
|
|
|
if (weap != NULL && weap->CheckAmmo (AWeapon::EitherFire, false))
|
|
|
|
{
|
|
|
|
return weap;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return player->ReadyWeapon;
|
|
|
|
}
|
|
|
|
|
|
|
|
CCMD (setslot)
|
|
|
|
{
|
|
|
|
int slot, i;
|
|
|
|
|
2006-10-31 14:53:21 +00:00
|
|
|
if (ParsingKeyConf && WeaponSection.IsEmpty())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
Printf ("You need to use weaponsection before using setslot\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (argv.argc() < 2 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
|
|
|
|
{
|
|
|
|
Printf ("Usage: setslot [slot] [weapons]\nCurrent slot assignments:\n");
|
|
|
|
for (slot = 0; slot < NUM_WEAPON_SLOTS; ++slot)
|
|
|
|
{
|
|
|
|
Printf (" Slot %d:", slot);
|
|
|
|
for (i = 0;
|
|
|
|
i < MAX_WEAPONS_PER_SLOT && LocalWeapons.Slots[slot].GetWeapon(i) != NULL;
|
|
|
|
++i)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
Printf (" %s", LocalWeapons.Slots[slot].GetWeapon(i)->TypeName.GetChars());
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
Printf ("\n");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LocalWeapons.Slots[slot].Clear();
|
|
|
|
if (argv.argc() == 2)
|
|
|
|
{
|
|
|
|
Printf ("Slot %d cleared\n", slot);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (i = 2; i < argv.argc(); ++i)
|
|
|
|
{
|
|
|
|
if (!LocalWeapons.Slots[slot].AddWeapon (argv[i]))
|
|
|
|
{
|
|
|
|
Printf ("Could not add %s to slot %d\n", argv[i], slot);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CCMD (addslot)
|
|
|
|
{
|
|
|
|
size_t slot;
|
|
|
|
|
|
|
|
if (argv.argc() != 3 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
|
|
|
|
{
|
|
|
|
Printf ("Usage: addslot <slot> <weapon>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!LocalWeapons.Slots[slot].AddWeapon (argv[2]))
|
|
|
|
{
|
|
|
|
Printf ("Could not add %s to slot %d\n", argv[2], slot);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CCMD (weaponsection)
|
|
|
|
{
|
|
|
|
if (argv.argc() != 2)
|
|
|
|
{
|
|
|
|
Printf ("Usage: weaponsection <ini name>\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Limit the section name to 32 chars
|
|
|
|
if (strlen(argv[1]) > 32)
|
|
|
|
{
|
|
|
|
argv[1][32] = 0;
|
|
|
|
}
|
2006-10-31 14:53:21 +00:00
|
|
|
WeaponSection = argv[1];
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
// If the ini already has definitions for this section, load them
|
|
|
|
char fullSection[32*3];
|
|
|
|
char *tackOn;
|
|
|
|
|
|
|
|
if (gameinfo.gametype == GAME_Hexen)
|
|
|
|
{
|
|
|
|
strcpy (fullSection, "Hexen");
|
|
|
|
tackOn = fullSection + 5;
|
|
|
|
}
|
|
|
|
else if (gameinfo.gametype == GAME_Heretic)
|
|
|
|
{
|
|
|
|
strcpy (fullSection, "Heretic");
|
|
|
|
tackOn = fullSection + 7;
|
|
|
|
}
|
|
|
|
else if (gameinfo.gametype == GAME_Strife)
|
|
|
|
{
|
|
|
|
strcpy (fullSection, "Strife");
|
|
|
|
tackOn = fullSection + 6;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
strcpy (fullSection, "Doom");
|
|
|
|
tackOn = fullSection + 4;
|
|
|
|
}
|
|
|
|
|
2006-10-31 14:53:21 +00:00
|
|
|
sprintf (tackOn, ".%s.WeaponSlots", WeaponSection.GetChars());
|
2006-02-24 04:48:15 +00:00
|
|
|
if (GameConfig->SetSection (fullSection))
|
|
|
|
{
|
|
|
|
LocalWeapons.RestoreSlots (*GameConfig);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CCMD (addslotdefault)
|
|
|
|
{
|
2006-05-10 02:40:43 +00:00
|
|
|
const PClass *type;
|
2006-02-24 04:48:15 +00:00
|
|
|
unsigned int slot;
|
|
|
|
|
|
|
|
if (argv.argc() != 3 || (slot = atoi (argv[1])) >= NUM_WEAPON_SLOTS)
|
|
|
|
{
|
|
|
|
Printf ("Usage: addslotdefault <slot> <weapon>\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2006-10-31 14:53:21 +00:00
|
|
|
if (ParsingKeyConf && WeaponSection.IsEmpty())
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
Printf ("You need to use weaponsection before using addslotdefault\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2006-05-10 02:40:43 +00:00
|
|
|
type = PClass::FindClass (argv[2]);
|
2006-02-24 04:48:15 +00:00
|
|
|
if (type == NULL || !type->IsDescendantOf (RUNTIME_CLASS(AWeapon)))
|
|
|
|
{
|
|
|
|
Printf ("%s is not a weapon\n", argv[2]);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (LocalWeapons.AddDefaultWeapon (slot, type))
|
|
|
|
{
|
|
|
|
case SLOTDEF_Full:
|
|
|
|
Printf ("Could not add %s to slot %d\n", argv[2], slot);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SLOTDEF_Added:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SLOTDEF_Exists:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-04-11 16:27:41 +00:00
|
|
|
int FWeaponSlots::RestoreSlots (FConfigFile &config)
|
2006-02-24 04:48:15 +00:00
|
|
|
{
|
|
|
|
char buff[MAX_WEAPONS_PER_SLOT*64];
|
|
|
|
const char *key, *value;
|
|
|
|
int slot;
|
2006-04-11 16:27:41 +00:00
|
|
|
int slotsread = 0;
|
2006-02-24 04:48:15 +00:00
|
|
|
|
|
|
|
buff[sizeof(buff)-1] = 0;
|
|
|
|
|
|
|
|
for (slot = 0; slot < NUM_WEAPON_SLOTS; ++slot)
|
|
|
|
{
|
|
|
|
Slots[slot].Clear ();
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
slot = key[5] - '0';
|
|
|
|
strncpy (buff, value, sizeof(buff)-1);
|
|
|
|
char *tok;
|
|
|
|
|
|
|
|
Slots[slot].Clear ();
|
|
|
|
tok = strtok (buff, " ");
|
|
|
|
while (tok != NULL)
|
|
|
|
{
|
|
|
|
Slots[slot].AddWeapon (tok);
|
|
|
|
tok = strtok (NULL, " ");
|
|
|
|
}
|
2006-04-11 16:27:41 +00:00
|
|
|
slotsread++;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
2006-04-11 16:27:41 +00:00
|
|
|
return slotsread;
|
2006-02-24 04:48:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void FWeaponSlots::SaveSlots (FConfigFile &config)
|
|
|
|
{
|
|
|
|
char buff[MAX_WEAPONS_PER_SLOT*64];
|
|
|
|
char keyname[16];
|
|
|
|
|
|
|
|
for (int i = 0; i < NUM_WEAPON_SLOTS; ++i)
|
|
|
|
{
|
|
|
|
int index = 0;
|
|
|
|
|
|
|
|
for (int j = 0; j < MAX_WEAPONS_PER_SLOT; ++j)
|
|
|
|
{
|
|
|
|
if (Slots[i].Weapons[j] == NULL)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (index > 0)
|
|
|
|
{
|
|
|
|
buff[index++] = ' ';
|
|
|
|
}
|
2006-05-10 02:40:43 +00:00
|
|
|
const char *name = Slots[i].Weapons[j]->TypeName.GetChars();
|
2006-02-24 04:48:15 +00:00
|
|
|
strcpy (buff+index, name);
|
|
|
|
index += (int)strlen (name);
|
|
|
|
}
|
|
|
|
if (index > 0)
|
|
|
|
{
|
|
|
|
sprintf (keyname, "Slot[%d]", i);
|
|
|
|
config.SetValueForKey (keyname, buff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int FWeaponSlot::CountWeapons ()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_WEAPONS_PER_SLOT; ++i)
|
|
|
|
{
|
|
|
|
if (Weapons[i] == NULL)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i;
|
|
|
|
}
|