- Added support for ST's QUARTERGRAVITY flag.

- Added a generalized version of Skulltag's A_CheckRailReload function.
- Fixed: DrawImage didn't take 0 as a valid image index.
- Added Gez's RandomSpawner submission with significant changes.
- Added optional blocks for MAPINFO map definitions. ZDoom doesn't use
  this feature itself but it allows other ports based on ZDoom
  to implement their own sets of options without making such a MAPINFO 
  unreadable by ZDoom.


SVN r1044 (trunk)
This commit is contained in:
Christoph Oelckers 2008-06-22 09:13:19 +00:00
parent b899fbeabe
commit 4ff07b68ee
16 changed files with 382 additions and 57 deletions

View file

@ -1,3 +1,13 @@
June 22, 2008 (Changes by Graf Zahl)
- Added support for ST's QUARTERGRAVITY flag.
- Added a generalized version of Skulltag's A_CheckRailReload function.
- Fixed: DrawImage didn't take 0 as a valid image index.
- Added Gez's RandomSpawner submission with significant changes.
- Added optional blocks for MAPINFO map definitions. ZDoom doesn't use
this feature itself but it allows other ports based on ZDoom
to implement their own sets of options without making such a MAPINFO
unreadable by ZDoom.
June 16, 2008 (Changes by Graf Zahl)
- Fixed: The mugshot would not reset on re-spawn.
- Fixed: Picking up a weapon would sometimes not activate the grin.

View file

@ -110,3 +110,5 @@ ACTOR(LookEx)
ACTOR(JumpIfTargetInLOS)
ACTOR(DamageMaster)
ACTOR(DamageChildren)
ACTOR(CheckForReload)
ACTOR(ResetReloadCounter)

View file

@ -6,6 +6,7 @@
#include "s_sound.h"
#include "a_doomglobal.h"
#include "statnums.h"
#include "thingdef/thingdef.h"
void A_Fire (AActor *); // from m_archvile.cpp
@ -242,12 +243,18 @@ void A_BrainSpit (AActor *self)
if (targ != NULL)
{
const PClass *spawntype = NULL;
int index = CheckIndex (1, NULL);
if (index >= 0) spawntype = PClass::FindClass ((ENamedName)StateParameters[index]);
if (spawntype == NULL) spawntype = RUNTIME_CLASS(ASpawnShot);
// spawn brain missile
spit = P_SpawnMissile (self, targ, RUNTIME_CLASS(ASpawnShot));
spit = P_SpawnMissile (self, targ, spawntype);
if (spit != NULL)
{
spit->target = targ;
spit->master = self;
// [RH] Do this correctly for any trajectory. Doom would divide by 0
// if the target had the same y coordinate as the spitter.
if ((spit->momx | spit->momy) == 0)
@ -265,7 +272,15 @@ void A_BrainSpit (AActor *self)
spit->reactiontime /= spit->state->GetTics();
}
S_Sound (self, CHAN_WEAPON, "brain/spit", 1, ATTN_NONE);
if (index >= 0)
{
S_Sound(self, CHAN_WEAPON, self->AttackSound, 1, ATTN_NONE);
}
else
{
// compatibility fallback
S_Sound (self, CHAN_WEAPON, "brain/spit", 1, ATTN_NONE);
}
}
}
@ -275,51 +290,109 @@ void A_SpawnFly (AActor *self)
AActor *fog;
AActor *targ;
int r;
const char *type;
if (--self->reactiontime)
return; // still flying
targ = self->target;
// First spawn teleport fire.
fog = Spawn<ASpawnFire> (targ->x, targ->y, targ->z, ALLOW_REPLACE);
S_Sound (fog, CHAN_BODY, "brain/spawn", 1, ATTN_NORM);
// Randomly select monster to spawn.
r = pr_spawnfly ();
// Probability distribution (kind of :),
// decreasing likelihood.
if (r < 50) type = "DoomImp";
else if (r < 90) type = "Demon";
else if (r < 120) type = "Spectre";
else if (r < 130) type = "PainElemental";
else if (r < 160) type = "Cacodemon";
else if (r < 162) type = "Archvile";
else if (r < 172) type = "Revenant";
else if (r < 192) type = "Arachnotron";
else if (r < 222) type = "Fatso";
else if (r < 246) type = "HellKnight";
else type = "BaronOfHell";
newmobj = Spawn (type, targ->x, targ->y, targ->z, ALLOW_REPLACE);
if (newmobj != NULL)
const PClass *spawntype = NULL;
int index = CheckIndex (1, NULL);
// First spawn teleport fire.
if (index >= 0)
{
// Make the new monster hate what the boss eye hates
AActor *eye = self->target;
if (eye != NULL)
spawntype = PClass::FindClass ((ENamedName)StateParameters[index]);
if (spawntype != NULL)
{
newmobj->CopyFriendliness (eye, false);
fog = Spawn (spawntype, targ->x, targ->y, targ->z, ALLOW_REPLACE);
if (fog != NULL) S_Sound (fog, CHAN_BODY, fog->SeeSound, 1, ATTN_NORM);
}
if (newmobj->SeeState != NULL && P_LookForPlayers (newmobj, true))
newmobj->SetState (newmobj->SeeState);
}
else
{
fog = Spawn<ASpawnFire> (targ->x, targ->y, targ->z, ALLOW_REPLACE);
if (fog != NULL) S_Sound (fog, CHAN_BODY, "brain/spawn", 1, ATTN_NORM);
}
if (!(newmobj->ObjectFlags & OF_EuthanizeMe))
FName SpawnName;
if (self->master != NULL)
{
FDropItem *di; // di will be our drop item list iterator
FDropItem *drop; // while drop stays as the reference point.
int n=0;
drop = di = GetDropItems(self->master->GetClass());
if (di != NULL)
{
// telefrag anything in this spot
P_TeleportMove (newmobj, newmobj->x, newmobj->y, newmobj->z, true);
while (di != NULL)
{
if (di->Name != NAME_None)
{
if (di->amount < 0) di->amount = 1; // default value is -1, we need a positive value.
n += di->amount; // this is how we can weight the list.
di = di->Next;
}
}
di = drop;
n = pr_spawnfly(n);
while (n > 0)
{
if (di->Name != NAME_None)
{
n -= di->amount; // logically, none of the -1 values have survived by now.
if (n > -1) di = di->Next; // If we get into the negatives, a spawnfog could be spawned...
// It would mean we've reached the end of the list of monsters.
}
}
SpawnName = di->Name;
}
}
if (SpawnName == NAME_None)
{
const char *type;
// Randomly select monster to spawn.
r = pr_spawnfly ();
// Probability distribution (kind of :),
// decreasing likelihood.
if (r < 50) type = "DoomImp";
else if (r < 90) type = "Demon";
else if (r < 120) type = "Spectre";
else if (r < 130) type = "PainElemental";
else if (r < 160) type = "Cacodemon";
else if (r < 162) type = "Archvile";
else if (r < 172) type = "Revenant";
else if (r < 192) type = "Arachnotron";
else if (r < 222) type = "Fatso";
else if (r < 246) type = "HellKnight";
else type = "BaronOfHell";
SpawnName = type;
}
spawntype = PClass::FindClass(SpawnName);
if (spawntype != NULL)
{
newmobj = Spawn (spawntype, targ->x, targ->y, targ->z, ALLOW_REPLACE);
if (newmobj != NULL)
{
// Make the new monster hate what the boss eye hates
AActor *eye = self->target;
if (eye != NULL)
{
newmobj->CopyFriendliness (eye, false);
}
if (newmobj->SeeState != NULL && P_LookForPlayers (newmobj, true))
newmobj->SetState (newmobj->SeeState);
if (!(newmobj->ObjectFlags & OF_EuthanizeMe))
{
// telefrag anything in this spot
P_TeleportMove (newmobj, newmobj->x, newmobj->y, newmobj->z, true);
}
}
}
@ -375,3 +448,4 @@ void DBrainState::Serialize (FArchive &arc)
arc << SerialTarget;
}
}

View file

@ -108,6 +108,48 @@ static void ClearClusterInfoStrings (cluster_info_t *cinfo);
static void ParseSkill (FScanner &sc);
static void G_VerifySkill();
struct FOptionalMapinfoParser
{
const char *keyword;
MIParseFunc parsefunc;
};
static TArray<FOptionalMapinfoParser> optmapinf(TArray<FOptionalMapinfoParser>::NoInit);
void AddOptionalMapinfoParser(const char *keyword, MIParseFunc parsefunc)
{
FOptionalMapinfoParser mi;
mi.keyword = keyword;
mi.parsefunc = parsefunc;
optmapinf.Push(mi);
}
static void ParseOptionalBlock(const char *keyword, FScanner &sc, level_info_t *info)
{
for(unsigned i=0;i<optmapinf.Size();i++)
{
if (!stricmp(keyword, optmapinf[i].keyword))
{
optmapinf[i].parsefunc(sc, info);
return;
}
}
int bracecount = 0;
while (sc.GetString())
{
if (sc.Compare("{")) bracecount++;
else if (sc.Compare("}"))
{
if (--bracecount < 0) return;
}
}
}
static FRandom pr_classchoice ("RandomPlayerClassChoice");
TArray<EndSequence> EndSequences;
@ -314,9 +356,6 @@ static const char *MapInfoMapLevel[] =
"noinfiniteflightpowerup",
"allowrespawn",
"teamdamage",
"fogdensity",
"outsidefogdensity",
"skyfog",
"teamplayon",
"teamplayoff",
"checkswitchrange",
@ -466,9 +505,6 @@ MapHandlers[] =
{ MITYPE_CLRFLAG, LEVEL_INFINITE_FLIGHT, 0 },
{ MITYPE_SETFLAG, LEVEL_ALLOWRESPAWN, 0 },
{ MITYPE_FLOAT, lioffset(teamdamage), 0 },
{ MITYPE_INT, lioffset(fogdensity), 0 },
{ MITYPE_INT, lioffset(outsidefogdensity), 0 },
{ MITYPE_INT, lioffset(skyfog), 0 },
{ MITYPE_SCFLAGS, LEVEL_FORCETEAMPLAYON, ~LEVEL_FORCETEAMPLAYOFF },
{ MITYPE_SCFLAGS, LEVEL_FORCETEAMPLAYOFF, ~LEVEL_FORCETEAMPLAYON },
{ MITYPE_SETFLAG, LEVEL_CHECKSWITCHRANGE, 0 },
@ -632,6 +668,19 @@ static FSpecialAction *CopySpecialActions(FSpecialAction *spec)
return spec;
}
static FOptionalMapinfoData *CopyOptData(FOptionalMapinfoData *opdata)
{
FOptionalMapinfoData **opt = &opdata;
while (*opt)
{
FOptionalMapinfoData *newop = (*opt)->Clone();
*opt = newop;
opt = &newop->Next;
}
return opdata;
}
static void CopyString (char *& string)
{
if (string != NULL)
@ -663,6 +712,12 @@ static void ClearLevelInfoStrings(level_info_t *linfo)
delete spac;
spac = next;
}
for (FOptionalMapinfoData *spac = linfo->opdata; spac != NULL; )
{
FOptionalMapinfoData *next = spac->Next;
delete spac;
spac = next;
}
}
static void ClearClusterInfoStrings(cluster_info_t *cinfo)
@ -743,6 +798,7 @@ static void G_DoParseMapInfo (int lump)
CopyString(levelinfo->soundinfo);
CopyString(levelinfo->sndseq);
levelinfo->specialactions = CopySpecialActions(levelinfo->specialactions);
levelinfo->opdata = CopyOptData(levelinfo->opdata);
if (HexenHack)
{
levelinfo->WallHorizLight = levelinfo->WallVertLight = 0;
@ -867,7 +923,28 @@ static void ParseMapInfoLower (FScanner &sc,
sc.UnGet ();
break;
}
entry = sc.MustMatchString (strings);
entry = sc.MatchString (strings);
if (entry == -1)
{
FString keyword = sc.String;
sc.MustGetString();
if (levelinfo != NULL)
{
if (sc.Compare("{"))
{
ParseOptionalBlock(keyword, sc, levelinfo);
continue;
}
else if (!stricmp(keyword, "nobotnodes")) // Ignore this Skulltag option if it appears
{
continue;
}
}
sc.ScriptError("Unknown keyword '%s'", keyword.GetChars());
}
handler = handlers + entry;
switch (handler->type)
{

View file

@ -136,6 +136,20 @@ struct FSpecialAction
class FCompressedMemFile;
class DScroller;
class FScanner;
struct level_info_t;
typedef void (*MIParseFunc)(FScanner &sc, level_info_t *info);
void AddOptionalMapinfoParser(const char *keyword, MIParseFunc parsefunc);
struct FOptionalMapinfoData
{
FOptionalMapinfoData *Next;
FName identifier;
FOptionalMapinfoData() { Next = NULL; identifier = NAME_None; }
virtual FOptionalMapinfoData *Clone() const = 0;
};
struct level_info_t
{
char mapname[9];
@ -187,13 +201,11 @@ struct level_info_t
char *sndseq;
char bordertexture[9];
int fogdensity;
int outsidefogdensity;
int skyfog;
FSpecialAction * specialactions;
float teamdamage;
FSpecialAction * specialactions;
FOptionalMapinfoData *opdata;
};
// [RH] These get zeroed every tic and are updated by thinkers.

View file

@ -218,6 +218,7 @@ public:
const PClass *AltProjectileType; // Projectile used by alternate attack
int SelectionOrder; // Lower-numbered weapons get picked first
fixed_t MoveCombatDist; // Used by bots, but do they *really* need it?
int ReloadCounter; // For A_CheckForReload
// In-inventory instance variables
TObjPtr<AAmmo> Ammo1, Ammo2;

View file

@ -0,0 +1,86 @@
#include "actor.h"
#include "info.h"
#include "m_random.h"
#include "p_local.h"
#include "p_enemy.h"
#include "s_sound.h"
#include "a_doomglobal.h"
#include "statnums.h"
#include "gstrings.h"
#include "a_action.h"
#include "thingdef/thingdef.h"
/*
- in the decorate definition define multiple drop items
- use the function GetDropItems to get the first drop item, then iterate over them and count how many dropitems are defined.
- with M_Random( ) % NUMBEROFDROPITEMS you get a random drop item number (let's call it n)
- use GetDropItems again to get the first drop item, then iterate to the n-th drop item
*/
static FRandom pr_randomspawn("RandomSpawn");
class ARandomSpawner : public AActor
{
DECLARE_STATELESS_ACTOR (ARandomSpawner, AActor)
void PostBeginPlay()
{
AActor *newmobj;
FDropItem *di; // di will be our drop item list iterator
FDropItem *drop; // while drop stays as the reference point.
int n=0;
Super::PostBeginPlay();
drop = di = GetDropItems(RUNTIME_TYPE(this));
// Always make sure it actually exists.
if (di != NULL)
{
// First, we get the size of the array...
while (di != NULL)
{
if (di->Name != NAME_None)
{
if (di->amount < 0) di->amount = 1; // default value is -1, we need a positive value.
n += di->amount; // this is how we can weight the list.
di = di->Next;
}
}
// Then we reset the iterator to the start position...
di = drop;
// Take a random number...
n = pr_randomspawn(n);
// And iterate in the array up to the random number chosen.
while (n > 0)
{
if (di->Name != NAME_None)
{
n -= di->amount;
di = di->Next;
}
}
// So now we can spawn the dropped item.
if (pr_randomspawn() <= di->probability) // prob 255 = always spawn, prob 0 = never spawn.
{
newmobj = Spawn(di->Name, x, y, z, ALLOW_REPLACE);
// copy everything relevant
newmobj->SpawnAngle = newmobj->angle = angle;
newmobj->special = special;
newmobj->args[0] = args[0];
newmobj->args[1] = args[1];
newmobj->args[2] = args[2];
newmobj->args[3] = args[3];
newmobj->args[4] = args[4];
newmobj->SpawnFlags = SpawnFlags;
newmobj->HandleSpawnFlags();
newmobj->momx = momx;
newmobj->momy = momy;
newmobj->momz = momz;
newmobj->CopyFriendliness(this, false);
}
}
}
};
IMPLEMENT_STATELESS_ACTOR (ARandomSpawner, Any, -1, 0)
END_DEFAULTS

View file

@ -53,7 +53,8 @@ void AWeapon::Serialize (FArchive &arc)
<< SelectionOrder
<< MoveCombatDist
<< Ammo1 << Ammo2 << SisterWeapon << GivenAsMorphWeapon
<< bAltFire;
<< bAltFire
<< ReloadCounter;
}
//===========================================================================

View file

@ -479,7 +479,7 @@ void DSBarInfo::doCommands(SBarInfoBlock &block, int xOffset, int yOffset, int a
}
else if((cmd.flags & DRAWIMAGE_INVENTORYICON))
texture = TexMan[cmd.sprite_index];
else if(cmd.image_index > 0)
else if(cmd.image_index >= 0)
texture = Images[cmd.image_index];
DrawGraphic(texture, cmd.x, cmd.y, xOffset, yOffset, alpha, !!(cmd.flags & DRAWIMAGE_TRANSLATABLE), false, !!(cmd.flags & DRAWIMAGE_OFFSET_CENTER));

View file

@ -447,6 +447,7 @@ void SBarInfo::ParseSBarInfoBlock(FScanner &sc, SBarInfoBlock &block)
}
else
{
//sc.CheckToken(TK_Identifier);
cmd.flags |= DRAWIMAGE_INVENTORYICON;
const PClass* item = PClass::FindClass(sc.String);
if(item == NULL || !PClass::FindClass("Inventory")->IsAncestorOf(item)) //must be a kind of Inventory

View file

@ -21,14 +21,14 @@ class FTextureID
friend void R_InitSpriteDefs ();
public:
FTextureID() {}
FTextureID() throw() {}
bool isNull() const { return texnum == 0; }
bool isValid() const { return texnum > 0; }
bool Exists() const { return texnum >= 0; }
void SetInvalid() { texnum = -1; }
bool operator ==(const FTextureID &other) const { return texnum == other.texnum; }
bool operator !=(const FTextureID &other) const { return texnum != other.texnum; }
FTextureID operator +(int offset);
FTextureID operator +(int offset) throw();
int GetIndex() const { return texnum; } // Use this only if you absolutely need the index!
// The switch list needs these to sort the switches by texture index

View file

@ -1416,7 +1416,7 @@ static void InitSpawnedItem(AActor *self, AActor *mo, int flags)
(deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer))
{
// Target the monster which last attacked the player
mo->target = attacker;
mo->LastHeard = mo->target = attacker;
}
}
}
@ -2261,3 +2261,50 @@ void A_DamageChildren(AActor * self)
}
// [KS] *** End of my modifications ***
//===========================================================================
//
// Modified code pointer from Skulltag
//
//===========================================================================
void A_CheckForReload( AActor *self )
{
if ( self->player == NULL || self->player->ReadyWeapon == NULL )
return;
int index = CheckIndex(2);
if (index<0) return;
AWeapon *weapon = self->player->ReadyWeapon;
int count = EvalExpressionI (StateParameters[index], self);
weapon->ReloadCounter = (weapon->ReloadCounter+1) % count;
// If we have not made our last shot...
if (weapon->ReloadCounter != 0)
{
// Go back to the refire frames, instead of continuing on to the reload frames.
DoJump(self, CallingState, StateParameters[index + 1]);
}
else
{
// We need to reload. However, don't reload if we're out of ammo.
weapon->CheckAmmo( false, false );
}
}
//===========================================================================
//
// Resets the counter for the above function
//
//===========================================================================
void A_ResetReloadCounter(AActor *self)
{
if ( self->player == NULL || self->player->ReadyWeapon == NULL )
return;
AWeapon *weapon = self->player->ReadyWeapon;
weapon->ReloadCounter = 0;
}

View file

@ -98,7 +98,8 @@ enum
DEPF_LOWGRAVITY,
DEPF_LONGMELEERANGE,
DEPF_SHORTMISSILERANGE,
DEPF_PICKUPFLASH
DEPF_PICKUPFLASH,
DEPF_QUARTERGRAVITY,
};
static flagdef ActorFlags[]=
@ -256,7 +257,9 @@ static flagdef ActorFlags[]=
DEFINE_DEPRECATED_FLAG(LOWGRAVITY),
DEFINE_DEPRECATED_FLAG(SHORTMISSILERANGE),
DEFINE_DEPRECATED_FLAG(LONGMELEERANGE),
DEFINE_DEPRECATED_FLAG(QUARTERGRAVITY),
DEFINE_DUMMY_FLAG(NONETID),
DEFINE_DUMMY_FLAG(ALLOWCLIENTSPAWN),
};
static flagdef InventoryFlags[] =
@ -292,7 +295,6 @@ static flagdef WeaponFlags[] =
DEFINE_FLAG(WIF, PRIMARY_USES_BOTH, AWeapon, WeaponFlags),
DEFINE_FLAG(WIF, WIMPY_WEAPON, AWeapon, WeaponFlags),
DEFINE_FLAG(WIF, POWERED_UP, AWeapon, WeaponFlags),
//DEFINE_FLAG(WIF, EXTREME_DEATH, AWeapon, WeaponFlags), // this should be removed now!
DEFINE_FLAG(WIF, STAFF2_KICKBACK, AWeapon, WeaponFlags),
DEFINE_FLAG(WIF_BOT, EXPLOSIVE, AWeapon, WeaponFlags),
DEFINE_FLAG2(WIF_BOT_MELEE, MELEEWEAPON, AWeapon, WeaponFlags),
@ -424,6 +426,9 @@ static void HandleDeprecatedFlags(AActor *defaults, bool set, int index)
case DEPF_LONGMELEERANGE:
defaults->meleethreshold = set? 196*FRACUNIT : 0;
break;
case DEPF_QUARTERGRAVITY:
defaults->gravity = set? FRACUNIT/4 : FRACUNIT;
break;
case DEPF_PICKUPFLASH:
if (set)
{

View file

@ -75,7 +75,7 @@
// SAVESIG should match SAVEVER.
// MINSAVEVER is the minimum level snapshot version that can be loaded.
#define MINSAVEVER 1036
#define MINSAVEVER 1044
#if SVN_REVISION_NUMBER < MINSAVEVER
// Never write a savegame with a version lower than what we need

View file

@ -50,9 +50,9 @@ class Actor extends Thinker
action native A_BrainScream();
action native A_BrainDie();
action native A_BrainAwake();
action native A_BrainSpit();
action native A_BrainSpit(optional class<Actor> spawntype);
action native A_SpawnSound();
action native A_SpawnFly();
action native A_SpawnFly(optional class<Actor> spawntype);
action native A_BrainExplode();
action native A_Die(optional name damagetype);
action native A_Detonate();
@ -215,5 +215,7 @@ class Inventory extends Actor
action native A_CheckReload();
action native A_GunFlash();
action native A_Saw(optional coerce sound fullsound, optional coerce sound hitsound, optional eval int damage, optional class<Actor> pufftype);
action native A_CheckForReload(eval int counter, state label);
action native A_ResetReloadCounter();
}

View file

@ -55,9 +55,11 @@
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
WholeProgramOptimization="false"
AdditionalIncludeDirectories="src\win32;src\sound;src;zlib;src\g_shared;src\g_doom;src\g_raven;src\g_heretic;src\g_hexen;src\g_strife;jpeg-6b;snes_spc\snes_spc"
PreprocessorDefinitions="NDEBUG,WIN32,_WIN32,_WINDOWS,USEASM,HAVE_STRUPR,HAVE_FILELENGTH"
StringPooling="true"
ExceptionHandling="1"
RuntimeLibrary="0"
EnableFunctionLevelLinking="true"
ForceConformanceInForLoopScope="true"
@ -106,6 +108,7 @@
TerminalServerAware="2"
OptimizeReferences="2"
EnableCOMDATFolding="2"
LinkTimeCodeGeneration="0"
SetChecksum="true"
/>
<Tool
@ -2037,6 +2040,10 @@
RelativePath=".\src\g_shared\a_quake.cpp"
>
</File>
<File
RelativePath=".\src\g_shared\a_randomspawner.cpp"
>
</File>
<File
RelativePath=".\src\g_shared\a_secrettrigger.cpp"
>