Merge branch 'master' into gonesolong
Conflicts: src/CMakeLists.txt src/actor.h src/g_heretic/a_hereticmisc.cpp src/g_heretic/a_hereticweaps.cpp src/g_heretic/a_ironlich.cpp src/info.h src/namedef.h src/p_buildmap.cpp src/p_enemy.cpp src/p_map.cpp src/p_mobj.cpp src/thingdef/thingdef_codeptr.cpp zdoom.vcproj
This commit is contained in:
commit
2d87eb0ba2
457 changed files with 13703 additions and 9290 deletions
|
|
@ -42,9 +42,9 @@ void ABasicArmor::Tick ()
|
|||
AbsorbCount = 0;
|
||||
if (!Icon.isValid())
|
||||
{
|
||||
const char *icon = gameinfo.ArmorIcon1;
|
||||
FString icon = gameinfo.ArmorIcon1;
|
||||
|
||||
if (SavePercent >= gameinfo.Armor2Percent && gameinfo.ArmorIcon2[0] != 0)
|
||||
if (SavePercent >= gameinfo.Armor2Percent && gameinfo.ArmorIcon2.Len() != 0)
|
||||
icon = gameinfo.ArmorIcon2;
|
||||
|
||||
if (icon[0] != 0)
|
||||
|
|
|
|||
|
|
@ -605,7 +605,13 @@ void APowerInvisibility::DoEffect ()
|
|||
case (NAME_Stencil):
|
||||
Owner->RenderStyle = STYLE_Stencil;
|
||||
break;
|
||||
case (NAME_None):
|
||||
case (NAME_AddStencil) :
|
||||
Owner->RenderStyle = STYLE_AddStencil;
|
||||
break;
|
||||
case (NAME_TranslucentStencil) :
|
||||
Owner->RenderStyle = STYLE_TranslucentStencil;
|
||||
break;
|
||||
case (NAME_None) :
|
||||
case (NAME_Cumulative):
|
||||
case (NAME_Translucent):
|
||||
Owner->RenderStyle = STYLE_Translucent;
|
||||
|
|
@ -685,7 +691,13 @@ int APowerInvisibility::AlterWeaponSprite (visstyle_t *vis)
|
|||
case (NAME_Stencil):
|
||||
vis->RenderStyle = STYLE_Stencil;
|
||||
break;
|
||||
case (NAME_None):
|
||||
case (NAME_TranslucentStencil) :
|
||||
vis->RenderStyle = STYLE_TranslucentStencil;
|
||||
break;
|
||||
case (NAME_AddStencil) :
|
||||
vis->RenderStyle = STYLE_AddStencil;
|
||||
break;
|
||||
case (NAME_None) :
|
||||
case (NAME_Cumulative):
|
||||
case (NAME_Translucent):
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class ACustomBridge : public AActor
|
|||
DECLARE_CLASS (ACustomBridge, AActor)
|
||||
public:
|
||||
void BeginPlay ();
|
||||
void Destroy();
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS(ACustomBridge)
|
||||
|
|
@ -58,6 +59,25 @@ void ACustomBridge::BeginPlay ()
|
|||
}
|
||||
}
|
||||
|
||||
void ACustomBridge::Destroy()
|
||||
{
|
||||
// Hexen originally just set a flag to make the bridge balls remove themselves in A_BridgeOrbit.
|
||||
// But this is not safe with custom bridge balls that do not necessarily call that function.
|
||||
// So the best course of action is to look for all bridge balls here and destroy them ourselves.
|
||||
|
||||
TThinkerIterator<AActor> it;
|
||||
AActor *thing;
|
||||
|
||||
while ((thing = it.Next()))
|
||||
{
|
||||
if (thing->target == this)
|
||||
{
|
||||
thing->Destroy();
|
||||
}
|
||||
}
|
||||
Super::Destroy();
|
||||
}
|
||||
|
||||
// Action functions for the non-Doom bridge --------------------------------
|
||||
|
||||
#define ORBIT_RADIUS 15
|
||||
|
|
@ -91,10 +111,6 @@ DEFINE_ACTION_FUNCTION(AActor, A_BridgeOrbit)
|
|||
// Set rotation radius
|
||||
if (self->target->args[4]) rotationradius = ((self->target->args[4] * self->target->radius) / (100 * FRACUNIT));
|
||||
|
||||
if (self->target->special1)
|
||||
{
|
||||
self->SetState (NULL);
|
||||
}
|
||||
self->angle += rotationspeed;
|
||||
self->x = self->target->x + rotationradius * finecosine[self->angle >> ANGLETOFINESHIFT];
|
||||
self->y = self->target->y + rotationradius * finesine[self->angle >> ANGLETOFINESHIFT];
|
||||
|
|
@ -121,7 +137,6 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BridgeInit)
|
|||
cy = self->y;
|
||||
cz = self->z;
|
||||
startangle = pr_orbit() << 24;
|
||||
self->special1 = 0;
|
||||
|
||||
// Spawn triad into world -- may be more than a triad now.
|
||||
int ballcount = self->args[2]==0 ? 3 : self->args[2];
|
||||
|
|
@ -136,14 +151,6 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BridgeInit)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* never used
|
||||
void A_BridgeRemove (AActor *self)
|
||||
{
|
||||
self->special1 = true; // Removing the bridge
|
||||
self->flags &= ~MF_SOLID;
|
||||
self->SetState (&ABridge::States[S_FREE_BRIDGE]);
|
||||
}
|
||||
*/
|
||||
|
||||
// Invisible bridge --------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -19,12 +19,29 @@ struct OneKey
|
|||
|
||||
bool check(AActor *owner)
|
||||
{
|
||||
// P_GetMapColorForKey() checks the key directly
|
||||
if (owner->IsKindOf (RUNTIME_CLASS(AKey)))
|
||||
if (owner->IsKindOf(RUNTIME_CLASS(AKey)))
|
||||
{
|
||||
// P_GetMapColorForKey() checks the key directly
|
||||
return owner->IsA(key);
|
||||
// Other calls check an actor that may have a key in its inventory.
|
||||
else
|
||||
return !!owner->FindInventory(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Other calls check an actor that may have a key in its inventory.
|
||||
AInventory *item;
|
||||
|
||||
for (item = owner->Inventory; item != NULL; item = item->Inventory)
|
||||
{
|
||||
if (item->IsA(key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (item->GetSpecies() == key->TypeName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -543,7 +543,7 @@ bool AInventory::SpecialDropAction (AActor *dropper)
|
|||
|
||||
bool AInventory::ShouldRespawn ()
|
||||
{
|
||||
if ((ItemFlags & IF_BIGPOWERUP) && !(dmflags & DF_RESPAWN_SUPER)) return false;
|
||||
if ((ItemFlags & IF_BIGPOWERUP) && !(dmflags2 & DF2_RESPAWN_SUPER)) return false;
|
||||
if (ItemFlags & IF_NEVERRESPAWN) return false;
|
||||
return !!(dmflags & DF_ITEMS_RESPAWN);
|
||||
}
|
||||
|
|
@ -831,7 +831,7 @@ void AInventory::BecomePickup ()
|
|||
LinkToWorld ();
|
||||
P_FindFloorCeiling (this);
|
||||
}
|
||||
flags = GetDefault()->flags | MF_DROPPED;
|
||||
flags = (GetDefault()->flags | MF_DROPPED) & ~MF_COUNTITEM;
|
||||
renderflags &= ~RF_INVISIBLE;
|
||||
SetState (SpawnState);
|
||||
}
|
||||
|
|
@ -1064,7 +1064,7 @@ void AInventory::Touch (AActor *toucher)
|
|||
|
||||
if (flags5 & MF5_COUNTSECRET)
|
||||
{
|
||||
P_GiveSecret(toucher, true, true);
|
||||
P_GiveSecret(toucher, true, true, -1);
|
||||
}
|
||||
|
||||
//Added by MC: Check if item taken was the roam destination of any bot
|
||||
|
|
@ -1868,7 +1868,10 @@ bool ABackpackItem::HandlePickup (AInventory *item)
|
|||
AInventory *ABackpackItem::CreateTossable ()
|
||||
{
|
||||
ABackpackItem *pack = static_cast<ABackpackItem *>(Super::CreateTossable());
|
||||
pack->bDepleted = true;
|
||||
if (pack != NULL)
|
||||
{
|
||||
pack->bDepleted = true;
|
||||
}
|
||||
return pack;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@
|
|||
#include "v_font.h"
|
||||
#include "p_spec.h"
|
||||
|
||||
EXTERN_CVAR(String, secretmessage)
|
||||
|
||||
class ASecretTrigger : public AActor
|
||||
{
|
||||
DECLARE_CLASS (ASecretTrigger, AActor)
|
||||
|
|
@ -62,7 +60,7 @@ void ASecretTrigger::PostBeginPlay ()
|
|||
|
||||
void ASecretTrigger::Activate (AActor *activator)
|
||||
{
|
||||
P_GiveSecret(activator, args[0] <= 1, (args[0] == 0 || args[0] == 2));
|
||||
P_GiveSecret(activator, args[0] <= 1, (args[0] == 0 || args[0] == 2), -1);
|
||||
Destroy ();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,10 +82,22 @@ void ASectorAction::Deactivate (AActor *source)
|
|||
flags2 |= MF2_DORMANT; // Projectiles can trigger
|
||||
}
|
||||
|
||||
bool ASectorAction::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASectorAction::TriggerAction(AActor *triggerer, int activationType)
|
||||
{
|
||||
if (DoTriggerAction(triggerer, activationType))
|
||||
{
|
||||
if (flags4 & MF4_STANDSTILL)
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ASectorAction::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
if (tracer != NULL)
|
||||
return barrier_cast<ASectorAction *>(tracer)->TriggerAction (triggerer, activationType);
|
||||
return barrier_cast<ASectorAction *>(tracer)->DoTriggerAction (triggerer, activationType);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
|
@ -93,7 +105,7 @@ bool ASectorAction::TriggerAction (AActor *triggerer, int activationType)
|
|||
bool ASectorAction::CheckTrigger (AActor *triggerer) const
|
||||
{
|
||||
if (special &&
|
||||
(triggerer->player ||
|
||||
((triggerer->player && !(flags & MF_FRIENDLY)) ||
|
||||
((flags & MF_AMBUSH) && (triggerer->flags2 & MF2_MCROSS)) ||
|
||||
((flags2 & MF2_DORMANT) && (triggerer->flags2 & MF2_PCROSS))))
|
||||
{
|
||||
|
|
@ -110,16 +122,16 @@ class ASecActEnter : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActEnter, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActEnter)
|
||||
|
||||
|
||||
bool ASecActEnter::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActEnter::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_Enter) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when leaving sector --------------------------------------------
|
||||
|
|
@ -128,16 +140,16 @@ class ASecActExit : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActExit, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActExit)
|
||||
|
||||
|
||||
bool ASecActExit::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActExit::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_Exit) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when hitting sector's floor ------------------------------------
|
||||
|
|
@ -146,7 +158,7 @@ class ASecActHitFloor : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActHitFloor, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
// Skull Tag uses 9999 for a special that is triggered whenever
|
||||
|
|
@ -154,10 +166,10 @@ public:
|
|||
IMPLEMENT_CLASS (ASecActHitFloor)
|
||||
|
||||
|
||||
bool ASecActHitFloor::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActHitFloor::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_HitFloor) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when hitting sector's ceiling ----------------------------------
|
||||
|
|
@ -166,16 +178,16 @@ class ASecActHitCeil : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActHitCeil, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActHitCeil)
|
||||
|
||||
|
||||
bool ASecActHitCeil::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActHitCeil::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_HitCeiling) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when using inside sector ---------------------------------------
|
||||
|
|
@ -184,16 +196,16 @@ class ASecActUse : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActUse, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActUse)
|
||||
|
||||
|
||||
bool ASecActUse::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActUse::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_Use) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when using a sector's wall -------------------------------------
|
||||
|
|
@ -202,16 +214,16 @@ class ASecActUseWall : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActUseWall, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActUseWall)
|
||||
|
||||
|
||||
bool ASecActUseWall::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActUseWall::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_UseWall) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when eyes go below fake floor ----------------------------------
|
||||
|
|
@ -220,16 +232,16 @@ class ASecActEyesDive : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActEyesDive, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActEyesDive)
|
||||
|
||||
|
||||
bool ASecActEyesDive::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActEyesDive::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_EyesDive) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when eyes go above fake floor ----------------------------------
|
||||
|
|
@ -238,16 +250,16 @@ class ASecActEyesSurface : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActEyesSurface, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActEyesSurface)
|
||||
|
||||
|
||||
bool ASecActEyesSurface::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActEyesSurface::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_EyesSurface) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when eyes go below fake floor ----------------------------------
|
||||
|
|
@ -256,16 +268,16 @@ class ASecActEyesBelowC : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActEyesBelowC, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActEyesBelowC)
|
||||
|
||||
|
||||
bool ASecActEyesBelowC::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActEyesBelowC::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_EyesBelowC) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when eyes go above fake floor ----------------------------------
|
||||
|
|
@ -274,16 +286,16 @@ class ASecActEyesAboveC : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActEyesAboveC, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActEyesAboveC)
|
||||
|
||||
|
||||
bool ASecActEyesAboveC::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActEyesAboveC::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_EyesAboveC) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
||||
// Triggered when eyes go below fake floor ----------------------------------
|
||||
|
|
@ -292,14 +304,14 @@ class ASecActHitFakeFloor : public ASectorAction
|
|||
{
|
||||
DECLARE_CLASS (ASecActHitFakeFloor, ASectorAction)
|
||||
public:
|
||||
bool TriggerAction (AActor *triggerer, int activationType);
|
||||
bool DoTriggerAction (AActor *triggerer, int activationType);
|
||||
};
|
||||
|
||||
IMPLEMENT_CLASS (ASecActHitFakeFloor)
|
||||
|
||||
|
||||
bool ASecActHitFakeFloor::TriggerAction (AActor *triggerer, int activationType)
|
||||
bool ASecActHitFakeFloor::DoTriggerAction (AActor *triggerer, int activationType)
|
||||
{
|
||||
bool didit = (activationType & SECSPAC_HitFakeFloor) ? CheckTrigger (triggerer) : false;
|
||||
return didit | Super::TriggerAction (triggerer, activationType);
|
||||
return didit | Super::DoTriggerAction (triggerer, activationType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1161,7 +1161,7 @@ AWeapon *FWeaponSlots::PickNextWeapon(player_t *player)
|
|||
return weap;
|
||||
}
|
||||
}
|
||||
while ((slot != startslot || index != startindex) && slotschecked < NUM_WEAPON_SLOTS);
|
||||
while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
|
||||
}
|
||||
return player->ReadyWeapon;
|
||||
}
|
||||
|
|
@ -1216,7 +1216,7 @@ AWeapon *FWeaponSlots::PickPrevWeapon (player_t *player)
|
|||
return weap;
|
||||
}
|
||||
}
|
||||
while ((slot != startslot || index != startindex) && slotschecked < NUM_WEAPON_SLOTS);
|
||||
while ((slot != startslot || index != startindex) && slotschecked <= NUM_WEAPON_SLOTS);
|
||||
}
|
||||
return player->ReadyWeapon;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -444,6 +444,20 @@ void ST_LoadCrosshair(bool alwaysload=false);
|
|||
void ST_Clear();
|
||||
extern FTexture *CrosshairImage;
|
||||
|
||||
FTextureID GetWeaponIcon(AWeapon *weapon);
|
||||
FTextureID GetInventoryIcon(AInventory *item, DWORD flags, bool *applyscale);
|
||||
|
||||
enum DI_Flags
|
||||
{
|
||||
DI_SKIPICON = 0x1,
|
||||
DI_SKIPALTICON = 0x2,
|
||||
DI_SKIPSPAWN = 0x4,
|
||||
DI_SKIPREADY = 0x8,
|
||||
DI_ALTICONFIRST = 0x10,
|
||||
|
||||
DI_DRAWINBOX = 0x20, // Set when either width or height is not zero
|
||||
|
||||
DI_FORCESCALE = 0x40,
|
||||
DI_ALTERNATEONFAIL = 0x80
|
||||
};
|
||||
|
||||
#endif /* __SBAR_H__ */
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ void FMugShot::Tick(player_t *player)
|
|||
CurrentState = NULL;
|
||||
}
|
||||
}
|
||||
if ((player->cmd.ucmd.buttons & (BT_ATTACK|BT_ALTATTACK)) && !(player->cheats & (CF_FROZEN | CF_TOTALLYFROZEN)) && player->ReadyWeapon)
|
||||
if (player->attackdown && !(player->cheats & (CF_FROZEN | CF_TOTALLYFROZEN)) && player->ReadyWeapon)
|
||||
{
|
||||
if (RampageTimer != ST_RAMPAGEDELAY)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -303,6 +303,8 @@ class SBarInfoMainBlock : public SBarInfoCommandFlowControl
|
|||
int Alpha() const { return currentAlpha; }
|
||||
// Same as Draw but takes into account ForceScaled and temporarily sets the scaling if needed.
|
||||
void DrawAux(const SBarInfoMainBlock *block, DSBarInfo *statusBar, int xOffset, int yOffset, int alpha);
|
||||
// Silence hidden overload warning since this is a special use class.
|
||||
using SBarInfoCommandFlowControl::Draw;
|
||||
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, int xOffset, int yOffset, int alpha)
|
||||
{
|
||||
this->xOffset = xOffset;
|
||||
|
|
@ -1187,8 +1189,12 @@ public:
|
|||
|
||||
if((offsetflags & SBarInfoCommand::CENTER) == SBarInfoCommand::CENTER)
|
||||
{
|
||||
dx -= (texture->GetScaledWidthDouble()/2.0)-texture->GetScaledLeftOffsetDouble();
|
||||
dy -= (texture->GetScaledHeightDouble()/2.0)-texture->GetScaledTopOffsetDouble();
|
||||
if (forceWidth < 0) dx -= (texture->GetScaledWidthDouble()/2.0)-texture->GetScaledLeftOffsetDouble();
|
||||
else dx -= forceWidth*(0.5-(texture->GetScaledLeftOffsetDouble()/texture->GetScaledWidthDouble()));
|
||||
//Unoptimalized formula is dx -= forceWidth/2.0-(texture->GetScaledLeftOffsetDouble()*forceWidth/texture->GetScaledWidthDouble());
|
||||
|
||||
if (forceHeight < 0) dy -= (texture->GetScaledHeightDouble()/2.0)-texture->GetScaledTopOffsetDouble();
|
||||
else dy -= forceHeight*(0.5-(texture->GetScaledTopOffsetDouble()/texture->GetScaledHeightDouble()));
|
||||
}
|
||||
|
||||
dx += xOffset;
|
||||
|
|
|
|||
|
|
@ -41,28 +41,65 @@
|
|||
// classes.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class CommandDrawImage : public SBarInfoCommand
|
||||
class CommandDrawImage : public SBarInfoCommandFlowControl
|
||||
{
|
||||
public:
|
||||
CommandDrawImage(SBarInfo *script) : SBarInfoCommand(script),
|
||||
translatable(false), type(NORMAL_IMAGE), image(-1), offset(static_cast<Offset> (TOP|LEFT)),
|
||||
CommandDrawImage(SBarInfo *script) : SBarInfoCommandFlowControl(script),
|
||||
translatable(false), type(NORMAL_IMAGE), image(-1), maxwidth(-1),
|
||||
maxheight(-1), spawnScaleX(1.0f), spawnScaleY(1.0f), flags(0),
|
||||
applyscale(false), offset(static_cast<Offset> (TOP|LEFT)),
|
||||
texture(NULL), alpha(FRACUNIT)
|
||||
{
|
||||
}
|
||||
|
||||
void Draw(const SBarInfoMainBlock *block, const DSBarInfo *statusBar)
|
||||
{
|
||||
if(flags & DI_ALTERNATEONFAIL)
|
||||
SBarInfoCommandFlowControl::Draw(block, statusBar);
|
||||
|
||||
if(texture == NULL)
|
||||
return;
|
||||
|
||||
int w = maxwidth, h = maxheight;
|
||||
|
||||
// We must calculate this per frame in order to prevent glitches with cl_capfps true.
|
||||
fixed_t frameAlpha = block->Alpha();
|
||||
if(alpha != FRACUNIT)
|
||||
frameAlpha = fixed_t(((double) block->Alpha() / (double) FRACUNIT) * ((double) alpha / (double) OPAQUE) * FRACUNIT);
|
||||
|
||||
|
||||
if(flags & DI_DRAWINBOX)
|
||||
{
|
||||
double scale1, scale2;
|
||||
scale1 = scale2 = 1.0f;
|
||||
double texwidth = (int) (texture->GetScaledWidthDouble()*spawnScaleX);
|
||||
double texheight = (int) (texture->GetScaledHeightDouble()*spawnScaleY);
|
||||
|
||||
if (w != -1 && (w<texwidth || (flags & DI_FORCESCALE)))
|
||||
{
|
||||
scale1 = w/texwidth;
|
||||
}
|
||||
if (h != -1 && (h<texheight || (flags & DI_FORCESCALE)))
|
||||
{
|
||||
scale2 = h/texheight;
|
||||
}
|
||||
|
||||
if (flags & DI_FORCESCALE)
|
||||
{
|
||||
if (w == -1 || (h != -1 && scale2<scale1))
|
||||
scale1=scale2;
|
||||
}
|
||||
else if (scale2<scale1) scale1=scale2;
|
||||
|
||||
w=(int)(texwidth*scale1);
|
||||
h=(int)(texheight*scale1);
|
||||
}
|
||||
else if (applyscale)
|
||||
{
|
||||
w=(int) (texture->GetScaledWidthDouble()*spawnScaleX);
|
||||
h=(int) (texture->GetScaledHeightDouble()*spawnScaleY);
|
||||
}
|
||||
statusBar->DrawGraphic(texture, imgx, imgy, block->XOffset(), block->YOffset(), frameAlpha, block->FullScreenOffsets(),
|
||||
translatable, false, offset);
|
||||
translatable, false, offset, false, w, h);
|
||||
}
|
||||
void Parse(FScanner &sc, bool fullScreenOffsets)
|
||||
{
|
||||
|
|
@ -98,7 +135,7 @@ class CommandDrawImage : public SBarInfoCommand
|
|||
type = HEXENARMOR_AMULET;
|
||||
else
|
||||
{
|
||||
sc.ScriptMessage("Unkown armor type: '%s'", sc.String);
|
||||
sc.ScriptMessage("Unknown armor type: '%s'", sc.String);
|
||||
type = HEXENARMOR_ARMOR;
|
||||
}
|
||||
sc.MustGetToken(',');
|
||||
|
|
@ -136,53 +173,100 @@ class CommandDrawImage : public SBarInfoCommand
|
|||
GetCoordinates(sc, fullScreenOffsets, imgx, imgy);
|
||||
if(sc.CheckToken(','))
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
if(sc.Compare("center"))
|
||||
offset = CENTER;
|
||||
else if(sc.Compare("centerbottom"))
|
||||
offset = static_cast<Offset> (HMIDDLE|BOTTOM);
|
||||
else
|
||||
sc.ScriptError("'%s' is not a valid alignment.", sc.String);
|
||||
// Use none instead of topleft in case we decide we want to use
|
||||
// alignments to remove the offset from images.
|
||||
if(!sc.CheckToken(TK_None))
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
if(sc.Compare("center"))
|
||||
offset = CENTER;
|
||||
else if(sc.Compare("centerbottom"))
|
||||
offset = static_cast<Offset> (HMIDDLE|BOTTOM);
|
||||
else
|
||||
sc.ScriptError("'%s' is not a valid alignment.", sc.String);
|
||||
}
|
||||
}
|
||||
sc.MustGetToken(';');
|
||||
if(sc.CheckToken(','))
|
||||
{
|
||||
sc.MustGetToken(TK_IntConst);
|
||||
if((maxwidth = sc.Number) > 0)
|
||||
flags |= DI_DRAWINBOX;
|
||||
else
|
||||
maxwidth = -1;
|
||||
sc.MustGetToken(',');
|
||||
sc.MustGetToken(TK_IntConst);
|
||||
if ((maxheight = sc.Number) > 0)
|
||||
flags |= DI_DRAWINBOX;
|
||||
else
|
||||
maxheight = -1;
|
||||
}
|
||||
if(sc.CheckToken(','))
|
||||
{
|
||||
while(sc.CheckToken(TK_Identifier))
|
||||
{
|
||||
if(sc.Compare("skipicon"))
|
||||
flags |= DI_SKIPICON;
|
||||
else if(sc.Compare("skipalticon"))
|
||||
flags |= DI_SKIPALTICON;
|
||||
else if(sc.Compare("skipspawn"))
|
||||
flags |= DI_SKIPSPAWN;
|
||||
else if(sc.Compare("skipready"))
|
||||
flags |= DI_SKIPREADY;
|
||||
else if(sc.Compare("alticonfirst"))
|
||||
flags |= DI_ALTICONFIRST;
|
||||
else if(sc.Compare("forcescale"))
|
||||
{
|
||||
if(flags & DI_DRAWINBOX)
|
||||
flags |= DI_FORCESCALE;
|
||||
}
|
||||
else if(sc.Compare("alternateonfail"))
|
||||
flags |= DI_ALTERNATEONFAIL;
|
||||
else
|
||||
sc.ScriptError("Unknown flag '%s'.", sc.String);
|
||||
if(!sc.CheckToken('|') && !sc.CheckToken(',')) break;
|
||||
}
|
||||
}
|
||||
if(flags & DI_ALTERNATEONFAIL)
|
||||
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
|
||||
else
|
||||
sc.MustGetToken(';');
|
||||
}
|
||||
void Tick(const SBarInfoMainBlock *block, const DSBarInfo *statusBar, bool hudChanged)
|
||||
{
|
||||
SBarInfoCommandFlowControl::Tick(block, statusBar, hudChanged);
|
||||
|
||||
texture = NULL;
|
||||
alpha = FRACUNIT;
|
||||
if (applyscale)
|
||||
{
|
||||
spawnScaleX = spawnScaleY = 1.0f;
|
||||
applyscale = false;
|
||||
}
|
||||
if(type == PLAYERICON)
|
||||
texture = TexMan[statusBar->CPlayer->mo->ScoreIcon];
|
||||
else if(type == AMMO1)
|
||||
{
|
||||
if(statusBar->ammo1 != NULL)
|
||||
texture = TexMan[statusBar->ammo1->Icon];
|
||||
AAmmo *ammo = statusBar->ammo1;
|
||||
if(ammo != NULL)
|
||||
GetIcon(ammo);
|
||||
}
|
||||
else if(type == AMMO2)
|
||||
{
|
||||
if(statusBar->ammo2 != NULL)
|
||||
texture = TexMan[statusBar->ammo2->Icon];
|
||||
AAmmo *ammo = statusBar->ammo2;
|
||||
if(ammo != NULL)
|
||||
GetIcon(ammo);
|
||||
}
|
||||
else if(type == ARMOR)
|
||||
{
|
||||
if(statusBar->armor != NULL && statusBar->armor->Amount != 0)
|
||||
texture = TexMan(statusBar->armor->Icon);
|
||||
ABasicArmor *armor = statusBar->armor;
|
||||
if(armor != NULL && armor->Amount != 0)
|
||||
GetIcon(armor);
|
||||
}
|
||||
else if(type == WEAPONICON)
|
||||
{
|
||||
AWeapon *weapon = statusBar->CPlayer->ReadyWeapon;
|
||||
if(weapon != NULL)
|
||||
{
|
||||
FTextureID icon;
|
||||
if (weapon->Icon.isValid())
|
||||
{
|
||||
icon = weapon->Icon;
|
||||
}
|
||||
else
|
||||
{
|
||||
icon = GetWeaponIcon(weapon);
|
||||
}
|
||||
texture = TexMan[icon];
|
||||
}
|
||||
GetIcon(weapon);
|
||||
}
|
||||
else if(type == SIGIL)
|
||||
{
|
||||
|
|
@ -213,8 +297,26 @@ class CommandDrawImage : public SBarInfoCommand
|
|||
texture = TexMan(statusBar->CPlayer->mo->InvSel->Icon);
|
||||
else if(image >= 0)
|
||||
texture = statusBar->Images[image];
|
||||
|
||||
if (flags & DI_ALTERNATEONFAIL)
|
||||
{
|
||||
SetTruth(texture == NULL || texture->UseType == FTexture::TEX_Null, block, statusBar);
|
||||
}
|
||||
}
|
||||
protected:
|
||||
void GetIcon(AInventory *item)
|
||||
{
|
||||
FTextureID icon = GetInventoryIcon(item, flags, &applyscale);
|
||||
|
||||
if (applyscale)
|
||||
{
|
||||
spawnScaleX = FIXED2FLOAT(item->scaleX);
|
||||
spawnScaleY = FIXED2FLOAT(item->scaleY);
|
||||
}
|
||||
|
||||
texture = TexMan[icon];
|
||||
}
|
||||
|
||||
enum ImageType
|
||||
{
|
||||
PLAYERICON,
|
||||
|
|
@ -238,6 +340,12 @@ class CommandDrawImage : public SBarInfoCommand
|
|||
ImageType type;
|
||||
int image;
|
||||
FTextureID sprite;
|
||||
int maxwidth;
|
||||
int maxheight;
|
||||
double spawnScaleX;
|
||||
double spawnScaleY;
|
||||
DWORD flags;
|
||||
bool applyscale; //Set remotely from from GetInventoryIcon when selected sprite comes from Spawn state
|
||||
// I'm using imgx/imgy here so that I can inherit drawimage with drawnumber for some commands.
|
||||
SBarInfoCoordinate imgx;
|
||||
SBarInfoCoordinate imgy;
|
||||
|
|
@ -722,7 +830,7 @@ class CommandDrawString : public SBarInfoCommand
|
|||
if(level.lumpnum != cache)
|
||||
{
|
||||
cache = level.lumpnum;
|
||||
str = level.mapname;
|
||||
str = level.MapName;
|
||||
str.ToUpper();
|
||||
RealignString();
|
||||
}
|
||||
|
|
@ -1479,11 +1587,11 @@ class CommandDrawMugShot : public SBarInfoCommand
|
|||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class CommandDrawSelectedInventory : public SBarInfoCommandFlowControl, private CommandDrawImage, private CommandDrawNumber
|
||||
class CommandDrawSelectedInventory : public CommandDrawImage, private CommandDrawNumber
|
||||
{
|
||||
public:
|
||||
CommandDrawSelectedInventory(SBarInfo *script) : SBarInfoCommandFlowControl(script),
|
||||
CommandDrawImage(script), CommandDrawNumber(script), alternateOnEmpty(false),
|
||||
CommandDrawSelectedInventory(SBarInfo *script) : CommandDrawImage(script),
|
||||
CommandDrawNumber(script), alternateOnEmpty(false),
|
||||
artiflash(false), alwaysShowCounter(false)
|
||||
{
|
||||
length = INT_MAX; // Counter size
|
||||
|
|
@ -1639,10 +1747,20 @@ class CommandGameMode : public SBarInfoCommandFlowControl
|
|||
|
||||
void Parse(FScanner &sc, bool fullScreenOffsets)
|
||||
{
|
||||
static bool warnUnknown = true;
|
||||
do
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
modes |= static_cast<GameModes> (1<<sc.MustMatchString(modeNames));
|
||||
int mode = sc.MatchString(modeNames);
|
||||
if(mode >= 0)
|
||||
modes |= static_cast<GameModes> (1<<mode);
|
||||
else if(warnUnknown)
|
||||
{
|
||||
// Only warn about unknowns for cross port compatibility
|
||||
// Also only warn once to keep logs from getting messy.
|
||||
warnUnknown = false;
|
||||
FScriptPosition(sc).Message(MSG_WARNING, "Ignoring unknown gamemode %s (future cases will be silently ignored).", sc.String);
|
||||
}
|
||||
}
|
||||
while(sc.CheckToken(','));
|
||||
SBarInfoCommandFlowControl::Parse(sc, fullScreenOffsets);
|
||||
|
|
@ -3011,7 +3129,7 @@ class CommandDrawGem : public SBarInfoCommand
|
|||
else
|
||||
sc.ScriptError("Unknown drawgem flag '%s'.", sc.String);
|
||||
if(!sc.CheckToken('|'))
|
||||
sc.MustGetToken(',');
|
||||
sc.MustGetToken(',');
|
||||
}
|
||||
sc.MustGetToken(TK_StringConst); //chain
|
||||
chain = script->newImage(sc.String);
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ CVAR (Bool, hud_showmonsters, true,CVAR_ARCHIVE); // Show monster stats on HUD
|
|||
CVAR (Bool, hud_showitems, false,CVAR_ARCHIVE); // Show item stats on HUD
|
||||
CVAR (Bool, hud_showstats, false, CVAR_ARCHIVE); // for stamina and accuracy.
|
||||
CVAR (Bool, hud_showscore, false, CVAR_ARCHIVE); // for user maintained score
|
||||
CVAR (Bool, hud_showweapons, true, CVAR_ARCHIVE); // Show weapons collected
|
||||
CVAR (Int , hud_showtime, 0, CVAR_ARCHIVE); // Show time on HUD
|
||||
CVAR (Int , hud_timecolor, CR_GOLD,CVAR_ARCHIVE); // Color of in-game time on HUD
|
||||
|
||||
|
|
@ -133,12 +134,12 @@ static void DrawImageToBox(FTexture * tex, int x, int y, int w, int h, int trans
|
|||
|
||||
if (tex)
|
||||
{
|
||||
int texwidth=tex->GetWidth();
|
||||
int texheight=tex->GetHeight();
|
||||
double texwidth=tex->GetScaledWidthDouble();
|
||||
double texheight=tex->GetScaledHeightDouble();
|
||||
|
||||
if (w<texwidth) scale1=(double)w/texwidth;
|
||||
if (w<texwidth) scale1=w/texwidth;
|
||||
else scale1=1.0f;
|
||||
if (h<texheight) scale2=(double)h/texheight;
|
||||
if (h<texheight) scale2=h/texheight;
|
||||
else scale2=1.0f;
|
||||
if (scale2<scale1) scale1=scale2;
|
||||
|
||||
|
|
@ -605,22 +606,40 @@ static int DrawAmmo(player_t *CPlayer, int x, int y)
|
|||
// Weapons List
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
FTextureID GetWeaponIcon(AWeapon *weapon) // This function is also used by SBARINFO
|
||||
FTextureID GetInventoryIcon(AInventory *item, DWORD flags, bool *applyscale=NULL) // This function is also used by SBARINFO
|
||||
{
|
||||
FTextureID AltIcon = GetHUDIcon(weapon->GetClass());
|
||||
FTextureID picnum, AltIcon = GetHUDIcon(item->GetClass());
|
||||
FState * state=NULL, *ReadyState;
|
||||
|
||||
FTextureID picnum = !AltIcon.isNull()? AltIcon : weapon->Icon;
|
||||
|
||||
if (picnum.isNull())
|
||||
picnum.SetNull();
|
||||
if (flags & DI_ALTICONFIRST)
|
||||
{
|
||||
if (weapon->SpawnState && weapon->SpawnState->sprite!=0)
|
||||
if (!(flags & DI_SKIPALTICON) && AltIcon.isValid())
|
||||
picnum = AltIcon;
|
||||
else if (!(flags & DI_SKIPICON))
|
||||
picnum = item->Icon;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(flags & DI_SKIPICON) && item->Icon.isValid())
|
||||
picnum = item->Icon;
|
||||
else if (!(flags & DI_SKIPALTICON))
|
||||
picnum = AltIcon;
|
||||
}
|
||||
|
||||
if (!picnum.isValid()) //isNull() is bad for checking, because picnum could be also invalid (-1)
|
||||
{
|
||||
if (!(flags & DI_SKIPSPAWN) && item->SpawnState && item->SpawnState->sprite!=0)
|
||||
{
|
||||
state = weapon->SpawnState;
|
||||
state = item->SpawnState;
|
||||
|
||||
if (applyscale != NULL && !(flags & DI_FORCESCALE))
|
||||
{
|
||||
*applyscale = true;
|
||||
}
|
||||
}
|
||||
// no spawn state - now try the ready state
|
||||
else if ((ReadyState = weapon->FindState(NAME_Ready)) && ReadyState->sprite!=0)
|
||||
// no spawn state - now try the ready state if it's weapon
|
||||
else if (!(flags & DI_SKIPREADY) && item->GetClass()->IsDescendantOf(RUNTIME_CLASS(AWeapon)) && (ReadyState = item->FindState(NAME_Ready)) && ReadyState->sprite!=0)
|
||||
{
|
||||
state = ReadyState;
|
||||
}
|
||||
|
|
@ -650,7 +669,7 @@ static void DrawOneWeapon(player_t * CPlayer, int x, int & y, AWeapon * weapon)
|
|||
if (weapon==CPlayer->ReadyWeapon || weapon==CPlayer->ReadyWeapon->SisterWeapon) trans=0xd999;
|
||||
}
|
||||
|
||||
FTextureID picnum = GetWeaponIcon(weapon);
|
||||
FTextureID picnum = GetInventoryIcon(weapon, DI_ALTICONFIRST);
|
||||
|
||||
if (picnum.isValid())
|
||||
{
|
||||
|
|
@ -953,7 +972,7 @@ void DrawHUD()
|
|||
CPlayer->mo->FindInventory<AHexenArmor>(), 5, hudheight-20);
|
||||
i=DrawKeys(CPlayer, hudwidth-4, hudheight-10);
|
||||
i=DrawAmmo(CPlayer, hudwidth-5, i);
|
||||
DrawWeapons(CPlayer, hudwidth-5, i);
|
||||
if (hud_showweapons) DrawWeapons(CPlayer, hudwidth - 5, i);
|
||||
DrawInventory(CPlayer, 144, hudheight-28);
|
||||
if (CPlayer->camera && CPlayer->camera->player)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ void ST_FormatMapName(FString &mapname, const char *mapnamecolor)
|
|||
|
||||
if (am_showmaplabel == 1 || (am_showmaplabel == 2 && !ishub))
|
||||
{
|
||||
mapname << level.mapname << ": ";
|
||||
mapname << level.MapName << ": ";
|
||||
}
|
||||
mapname << mapnamecolor << level.LevelName;
|
||||
}
|
||||
|
|
@ -1090,12 +1090,12 @@ void DBaseStatusBar::RefreshBackground () const
|
|||
|
||||
if (setblocks >= 10)
|
||||
{
|
||||
const gameborder_t *border = gameinfo.border;
|
||||
FTexture *p;
|
||||
|
||||
p = TexMan[border->b];
|
||||
screen->FlatFill(0, y, x, y + p->GetHeight(), p, true);
|
||||
screen->FlatFill(x2, y, SCREENWIDTH, y + p->GetHeight(), p, true);
|
||||
FTexture *p = TexMan[gameinfo.Border.b];
|
||||
if (p != NULL)
|
||||
{
|
||||
screen->FlatFill(0, y, x, y + p->GetHeight(), p, true);
|
||||
screen->FlatFill(x2, y, SCREENWIDTH, y + p->GetHeight(), p, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue