Merge commit 'e73969d' into gzd4-12-merge

This commit is contained in:
Rachael Alexanderson 2024-12-18 10:20:53 -05:00
commit c2bce2760b
No known key found for this signature in database
GPG key ID: 26A8ACCE97115EE0
100 changed files with 1624 additions and 714 deletions

View file

@ -203,21 +203,21 @@ Note: All <bool> fields default to false unless mentioned otherwise.
nogradient_top = <bool>; // disables color gradient on upper tier. (Hardware rendering only.)
flipgradient_top = <bool>; // flips gradient colors on upper tier. (Hardware rendering only.)
clampgradient_top = <bool>; // clamps gradient on upper tier to actual bounds (default is the entire front sector height, hardware rendering only.)
useowncolors_top = <bool>; // Set to 1 to use the colors set in the sidedef. Default is using the colors from the owning sector.
useowncolors_top = <bool>; // Set to 1 to use the colors set in the sidedef. Default is true if uppercolor or lowercolor are set, otherwise it's false
uppercolor_top = <int>; // Material color of the top of the upper tier.
lowercolor_top = <int>; // Material color of the bottom of the upper tier. (Hardware rendering only.)
nogradient_mid = <bool>; // disables color gradient on middle tier. (Hardware rendering only.)
flipgradient_mid = <bool>; // flips gradient colors on middle tier. (Hardware rendering only.)
clampgradient_mid = <bool>; // clamps gradient on middle tier to actual bounds (default is the entire front sector height, hardware rendering only.)
useowncolors_mid = <bool>; // Set to 1 to use the colors set in the sidedef. Default is using the colors from the owning sector.
useowncolors_mid = <bool>; // Set to 1 to use the colors set in the sidedef. Default is true if uppercolor or lowercolor are set, otherwise it's false
uppercolor_mid = <int>; // Material color of the top of the middle tier.
lowercolor_mid = <int>; // Material color of the bottom of the middle tier. (Hardware rendering only.)
nogradient_bottom = <bool>; // disables color gradient on lower tier. (Hardware rendering only.)
flipgradient_bottom = <bool>; // flips gradient colors on lower tier. (Hardware rendering only.)
clampgradient_bottom = <bool>;// clamps gradient on lower tier to actual bounds (default is the entire front sector height, hardware rendering only.)
useowncolors_bottom = <bool>; // Set to 1 to use the colors set in the sidedef. Default is using the colors from the owning sector.
useowncolors_bottom = <bool>; // Set to 1 to use the colors set in the sidedef. Default is true if uppercolor or lowercolor are set, otherwise it's false
uppercolor_bottom = <int>; // Material color of the top of the lower tier.
lowercolor_bottom = <int>; // Material color of the bottom of the lower tier. (Hardware rendering only.)
@ -307,6 +307,8 @@ Note: All <bool> fields default to false unless mentioned otherwise.
leakiness = <int>; // Probability of leaking through radiation suit (0 = never, 256 = always), default = 0.
damageterraineffect = <bool>; // Will spawn a terrain splash when damage is inflicted. Default = false.
damagehazard = <bool>; // Changes damage model to Strife's delayed damage for the given sector. Default = false.
hurtmonsters = <bool>; // Non-players like monsters and decorations are hurt by this sector in the same manner as player. Doesn't work with damagehazard.
harminair = <bool>; // Actors in this sector are harmed by the damage effects of the floor even if they aren't touching it.
floorterrain = <string>; // Sets the terrain for the sector's floor. Default = 'use the flat texture's terrain definition.'
ceilingterrain = <string>; // Sets the terrain for the sector's ceiling. Default = 'use the flat texture's terrain definition.'
floor_reflect = <float>; // reflectiveness of floor (OpenGL only, not functional on sloped sectors)

View file

@ -169,7 +169,7 @@ static const char *TelefragSounds[] =
#endif
static int LastAnnounceTime;
static FRandom pr_bbannounce ("BBAnnounce");
static FCRandom pr_bbannounce ("BBAnnounce");
// CODE --------------------------------------------------------------------

View file

@ -43,6 +43,8 @@
#include "s_soundinternal.h"
#include "i_time.h"
EXTERN_CVAR(Bool, cl_capfps)
class FBurnTexture : public FTexture
{
TArray<uint32_t> WorkBuffer;
@ -163,6 +165,8 @@ protected:
public:
virtual ~Wiper();
virtual bool Run(int ticks) = 0;
virtual bool RunInterpolated(double ticks) { return true; };
virtual bool Interpolatable() { return false; }
virtual void SetTextures(FGameTexture* startscreen, FGameTexture* endscreen)
{
startScreen = startscreen;
@ -177,9 +181,11 @@ class Wiper_Crossfade : public Wiper
{
public:
bool Run(int ticks) override;
bool RunInterpolated(double ticks) override;
bool Interpolatable() override { return true; }
private:
int Clock = 0;
float Clock = 0;
};
class Wiper_Melt : public Wiper
@ -187,10 +193,12 @@ class Wiper_Melt : public Wiper
public:
Wiper_Melt();
bool Run(int ticks) override;
bool RunInterpolated(double ticks) override;
bool Interpolatable() override { return true; }
private:
enum { WIDTH = 320, HEIGHT = 200 };
int y[WIDTH];
double y[WIDTH];
};
class Wiper_Burn : public Wiper
@ -286,7 +294,23 @@ bool Wiper_Crossfade::Run(int ticks)
Clock += ticks;
DrawTexture(twod, startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, DTA_Alpha, clamp(Clock / 32.f, 0.f, 1.f), TAG_DONE);
return Clock >= 32;
return Clock >= 32.;
}
//==========================================================================
//
// OpenGLFrameBuffer :: Wiper_Crossfade :: Run
//
// Fades the old screen into the new one over 32 ticks.
//
//==========================================================================
bool Wiper_Crossfade::RunInterpolated(double ticks)
{
Clock += ticks;
DrawTexture(twod, startScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, DTA_Alpha, clamp(Clock / 32.f, 0.f, 1.f), TAG_DONE);
return Clock >= 32.;
}
//==========================================================================
@ -300,7 +324,7 @@ Wiper_Melt::Wiper_Melt()
y[0] = -(M_Random() & 15);
for (int i = 1; i < WIDTH; ++i)
{
y[i] = clamp(y[i-1] + (M_Random() % 3) - 1, -15, 0);
y[i] = clamp(y[i-1] + (double)(M_Random() % 3) - 1., -15., 0.);
}
}
@ -325,25 +349,25 @@ bool Wiper_Melt::Run(int ticks)
{
if (y[i] < HEIGHT)
{
if (y[i] < 0)
y[i]++;
else if (y[i] < 16)
y[i] += y[i] + 1;
if (y[i] < 0.)
y[i] = y[i] + 1.;
else if (y[i] < 16.)
y[i] += y[i] + 1.;
else
y[i] = min<int>(y[i] + 8, HEIGHT);
y[i] = min<double>(y[i] + 8., HEIGHT);
done = false;
}
if (ticks == 0)
{
struct {
int32_t x;
int32_t y;
double y;
} dpt;
struct {
int32_t left;
int32_t top;
double top;
int32_t right;
int32_t bottom;
double bottom;
} rect;
// Only draw for the final tick.
@ -351,7 +375,7 @@ bool Wiper_Melt::Run(int ticks)
int w = startScreen->GetTexelWidth();
int h = startScreen->GetTexelHeight();
dpt.x = i * w / WIDTH;
dpt.y = max(0, y[i] * h / HEIGHT);
dpt.y = max(0., y[i] * (double)h / (double)HEIGHT);
rect.left = dpt.x;
rect.top = 0;
rect.right = (i + 1) * w / WIDTH;
@ -366,6 +390,77 @@ bool Wiper_Melt::Run(int ticks)
return done;
}
//==========================================================================
//
// Wiper_Melt :: RunInterpolated
//
// Melts the old screen into the new one over 32 ticks (interpolated).
//
//==========================================================================
bool Wiper_Melt::RunInterpolated(double ticks)
{
bool done = false;
DrawTexture(twod, endScreen, 0, 0, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_Masked, false, TAG_DONE);
// Copy the old screen in vertical strips on top of the new one.
while (ticks > 0.)
{
done = true;
for (int i = 0; i < WIDTH; i++)
{
if (y[i] < (double)HEIGHT)
{
if (ticks > 0. && ticks < 1.)
{
if (y[i] < 0)
y[i] += ticks;
else if (y[i] < 16)
y[i] += (y[i] + 1) * ticks;
else
y[i] = min<double>(y[i] + (8 * ticks), (double)HEIGHT);
}
else if (y[i] < 0.)
y[i] = y[i] + 1.;
else if (y[i] < 16.)
y[i] += y[i] + 1.;
else
y[i] = min<double>(y[i] + 8., HEIGHT);
done = false;
}
}
ticks -= 1.;
}
for (int i = 0; i < WIDTH; i++)
{
struct {
int32_t x;
double y;
} dpt;
struct {
int32_t left;
double top;
int32_t right;
double bottom;
} rect;
// Only draw for the final tick.
int w = startScreen->GetTexelWidth();
double h = startScreen->GetTexelHeight();
dpt.x = i * w / WIDTH;
dpt.y = max(0., y[i] * (double)h / (double)HEIGHT);
rect.left = dpt.x;
rect.top = 0;
rect.right = (i + 1) * w / WIDTH;
rect.bottom = h - dpt.y;
if (rect.bottom > rect.top)
{
DrawTexture(twod, startScreen, 0, dpt.y, DTA_FlipY, screen->RenderTextureIsFlipped(), DTA_ClipLeft, rect.left, DTA_ClipRight, rect.right, DTA_Masked, false, TAG_DONE);
}
}
return done;
}
//==========================================================================
//
// OpenGLFrameBuffer :: Wiper_Burn Constructor
@ -504,6 +599,7 @@ void PerformWipe(FTexture* startimg, FTexture* endimg, int wipe_type, bool stops
{
// wipe update
uint64_t wipestart, nowtime, diff;
double diff_frac;
bool done;
GSnd->SetSfxPaused(true, 1);
@ -519,20 +615,34 @@ void PerformWipe(FTexture* startimg, FTexture* endimg, int wipe_type, bool stops
do
{
do
if (wiper->Interpolatable() && !cl_capfps)
{
I_WaitVBL(2);
nowtime = I_msTime();
diff = (nowtime - wipestart) * 40 / 1000; // Using 35 here feels too slow.
} while (diff < 1);
wipestart = nowtime;
twod->Begin(screen->GetWidth(), screen->GetHeight());
done = wiper->Run(1);
if (overlaydrawer) overlaydrawer();
twod->End();
screen->Update();
twod->OnFrameDone();
diff_frac = (nowtime - wipestart) * 40. / 1000.; // Using 35 here feels too slow.
wipestart = nowtime;
twod->Begin(screen->GetWidth(), screen->GetHeight());
done = wiper->RunInterpolated(diff_frac);
if (overlaydrawer) overlaydrawer();
twod->End();
screen->Update();
twod->OnFrameDone();
}
else
{
do
{
I_WaitVBL(2);
nowtime = I_msTime();
diff = (nowtime - wipestart) * 40 / 1000; // Using 35 here feels too slow.
} while (diff < 1);
wipestart = nowtime;
twod->Begin(screen->GetWidth(), screen->GetHeight());
done = wiper->Run(1);
if (overlaydrawer) overlaydrawer();
twod->End();
screen->Update();
twod->OnFrameDone();
}
} while (!done);
delete wiper;
I_FreezeTime(false);

View file

@ -62,6 +62,9 @@ CUSTOM_CVAR(Int, snd_samplerate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, snd_buffersize, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Int, snd_hrtf, -1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Float, snd_footstepvolume, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
#if !defined(NO_OPENAL)
#define DEF_BACKEND "openal"
#else

View file

@ -61,7 +61,7 @@ enum
{
DEFAULT_PITCH = 128,
};
static FRandom pr_soundpitch ("SoundPitch");
static FCRandom pr_soundpitch ("SoundPitch");
SoundEngine* soundEngine;
//==========================================================================

View file

@ -132,7 +132,8 @@ enum
PRE_CONACK, // Sent from host to guest to acknowledge PRE_CONNECT receipt
PRE_ALLFULL, // Sent from host to an unwanted guest
PRE_ALLHEREACK, // Sent from guest to host to acknowledge PRE_ALLHEREACK receipt
PRE_GO // Sent from host to guest to continue game startup
PRE_GO, // Sent from host to guest to continue game startup
PRE_IN_PROGRESS, // Sent from host to guest if the game has already started
};
// Set PreGamePacket.fake to this so that the game rejects any pregame packets
@ -269,6 +270,8 @@ void PacketSend (void)
// I_Error ("SendPacket error: %s",strerror(errno));
}
void PreSend(const void* buffer, int bufferlen, const sockaddr_in* to);
void SendConAck(int num_connected, int num_needed);
//
// PacketGet
@ -303,7 +306,7 @@ void PacketGet (void)
GetPlayerName(node).GetChars());
}
doomcom.data[0] = 0x80; // NCMD_EXIT
doomcom.data[0] = NCMD_EXIT;
c = 1;
}
else if (err != WSAEWOULDBLOCK)
@ -341,10 +344,11 @@ void PacketGet (void)
}
else if (c > 0)
{ //The packet is not from any in-game node, so we might as well discard it.
// Don't show the message for disconnect notifications.
if (c != 2 || TransmitBuffer[0] != PRE_FAKE || TransmitBuffer[1] != PRE_DISCONNECT)
if (TransmitBuffer[0] == PRE_FAKE)
{
DPrintf(DMSG_WARNING, "Dropped packet: Unknown host (%s:%d)\n", inet_ntoa(fromaddress.sin_addr), fromaddress.sin_port);
// If it's someone waiting in the lobby, let them know the game already started
uint8_t msg[] = { PRE_FAKE, PRE_IN_PROGRESS };
PreSend(msg, 2, &fromaddress);
}
doomcom.remotenode = -1;
return;
@ -369,7 +373,22 @@ sockaddr_in *PreGet (void *buffer, int bufferlen, bool noabort)
int err = WSAGetLastError();
if (err == WSAEWOULDBLOCK || (noabort && err == WSAECONNRESET))
return NULL; // no packet
I_Error ("PreGet: %s", neterror ());
if (doomcom.consoleplayer == 0)
{
int node = FindNode(&fromaddress);
I_NetMessage("Got unexpected disconnect.");
doomcom.numnodes--;
for (; node < doomcom.numnodes; ++node)
sendaddress[node] = sendaddress[node + 1];
// Let remaining guests know that somebody left.
SendConAck(doomcom.numnodes, doomcom.numplayers);
}
else
{
I_NetError("The host disbanded the game unexpectedly");
}
}
return &fromaddress;
}
@ -499,7 +518,7 @@ void SendAbort (void)
}
}
static void SendConAck (int num_connected, int num_needed)
void SendConAck (int num_connected, int num_needed)
{
PreGamePacket packet;
@ -708,7 +727,7 @@ bool HostGame (int i)
doomcom.numnodes = 1;
I_NetInit ("Waiting for players", numplayers);
I_NetInit ("Hosting game", numplayers);
// Wait for numplayers-1 different connections
if (!I_NetLoop (Host_CheckForConnects, (void *)(intptr_t)numplayers))
@ -783,13 +802,15 @@ bool Guest_ContactHost (void *userdata)
}
else if (packet.Message == PRE_DISCONNECT)
{
doomcom.numnodes = 0;
I_FatalError ("The host cancelled the game.");
I_NetError("The host cancelled the game.");
}
else if (packet.Message == PRE_ALLFULL)
{
doomcom.numnodes = 0;
I_FatalError ("The game is full.");
I_NetError("The game is full.");
}
else if (packet.Message == PRE_IN_PROGRESS)
{
I_NetError("The game was already started.");
}
}
}
@ -850,7 +871,7 @@ bool Guest_WaitForOthers (void *userdata)
return true;
case PRE_DISCONNECT:
I_FatalError ("The host cancelled the game.");
I_NetError("The host cancelled the game.");
break;
}
}
@ -875,6 +896,7 @@ bool JoinGame (int i)
BuildAddress (&sendaddress[1], Args->GetArg(i+1));
sendplayer[1] = 0;
doomcom.numnodes = 2;
doomcom.consoleplayer = -1;
// Let host know we are here
@ -1046,6 +1068,13 @@ void I_NetMessage(const char* text, ...)
#endif
}
void I_NetError(const char* error)
{
doomcom.numnodes = 0;
StartWindow->NetClose();
I_FatalError(error);
}
// todo: later these must be dispatched by the main menu, not the start screen.
void I_NetProgress(int val)
{

View file

@ -7,6 +7,7 @@
int I_InitNetwork (void);
void I_NetCmd (void);
void I_NetMessage(const char*, ...);
void I_NetError(const char* error);
void I_NetProgress(int val);
void I_NetInit(const char* msg, int num);
bool I_NetLoop(bool (*timer_callback)(void*), void* userdata);

View file

@ -83,7 +83,7 @@ FRandom pr_exrandom("EX_Random");
// PUBLIC DATA DEFINITIONS -------------------------------------------------
FRandom M_Random;
FCRandom M_Random;
// Global seed. This is modified predictably to initialize every RNG.
uint32_t rngseed;
@ -126,8 +126,8 @@ CCMD(rngseed)
// PRIVATE DATA DEFINITIONS ------------------------------------------------
FRandom *FRandom::RNGList;
static TDeletingArray<FRandom *> NewRNGs;
FRandom *FRandom::RNGList, *FRandom::CRNGList;
static TDeletingArray<FRandom *> NewRNGs, NewCRNGs;
// CODE --------------------------------------------------------------------
@ -139,14 +139,22 @@ static TDeletingArray<FRandom *> NewRNGs;
//
//==========================================================================
FRandom::FRandom ()
: NameCRC (0)
FRandom::FRandom (bool client)
: NameCRC (0), bClient(client)
{
#ifndef NDEBUG
Name = NULL;
#endif
Next = RNGList;
RNGList = this;
if (bClient)
{
Next = CRNGList;
CRNGList = this;
}
else
{
Next = RNGList;
RNGList = this;
}
Init(0);
}
@ -158,7 +166,7 @@ FRandom::FRandom ()
//
//==========================================================================
FRandom::FRandom (const char *name)
FRandom::FRandom (const char *name, bool client) : bClient(client)
{
NameCRC = CalcCRC32 ((const uint8_t *)name, (unsigned int)strlen (name));
#ifndef NDEBUG
@ -170,7 +178,7 @@ FRandom::FRandom (const char *name)
#endif
// Insert the RNG in the list, sorted by CRC
FRandom **prev = &RNGList, *probe = RNGList;
FRandom **prev = (bClient ? &CRNGList : &RNGList), * probe = (bClient ? CRNGList : RNGList);
while (probe != NULL && probe->NameCRC < NameCRC)
{
@ -205,8 +213,8 @@ FRandom::~FRandom ()
FRandom *last = NULL;
prev = &RNGList;
rng = RNGList;
prev = bClient ? &CRNGList : &RNGList;
rng = bClient ? CRNGList : RNGList;
while (rng != NULL && rng != this)
{
@ -237,6 +245,11 @@ void FRandom::StaticClearRandom ()
{
rng->Init(rngseed);
}
for (FRandom* rng = FRandom::CRNGList; rng != NULL; rng = rng->Next)
{
rng->Init(rngseed);
}
}
//==========================================================================
@ -345,15 +358,15 @@ void FRandom::StaticReadRNGState(FSerializer &arc)
//
//==========================================================================
FRandom *FRandom::StaticFindRNG (const char *name)
FRandom *FRandom::StaticFindRNG (const char *name, bool client)
{
uint32_t NameCRC = CalcCRC32 ((const uint8_t *)name, (unsigned int)strlen (name));
// Use the default RNG if this one happens to have a CRC of 0.
if (NameCRC == 0) return &pr_exrandom;
if (NameCRC == 0) return client ? &M_Random : &pr_exrandom;
// Find the RNG in the list, sorted by CRC
FRandom **prev = &RNGList, *probe = RNGList;
FRandom **prev = (client ? &CRNGList : &RNGList), *probe = (client ? CRNGList : RNGList);
while (probe != NULL && probe->NameCRC < NameCRC)
{
@ -364,14 +377,32 @@ FRandom *FRandom::StaticFindRNG (const char *name)
if (probe == NULL || probe->NameCRC != NameCRC)
{
// A matching RNG doesn't exist yet so create it.
probe = new FRandom(name);
probe = new FRandom(name, client);
// Store the new RNG for destruction when ZDoom quits.
NewRNGs.Push(probe);
if (client)
NewCRNGs.Push(probe);
else
NewRNGs.Push(probe);
}
return probe;
}
void FRandom::SaveRNGState(TArray<FRandom>& backups)
{
for (auto cur = RNGList; cur != nullptr; cur = cur->Next)
backups.Push(*cur);
}
void FRandom::RestoreRNGState(TArray<FRandom>& backups)
{
unsigned int i = 0u;
for (auto cur = RNGList; cur != nullptr; cur = cur->Next)
*cur = backups[i++];
backups.Clear();
}
//==========================================================================
//
// FRandom :: StaticPrintSeeds

View file

@ -44,9 +44,9 @@ class FSerializer;
class FRandom : public SFMTObj
{
public:
FRandom ();
FRandom (const char *name);
~FRandom ();
FRandom() : FRandom(false) {}
FRandom(const char* name) : FRandom(name, false) {}
~FRandom();
int Seed() const
{
@ -170,20 +170,34 @@ public:
static void StaticClearRandom ();
static void StaticReadRNGState (FSerializer &arc);
static void StaticWriteRNGState (FSerializer &file);
static FRandom *StaticFindRNG(const char *name);
static FRandom *StaticFindRNG(const char *name, bool client);
static void SaveRNGState(TArray<FRandom>& backups);
static void RestoreRNGState(TArray<FRandom>& backups);
#ifndef NDEBUG
static void StaticPrintSeeds ();
#endif
protected:
FRandom(bool client);
FRandom(const char* name, bool client);
private:
#ifndef NDEBUG
const char *Name;
#endif
FRandom *Next;
uint32_t NameCRC;
bool bClient;
static FRandom *RNGList;
static FRandom *RNGList, *CRNGList;
};
class FCRandom : public FRandom
{
public:
FCRandom() : FRandom(true) {}
FCRandom(const char* name) : FRandom(name, true) {}
};
extern uint32_t rngseed; // The starting seed (not part of state)
@ -193,6 +207,6 @@ extern bool use_staticrng;
// M_Random can be used for numbers that do not affect gameplay
extern FRandom M_Random;
extern FCRandom M_Random;
#endif

View file

@ -41,6 +41,12 @@ xx(Random2)
xx(RandomPick)
xx(FRandomPick)
xx(SetRandomSeed)
xx(CRandom)
xx(CFRandom)
xx(CRandom2)
xx(CRandomPick)
xx(CFRandomPick)
xx(CSetRandomSeed)
xx(BuiltinRandomSeed)
xx(BuiltinNew)
xx(GetClass)

View file

@ -252,7 +252,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &
FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimModelOverride &mo, struct AnimModelOverride *def);
FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnim &ao, ModelAnim *def);
FSerializer &Serialize(FSerializer &arc, const char *key, ModelAnimFrame &ao, ModelAnimFrame *def);
FSerializer& Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval);
FSerializer &Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval);
FSerializer &Serialize(FSerializer& arc, const char* key, struct FStandaloneAnimation& value, struct FStandaloneAnimation* defval);
void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p);

View file

@ -55,6 +55,7 @@ public:
virtual void NetInit(const char *message, int num_players) {}
virtual void NetProgress(int count) {}
virtual void NetDone() {}
virtual void NetClose() {}
virtual bool NetLoop(bool (*timer_callback)(void *), void *userdata) { return false; }
virtual void AppendStatusLine(const char* status) {}
virtual void LoadingStatus(const char* message, int colors) {}
@ -74,6 +75,7 @@ public:
void NetProgress(int count);
void NetMessage(const char* format, ...); // cover for printf
void NetDone();
void NetClose();
bool NetLoop(bool (*timer_callback)(void*), void* userdata);
protected:
int NetMaxPos, NetCurPos;

View file

@ -46,6 +46,8 @@
#include "symbols.h"
#include "types.h"
#include "p_visualthinker.h"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
@ -421,7 +423,7 @@ PClass *PClass::FindClass (FName zaname)
//
//==========================================================================
DObject *PClass::CreateNew()
DObject *PClass::CreateNew(int *statnum)
{
uint8_t *mem = (uint8_t *)M_Malloc (Size);
assert (mem != nullptr);
@ -444,6 +446,12 @@ DObject *PClass::CreateNew()
((DObject *)mem)->SetClass (const_cast<PClass *>(this));
InitializeSpecials(mem, Defaults, &PClass::SpecialInits);
if(statnum && ((DObject *)mem)->IsKindOf(RUNTIME_CLASS(DVisualThinker)))
{
*statnum = STAT_VISUALTHINKER;
}
return (DObject *)mem;
}

View file

@ -90,7 +90,7 @@ public:
PClass();
~PClass();
void InsertIntoHash(bool native);
DObject *CreateNew();
DObject *CreateNew(int *statnum = nullptr);
PClass *CreateDerivedClass(FName name, unsigned int size, bool *newlycreated = nullptr, int fileno = 0);
void InitializeActorInfo();

View file

@ -66,6 +66,7 @@ public:
void NetInit(const char* message, int playerCount);
void NetProgress(int count);
void NetDone();
void NetClose();
private:
NSWindow* m_window;

View file

@ -531,3 +531,8 @@ void FConsoleWindow::NetDone()
m_netAbortButton = nil;
}
}
void FConsoleWindow::NetClose()
{
// TODO: Implement this
}

View file

@ -110,6 +110,11 @@ void FBasicStartupScreen::NetDone()
FConsoleWindow::GetInstance().NetDone();
}
void FBasicStartupScreen::NetClose()
{
FConsoleWindow::GetInstance().NetClose();
}
bool FBasicStartupScreen::NetLoop(bool (*timerCallback)(void*), void* const userData)
{
while (true)

View file

@ -54,17 +54,6 @@ bool I_WriteIniFailed (const char* filename);
class FGameTexture;
bool I_SetCursor(FGameTexture *);
static inline char *strlwr(char *str)
{
char *ptr = str;
while(*ptr)
{
*ptr = tolower(*ptr);
++ptr;
}
return str;
}
inline int I_GetNumaNodeCount() { return 1; }
inline int I_GetNumaNodeThreadCount(int numaNode) { return std::max<int>(std::thread::hardware_concurrency(), 1); }
inline void I_SetThreadNumaNode(std::thread &thread, int numaNode) { }

View file

@ -57,6 +57,7 @@ class FTTYStartupScreen : public FStartupScreen
void NetInit(const char *message, int num_players);
void NetProgress(int count);
void NetDone();
void NetClose();
bool NetLoop(bool (*timer_callback)(void *), void *userdata);
protected:
bool DidNetInit;
@ -237,6 +238,11 @@ void FTTYStartupScreen::NetProgress(int count)
}
}
void FTTYStartupScreen::NetClose()
{
// TODO: Implement this
}
//===========================================================================
//
// FTTYStartupScreen :: NetLoop

View file

@ -201,3 +201,8 @@ bool FBasicStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata
{
return NetStartWindow::RunMessageLoop(timer_callback, userdata);
}
void FBasicStartupScreen::NetClose()
{
NetStartWindow::CloseNetStartPane();
}

View file

@ -8268,8 +8268,12 @@ static bool CheckFunctionCompatiblity(FScriptPosition &ScriptPosition, PFunction
FxFunctionCall::FxFunctionCall(FName methodname, FName rngname, FArgumentList &&args, const FScriptPosition &pos)
: FxExpression(EFX_FunctionCall, pos)
{
const bool isClient = methodname == NAME_CRandom || methodname == NAME_CFRandom
|| methodname == NAME_CRandomPick || methodname == NAME_CFRandomPick
|| methodname == NAME_CRandom2 || methodname == NAME_CSetRandomSeed;
MethodName = methodname;
RNG = &pr_exrandom;
RNG = isClient ? &M_Random : &pr_exrandom;
ArgList = std::move(args);
if (rngname != NAME_None)
{
@ -8281,7 +8285,16 @@ FxFunctionCall::FxFunctionCall(FName methodname, FName rngname, FArgumentList &&
case NAME_FRandomPick:
case NAME_Random2:
case NAME_SetRandomSeed:
RNG = FRandom::StaticFindRNG(rngname.GetChars());
RNG = FRandom::StaticFindRNG(rngname.GetChars(), false);
break;
case NAME_CRandom:
case NAME_CFRandom:
case NAME_CRandomPick:
case NAME_CFRandomPick:
case NAME_CRandom2:
case NAME_CSetRandomSeed:
RNG = FRandom::StaticFindRNG(rngname.GetChars(), true);
break;
default:
@ -8547,13 +8560,22 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
}
break;
case NAME_CSetRandomSeed:
if (CheckArgSize(NAME_CRandom, ArgList, 1, 1, ScriptPosition))
{
func = new FxRandomSeed(RNG, ArgList[0], ScriptPosition, ctx.FromDecorate);
ArgList[0] = nullptr;
}
break;
case NAME_Random:
case NAME_CRandom:
// allow calling Random without arguments to default to (0, 255)
if (ArgList.Size() == 0)
{
func = new FxRandom(RNG, new FxConstant(0, ScriptPosition), new FxConstant(255, ScriptPosition), ScriptPosition, ctx.FromDecorate);
}
else if (CheckArgSize(NAME_Random, ArgList, 2, 2, ScriptPosition))
else if (CheckArgSize(MethodName, ArgList, 2, 2, ScriptPosition))
{
func = new FxRandom(RNG, ArgList[0], ArgList[1], ScriptPosition, ctx.FromDecorate);
ArgList[0] = ArgList[1] = nullptr;
@ -8561,7 +8583,8 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
break;
case NAME_FRandom:
if (CheckArgSize(NAME_FRandom, ArgList, 2, 2, ScriptPosition))
case NAME_CFRandom:
if (CheckArgSize(MethodName, ArgList, 2, 2, ScriptPosition))
{
func = new FxFRandom(RNG, ArgList[0], ArgList[1], ScriptPosition);
ArgList[0] = ArgList[1] = nullptr;
@ -8570,14 +8593,17 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
case NAME_RandomPick:
case NAME_FRandomPick:
case NAME_CRandomPick:
case NAME_CFRandomPick:
if (CheckArgSize(MethodName, ArgList, 1, -1, ScriptPosition))
{
func = new FxRandomPick(RNG, ArgList, MethodName == NAME_FRandomPick, ScriptPosition, ctx.FromDecorate);
func = new FxRandomPick(RNG, ArgList, MethodName == NAME_FRandomPick || MethodName == NAME_CFRandomPick, ScriptPosition, ctx.FromDecorate);
}
break;
case NAME_Random2:
if (CheckArgSize(NAME_Random2, ArgList, 0, 1, ScriptPosition))
case NAME_CRandom2:
if (CheckArgSize(MethodName, ArgList, 0, 1, ScriptPosition))
{
func = new FxRandom2(RNG, ArgList.Size() == 0? nullptr : ArgList[0], ScriptPosition, ctx.FromDecorate);
if (ArgList.Size() > 0) ArgList[0] = nullptr;

View file

@ -879,10 +879,18 @@ void FFunctionBuildList::Build()
{
if (!item.Code->CheckReturn())
{
auto newcmpd = new FxCompoundStatement(item.Code->ScriptPosition);
newcmpd->Add(item.Code);
newcmpd->Add(new FxReturnStatement(nullptr, item.Code->ScriptPosition));
item.Code = newcmpd->Resolve(ctx);
if (ctx.ReturnProto == nullptr || !ctx.ReturnProto->ReturnTypes.Size())
{
auto newcmpd = new FxCompoundStatement(item.Code->ScriptPosition);
newcmpd->Add(item.Code);
newcmpd->Add(new FxReturnStatement(nullptr, item.Code->ScriptPosition));
item.Code = newcmpd->Resolve(ctx);
}
else
{
item.Code->ScriptPosition.Message(MSG_ERROR, "Missing return statement in %s", item.PrintableName.GetChars());
continue;
}
}
item.Proto = ctx.ReturnProto;

View file

@ -41,6 +41,7 @@
#include "c_cvars.h"
#include "c_bind.h"
#include "c_dispatch.h"
#include "m_misc.h"
#include "menu.h"
#include "vm.h"
@ -1032,6 +1033,17 @@ DEFINE_ACTION_FUNCTION(_CVar, FindCVar)
ACTION_RETURN_POINTER(FindCVar(name.GetChars(), nullptr));
}
static int SaveConfig()
{
return M_SaveDefaults(nullptr);
}
DEFINE_ACTION_FUNCTION_NATIVE(_CVar, SaveConfig, SaveConfig)
{
PARAM_PROLOGUE;
ACTION_RETURN_INT(M_SaveDefaults(nullptr));
}
//=============================================================================
//
//

View file

@ -31,6 +31,11 @@ void NetStartWindow::HideNetStartPane()
Instance = nullptr;
}
void NetStartWindow::CloseNetStartPane()
{
NetStartWindow::NetClose();
}
void NetStartWindow::SetNetStartProgress(int pos)
{
if (Instance)
@ -64,6 +69,12 @@ bool NetStartWindow::RunMessageLoop(bool (*newtimer_callback)(void*), void* newu
return Instance->exitreason;
}
void NetStartWindow::NetClose()
{
if (Instance != nullptr)
Instance->OnClose();
}
NetStartWindow::NetStartWindow() : Widget(nullptr, WidgetType::Window)
{
SetWindowBackground(Colorf::fromRgba8(51, 51, 51));

View file

@ -12,8 +12,10 @@ class NetStartWindow : public Widget
public:
static void ShowNetStartPane(const char* message, int maxpos);
static void HideNetStartPane();
static void CloseNetStartPane();
static void SetNetStartProgress(int pos);
static bool RunMessageLoop(bool (*timer_callback)(void*), void* userdata);
static void NetClose();
private:
NetStartWindow();

View file

@ -701,6 +701,36 @@ void EventManager::WorldThingDied(AActor* actor, AActor* inflictor)
handler->WorldThingDied(actor, inflictor);
}
bool EventManager::WorldHitscanPreFired(AActor* actor, DAngle angle, double distance, DAngle pitch, int damage, FName damageType, PClassActor *pufftype, int flags, double sz, double offsetforward, double offsetside)
{
// don't call anything if actor was destroyed on PostBeginPlay/BeginPlay/whatever.
if (actor->ObjectFlags & OF_EuthanizeMe)
return false;
bool ret = false;
if (ShouldCallStatic(true)) ret = staticEventManager.WorldHitscanPreFired(actor, angle, distance, pitch, damage, damageType, pufftype, flags, sz, offsetforward, offsetside);
if (!ret)
{
for (DStaticEventHandler* handler = FirstEventHandler; handler && ret == false; handler = handler->next)
ret = handler->WorldHitscanPreFired(actor, angle, distance, pitch, damage, damageType, pufftype, flags, sz, offsetforward, offsetside);
}
return ret;
}
void EventManager::WorldHitscanFired(AActor* actor, const DVector3& AttackPos, const DVector3& DamagePosition, AActor* Inflictor, int flags)
{
// don't call anything if actor was destroyed on PostBeginPlay/BeginPlay/whatever.
if (actor->ObjectFlags & OF_EuthanizeMe)
return;
if (ShouldCallStatic(true)) staticEventManager.WorldHitscanFired(actor, AttackPos, DamagePosition, Inflictor, flags);
for (DStaticEventHandler* handler = FirstEventHandler; handler; handler = handler->next)
handler->WorldHitscanFired(actor, AttackPos, DamagePosition, Inflictor, flags);
}
void EventManager::WorldThingGround(AActor* actor, FState* st)
{
// don't call anything if actor was destroyed on PostBeginPlay/BeginPlay/whatever.
@ -1026,6 +1056,14 @@ DEFINE_FIELD_X(WorldEvent, FWorldEvent, DamagePosition);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, DamageIsRadius);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, NewDamage);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, CrushedState);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, AttackPos);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, AttackAngle);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, AttackPitch);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, AttackDistance);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, AttackOffsetForward);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, AttackOffsetSide);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, AttackZ);
DEFINE_FIELD_X(WorldEvent, FWorldEvent, AttackPuffType);
DEFINE_FIELD_X(PlayerEvent, FPlayerEvent, PlayerNumber);
DEFINE_FIELD_X(PlayerEvent, FPlayerEvent, IsReturn);
@ -1729,6 +1767,51 @@ void DStaticEventHandler::WorldThingDied(AActor* actor, AActor* inflictor)
}
}
bool DStaticEventHandler::WorldHitscanPreFired(AActor* actor, DAngle angle, double distance, DAngle pitch, int damage, FName damageType, PClassActor *pufftype, int flags, double sz, double offsetforward, double offsetside)
{
IFVIRTUAL(DStaticEventHandler, WorldHitscanPreFired)
{
// don't create excessive DObjects if not going to be processed anyway
if (isEmpty(func)) return false;
FWorldEvent e = owner->SetupWorldEvent();
e.Thing = actor;
e.AttackAngle = angle;
e.AttackPitch = pitch;
e.AttackDistance = distance;
e.Damage = damage;
e.DamageType = damageType;
e.AttackPuffType = pufftype;
e.AttackOffsetForward = offsetforward;
e.AttackOffsetSide = offsetside;
e.AttackZ = sz;
e.DamageFlags = flags;
int processed;
VMReturn results[1] = { &processed };
VMValue params[2] = { (DStaticEventHandler*)this, &e };
VMCall(func, params, 2, results, 1);
return !!processed;
}
return false;
}
void DStaticEventHandler::WorldHitscanFired(AActor* actor, const DVector3& AttackPos, const DVector3& DamagePosition, AActor* Inflictor, int flags)
{
IFVIRTUAL(DStaticEventHandler, WorldHitscanFired)
{
// don't create excessive DObjects if not going to be processed anyway
if (isEmpty(func)) return;
FWorldEvent e = owner->SetupWorldEvent();
e.Thing = actor;
e.AttackPos = AttackPos;
e.DamagePosition = DamagePosition;
e.Inflictor = Inflictor;
e.DamageFlags = flags;
VMValue params[2] = { (DStaticEventHandler*)this, &e };
VMCall(func, params, 2, nullptr, 0);
}
}
void DStaticEventHandler::WorldThingGround(AActor* actor, FState* st)
{
IFVIRTUAL(DStaticEventHandler, WorldThingGround)
@ -1743,7 +1826,6 @@ void DStaticEventHandler::WorldThingGround(AActor* actor, FState* st)
}
}
void DStaticEventHandler::WorldThingRevived(AActor* actor)
{
IFVIRTUAL(DStaticEventHandler, WorldThingRevived)

14
src/events.h Executable file → Normal file
View file

@ -311,6 +311,8 @@ public:
void WorldThingRevived(AActor* actor);
void WorldThingDamaged(AActor* actor, AActor* inflictor, AActor* source, int damage, FName mod, int flags, DAngle angle);
void WorldThingDestroyed(AActor* actor);
bool WorldHitscanPreFired(AActor* actor, DAngle angle, double distance, DAngle pitch, int damage, FName damageType, PClassActor *pufftype, int flags, double sz, double offsetforward, double offsetside);
void WorldHitscanFired(AActor* actor, const DVector3& AttackPos, const DVector3& DamagePosition, AActor* Inflictor, int flags);
void WorldLinePreActivated(line_t* line, AActor* actor, int activationType, bool* shouldactivate);
void WorldLineActivated(line_t* line, AActor* actor, int activationType);
int WorldSectorDamaged(sector_t* sector, AActor* source, int damage, FName damagetype, int part, DVector3 position, bool isradius);
@ -393,6 +395,14 @@ struct FWorldEvent
bool DamageIsRadius; // radius damage yes/no
int NewDamage = 0; // sector/line damaged. allows modifying damage
FState* CrushedState = nullptr; // custom crush state set in thingground
DVector3 AttackPos; //hitscan point of origin
DAngle AttackAngle;
DAngle AttackPitch;
double AttackDistance = 0;
double AttackOffsetForward = 0;
double AttackOffsetSide = 0;
double AttackZ = 0;
PClassActor* AttackPuffType = nullptr;
};
struct FPlayerEvent
@ -467,6 +477,10 @@ struct EventManager
void WorldThingSpawned(AActor* actor);
// called after AActor::Die of each actor.
void WorldThingDied(AActor* actor, AActor* inflictor);
// called when a hitscan attack is fired (can be overridden to block it)
bool WorldHitscanPreFired(AActor* actor, DAngle angle, double distance, DAngle pitch, int damage, FName damageType, PClassActor *pufftype, int flags, double sz, double offsetforward, double offsetside);
// called when a hitscan attack has been fired
void WorldHitscanFired(AActor* actor, const DVector3& AttackPos, const DVector3& DamagePosition, AActor* Inflictor, int flags);
// called inside AActor::Grind just before the corpse is destroyed
void WorldThingGround(AActor* actor, FState* st);
// called after AActor::Revive.

View file

@ -96,6 +96,8 @@
static FRandom pr_dmspawn ("DMSpawn");
static FRandom pr_pspawn ("PlayerSpawn");
extern int startpos, laststartpos;
bool WriteZip(const char* filename, const FileSys::FCompressedBuffer* content, size_t contentcount);
bool G_CheckDemoStatus (void);
void G_ReadDemoTiccmd (ticcmd_t *cmd, int player);
@ -2146,7 +2148,9 @@ void G_DoLoadGame ()
arc("ticrate", time[0])
("leveltime", time[1])
("globalfreeze", globalfreeze);
("globalfreeze", globalfreeze)
("startpos", startpos)
("laststartpos", laststartpos);
// dearchive all the modifications
level.time = Scale(time[1], TICRATE, time[0]);
@ -2437,6 +2441,10 @@ void G_DoSaveGame (bool okForQuicksave, bool forceQuicksave, FString filename, c
savegameglobals("leveltime", level.time);
}
savegameglobals("globalfreeze", globalfreeze)
("startpos", startpos)
("laststartpos", laststartpos);
STAT_Serialize(savegameglobals);
FRandom::StaticWriteRNGState(savegameglobals);
P_WriteACSDefereds(savegameglobals);

View file

@ -111,6 +111,8 @@ EXTERN_CVAR (Int, disableautosave)
EXTERN_CVAR (String, playerclass)
extern uint8_t globalfreeze, globalchangefreeze;
int startpos = 0; // [RH] Support for multiple starts per level
int laststartpos = 0;
#define SNAP_ID MAKE_ID('s','n','A','p')
#define DSNP_ID MAKE_ID('d','s','N','p')
@ -652,7 +654,9 @@ void G_InitNew (const char *mapname, bool bTitleLevel)
gamestate = GS_LEVEL;
}
G_DoLoadLevel (mapname, 0, false, !savegamerestore);
if (!savegamerestore)
startpos = laststartpos = 0;
G_DoLoadLevel (mapname, startpos, false, !savegamerestore);
if (!savegamerestore && (gameinfo.gametype == GAME_Strife || (SBarInfoScript[SCRIPT_CUSTOM] != nullptr && SBarInfoScript[SCRIPT_CUSTOM]->GetGameType() == GAME_Strife)))
{
@ -669,7 +673,6 @@ void G_InitNew (const char *mapname, bool bTitleLevel)
// G_DoCompleted
//
static FString nextlevel;
static int startpos; // [RH] Support for multiple starts per level
extern int NoWipe; // [RH] Don't wipe when travelling in hubs
static int changeflags;
static bool unloading;
@ -1100,7 +1103,6 @@ void G_DoCompleted (void)
if (gamestate == GS_TITLELEVEL)
{
G_DoLoadLevel (nextlevel, startpos, false, false);
startpos = 0;
viewactive = true;
return;
}
@ -1373,7 +1375,6 @@ void G_DoLoadLevel(const FString &nextmapname, int position, bool autosave, bool
void FLevelLocals::DoLoadLevel(const FString &nextmapname, int position, bool autosave, bool newGame)
{
MapName = nextmapname;
static int lastposition = 0;
int i;
if (NextSkill >= 0)
@ -1385,9 +1386,9 @@ void FLevelLocals::DoLoadLevel(const FString &nextmapname, int position, bool au
}
if (position == -1)
position = lastposition;
position = laststartpos;
else
lastposition = position;
laststartpos = position;
Init();
StatusBar->DetachAllMessages ();
@ -1579,7 +1580,6 @@ void G_DoWorldDone (void)
}
primaryLevel->StartTravel ();
G_DoLoadLevel (nextlevel, startpos, true, false);
startpos = 0;
gameaction = ga_nothing;
viewactive = true;
}
@ -2407,6 +2407,24 @@ void FLevelLocals::ApplyCompatibility2()
i_compatflags2 = GetCompatibility2(compatflags2) | ii_compatflags2;
}
AActor* FLevelLocals::SelectActorFromTID(int tid, size_t index, AActor* defactor)
{
if (tid == 0)
return defactor;
AActor* actor = nullptr;
size_t cur = 0u;
auto it = GetActorIterator(tid);
while ((actor = it.Next()) != nullptr)
{
if (cur == index)
return actor;
++cur;
}
return nullptr;
}
//==========================================================================
// IsPointInMap
//

View file

@ -149,6 +149,7 @@ struct FLevelLocals
int GetCompatibility2(int mask);
void ApplyCompatibility();
void ApplyCompatibility2();
AActor* SelectActorFromTID(int tid, size_t index, AActor* defactor);
void Init();
@ -166,8 +167,8 @@ private:
void SerializePlayers(FSerializer &arc, bool skipload);
void CopyPlayer(player_t *dst, player_t *src, const char *name);
void ReadOnePlayer(FSerializer &arc, bool skipload);
void ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload);
void ReadOnePlayer(FSerializer &arc, bool fromHub);
void ReadMultiplePlayers(FSerializer &arc, int numPlayers, bool fromHub);
void SerializeSounds(FSerializer &arc);
void PlayerSpawnPickClass (int playernum);
@ -198,7 +199,7 @@ public:
void ClearDynamic3DFloorData();
void WorldDone(void);
void AirControlChanged();
AActor *SelectTeleDest(int tid, int tag, bool norandom);
AActor *SelectTeleDest(int tid, int tag, bool norandom, bool isPlayer);
bool AlignFlat(int linenum, int side, int fc);
void ReplaceTextures(const char *fromname, const char *toname, int flags);
@ -424,11 +425,9 @@ public:
DThinker *CreateThinker(PClass *cls, int statnum = STAT_DEFAULT)
{
DThinker *thinker = static_cast<DThinker*>(cls->CreateNew());
DThinker *thinker = static_cast<DThinker*>(cls->CreateNew(&statnum));
assert(thinker->IsKindOf(RUNTIME_CLASS(DThinker)));
thinker->ObjectFlags |= OF_JustSpawned;
if (thinker->IsKindOf(RUNTIME_CLASS(DVisualThinker))) // [MC] This absolutely must happen for this class!
statnum = STAT_VISUALTHINKER;
Thinkers.Link(thinker, statnum);
thinker->Level = this;
return thinker;

View file

@ -3225,9 +3225,9 @@ class CommandDrawGem : public SBarInfoCommand
int goalValue;
private:
int chainWiggle;
static FRandom pr_chainwiggle;
static FCRandom pr_chainwiggle;
};
FRandom CommandDrawGem::pr_chainwiggle; //use the same method of chain wiggling as heretic.
FCRandom CommandDrawGem::pr_chainwiggle; //use the same method of chain wiggling as heretic.
////////////////////////////////////////////////////////////////////////////////

View file

@ -60,8 +60,8 @@ static TArray<uint8_t> DecalTranslations;
// Sometimes two machines in a game will disagree on the state of
// decals. I do not know why.
static FRandom pr_decalchoice ("DecalChoice");
static FRandom pr_decal ("Decal");
static FCRandom pr_decalchoice ("DecalChoice");
static FCRandom pr_decal ("Decal");
class FDecalGroup : public FDecalBase
{

View file

@ -77,13 +77,14 @@ enum ETerrainKeywords
TR_DAMAGETIMEMASK,
TR_FOOTCLIP,
TR_STEPVOLUME,
TR_WALKINGSTEPTIME,
TR_RUNNINGSTEPTIME,
TR_WALKSTEPTICS,
TR_RUNSTEPTICS,
TR_LEFTSTEPSOUNDS,
TR_RIGHTSTEPSOUNDS,
TR_LIQUID,
TR_FRICTION,
TR_ALLOWPROTECTION
TR_ALLOWPROTECTION,
TR_STEPSOUNDS
};
enum EGenericType
@ -95,7 +96,6 @@ enum EGenericType
GEN_Splash,
GEN_Float,
GEN_Double,
GEN_Time,
GEN_Bool,
GEN_Int,
GEN_Custom,
@ -179,14 +179,17 @@ static const char *TerrainKeywords[] =
"damagetimemask",
"footclip",
"stepvolume",
"walkingsteptime",
"runningsteptime",
"walksteptics",
"runsteptics",
"leftstepsounds",
"rightstepsounds",
"liquid",
"friction",
"allowprotection",
"damageonland",
"stepsounds",
"stepdistance",
"stepdistanceminvel",
NULL
};
@ -215,14 +218,17 @@ static FGenericParse TerrainParser[] =
{ GEN_Int, {myoffsetof(FTerrainDef, DamageTimeMask)} },
{ GEN_Double, {myoffsetof(FTerrainDef, FootClip)} },
{ GEN_Float, {myoffsetof(FTerrainDef, StepVolume)} },
{ GEN_Time, {myoffsetof(FTerrainDef, WalkStepTics)} },
{ GEN_Time, {myoffsetof(FTerrainDef, RunStepTics)} },
{ GEN_Int, {myoffsetof(FTerrainDef, WalkStepTics)} },
{ GEN_Int, {myoffsetof(FTerrainDef, RunStepTics)} },
{ GEN_Sound, {myoffsetof(FTerrainDef, LeftStepSound)} },
{ GEN_Sound, {myoffsetof(FTerrainDef, RightStepSound)} },
{ GEN_Bool, {myoffsetof(FTerrainDef, IsLiquid)} },
{ GEN_Custom, {(size_t)ParseFriction} },
{ GEN_Bool, {myoffsetof(FTerrainDef, AllowProtection)} },
{ GEN_Bool, {myoffsetof(FTerrainDef, DamageOnLand)} },
{ GEN_Sound, {myoffsetof(FTerrainDef, StepSound)} },
{ GEN_Double, {myoffsetof(FTerrainDef, StepDistance)} },
{ GEN_Double, {myoffsetof(FTerrainDef, StepDistanceMinVel)} },
};
@ -597,11 +603,6 @@ static void GenericParse (FScanner &sc, FGenericParse *parser, const char **keyw
SET_FIELD(double, sc.Float);
break;
case GEN_Time:
sc.MustGetFloat ();
SET_FIELD (int, (int)(sc.Float * TICRATE));
break;
case GEN_Bool:
SET_FIELD (bool, true);
break;
@ -747,3 +748,6 @@ DEFINE_FIELD(FTerrainDef, AllowProtection)
DEFINE_FIELD(FTerrainDef, DamageOnLand)
DEFINE_FIELD(FTerrainDef, Friction)
DEFINE_FIELD(FTerrainDef, MoveFactor)
DEFINE_FIELD(FTerrainDef, StepSound)
DEFINE_FIELD(FTerrainDef, StepDistance)
DEFINE_FIELD(FTerrainDef, StepDistanceMinVel)

View file

@ -118,6 +118,9 @@ struct FTerrainDef
bool DamageOnLand;
double Friction;
double MoveFactor;
FSoundID StepSound;
double StepDistance;
double StepDistanceMinVel;
};
extern TArray<FSplashDef> Splashes;

View file

@ -504,6 +504,8 @@ enum
SECMF_OVERLAPPING = 512, // floor and ceiling overlap and require special renderer action.
SECMF_NOSKYWALLS = 1024, // Do not draw "sky walls"
SECMF_LIFT = 2048, // For MBF monster AI
SECMF_HURTMONSTERS = 4096, // Monsters in this sector are hurt like players.
SECMF_HARMINAIR = 8192, // Actors in this sector are also hurt mid-air.
};
enum
@ -1062,6 +1064,16 @@ public:
return pos == floor ? floorplane : ceilingplane;
}
void SetPlaneReflectivity(int pos, double val)
{
reflect[pos] = val;
}
double GetPlaneReflectivity(int pos)
{
return reflect[pos];
}
bool isSecret() const
{
return !!(Flags & SECF_SECRET);

View file

@ -56,7 +56,7 @@ FTextureAnimator TexAnim;
// PRIVATE DATA DEFINITIONS ------------------------------------------------
static FRandom pr_animatepictures ("AnimatePics");
static FCRandom pr_animatepictures ("AnimatePics");
// CODE --------------------------------------------------------------------

View file

@ -148,8 +148,22 @@ static int ParseStandardProperty(FScanner &scanner, UMapEntry *mape, int *id24_l
}
else if (!pname.CompareNoCase("label"))
{
scanner.MustGetToken(TK_StringConst);
mape->label = scanner.String;
if (scanner.CheckToken(TK_Identifier))
{
if (!stricmp(scanner.String, "clear"))
{
mape->label = "*";
}
else
{
scanner.ScriptError("Either 'clear' or string constant expected");
}
}
else
{
scanner.MustGetToken(TK_StringConst);
mape->label = scanner.String;
}
}
else if (!pname.CompareNoCase("next"))
{

View file

@ -56,19 +56,16 @@
class DLevelPostProcessor : public DObject
{
DECLARE_ABSTRACT_CLASS(DLevelPostProcessor, DObject)
DECLARE_CLASS(DLevelPostProcessor, DObject)
public:
MapLoader *loader;
FLevelLocals *Level;
};
IMPLEMENT_CLASS(DLevelPostProcessor, true, false);
IMPLEMENT_CLASS(DLevelPostProcessor, false, false);
void MapLoader::PostProcessLevel(FName checksum)
{
auto lc = Create<DLevelPostProcessor>();
lc->loader = this;
lc->Level = Level;
for(auto cls : PClass::AllClasses)
{
if (cls->IsDescendantOf(RUNTIME_CLASS(DLevelPostProcessor)))
@ -87,8 +84,14 @@ void MapLoader::PostProcessLevel(FName checksum)
continue;
}
auto lc = static_cast<DLevelPostProcessor*>(cls->CreateNew());
lc->loader = this;
lc->Level = Level;
VMValue param[] = { lc, checksum.GetIndex(), &Level->MapName };
VMCall(func->Variants[0].Implementation, param, 3, nullptr, 0);
lc->Destroy();
}
}
}

View file

@ -2050,6 +2050,14 @@ public:
Flag(sec->Flags, SECF_HAZARD, key);
break;
case NAME_hurtmonsters:
Flag(sec->MoreFlags, SECMF_HURTMONSTERS, key);
break;
case NAME_harminair:
Flag(sec->MoreFlags, SECMF_HARMINAIR, key);
break;
case NAME_floorterrain:
sec->terrainnum[sector_t::floor] = P_FindTerrain(CheckString(key));
break;

View file

@ -812,6 +812,8 @@ xx(damageinterval)
xx(leakiness)
xx(damageterraineffect)
xx(damagehazard)
xx(hurtmonsters)
xx(harminair)
xx(floorterrain)
xx(ceilingterrain)
xx(floor_reflect)

View file

@ -62,7 +62,7 @@
#include "doommenu.h"
#include "g_game.h"
static FRandom pr_randomspeech("RandomSpeech");
static FCRandom pr_randomspeech("RandomSpeech");
static int ConversationMenuY;

View file

@ -627,8 +627,8 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload)
{
if (arc.BeginObject(nullptr))
{
const char *n = Players[i]->userinfo.GetName();
arc.StringPtr("playername", n);
FString name = Players[i]->userinfo.GetName();
arc("playername", name);
Players[i]->Serialize(arc);
arc.EndObject();
}
@ -651,7 +651,7 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload)
}
else
{
ReadMultiplePlayers(arc, numPlayers, numPlayersNow, skipload);
ReadMultiplePlayers(arc, numPlayers, skipload);
}
arc.EndArray();
@ -680,53 +680,41 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload)
//
//==========================================================================
void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool skipload)
void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool fromHub)
{
int i;
const char *name = NULL;
bool didIt = false;
if (!arc.BeginObject(nullptr))
return;
if (arc.BeginObject(nullptr))
FString name = {};
arc("playername", name);
player_t temp = {};
temp.Serialize(arc);
for (int i = 0; i < MAXPLAYERS; ++i)
{
arc.StringPtr("playername", name);
if (!PlayerInGame(i))
continue;
for (i = 0; i < MAXPLAYERS; ++i)
if (!fromHub)
{
if (playeringame[i])
{
if (!didIt)
{
didIt = true;
player_t playerTemp;
playerTemp.Serialize(arc);
if (!skipload)
{
// This temp player has undefined pitch limits, so set them to something
// that should leave the pitch stored in the savegame intact when
// rendering. The real pitch limits will be set by P_SerializePlayers()
// via a net command, but that won't be processed in time for a screen
// wipe, so we need something here.
playerTemp.MaxPitch = playerTemp.MinPitch = playerTemp.mo->Angles.Pitch;
CopyPlayer(Players[i], &playerTemp, name);
}
else
{
// we need the player actor, so that G_FinishTravel can destroy it later.
Players[i]->mo = playerTemp.mo;
}
}
else
{
if (Players[i]->mo != NULL)
{
Players[i]->mo->Destroy();
Players[i]->mo = NULL;
}
}
}
// This temp player has undefined pitch limits, so set them to something
// that should leave the pitch stored in the savegame intact when
// rendering. The real pitch limits will be set by P_SerializePlayers()
// via a net command, but that won't be processed in time for a screen
// wipe, so we need something here.
temp.MaxPitch = temp.MinPitch = temp.mo->Angles.Pitch;
CopyPlayer(Players[i], &temp, name.GetChars());
}
arc.EndObject();
else
{
// we need the player actor, so that G_FinishTravel can destroy it later.
Players[i]->mo = temp.mo;
}
break;
}
arc.EndObject();
}
//==========================================================================
@ -735,109 +723,96 @@ void FLevelLocals::ReadOnePlayer(FSerializer &arc, bool skipload)
//
//==========================================================================
void FLevelLocals::ReadMultiplePlayers(FSerializer &arc, int numPlayers, int numPlayersNow, bool skipload)
struct NetworkPlayerInfo
{
// For two or more players, read each player into a temporary array.
int i, j;
const char **nametemp = new const char *[numPlayers];
player_t *playertemp = new player_t[numPlayers];
uint8_t *tempPlayerUsed = new uint8_t[numPlayers];
uint8_t playerUsed[MAXPLAYERS];
FString Name = {};
player_t Info = {};
bool bUsed = false;
};
for (i = 0; i < numPlayers; ++i)
void FLevelLocals::ReadMultiplePlayers(FSerializer &arc, int numPlayers, bool fromHub)
{
TArray<NetworkPlayerInfo> tempPlayers = {};
tempPlayers.Reserve(numPlayers);
TArray<bool> assignedPlayers = {};
assignedPlayers.Reserve(MAXPLAYERS);
// Read all the save game players into a temporary array
for (auto& p : tempPlayers)
{
nametemp[i] = NULL;
if (arc.BeginObject(nullptr))
{
arc.StringPtr("playername", nametemp[i]);
playertemp[i].Serialize(arc);
arc("playername", p.Name);
p.Info.Serialize(arc);
arc.EndObject();
}
tempPlayerUsed[i] = 0;
}
for (i = 0; i < MAXPLAYERS; ++i)
{
playerUsed[i] = playeringame[i] ? 0 : 2;
}
if (!skipload)
// Now try to match players from the savegame with players present
// based on their names. If two players in the savegame have the
// same name, then they are assigned to players in the current game
// on a first-come, first-served basis.
for (int i = 0; i < MAXPLAYERS; ++i)
{
// Now try to match players from the savegame with players present
// based on their names. If two players in the savegame have the
// same name, then they are assigned to players in the current game
// on a first-come, first-served basis.
for (i = 0; i < numPlayers; ++i)
{
for (j = 0; j < MAXPLAYERS; ++j)
{
if (playerUsed[j] == 0 && stricmp(players[j].userinfo.GetName(), nametemp[i]) == 0)
{ // Found a match, so copy our temp player to the real player
Printf("Found player %d (%s) at %d\n", i, nametemp[i], j);
CopyPlayer(Players[j], &playertemp[i], nametemp[i]);
playerUsed[j] = 1;
tempPlayerUsed[i] = 1;
break;
}
}
}
if (!PlayerInGame(i))
continue;
// Any players that didn't have matching names are assigned to existing
// players on a first-come, first-served basis.
for (i = 0; i < numPlayers; ++i)
for (auto& p : tempPlayers)
{
if (tempPlayerUsed[i] == 0)
if (!p.bUsed && !p.Name.Compare(Players[i]->userinfo.GetName()))
{
for (j = 0; j < MAXPLAYERS; ++j)
// Found a match, so copy our temp player to the real player
if (!fromHub)
{
if (playerUsed[j] == 0)
{
Printf("Assigned player %d (%s) to %d (%s)\n", i, nametemp[i], j, players[j].userinfo.GetName());
CopyPlayer(&players[j], &playertemp[i], nametemp[i]);
playerUsed[j] = 1;
tempPlayerUsed[i] = 1;
break;
}
Printf("Found %s's (%d) data\n", Players[i]->userinfo.GetName(), i);
CopyPlayer(Players[i], &p.Info, p.Name.GetChars());
}
}
}
// Make sure any extra players don't have actors spawned yet. Happens if the players
// present now got the same slots as they had in the save, but there are not as many
// as there were in the save.
for (j = 0; j < MAXPLAYERS; ++j)
{
if (playerUsed[j] == 0)
{
if (players[j].mo != NULL)
else
{
players[j].mo->Destroy();
players[j].mo = NULL;
Players[i]->mo = p.Info.mo;
}
}
}
// Remove any temp players that were not used. Happens if there are fewer players
// than there were in the save, and they got shuffled.
for (i = 0; i < numPlayers; ++i)
{
if (tempPlayerUsed[i] == 0)
{
playertemp[i].mo->Destroy();
playertemp[i].mo = NULL;
p.bUsed = true;
assignedPlayers[i] = true;
break;
}
}
}
else
// Any players that didn't have matching names are assigned to existing
// players on a first-come, first-served basis.
for (int i = 0; i < MAXPLAYERS; ++i)
{
for (i = 0; i < numPlayers; ++i)
if (!PlayerInGame(i) || assignedPlayers[i])
continue;
for (auto& p : tempPlayers)
{
players[i].mo = playertemp[i].mo;
if (!p.bUsed)
{
if (!fromHub)
{
Printf("Assigned %s (%d) to %s's data\n", Players[i]->userinfo.GetName(), i, p.Name.GetChars());
CopyPlayer(Players[i], &p.Info, p.Name.GetChars());
}
else
{
Players[i]->mo = p.Info.mo;
}
p.bUsed = true;
break;
}
}
}
delete[] tempPlayerUsed;
delete[] playertemp;
delete[] nametemp;
// Remove any temp players that were not used. Happens if there are now
// less players in the game than there were in the save
for (auto& p : tempPlayers)
{
if (!p.bUsed)
p.Info.mo->Destroy();
}
}
//==========================================================================

View file

@ -67,7 +67,7 @@
static FMemArena DynLightArena(sizeof(FDynamicLight) * 200);
static TArray<FDynamicLight*> FreeList;
static FRandom randLight;
static FCRandom randLight;
extern TArray<FLightDefaults *> StateLights;
@ -405,10 +405,14 @@ void FDynamicLight::UpdateLocation()
Pos = target->Vec3Offset(m_off.X * c + m_off.Y * s, m_off.X * s - m_off.Y * c, m_off.Z + target->GetBobOffset());
Sector = target->subsector->sector; // Get the render sector. target->Sector is the sector according to play logic.
// Some z-coordinate fudging to prevent the light from getting too close to the floor or ceiling planes. With proper attenuation this would render them invisible.
// A distance of 5 is needed so that the light's effect doesn't become too small.
if (Z() < target->floorz + 5.) Pos.Z = target->floorz + 5.;
else if (Z() > target->ceilingz - 5.) Pos.Z = target->ceilingz - 5.;
if (!(target->flags5 & MF5_NOINTERACTION))
{
// Some z-coordinate fudging to prevent the light from getting too close to the floor or ceiling planes. With proper attenuation this would render them invisible.
// A distance of 5 is needed so that the light's effect doesn't become too small.
// [SP] don't do this if +NOINTERACTION is set, since the object can fly right through floors and ceilings with that flag
if (Z() < target->floorz + 5.) Pos.Z = target->floorz + 5.;
else if (Z() > target->ceilingz - 5.) Pos.Z = target->ceilingz - 5.;
}
// The radius being used here is always the maximum possible with the
// current settings. This avoids constant relinking of flickering lights

View file

@ -443,7 +443,9 @@ enum ActorFlag9
MF9_SHADOWBLOCK = 0x00000004, // [inkoalawetrust] Actors in the line of fire with this flag trigger the MF_SHADOW aiming penalty.
MF9_SHADOWAIMVERT = 0x00000008, // [inkoalawetrust] Monster aim is also offset vertically when aiming at shadow actors.
MF9_DECOUPLEDANIMATIONS = 0x00000010, // [RL0] Decouple model animations from states
MF9_NOSECTORDAMAGE = 0x00000020, // [inkoalawetrust] Actor ignores any sector-based damage (i.e damaging floors, NOT crushers)
MF9_ISPUFF = 0x00000040, // [AA] Set on actors by P_SpawnPuff
MF9_FORCESECTORDAMAGE = 0x00000080, // [inkoalawetrust] Actor ALWAYS takes hurt floor damage if there's any. Even if the floor doesn't have SECMF_HURTMONSTERS.
};
// --- mobj.renderflags ---

View file

@ -46,6 +46,8 @@
#include "g_cvars.h"
#include "d_main.h"
#include "p_visualthinker.h"
static int ThinkCount;
static cycle_t ThinkCycles;
extern cycle_t BotSupportCycles;

View file

@ -43,10 +43,10 @@
// State.
#include "serializer.h"
static FRandom pr_flicker ("Flicker");
static FRandom pr_lightflash ("LightFlash");
static FRandom pr_strobeflash ("StrobeFlash");
static FRandom pr_fireflicker ("FireFlicker");
static FCRandom pr_flicker ("Flicker");
static FCRandom pr_lightflash ("LightFlash");
static FCRandom pr_strobeflash ("StrobeFlash");
static FCRandom pr_fireflicker ("FireFlicker");
//-----------------------------------------------------------------------------

View file

@ -36,7 +36,7 @@
#include "actorinlines.h"
#include <p_maputl.h>
static FRandom pr_quake ("Quake");
static FCRandom pr_quake ("Quake");
IMPLEMENT_CLASS(DEarthquake, false, true)

View file

@ -198,39 +198,40 @@ void P_Add3DFloor(sector_t* sec, sector_t* sec2, line_t* master, int flags, int
//==========================================================================
//
// P_PlayerOnSpecial3DFloor
// Checks to see if a player is standing on or is inside a 3D floor (water)
// P_ActorOnSpecial3DFloor
// Checks to see if an actor is standing on or is inside a 3D floor (water)
// and applies any specials..
//
//==========================================================================
void P_PlayerOnSpecial3DFloor(player_t* player)
void P_ActorOnSpecial3DFloor(AActor* victim)
{
for(auto rover : player->mo->Sector->e->XFloor.ffloors)
for(auto rover : victim->Sector->e->XFloor.ffloors)
{
if (!(rover->flags & FF_EXISTS)) continue;
if (rover->flags & FF_FIX) continue;
if (!checkForSpecialSector(victim, rover->model)) continue;
// Check the 3D floor's type...
if(rover->flags & FF_SOLID)
{
// Player must be on top of the floor to be affected...
if(player->mo->Z() != rover->top.plane->ZatPoint(player->mo)) continue;
if (victim->Z() != rover->top.plane->ZatPoint(victim)) continue;
}
else
{
//Water and DEATH FOG!!! heh
if ((rover->flags & FF_NODAMAGE) ||
player->mo->Z() > rover->top.plane->ZatPoint(player->mo) ||
player->mo->Top() < rover->bottom.plane->ZatPoint(player->mo))
victim->Z() > rover->top.plane->ZatPoint(victim) ||
victim->Top() < rover->bottom.plane->ZatPoint(victim))
continue;
}
// Apply sector specials
P_PlayerInSpecialSector(player, rover->model);
P_ActorInSpecialSector(victim, rover->model,rover);
// Apply flat specials (using the ceiling!)
P_PlayerOnSpecialFlat(player, rover->model->GetTerrain(rover->top.isceiling));
P_ActorOnSpecialFlat(victim, rover->model->GetTerrain(rover->top.isceiling));
break;
}

View file

@ -113,8 +113,7 @@ struct lightlist_t
class player_t;
void P_PlayerOnSpecial3DFloor(player_t* player);
void P_ActorOnSpecial3DFloor(AActor* victim);
bool P_CheckFor3DFloorHit(AActor * mo, double z, bool trigger);
bool P_CheckFor3DCeilingHit(AActor * mo, double z, bool trigger);

View file

@ -4806,6 +4806,8 @@ enum EACSFunctions
ACSF_GetSectorHealth,
ACSF_GetLineHealth,
ACSF_SetSubtitleNumber,
ACSF_GetNetID,
ACSF_SetActivatorByNetID,
// Eternity's
ACSF_GetLineX = 300,
@ -5380,6 +5382,13 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, int32_t *args, int &
actor = Level->SingleActorFromTID(args[0], activator);
return actor != NULL? DoubleToACS(actor->Vel.Z) : 0;
case ACSF_GetNetID:
MIN_ARG_COUNT(2);
actor = Level->SelectActorFromTID(args[0], args[1], activator);
if (argCount > 2)
actor = COPY_AAPTREX(Level, actor, args[2]);
return actor != nullptr ? actor->GetNetworkID() : NetworkEntityManager::WorldNetID;
case ACSF_SetPointer:
MIN_ARG_COUNT(2);
if (activator)
@ -5431,6 +5440,18 @@ int DLevelScript::CallFunction(int argCount, int funcIndex, int32_t *args, int &
}
return 0;
case ACSF_SetActivatorByNetID:
MIN_ARG_COUNT(1);
actor = dyn_cast<AActor>(NetworkEntityManager::GetNetworkEntity(args[0]));
if (argCount > 1)
actor = COPY_AAPTREX(Level, actor, args[1]);
if (actor != nullptr)
{
activator = actor;
return 1;
}
return 0;
case ACSF_GetActorViewHeight:
MIN_ARG_COUNT(1);
actor = Level->SingleActorFromTID(args[0], activator);

View file

@ -50,6 +50,7 @@
#include "actorinlines.h"
#include "g_game.h"
#include "serializer_doom.h"
#include "p_visualthinker.h"
#include "hwrenderer/scene/hw_drawstructs.h"
@ -64,7 +65,7 @@ CVAR (Int, r_rail_trailsparsity, 1, CVAR_ARCHIVE);
CVAR (Bool, r_particles, true, 0);
EXTERN_CVAR(Int, r_maxparticles);
FRandom pr_railtrail("RailTrail");
FCRandom pr_railtrail("RailTrail");
#define FADEFROMTTL(a) (1.f/(a))
@ -106,50 +107,45 @@ static const struct ColorList {
{NULL, 0, 0, 0 }
};
inline particle_t *NewParticle (FLevelLocals *Level, bool replace = false)
static void FreeParticle(FLevelLocals* Level, particle_t* particle)
{
particle_t *result = nullptr;
// [MC] Thanks to RaveYard and randi for helping me with this addition.
// Array's filled up
if (Level->InactiveParticles == NO_PARTICLE)
auto prev = particle->tprev == NO_PARTICLE? nullptr : &Level->Particles[particle->tprev];
int pindex = (int)(particle - Level->Particles.Data());
auto tnext = particle->tnext;
assert(!prev || (prev->tnext == pindex));
if (prev)
prev->tnext = tnext;
else
Level->ActiveParticles = tnext;
if (tnext != NO_PARTICLE)
{
if (replace)
{
result = &Level->Particles[Level->OldestParticle];
particle_t* next = &Level->Particles[tnext];
assert(next->tprev == pindex);
next->tprev = particle->tprev;
}
if (Level->OldestParticle == pindex)
{
assert(tnext == NO_PARTICLE);
Level->OldestParticle = particle->tprev;
}
memset(particle, 0, sizeof(particle_t));
particle->tnext = Level->InactiveParticles;
Level->InactiveParticles = pindex;
}
// There should be NO_PARTICLE for the oldest's tnext
if (result->tprev != NO_PARTICLE)
{
// tnext: youngest to oldest
// tprev: oldest to youngest
// 2nd oldest -> oldest
particle_t *nbottom = &Level->Particles[result->tprev];
nbottom->tnext = NO_PARTICLE;
// now oldest becomes youngest
Level->OldestParticle = result->tprev;
result->tnext = Level->ActiveParticles;
result->tprev = NO_PARTICLE;
Level->ActiveParticles = uint32_t(result - Level->Particles.Data());
// youngest -> 2nd youngest
particle_t* ntop = &Level->Particles[result->tnext];
ntop->tprev = Level->ActiveParticles;
}
// [MC] Future proof this by resetting everything when replacing a particle.
auto tnext = result->tnext;
auto tprev = result->tprev;
*result = {};
result->tnext = tnext;
result->tprev = tprev;
}
return result;
static particle_t *NewParticle (FLevelLocals *Level, bool replace = false)
{
// Array's filled up
if (Level->InactiveParticles == NO_PARTICLE && Level->OldestParticle != NO_PARTICLE)
{
if (!replace) return nullptr;
FreeParticle(Level, &Level->Particles[Level->OldestParticle]);
}
// Array isn't full.
uint32_t current = Level->ActiveParticles;
result = &Level->Particles[Level->InactiveParticles];
auto result = &Level->Particles[Level->InactiveParticles];
Level->InactiveParticles = result->tnext;
result->tnext = current;
result->tprev = NO_PARTICLE;
@ -310,19 +306,7 @@ void P_ThinkParticles (FLevelLocals *Level)
particle->size += particle->sizestep;
if (particle->alpha <= 0 || --particle->ttl <= 0 || (particle->size <= 0))
{ // The particle has expired, so free it
*particle = {};
if (prev)
prev->tnext = i;
else
Level->ActiveParticles = i;
if (i != NO_PARTICLE)
{
particle_t *next = &Level->Particles[i];
next->tprev = particle->tprev;
}
particle->tnext = Level->InactiveParticles;
Level->InactiveParticles = (int)(particle - Level->Particles.Data());
FreeParticle(Level, particle);
continue;
}
@ -381,9 +365,9 @@ void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &v
particle->sizestep = sizestep;
particle->texture = texture;
particle->style = style;
particle->Roll = startroll;
particle->RollVel = rollvel;
particle->RollAcc = rollacc;
particle->Roll = (float)startroll;
particle->RollVel = (float)rollvel;
particle->RollAcc = (float)rollacc;
particle->flags = flags;
if(flags & SPF_LOCAL_ANIM)
{
@ -1022,7 +1006,6 @@ void DVisualThinker::Construct()
PT.subsector = nullptr;
cursector = nullptr;
PT.color = 0xffffff;
spr = new HWSprite();
AnimatedTexture.SetNull();
}
@ -1034,11 +1017,6 @@ DVisualThinker::DVisualThinker()
void DVisualThinker::OnDestroy()
{
PT.alpha = 0.0; // stops all rendering.
if(spr)
{
delete spr;
spr = nullptr;
}
Super::OnDestroy();
}
@ -1067,6 +1045,33 @@ static DVisualThinker* SpawnVisualThinker(FLevelLocals* Level, PClass* type)
return DVisualThinker::NewVisualThinker(Level, type);
}
void DVisualThinker::UpdateSector(subsector_t * newSubsector)
{
assert(newSubsector);
if(PT.subsector != newSubsector)
{
PT.subsector = newSubsector;
cursector = newSubsector->sector;
}
}
void DVisualThinker::UpdateSector()
{
UpdateSector(Level->PointInRenderSubsector(PT.Pos));
}
static void UpdateSector(DVisualThinker * self)
{
self->UpdateSector();
}
DEFINE_ACTION_FUNCTION_NATIVE(DVisualThinker, UpdateSector, UpdateSector)
{
PARAM_SELF_PROLOGUE(DVisualThinker);
self->UpdateSector();
return 0;
}
DEFINE_ACTION_FUNCTION_NATIVE(FLevelLocals, SpawnVisualThinker, SpawnVisualThinker)
{
PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals);
@ -1085,6 +1090,19 @@ void DVisualThinker::UpdateSpriteInfo()
}
}
static void UpdateSpriteInfo(DVisualThinker * self)
{
self->UpdateSpriteInfo();
}
DEFINE_ACTION_FUNCTION_NATIVE(DVisualThinker, UpdateSpriteInfo, UpdateSpriteInfo)
{
PARAM_SELF_PROLOGUE(DVisualThinker);
self->UpdateSpriteInfo();
return 0;
}
// This runs just like Actor's, make sure to call Super.Tick() in ZScript.
void DVisualThinker::Tick()
{
@ -1114,27 +1132,27 @@ void DVisualThinker::Tick()
PT.Pos.Y = newxy.Y;
PT.Pos.Z += PT.Vel.Z;
PT.subsector = Level->PointInRenderSubsector(PT.Pos);
cursector = PT.subsector->sector;
subsector_t * ss = Level->PointInRenderSubsector(PT.Pos);
// Handle crossing a sector portal.
if (!cursector->PortalBlocksMovement(sector_t::ceiling))
if (!ss->sector->PortalBlocksMovement(sector_t::ceiling))
{
if (PT.Pos.Z > cursector->GetPortalPlaneZ(sector_t::ceiling))
if (PT.Pos.Z > ss->sector->GetPortalPlaneZ(sector_t::ceiling))
{
PT.Pos += cursector->GetPortalDisplacement(sector_t::ceiling);
PT.subsector = Level->PointInRenderSubsector(PT.Pos);
cursector = PT.subsector->sector;
PT.Pos += ss->sector->GetPortalDisplacement(sector_t::ceiling);
ss = Level->PointInRenderSubsector(PT.Pos);
}
}
else if (!cursector->PortalBlocksMovement(sector_t::floor))
else if (!ss->sector->PortalBlocksMovement(sector_t::floor))
{
if (PT.Pos.Z < cursector->GetPortalPlaneZ(sector_t::floor))
if (PT.Pos.Z < ss->sector->GetPortalPlaneZ(sector_t::floor))
{
PT.Pos += cursector->GetPortalDisplacement(sector_t::floor);
PT.subsector = Level->PointInRenderSubsector(PT.Pos);
cursector = PT.subsector->sector;
PT.Pos += ss->sector->GetPortalDisplacement(sector_t::floor);
ss = Level->PointInRenderSubsector(PT.Pos);
}
}
UpdateSector(ss);
UpdateSpriteInfo();
}
@ -1142,7 +1160,7 @@ int DVisualThinker::GetLightLevel(sector_t* rendersector) const
{
int lightlevel = rendersector->GetSpriteLight();
if (bAddLightLevel)
if (flags & VTF_AddLightLevel)
{
lightlevel += LightLevel;
}
@ -1155,7 +1173,7 @@ int DVisualThinker::GetLightLevel(sector_t* rendersector) const
FVector3 DVisualThinker::InterpolatedPosition(double ticFrac) const
{
if (bDontInterpolate) return FVector3(PT.Pos);
if (flags & VTF_DontInterpolate) return FVector3(PT.Pos);
DVector3 proc = Prev + (ticFrac * (PT.Pos - Prev));
return FVector3(proc);
@ -1164,7 +1182,7 @@ FVector3 DVisualThinker::InterpolatedPosition(double ticFrac) const
float DVisualThinker::InterpolatedRoll(double ticFrac) const
{
if (bDontInterpolate) return PT.Roll;
if (flags & VTF_DontInterpolate) return PT.Roll;
return float(PrevRoll + (PT.Roll - PrevRoll) * ticFrac);
}
@ -1244,17 +1262,29 @@ int DVisualThinker::GetRenderStyle()
float DVisualThinker::GetOffset(bool y) const // Needed for the renderer.
{
if (y)
return (float)(bFlipOffsetY ? Offset.Y : -Offset.Y);
return (float)((flags & VTF_FlipOffsetY) ? Offset.Y : -Offset.Y);
else
return (float)(bFlipOffsetX ? Offset.X : -Offset.X);
return (float)((flags & VTF_FlipOffsetX) ? Offset.X : -Offset.X);
}
FSerializer& Serialize(FSerializer& arc, const char* key, FStandaloneAnimation& value, FStandaloneAnimation* defval)
{
arc.BeginObject(key);
arc("SwitchTic", value.SwitchTic);
arc("AnimIndex", value.AnimIndex);
arc("CurFrame", value.CurFrame);
arc("Ok", value.ok);
arc("AnimType", value.AnimType);
arc.EndObject();
return arc;
}
void DVisualThinker::Serialize(FSerializer& arc)
{
Super::Serialize(arc);
arc
("pos", PT.Pos)
arc ("pos", PT.Pos)
("vel", PT.Vel)
("prev", Prev)
("scale", Scale)
@ -1267,15 +1297,15 @@ void DVisualThinker::Serialize(FSerializer& arc)
("translation", Translation)
("cursector", cursector)
("scolor", PT.color)
("flipx", bXFlip)
("flipy", bYFlip)
("dontinterpolate", bDontInterpolate)
("addlightlevel", bAddLightLevel)
("flipoffsetx", bFlipOffsetX)
("flipoffsetY", bFlipOffsetY)
("lightlevel", LightLevel)
("flags", PT.flags);
("animData", PT.animData)
("flags", PT.flags)
("visualThinkerFlags", flags);
if(arc.isReading())
{
UpdateSector();
}
}
IMPLEMENT_CLASS(DVisualThinker, false, false);
@ -1286,6 +1316,7 @@ DEFINE_FIELD_NAMED(DVisualThinker, PT.Roll, Roll);
DEFINE_FIELD_NAMED(DVisualThinker, PT.alpha, Alpha);
DEFINE_FIELD_NAMED(DVisualThinker, PT.texture, Texture);
DEFINE_FIELD_NAMED(DVisualThinker, PT.flags, Flags);
DEFINE_FIELD_NAMED(DVisualThinker, flags, VisualThinkerFlags);
DEFINE_FIELD(DVisualThinker, Prev);
DEFINE_FIELD(DVisualThinker, Scale);
@ -1294,9 +1325,3 @@ DEFINE_FIELD(DVisualThinker, PrevRoll);
DEFINE_FIELD(DVisualThinker, Translation);
DEFINE_FIELD(DVisualThinker, LightLevel);
DEFINE_FIELD(DVisualThinker, cursector);
DEFINE_FIELD(DVisualThinker, bXFlip);
DEFINE_FIELD(DVisualThinker, bYFlip);
DEFINE_FIELD(DVisualThinker, bDontInterpolate);
DEFINE_FIELD(DVisualThinker, bAddLightLevel);
DEFINE_FIELD(DVisualThinker, bFlipOffsetX);
DEFINE_FIELD(DVisualThinker, bFlipOffsetY);

View file

@ -147,56 +147,4 @@ struct SPortalHit
void P_DrawRailTrail(AActor *source, TArray<SPortalHit> &portalhits, int color1, int color2, double maxdiff = 0, int flags = 0, PClassActor *spawnclass = NULL, DAngle angle = nullAngle, int duration = TICRATE, double sparsity = 1.0, double drift = 1.0, int SpiralOffset = 270, DAngle pitch = nullAngle);
void P_DrawSplash (FLevelLocals *Level, int count, const DVector3 &pos, DAngle angle, int kind);
void P_DrawSplash2 (FLevelLocals *Level, int count, const DVector3 &pos, DAngle angle, int updown, int kind);
void P_DisconnectEffect (AActor *actor);
//===========================================================================
//
// VisualThinkers
// by Major Cooke
// Credit to phantombeta, RicardoLuis0 & RaveYard for aid
//
//===========================================================================
class HWSprite;
struct FTranslationID;
class DVisualThinker : public DThinker
{
DECLARE_CLASS(DVisualThinker, DThinker);
public:
DVector3 Prev;
DVector2 Scale,
Offset;
float PrevRoll;
int16_t LightLevel;
FTranslationID Translation;
FTextureID AnimatedTexture;
sector_t *cursector;
bool bFlipOffsetX,
bFlipOffsetY,
bXFlip,
bYFlip, // flip the sprite on the x/y axis.
bDontInterpolate, // disable all interpolation
bAddLightLevel; // adds sector light level to 'LightLevel'
// internal only variables
particle_t PT;
HWSprite *spr; //in an effort to cache the result.
DVisualThinker();
void Construct();
void OnDestroy() override;
static DVisualThinker* NewVisualThinker(FLevelLocals* Level, PClass* type);
void SetTranslation(FName trname);
int GetRenderStyle();
bool isFrozen();
int GetLightLevel(sector_t *rendersector) const;
FVector3 InterpolatedPosition(double ticFrac) const;
float InterpolatedRoll(double ticFrac) const;
void Tick() override;
void UpdateSpriteInfo();
void Serialize(FSerializer& arc) override;
float GetOffset(bool y) const;
};
void P_DisconnectEffect (AActor *actor);

View file

@ -464,6 +464,12 @@ static int P_IsUnderDamage(AActor* actor)
dir |= cl->getDirection();
}
// Q: consider crushing 3D floors too?
// [inkoalawetrust] Check for sectors that can harm the actor.
if (!(actor->flags9 & MF9_NOSECTORDAMAGE) && seclist->m_sector->damageamount > 0)
{
if (seclist->m_sector->MoreFlags & SECMF_HARMINAIR || actor->isAtZ(seclist->m_sector->LowestFloorAt(actor)) || actor->waterlevel)
return (actor->player || (actor->player == nullptr && seclist->m_sector->MoreFlags & SECMF_HURTMONSTERS)) ? -1 : 0;
}
}
return dir;
}
@ -1303,6 +1309,161 @@ int P_IsVisible(AActor *lookee, AActor *other, INTBOOL allaround, FLookExParams
return P_CheckSight(lookee, other, SF_SEEPASTSHOOTABLELINES);
}
bool isTargetablePlayer(AActor *actor, player_t *player, INTBOOL allaround, void* lookparams)
{
FLookExParams* params = (FLookExParams*)lookparams;
if (!(player->mo->flags & MF_SHOOTABLE))
return false; // not shootable (observer or dead)
if (actor->IsFriend(player->mo))
return false; // same +MF_FRIENDLY, ignore
if (player->cheats & CF_NOTARGET)
return false; // no target
if (player->health <= 0)
return false; // dead
if (!P_IsVisible(actor, player->mo, allaround, params))
return false; // out of sight
// [RC] Well, let's let special monsters with this flag active be able to see
// the player then, eh?
if (!(actor->flags6 & MF6_SEEINVISIBLE))
{
if ((player->mo->flags & MF_SHADOW && !(actor->Level->i_compatflags & COMPATF_INVISIBILITY)) ||
player->mo->flags3 & MF3_GHOST)
{
if (player->mo->Distance2D(actor) > 128 && player->mo->Vel.XY().LengthSquared() < 5 * 5)
{ // Player is sneaking - can't detect
return false;
}
if (pr_lookforplayers() < 225)
{ // Player isn't sneaking, but still didn't detect
return false;
}
}
}
return true;
}
bool ValidEnemyInBlock(AActor* lookee, AActor* other, void* lookparams)
{
FLookExParams* params = (FLookExParams*)lookparams;
if (!(other->flags & MF_SHOOTABLE))
return false; // not shootable (observer or dead)
if (other == lookee)
return false; // is self
if (other->health <= 0)
return false; // dead
if (other->flags2 & MF2_DORMANT)
return false; // don't target dormant things
if (!(other->flags3 & MF3_ISMONSTER))
return false; // don't target it if it isn't a monster (could be a barrel)
if (other->flags7 & MF7_NEVERTARGET)
return false;
bool keepChecking = false;
if (lookee->flags & MF_FRIENDLY)
{
if (other->flags & MF_FRIENDLY)
{
if (!lookee->IsFriend(other))
{
// This is somebody else's friend, so go after it
keepChecking = true;
}
else if (other->target != NULL && !(other->target->flags & MF_FRIENDLY))
{
other = other->target;
if (!(other->flags & MF_SHOOTABLE) ||
other->health <= 0 ||
(other->flags2 & MF2_DORMANT))
{
return false;
}
}
}
else
{
keepChecking = true;
}
}
else if (lookee->flags8 & MF8_SEEFRIENDLYMONSTERS && other->flags & MF_FRIENDLY)
{
keepChecking = true;
}
// [MBF] If the monster is already engaged in a one-on-one attack
// with a healthy friend, don't attack around 60% the time.
// [GrafZahl] This prevents friendlies from attacking all the same
// target.
if (keepChecking)
{
AActor* targ = other->target;
if (targ && targ->target == other && pr_skiptarget() > 100 && lookee->IsFriend(targ) &&
targ->health * 2 >= targ->SpawnHealth())
{
return false;
}
}
// [KS] Hey, shouldn't there be a check for MF3_NOSIGHTCHECK here?
if (!keepChecking || !P_IsVisible(lookee, other, true, params))
return false; // out of sight
return true;
}
//============================================================================
//
// LookForEnemiesEx
//
// [inkoalawetrust] Return a script array of all valid enemies of the caller
// in range. For ZScript.
//
//============================================================================
DEFINE_ACTION_FUNCTION(AActor, LookForEnemiesEx)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_OUTPOINTER(targets,TArray<AActor*>);
PARAM_FLOAT(range);
PARAM_BOOL(noPlayers);
PARAM_BOOL(allaround);
PARAM_POINTER(params, FLookExParams);
if (targets == nullptr)
ThrowAbortException(X_WRITE_NIL,"No targets array passed");
if (range == -1)
range = self->friendlyseeblocks * FBlockmap::MAPBLOCKUNITS;
FPortalGroupArray check(FPortalGroupArray::PGA_Full3d);
FMultiBlockThingsIterator it(check, self, range, false);
FMultiBlockThingsIterator::CheckResult cres;
while (it.Next(&cres))
{
if (cres.thing->player == nullptr && ValidEnemyInBlock(cres.thing, self, params) ||
!noPlayers && cres.thing->player && isTargetablePlayer(self, cres.thing->player, allaround, params))
targets->Push(cres.thing);
}
ACTION_RETURN_INT(targets->Size());
}
//---------------------------------------------------------------------------
//
// FUNC P_LookForMonsters
@ -1546,80 +1707,10 @@ AActor *LookForEnemiesInBlock (AActor *lookee, int index, void *extparam)
for (block = lookee->Level->blockmap.blocklinks[index]; block != NULL; block = block->NextActor)
{
link = block->Me;
if (!(link->flags & MF_SHOOTABLE))
continue; // not shootable (observer or dead)
if (link == lookee)
if (!ValidEnemyInBlock(lookee, block->Me, params))
continue;
if (link->health <= 0)
continue; // dead
if (link->flags2 & MF2_DORMANT)
continue; // don't target dormant things
if (!(link->flags3 & MF3_ISMONSTER))
continue; // don't target it if it isn't a monster (could be a barrel)
if (link->flags7 & MF7_NEVERTARGET)
continue;
other = NULL;
if (lookee->flags & MF_FRIENDLY)
{
if (link->flags & MF_FRIENDLY)
{
if (!lookee->IsFriend(link))
{
// This is somebody else's friend, so go after it
other = link;
}
else if (link->target != NULL && !(link->target->flags & MF_FRIENDLY))
{
other = link->target;
if (!(other->flags & MF_SHOOTABLE) ||
other->health <= 0 ||
(other->flags2 & MF2_DORMANT))
{
other = NULL;;
}
}
}
else
{
other = link;
}
}
else if (lookee->flags8 & MF8_SEEFRIENDLYMONSTERS && link->flags & MF_FRIENDLY)
{
other = link;
}
// [MBF] If the monster is already engaged in a one-on-one attack
// with a healthy friend, don't attack around 60% the time.
// [GrafZahl] This prevents friendlies from attacking all the same
// target.
if (other)
{
AActor *targ = other->target;
if (targ && targ->target == other && pr_skiptarget() > 100 && lookee->IsFriend (targ) &&
targ->health*2 >= targ->SpawnHealth())
{
continue;
}
}
// [KS] Hey, shouldn't there be a check for MF3_NOSIGHTCHECK here?
if (other == NULL || !P_IsVisible (lookee, other, true, params))
continue; // out of sight
return other;
return block->Me;
}
return NULL;
}
@ -1819,38 +1910,8 @@ int P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params)
player = actor->Level->Players[pnum];
if (!(player->mo->flags & MF_SHOOTABLE))
continue; // not shootable (observer or dead)
if (actor->IsFriend(player->mo))
continue; // same +MF_FRIENDLY, ignore
if (player->cheats & CF_NOTARGET)
continue; // no target
if (player->health <= 0)
continue; // dead
if (!P_IsVisible (actor, player->mo, allaround, params))
continue; // out of sight
// [RC] Well, let's let special monsters with this flag active be able to see
// the player then, eh?
if(!(actor->flags6 & MF6_SEEINVISIBLE))
{
if ((player->mo->flags & MF_SHADOW && !(actor->Level->i_compatflags & COMPATF_INVISIBILITY)) ||
player->mo->flags3 & MF3_GHOST)
{
if (player->mo->Distance2D (actor) > 128 && player->mo->Vel.XY().LengthSquared() < 5*5)
{ // Player is sneaking - can't detect
continue;
}
if (pr_lookforplayers() < 225)
{ // Player isn't sneaking, but still didn't detect
continue;
}
}
}
if (!isTargetablePlayer(actor, player, allaround, params))
continue;
// [RH] Need to be sure the reactiontime is 0 if the monster is
// leaving its goal to go after a player.

View file

@ -352,7 +352,7 @@ void P_TraceBleed (int damage, AActor *target, AActor *missile); // missile ver
void P_TraceBleed(int damage, FTranslatedLineTarget *t, AActor *puff); // hitscan version
void P_TraceBleed (int damage, AActor *target); // random direction version
bool P_HitFloor (AActor *thing);
bool P_HitWater (AActor *thing, sector_t *sec, const DVector3 &pos, bool checkabove = false, bool alert = true, bool force = false);
bool P_HitWater (AActor *thing, sector_t *sec, const DVector3 &pos, bool checkabove = false, bool alert = true, bool force = false, int flags = 0);
struct FRailParams

View file

@ -81,6 +81,7 @@
#include "r_utility.h"
#include "p_blockmap.h"
#include "p_3dmidtex.h"
#include "events.h"
#include "vm.h"
#include "d_main.h"
@ -121,13 +122,20 @@ TArray<spechit_t> portalhit;
//
//==========================================================================
bool P_ShouldPassThroughPlayer(AActor *self, AActor *other)
static int P_ShouldPassThroughPlayer(AActor *self, AActor *other)
{
return (dmflags3 & DF3_NO_PLAYER_CLIP) &&
other->player && other->player->mo == other &&
self->IsFriend(other);
}
DEFINE_ACTION_FUNCTION_NATIVE(AActor, ShouldPassThroughPlayer, P_ShouldPassThroughPlayer)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_OBJECT_NOT_NULL(other, AActor);
ACTION_RETURN_BOOL(P_ShouldPassThroughPlayer(self, other));
}
//==========================================================================
//
// CanCollideWith
@ -2100,10 +2108,13 @@ int P_TestMobjZ(AActor *actor, bool quick, AActor **pOnmobj)
while (it.Next(&cres))
{
AActor *thing = cres.thing;
if (!quick && onmobj != NULL && thing->Top() < onmobj->Top())
{ // something higher is in the way
continue;
}
double blockdist = thing->radius + actor->radius;
if (fabs(thing->X() - cres.Position.X) >= blockdist || fabs(thing->Y() - cres.Position.Y) >= blockdist)
{
if (thing == actor)
{ // Don't clip against self.
continue;
}
if (thing->flags2 & MF2_THRUACTORS)
@ -2114,19 +2125,24 @@ int P_TestMobjZ(AActor *actor, bool quick, AActor **pOnmobj)
{
continue;
}
if ((actor->flags6 & MF6_THRUSPECIES) && (thing->GetSpecies() == actor->GetSpecies()))
{
continue;
}
if (!(thing->flags & MF_SOLID))
{ // Can't hit thing
continue;
}
if (thing->flags & (MF_SPECIAL | MF_NOCLIP))
if ((thing->flags & (MF_SPECIAL | MF_NOCLIP)) || (thing->flags6 & MF6_TOUCHY))
{ // [RH] Specials and noclippers don't block moves
continue;
}
if (thing->flags & (MF_CORPSE))
const double blockdist = thing->radius + actor->radius;
if (fabs(thing->X() - cres.Position.X) >= blockdist || fabs(thing->Y() - cres.Position.Y) >= blockdist)
{
continue;
}
if ((actor->flags6 & MF6_THRUSPECIES) && thing->GetSpecies() == actor->GetSpecies())
{
continue;
}
if (thing->flags & MF_CORPSE)
{ // Corpses need a few more checks
if (!(actor->flags & MF_ICECORPSE))
continue;
@ -2135,33 +2151,78 @@ int P_TestMobjZ(AActor *actor, bool quick, AActor **pOnmobj)
{ // [RH] Only bridges block pickup items
continue;
}
if (thing == actor)
{ // Don't clip against self
continue;
}
if ((actor->flags & MF_MISSILE) && (thing == actor->target))
{ // Don't clip against whoever shot the missile.
if (actor->player != nullptr && P_ShouldPassThroughPlayer(actor, thing))
{
continue;
}
if (actor->Z() > thing->Top())
{ // over thing
continue;
}
else if (actor->Top() <= thing->Z())
if (actor->Top() <= thing->Z())
{ // under thing
continue;
}
else if (!quick && onmobj != NULL && thing->Top() < onmobj->Top())
{ // something higher is in the way
continue;
}
else if (!P_CanCollideWith(actor, thing))
if (!P_CanCollideWith(actor, thing))
{ // If they cannot collide, they cannot block each other.
continue;
}
if (actor->player && P_ShouldPassThroughPlayer(actor, thing))
if ((actor->flags & MF_MISSILE) || ((actor->BounceFlags & BOUNCE_MBF) && !(actor->flags & MF_SOLID)))
{
continue;
if (thing->flags2 & MF2_NONSHOOTABLE)
{
continue;
}
if ((thing->flags3 & MF3_GHOST) && (actor->flags2 & MF2_THRUGHOST))
{
continue;
}
if ((thing->flags4 & MF4_SPECTRAL) && !(actor->flags4 & MF4_SPECTRAL))
{
continue;
}
if (actor->target != nullptr)
{
if ((actor->flags6 & MF6_MTHRUSPECIES) && actor->target->GetSpecies() == thing->GetSpecies())
{
continue;
}
if (actor->target == thing)
{
if (!(actor->flags8 & MF8_HITOWNER))
{
continue;
}
}
else if (actor->target->player != nullptr && P_ShouldPassThroughPlayer(actor->target, thing))
{
continue;
}
}
if ((thing->flags7 & MF7_THRUREFLECT) && (thing->flags2 & MF2_REFLECTIVE) && (actor->flags & MF_MISSILE))
{
continue;
}
double clipheight = thing->Height;
if (thing->projectilepassheight > 0)
{
clipheight = thing->projectilepassheight;
}
else if (thing->projectilepassheight < 0 && (thing->Level->i_compatflags & COMPATF_MISSILECLIP))
{
clipheight = -thing->projectilepassheight;
}
if (actor->Z() > thing->Z() + clipheight)
{ // Over thing
continue;
}
if ((actor->flags2 & MF2_RIP) && !(thing->flags5 & MF5_DONTRIP)
&& (!(actor->flags6 & MF6_NOBOSSRIP) || !(thing->flags2 & MF2_BOSS))
&& CheckRipLevel(thing, actor))
{
continue;
}
}
onmobj = thing;
@ -4620,6 +4681,12 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
DAngle pitch, int damage, FName damageType, PClassActor *pufftype, int flags, FTranslatedLineTarget*victim, int *actualdamage,
double sz, double offsetforward, double offsetside)
{
if (t1->Level->localEventManager->WorldHitscanPreFired(t1, angle, distance, pitch, damage, damageType, pufftype, flags, sz, offsetforward, offsetside))
{
return nullptr;
}
bool nointeract = !!(flags & LAF_NOINTERACT);
DVector3 direction;
double shootz;
@ -4634,6 +4701,7 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
if (flags & LAF_NORANDOMPUFFZ)
puffFlags |= PF_NORANDOMZ;
if (victim != NULL)
{
memset(victim, 0, sizeof(*victim));
@ -4724,6 +4792,7 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
// LAF_ABSOFFSET: Ignore the angle.
DVector3 tempos;
DVector3 puffpos;
if (flags & LAF_ABSPOSITION)
{
@ -4759,7 +4828,8 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
if (nointeract || (puffDefaults && puffDefaults->flags3 & MF3_ALWAYSPUFF))
{ // Spawn the puff anyway
puff = P_SpawnPuff(t1, pufftype, trace.HitPos, trace.SrcAngleFromTarget, trace.SrcAngleFromTarget, 2, puffFlags);
puffpos = trace.HitPos;
puff = P_SpawnPuff(t1, pufftype, puffpos, trace.SrcAngleFromTarget, trace.SrcAngleFromTarget, 2, puffFlags);
if (nointeract)
{
@ -4789,7 +4859,8 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
if (nointeract || trace.HitType != TRACE_HitWall || ((trace.Line->special != Line_Horizon) || spawnSky))
{
DVector2 pos = t1->Level->GetPortalOffsetPosition(trace.HitPos.X, trace.HitPos.Y, -trace.HitVector.X * 4, -trace.HitVector.Y * 4);
puff = P_SpawnPuff(t1, pufftype, DVector3(pos, trace.HitPos.Z - trace.HitVector.Z * 4), trace.SrcAngleFromTarget,
puffpos = DVector3(pos, trace.HitPos.Z - trace.HitVector.Z * 4);
puff = P_SpawnPuff(t1, pufftype, puffpos, trace.SrcAngleFromTarget,
trace.SrcAngleFromTarget - DAngle::fromDeg(90), 0, puffFlags);
puff->radius = 1/65536.;
@ -4836,6 +4907,7 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
{
// Hit a thing, so it could be either a puff or blood
DVector3 bleedpos = trace.HitPos;
puffpos = bleedpos;
// position a bit closer for puffs/blood if using compatibility mode.
if (trace.Actor->Level->i_compatflags & COMPATF_HITSCAN)
{
@ -4928,6 +5000,9 @@ AActor *P_LineAttack(AActor *t1, DAngle angle, double distance,
SpawnDeepSplash(t1, trace, puff);
}
}
t1->Level->localEventManager->WorldHitscanFired(t1, tempos, puffpos, puff, flags);
if (killPuff && puff != NULL)
{
puff->Destroy();
@ -5378,16 +5453,30 @@ static ETraceStatus ProcessRailHit(FTraceResults &res, void *userdata)
//==========================================================================
void P_RailAttack(FRailParams *p)
{
DVector3 start;
FTraceResults trace;
AActor *source = p->source;
PClassActor *puffclass = p->puff;
if (puffclass == NULL)
{
puffclass = PClass::FindActor(NAME_BulletPuff);
}
assert(puffclass != NULL); // Because we set it to a default above
AActor *puffDefaults = GetDefaultByType(puffclass->GetReplacement(source->Level)); //Contains all the flags such as FOILINVUL, etc.
FName damagetype = (puffDefaults == NULL || puffDefaults->DamageType == NAME_None) ? FName(NAME_Railgun) : puffDefaults->DamageType;
int flags;
// disabled because not complete yet.
flags = (puffDefaults->flags6 & MF6_NOTRIGGER) ? TRACE_ReportPortals : TRACE_PCross | TRACE_Impact | TRACE_ReportPortals;
if (source->Level->localEventManager->WorldHitscanPreFired(source, source->Angles.Yaw + p->angleoffset, p->distance, source->Angles.Pitch + p->pitchoffset, p->damage, damagetype, puffclass, flags, p->offset_z, 0, p->offset_xy))
{
return;
}
DVector3 start;
FTraceResults trace;
AActor *source = p->source;
DAngle pitch = source->Angles.Pitch + p->pitchoffset;
DAngle angle = source->Angles.Yaw + p->angleoffset;
@ -5416,13 +5505,6 @@ void P_RailAttack(FRailParams *p)
start.Y = xy.Y;
start.Z = shootz;
int flags;
assert(puffclass != NULL); // Because we set it to a default above
AActor *puffDefaults = GetDefaultByType(puffclass->GetReplacement(source->Level)); //Contains all the flags such as FOILINVUL, etc.
// disabled because not complete yet.
flags = (puffDefaults->flags6 & MF6_NOTRIGGER) ? TRACE_ReportPortals : TRACE_PCross | TRACE_Impact | TRACE_ReportPortals;
rail_data.StopAtInvul = (puffDefaults->flags3 & MF3_FOILINVUL) ? false : true;
rail_data.MThruSpecies = ((puffDefaults->flags6 & MF6_MTHRUSPECIES)) ? true : false;
@ -5459,8 +5541,7 @@ void P_RailAttack(FRailParams *p)
// Hurt anything the trace hit
unsigned int i;
FName damagetype = (puffDefaults == NULL || puffDefaults->DamageType == NAME_None) ? FName(NAME_Railgun) : puffDefaults->DamageType;
for (i = 0; i < rail_data.RailHits.Size(); i++)
{
bool spawnpuff;
@ -5548,6 +5629,9 @@ void P_RailAttack(FRailParams *p)
}
}
}
source->Level->localEventManager->WorldHitscanFired(source, start, trace.HitPos, thepuff, flags);
if (thepuff != NULL)
{
if (trace.Crossed3DWater || trace.CrossedWater)

View file

@ -4435,6 +4435,14 @@ void AActor::Tick ()
// must have been removed
if (ObjectFlags & OF_EuthanizeMe) return;
}
//[inkoalawetrust] Genericized level damage handling that makes sector, 3D floor, and TERRAIN flat damage affect monsters and other NPCs too.
P_ActorOnSpecial3DFloor(this); //3D floors must be checked separately to see if their control sector allows non-player damage
if (checkForSpecialSector(this,Sector))
{
P_ActorInSpecialSector(this,Sector);
if (!isAbove(Sector->floorplane.ZatPoint(this)) || waterlevel) // Actor must be touching the floor for TERRAIN flats.
P_ActorOnSpecialFlat(this, P_GetThingFloorType(this));
}
if (tics != -1)
{
@ -6652,7 +6660,13 @@ int P_GetThingFloorType (AActor *thing)
// Returns true if hit liquid and splashed, false if not.
//---------------------------------------------------------------------------
bool P_HitWater (AActor * thing, sector_t * sec, const DVector3 &pos, bool checkabove, bool alert, bool force)
enum HitWaterFlags
{
THW_SMALL = 1 << 0,
THW_NOVEL = 1 << 1,
};
bool P_HitWater (AActor * thing, sector_t * sec, const DVector3 &pos, bool checkabove, bool alert, bool force, int flags)
{
if (thing->player && (thing->player->cheats & CF_PREDICTING))
return false;
@ -6740,13 +6754,13 @@ foundone:
// Don't splash for living things with small vertical velocities.
// There are levels where the constant splashing from the monsters gets extremely annoying
if (((thing->flags3&MF3_ISMONSTER || thing->player) && thing->Vel.Z >= -6) && !force)
if (!(flags & THW_NOVEL) && ((thing->flags3 & MF3_ISMONSTER || thing->player) && thing->Vel.Z >= -6) && !force)
return Terrains[terrainnum].IsLiquid;
splash = &Splashes[splashnum];
// Small splash for small masses
if (thing->Mass < 10)
if (flags & THW_SMALL || thing->Mass < 10)
smallsplash = true;
if (!(thing->flags3 & MF3_DONTSPLASH))
@ -6811,7 +6825,8 @@ DEFINE_ACTION_FUNCTION(AActor, HitWater)
PARAM_BOOL(checkabove);
PARAM_BOOL(alert);
PARAM_BOOL(force);
ACTION_RETURN_BOOL(P_HitWater(self, sec, DVector3(x, y, z), checkabove, alert, force));
PARAM_INT(flags);
ACTION_RETURN_BOOL(P_HitWater(self, sec, DVector3(x, y, z), checkabove, alert, force, flags));
}
@ -7820,6 +7835,19 @@ void AActor::Revive()
target = nullptr;
lastenemy = nullptr;
// Make sure to clear poison damage.
PoisonDamageReceived = 0;
PoisonDamageTypeReceived = NAME_None;
PoisonDurationReceived = 0;
PoisonPeriodReceived = 0;
Poisoner = nullptr;
if (player != nullptr)
{
player->poisoncount = 0;
player->poisoner = nullptr;
player->poisontype = player->poisonpaintype = NAME_None;
}
// [RH] If it's a monster, it gets to count as another kill
if (CountsAsKill())
{

View file

@ -100,7 +100,7 @@
#include "c_console.h"
#include "p_spec_thinkers.h"
static FRandom pr_playerinspecialsector ("PlayerInSpecialSector");
static FRandom pr_actorinspecialsector ("ActorInSpecialSector");
EXTERN_CVAR(Bool, cl_predict_specials)
EXTERN_CVAR(Bool, forcewater)
@ -419,23 +419,26 @@ bool P_PredictLine(line_t *line, AActor *mo, int side, int activationType)
}
//
// P_PlayerInSpecialSector
// P_ActorInSpecialSector
// Called every tic frame
// that the player origin is in a special sector
// that the actor origin is in a special sector
//
void P_PlayerInSpecialSector (player_t *player, sector_t * sector)
void P_ActorInSpecialSector (AActor *victim, sector_t * sector, F3DFloor* Ffloor)
{
if (sector == NULL)
{
// Falling, not all the way down yet?
sector = player->mo->Sector;
if (!player->mo->isAtZ(sector->LowestFloorAt(player->mo))
&& !player->mo->waterlevel)
{
return;
}
}
sector = victim->Sector;
// Falling, not all the way down yet?
bool evilAir = (sector->MoreFlags & SECMF_HARMINAIR);
bool SolidFfloor = Ffloor != nullptr && (Ffloor->flags & FF_SOLID);
if ((!evilAir && !(Ffloor != nullptr && !SolidFfloor)) && !victim->waterlevel)
{
// [inkoalawetrust] Check for 3D floors differently, because non-FF_INVERTSECTOR ffloors have their floor plane as the 3D floor BOTTOM.
double theZ = Ffloor == nullptr ? sector->LowestFloorAt(victim) : Ffloor->top.plane->ZatPoint(victim);
if (!victim->isAtZ(theZ))
return;
}
// Has hit ground.
auto Level = sector->Level;
@ -445,7 +448,7 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector)
if (sector->damageinterval <= 0)
sector->damageinterval = 32; // repair invalid damageinterval values
if (sector->Flags & (SECF_EXIT1 | SECF_EXIT2))
if (victim->player && sector->Flags & (SECF_EXIT1 | SECF_EXIT2))
{
for (int i = 0; i < MAXPLAYERS; i++)
if (playeringame[i])
@ -464,7 +467,7 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector)
// different damage types yet, so that's not happening for now.
// [MK] account for subclasses that may have "Full" protection (i.e.: prevent leaky damage)
int ironfeet = 0;
for (auto i = player->mo->Inventory; i != NULL; i = i->Inventory)
for (auto i = victim->Inventory; i != NULL; i = i->Inventory)
{
if (i->IsKindOf(NAME_PowerIronFeet))
{
@ -476,28 +479,28 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector)
}
}
if (sector->Flags & SECF_ENDGODMODE) player->cheats &= ~CF_GODMODE;
if ((ironfeet == 0 || (ironfeet < 2 && pr_playerinspecialsector() < sector->leakydamage)))
if (victim->player && sector->Flags & SECF_ENDGODMODE) victim->player->cheats &= ~CF_GODMODE;
if ((ironfeet == 0 || (ironfeet < 2 && pr_actorinspecialsector() < sector->leakydamage)))
{
if (sector->Flags & SECF_HAZARD)
if (victim->player && sector->Flags & SECF_HAZARD)
{
player->hazardcount += sector->damageamount;
player->hazardtype = sector->damagetype;
player->hazardinterval = sector->damageinterval;
victim->player->hazardcount += sector->damageamount;
victim->player->hazardtype = sector->damagetype;
victim->player->hazardinterval = sector->damageinterval;
}
else if (Level->time % sector->damageinterval == 0)
{
if (!(player->cheats & (CF_GODMODE | CF_GODMODE2)))
if (!(victim->player && victim->player->cheats & (CF_GODMODE | CF_GODMODE2)))
{
P_DamageMobj(player->mo, NULL, NULL, sector->damageamount, sector->damagetype);
P_DamageMobj(victim, NULL, NULL, sector->damageamount, sector->damagetype);
}
if ((sector->Flags & SECF_ENDLEVEL) && player->health <= 10 && (!deathmatch || !(dmflags & DF_NO_EXIT)))
if (victim->player && (sector->Flags & SECF_ENDLEVEL) && victim->player->health <= 10 && (!deathmatch || !(dmflags & DF_NO_EXIT)))
{
Level->ExitLevel(0, false);
}
if (sector->Flags & SECF_DMGTERRAINFX)
{
P_HitWater(player->mo, player->mo->Sector, player->mo->Pos(), false, true, true);
P_HitWater(victim, victim->Sector, victim->Pos(), false, true, true);
}
}
}
@ -506,14 +509,14 @@ void P_PlayerInSpecialSector (player_t *player, sector_t * sector)
{
if (Level->time % sector->damageinterval == 0)
{
P_GiveBody(player->mo, -sector->damageamount, 100);
P_GiveBody(victim, -sector->damageamount, 100);
}
}
if (sector->isSecret())
if (victim->player && sector->isSecret())
{
sector->ClearSecret();
P_GiveSecret(Level, player->mo, true, true, sector->Index());
P_GiveSecret(Level, victim, true, true, sector->Index());
}
}
@ -652,13 +655,13 @@ DEFINE_ACTION_FUNCTION(FLevelLocals, GiveSecret)
//============================================================================
//
// P_PlayerOnSpecialFlat
// P_ActorOnSpecialFlat
//
//============================================================================
void P_PlayerOnSpecialFlat (player_t *player, int floorType)
void P_ActorOnSpecialFlat (AActor *victim, int floorType)
{
auto Level = player->mo->Level;
auto Level = victim->Level;
if (Terrains[floorType].DamageAmount &&
!(Level->time % (Terrains[floorType].DamageTimeMask+1)))
@ -668,7 +671,7 @@ void P_PlayerOnSpecialFlat (player_t *player, int floorType)
if (Terrains[floorType].AllowProtection)
{
auto pitype = PClass::FindActor(NAME_PowerIronFeet);
for (ironfeet = player->mo->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
for (ironfeet = victim->Inventory; ironfeet != NULL; ironfeet = ironfeet->Inventory)
{
if (ironfeet->IsKindOf (pitype))
break;
@ -678,20 +681,18 @@ void P_PlayerOnSpecialFlat (player_t *player, int floorType)
int damage = 0;
if (ironfeet == NULL)
{
damage = P_DamageMobj (player->mo, NULL, NULL, Terrains[floorType].DamageAmount,
damage = P_DamageMobj (victim, NULL, NULL, Terrains[floorType].DamageAmount,
Terrains[floorType].DamageMOD);
}
if (damage > 0 && Terrains[floorType].Splash != -1)
{
S_Sound (player->mo, CHAN_AUTO, 0,
S_Sound (victim, CHAN_AUTO, 0,
Splashes[Terrains[floorType].Splash].NormalSplashSound, 1,
ATTN_IDLE);
}
}
}
//
// P_UpdateSpecials
// Animate planes, scroll walls, etc.

View file

@ -35,6 +35,7 @@
#include "dsectoreffect.h"
#include "doomdata.h"
#include "r_state.h"
#include "d_player.h"
class FScanner;
struct level_info_t;
@ -90,13 +91,22 @@ bool P_ActivateLine (line_t *ld, AActor *mo, int side, int activationType, DVect
bool P_TestActivateLine (line_t *ld, AActor *mo, int side, int activationType, DVector3 *optpos = NULL);
bool P_PredictLine (line_t *ld, AActor *mo, int side, int activationType);
void P_PlayerInSpecialSector (player_t *player, sector_t * sector=NULL);
void P_PlayerOnSpecialFlat (player_t *player, int floorType);
void P_ActorInSpecialSector (AActor *victim, sector_t * sector = NULL, F3DFloor* Ffloor = NULL);
void P_ActorOnSpecialFlat (AActor *victim, int floorType);
void P_SectorDamage(FLevelLocals *Level, int tag, int amount, FName type, PClassActor *protectClass, int flags);
void P_SetSectorFriction (FLevelLocals *level, int tag, int amount, bool alterFlag);
double FrictionToMoveFactor(double friction);
void P_GiveSecret(FLevelLocals *Level, AActor *actor, bool printmessage, bool playsound, int sectornum);
inline bool checkForSpecialSector(AActor* mo, sector_t* sec)
{
bool afsdnope = !!(mo->flags9 & MF9_NOSECTORDAMAGE);
bool afsdforce = !!(mo->flags9 & MF9_FORCESECTORDAMAGE);
bool sfhurtmonsters = !!(sec->MoreFlags & SECMF_HURTMONSTERS);
bool isplayer = (mo->player != nullptr) && (mo == mo->player->mo);
return ((!afsdnope || afsdforce) && (isplayer || sfhurtmonsters || afsdforce));
}
//
// getNextSector()
// Return sector_t * of sector next to current.

View file

@ -49,7 +49,7 @@
#include "actorinlines.h"
#include "animations.h"
static FRandom pr_switchanim ("AnimSwitch");
static FCRandom pr_switchanim ("AnimSwitch");
class DActiveButton : public DThinker
{

View file

@ -37,6 +37,7 @@
#define FUDGEFACTOR 10
static FRandom pr_teleport ("Teleport");
static FRandom pr_playerteleport("PlayerTeleport");
CVAR (Bool, telezoom, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
@ -271,7 +272,7 @@ DEFINE_ACTION_FUNCTION(AActor, Teleport)
//
//-----------------------------------------------------------------------------
AActor *FLevelLocals::SelectTeleDest (int tid, int tag, bool norandom)
AActor *FLevelLocals::SelectTeleDest (int tid, int tag, bool norandom, bool isPlayer)
{
AActor *searcher;
@ -323,7 +324,8 @@ AActor *FLevelLocals::SelectTeleDest (int tid, int tag, bool norandom)
{
if (count != 1 && !norandom)
{
count = 1 + (pr_teleport() % count);
// Players get their own RNG seed to reduce likelihood of breaking prediction.
count = 1 + ((isPlayer ? pr_playerteleport() : pr_teleport()) % count);
}
searcher = NULL;
while (count > 0)
@ -394,7 +396,7 @@ bool FLevelLocals::EV_Teleport (int tid, int tag, line_t *line, int side, AActor
{ // Don't teleport if hit back of line, so you can get out of teleporter.
return 0;
}
searcher = SelectTeleDest(tid, tag, predicting);
searcher = SelectTeleDest(tid, tag, false, thing->player != nullptr && thing->player->mo == thing);
if (searcher == NULL)
{
return false;

View file

@ -144,6 +144,8 @@ static DVector3 LastPredictedPosition;
static int LastPredictedPortalGroup;
static int LastPredictedTic;
static TArray<FRandom> PredictionRNG;
static player_t PredictionPlayerBackup;
static AActor *PredictionActor;
static TArray<uint8_t> PredictionActorBackupArray;
@ -1204,15 +1206,6 @@ DEFINE_ACTION_FUNCTION(APlayerPawn, CheckMusicChange)
void P_CheckEnvironment(player_t *player)
{
P_PlayerOnSpecial3DFloor(player);
P_PlayerInSpecialSector(player);
if (!player->mo->isAbove(player->mo->Sector->floorplane.ZatPoint(player->mo)) ||
player->mo->waterlevel)
{
// Player must be touching the floor
P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo));
}
if (player->mo->Vel.Z <= -player->mo->FloatVar(NAME_FallingScreamMinSpeed) &&
player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && player->mo->alternative == nullptr &&
player->mo->waterlevel == 0)
@ -1470,6 +1463,8 @@ void P_PredictPlayer (player_t *player)
return;
}
FRandom::SaveRNGState(PredictionRNG);
// Save original values for restoration later
PredictionPlayerBackup.CopyFrom(*player, false);
@ -1609,6 +1604,8 @@ void P_UnPredictPlayer ()
// Q: Can this happen? If yes, can we continue?
}
FRandom::RestoreRNGState(PredictionRNG);
AActor *savedcamera = player->camera;
auto &actInvSel = act->PointerVar<AActor*>(NAME_InvSel);

View file

@ -0,0 +1,62 @@
#pragma once
#include "palettecontainer.h"
#include "hwrenderer/scene/hw_drawstructs.h"
//===========================================================================
//
// VisualThinkers
// by Major Cooke
// Credit to phantombeta, RicardoLuis0 & RaveYard for aid
//
//===========================================================================
enum EVisualThinkerFlags
{
VTF_FlipOffsetX = 1 << 0,
VTF_FlipOffsetY = 1 << 1,
VTF_FlipX = 1 << 2,
VTF_FlipY = 1 << 3, // flip the sprite on the x/y axis.
VTF_DontInterpolate = 1 << 4, // disable all interpolation
VTF_AddLightLevel = 1 << 5, // adds sector light level to 'LightLevel'
};
class DVisualThinker : public DThinker
{
DECLARE_CLASS(DVisualThinker, DThinker);
void UpdateSector(subsector_t * newSubsector);
public:
DVector3 Prev;
DVector2 Scale,
Offset;
float PrevRoll;
int16_t LightLevel;
FTranslationID Translation;
FTextureID AnimatedTexture;
sector_t *cursector;
int flags;
// internal only variables
particle_t PT;
DVisualThinker();
void Construct();
void OnDestroy() override;
static DVisualThinker* NewVisualThinker(FLevelLocals* Level, PClass* type);
void SetTranslation(FName trname);
int GetRenderStyle();
bool isFrozen();
int GetLightLevel(sector_t *rendersector) const;
FVector3 InterpolatedPosition(double ticFrac) const;
float InterpolatedRoll(double ticFrac) const;
void Tick() override;
void UpdateSpriteInfo();
void UpdateSector();
void Serialize(FSerializer& arc) override;
float GetOffset(bool y) const;
};

View file

@ -45,6 +45,8 @@
#include "hw_walldispatcher.h"
#include "hw_flatdispatcher.h"
#include "p_visualthinker.h"
#ifdef ARCH_IA32
#include <immintrin.h>
#endif // ARCH_IA32
@ -289,12 +291,12 @@ void HWDrawInfo::AddLine (seg_t *seg, bool portalclip, FRenderState& state)
angle_t startAngleR = clipperr.PointToPseudoAngle(seg->v2->fX(), seg->v2->fY());
angle_t endAngleR = clipperr.PointToPseudoAngle(seg->v1->fX(), seg->v1->fY());
if(r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && (startAngleR - endAngleR >= ANGLE_180))
if(Viewpoint.IsAllowedOoB() && r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && (startAngleR - endAngleR >= ANGLE_180))
{
if (!seg->backsector) clipperr.SafeAddClipRange(startAngleR, endAngleR);
else if((seg->sidedef != nullptr) && !uint8_t(seg->sidedef->Flags & WALLF_POLYOBJ) && (currentsector->sectornum != seg->backsector->sectornum))
{
if (in_area == area_default) in_area = hw_CheckViewArea(seg->v2, seg->v1, seg->frontsector, seg->backsector);
if (in_area == area_default) in_area = hw_CheckViewArea(seg->v1, seg->v2, seg->frontsector, seg->backsector);
backsector = hw_FakeFlat(drawctx, seg->backsector, in_area, true);
if (hw_CheckClip(seg->sidedef, currentsector, backsector)) clipperr.SafeAddClipRange(startAngleR, endAngleR);
}
@ -670,10 +672,9 @@ void HWDrawInfo::RenderParticles(subsector_t *sub, sector_t *front, FRenderState
int clipres = mClipPortal->ClipPoint(sp->PT.Pos.XY());
if (clipres == PClip_InFront) continue;
}
assert(sp->spr);
sp->spr->ProcessParticle(this, state, &sp->PT, front, sp);
HWSprite sprite;
sprite.ProcessParticle(this, state, &sp->PT, front, sp);
}
for (int i = Level->ParticlesInSubsec[sub->Index()]; i != NO_PARTICLE; i = Level->Particles[i].snext)
{
@ -1015,7 +1016,7 @@ void HWDrawInfo::RenderBSP(void *node, bool drawpsprites, FRenderState& state)
// Give the DrawInfo the viewpoint in fixed point because that's what the nodes are.
viewx = FLOAT2FIXED(Viewpoint.Pos.X);
viewy = FLOAT2FIXED(Viewpoint.Pos.Y);
if (r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && Viewpoint.IsAllowedOoB() && (Viewpoint.camera->ViewPos->Flags & VPSF_ABSOLUTEOFFSET))
if (r_radarclipper && !(Level->flags3 & LEVEL3_NOFOGOFWAR) && Viewpoint.IsAllowedOoB())
{
if (Viewpoint.camera->tracer != nullptr)
{

View file

@ -59,6 +59,8 @@
#include "hw_drawcontext.h"
#include "quaternion.h"
#include "p_visualthinker.h"
extern TArray<spritedef_t> sprites;
extern TArray<spriteframe_t> SpriteFrames;
extern uint32_t r_renderercaps;
@ -1650,7 +1652,7 @@ void HWSprite::AdjustVisualThinker(HWDrawInfo* di, DVisualThinker* spr, sector_t
? TexAnim.UpdateStandaloneAnimation(spr->PT.animData, di->Level->maptime + timefrac)
: spr->PT.texture, !custom_anim);
if (spr->bDontInterpolate)
if (spr->flags & VTF_DontInterpolate)
timefrac = 0.;
FVector3 interp = spr->InterpolatedPosition(timefrac);
@ -1680,13 +1682,12 @@ void HWSprite::AdjustVisualThinker(HWDrawInfo* di, DVisualThinker* spr, sector_t
double mult = 1.0 / sqrt(ps); // shrink slightly
r.Scale(mult * ps, mult);
}
if (spr->bXFlip)
if (spr->flags & VTF_FlipX)
{
std::swap(ul,ur);
r.left = -r.width - r.left; // mirror the sprite's x-offset
}
if (spr->bYFlip) std::swap(vt,vb);
if (spr->flags & VTF_FlipY) std::swap(vt,vb);
float viewvecX = vp.ViewVector.X;
float viewvecY = vp.ViewVector.Y;

View file

@ -94,8 +94,8 @@ struct InterpolationViewer
// PRIVATE DATA DECLARATIONS -----------------------------------------------
static TArray<InterpolationViewer> PastViewers;
static FRandom pr_torchflicker ("TorchFlicker");
static FRandom pr_hom;
static FCRandom pr_torchflicker ("TorchFlicker");
static FCRandom pr_hom;
bool NoInterpolateView; // GL needs access to this.
static TArray<DVector3a> InterpolationPath;
@ -464,7 +464,8 @@ bool P_NoInterpolation(player_t const *player, AActor const *actor)
&& player->mo->reactiontime == 0
&& !NoInterpolateView
&& !paused
&& !LocalKeyboardTurner;
&& !LocalKeyboardTurner
&& !player->mo->isFrozen();
}
//==========================================================================

View file

@ -47,9 +47,9 @@
extern FRandom pr_exrandom;
static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls);
static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls);
static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls);
static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls, bool client);
static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls, bool client);
static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls, bool client);
static FxExpression *ParseAbs(FScanner &sc, PClassActor *cls);
static FxExpression *ParseAtan2(FScanner &sc, FName identifier, PClassActor *cls);
static FxExpression *ParseMinMax(FScanner &sc, FName identifier, PClassActor *cls);
@ -491,12 +491,17 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls)
{
case NAME_Random:
case NAME_FRandom:
return ParseRandom(sc, identifier, cls);
case NAME_CRandom:
case NAME_CFRandom:
return ParseRandom(sc, identifier, cls, identifier == NAME_CRandom || identifier == NAME_CFRandom);
case NAME_RandomPick:
case NAME_FRandomPick:
return ParseRandomPick(sc, identifier, cls);
case NAME_CRandomPick:
case NAME_CFRandomPick:
return ParseRandomPick(sc, identifier, cls, identifier == NAME_CRandomPick || identifier == NAME_CFRandomPick);
case NAME_Random2:
return ParseRandom2(sc, cls);
case NAME_CRandom2:
return ParseRandom2(sc, cls, identifier == NAME_CRandom2);
default:
if (cls != nullptr)
{
@ -559,26 +564,26 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls)
return NULL;
}
static FRandom *ParseRNG(FScanner &sc)
static FRandom *ParseRNG(FScanner &sc, bool client)
{
FRandom *rng;
if (sc.CheckToken('['))
{
sc.MustGetToken(TK_Identifier);
rng = FRandom::StaticFindRNG(sc.String);
rng = FRandom::StaticFindRNG(sc.String, client);
sc.MustGetToken(']');
}
else
{
rng = &pr_exrandom;
rng = client ? &M_Random : &pr_exrandom;
}
return rng;
}
static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls)
static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls, bool client)
{
FRandom *rng = ParseRNG(sc);
FRandom *rng = ParseRNG(sc, client);
sc.MustGetToken('(');
FxExpression *min = ParseExpressionM (sc, cls);
@ -586,7 +591,7 @@ static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cl
FxExpression *max = ParseExpressionM (sc, cls);
sc.MustGetToken(')');
if (identifier == NAME_Random)
if (identifier == NAME_Random || identifier == NAME_CRandom)
{
return new FxRandom(rng, min, max, sc, true);
}
@ -596,7 +601,7 @@ static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cl
}
}
static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls)
static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls, bool client)
{
bool floaty = identifier == NAME_FRandomPick;
FRandom *rng;
@ -604,7 +609,7 @@ static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor
list.Clear();
int index = 0;
rng = ParseRNG(sc);
rng = ParseRNG(sc, client);
sc.MustGetToken('(');
for (;;)
@ -618,9 +623,9 @@ static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor
return new FxRandomPick(rng, list, floaty, sc, true);
}
static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls)
static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls, bool client)
{
FRandom *rng = ParseRNG(sc);
FRandom *rng = ParseRNG(sc, client);
FxExpression *mask = NULL;
sc.MustGetToken('(');

View file

@ -49,9 +49,6 @@
#include "v_text.h"
#include "m_argv.h"
#include "v_video.h"
#ifndef _MSC_VER
#include "i_system.h" // for strlwr()
#endif // !_MSC_VER
void ParseOldDecoration(FScanner &sc, EDefinitionType def, PNamespace *ns);
EXTERN_CVAR(Bool, strictdecorate);
@ -945,15 +942,12 @@ static void ParseActorProperty(FScanner &sc, Baggage &bag)
"Spawn", "See", "Melee", "Missile", "Pain", "Death", "XDeath", "Burn",
"Ice", "Raise", "Crash", "Crush", "Wound", "Disintegrate", "Heal", NULL };
strlwr (sc.String);
FString propname = sc.String;
if (sc.CheckString ("."))
{
sc.MustGetString ();
propname += '.';
strlwr (sc.String);
propname += sc.String;
}
else

View file

@ -45,9 +45,6 @@
#include "thingdef.h"
#include "codegen.h"
#include "backend/codegen_doom.h"
#ifndef _MSC_VER
#include "i_system.h" // for strlwr()
#endif // !_MSC_VER
//==========================================================================
//***
@ -562,11 +559,8 @@ FxExpression *ParseActions(FScanner &sc, FState state, FString statestring, Bagg
FxExpression* ParseAction(FScanner &sc, FState state, FString statestring, Baggage &bag)
{
// Make the action name lowercase
strlwr (sc.String);
FxExpression *call = DoActionSpecials(sc, state, bag);
if (call != NULL)
if (call != nullptr)
{
return call;
}

View file

@ -351,7 +351,9 @@ static FFlagDef ActorFlagDefs[]=
DEFINE_FLAG(MF9, SHADOWBLOCK, AActor, flags9),
DEFINE_FLAG(MF9, SHADOWAIMVERT, AActor, flags9),
DEFINE_FLAG(MF9, DECOUPLEDANIMATIONS, AActor, flags9),
DEFINE_FLAG(MF9, NOSECTORDAMAGE, AActor, flags9),
DEFINE_PROTECTED_FLAG(MF9, ISPUFF, AActor, flags9), //[AA] was spawned by SpawnPuff
DEFINE_FLAG(MF9, FORCESECTORDAMAGE, AActor, flags9),
// Effect flags
DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects),

View file

@ -869,6 +869,34 @@ DEFINE_ACTION_FUNCTION_NATIVE(_Sector, SetXOffset, SetXOffset)
ACTION_RETURN_INT(self->GetLightLevel());
}
static void SetPlaneReflectivity(sector_t* self, int pos, double val)
{
if (pos < 0 || pos > 1) ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "pos must be either 0 or 1");
self->SetPlaneReflectivity(pos, val);
}
DEFINE_ACTION_FUNCTION_NATIVE(_Sector, SetPlaneReflectivity, SetPlaneReflectivity)
{
PARAM_SELF_STRUCT_PROLOGUE(sector_t);
PARAM_INT(pos);
PARAM_FLOAT(val)
SetPlaneReflectivity(self, pos, val);
return 0;
}
static double GetPlaneReflectivity(sector_t* self, int pos)
{
if (pos < 0 || pos > 1) ThrowAbortException(X_ARRAY_OUT_OF_BOUNDS, "pos must be either 0 or 1");
return self->GetPlaneReflectivity(pos);
}
DEFINE_ACTION_FUNCTION_NATIVE(_Sector, GetPlaneReflectivity, GetPlaneReflectivity)
{
PARAM_SELF_STRUCT_PROLOGUE(sector_t);
PARAM_INT(pos);
ACTION_RETURN_FLOAT(GetPlaneReflectivity(self, pos));
}
static int PortalBlocksView(sector_t *self, int pos)
{
return self->PortalBlocksView(pos);
@ -2253,7 +2281,8 @@ void FormatMapName(FLevelLocals *self, int cr, FString *result)
// If a label is specified, use it uncontitionally here.
if (self->info->MapLabel.IsNotEmpty())
{
*result << self->info->MapLabel << ": ";
if (self->info->MapLabel.Compare("*"))
*result << self->info->MapLabel << ": ";
}
else if (am_showmaplabel == 1 || (am_showmaplabel == 2 && !ishub))
{

View file

@ -161,17 +161,14 @@ bool ZCCDoomCompiler::CompileProperties(PClass *type, TArray<ZCC_Property *> &Pr
bool ZCCDoomCompiler::CompileFlagDefs(PClass *type, TArray<ZCC_FlagDef *> &Properties, FName prefix)
{
if (!type->IsDescendantOf(RUNTIME_CLASS(AActor)))
{
Error(Properties[0], "Flags can only be defined for actors");
return false;
}
//[RL0] allow property-less flagdefs for non-actors
bool isActor = type->IsDescendantOf(RUNTIME_CLASS(AActor));
for (auto p : Properties)
{
PField *field;
FName referenced = FName(p->RefName);
if (FName(p->NodeName) == FName("prefix") && fileSystem.GetFileContainer(Lump) == 0)
if (isActor && FName(p->NodeName) == FName("prefix") && fileSystem.GetFileContainer(Lump) == 0)
{
// only for internal definitions: Allow setting a prefix. This is only for compatiblity with the old DECORATE property parser, but not for general use.
prefix = referenced;
@ -191,24 +188,28 @@ bool ZCCDoomCompiler::CompileFlagDefs(PClass *type, TArray<ZCC_FlagDef *> &Prope
}
}
else field = nullptr;
FString qualifiedname;
// Store the full qualified name and prepend some 'garbage' to the name so that no conflicts with other symbol types can happen.
// All these will be removed from the symbol table after the compiler finishes to free up the allocated space.
FName name = FName(p->NodeName);
for (int i = 0; i < 2; i++)
{
if (i == 0) qualifiedname.Format("@flagdef@%s", name.GetChars());
else
{
if (prefix == NAME_None) continue;
qualifiedname.Format("@flagdef@%s.%s", prefix.GetChars(), name.GetChars());
}
if (!type->VMType->Symbols.AddSymbol(Create<PPropFlag>(qualifiedname, field, p->BitValue, i == 0 && prefix != NAME_None)))
if(isActor)
{
FString qualifiedname;
// Store the full qualified name and prepend some 'garbage' to the name so that no conflicts with other symbol types can happen.
// All these will be removed from the symbol table after the compiler finishes to free up the allocated space.
for (int i = 0; i < 2; i++)
{
Error(p, "Unable to add flag definition %s to class %s", FName(p->NodeName).GetChars(), type->TypeName.GetChars());
if (i == 0) qualifiedname.Format("@flagdef@%s", name.GetChars());
else
{
if (prefix == NAME_None) continue;
qualifiedname.Format("@flagdef@%s.%s", prefix.GetChars(), name.GetChars());
}
if (!type->VMType->Symbols.AddSymbol(Create<PPropFlag>(qualifiedname, field, p->BitValue, i == 0 && prefix != NAME_None)))
{
Error(p, "Unable to add flag definition %s to class %s", FName(p->NodeName).GetChars(), type->TypeName.GetChars());
}
}
}

View file

@ -1163,7 +1163,7 @@ TArray<uint8_t> DoomSoundEngine::ReadSound(int lumpnum)
// This is overridden to use a synchronized RNG.
//
//==========================================================================
static FRandom pr_randsound("RandSound");
static FCRandom pr_randsound("RandSound");
FSoundID DoomSoundEngine::PickReplacement(FSoundID refid)
{

View file

@ -288,7 +288,7 @@ static const hexenseq_t HexenSequences[] = {
static int SeqTrans[MAX_SNDSEQS*3];
static FRandom pr_sndseq ("SndSeq");
static FCRandom pr_sndseq ("SndSeq");
// CODE --------------------------------------------------------------------

View file

@ -55,7 +55,7 @@ const char *GetVersionString();
// These are for zscript versioning.
#define ZSCRIPT_VER_MAJOR 4
#define ZSCRIPT_VER_MINOR 13
#define ZSCRIPT_VER_MINOR 14
#define ZSCRIPT_VER_REVISION 0
// This should always refer to the VkDoom version a derived port is based on and not reflect the derived port's version number!

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

View file

@ -78,6 +78,7 @@ DoomEdNums
9081 = SkyPicker
9082 = SectorSilencer
9083 = SkyCamCompat
9084 = OrthographicCamera
9200 = Decal
9300 = "$PolyAnchor"
9301 = "$PolySpawn"

View file

@ -773,7 +773,7 @@ class Actor : Thinker native
native Actor SpawnPuff(class<Actor> pufftype, vector3 pos, double hitdir, double particledir, int updown, int flags = 0, Actor victim = null);
native void SpawnBlood (Vector3 pos1, double dir, int damage);
native void BloodSplatter (Vector3 pos, double hitangle, bool axe = false);
native bool HitWater (sector sec, Vector3 pos, bool checkabove = false, bool alert = true, bool force = false);
native bool HitWater (sector sec, Vector3 pos, bool checkabove = false, bool alert = true, bool force = false, int flags = 0);
native void PlaySpawnSound(Actor missile);
native clearscope bool CountsAsKill() const;
@ -844,6 +844,7 @@ class Actor : Thinker native
native void Thrust(double speed = 1e37, double angle = 1e37);
native clearscope bool isFriend(Actor other) const;
native clearscope bool isHostile(Actor other) const;
native clearscope bool ShouldPassThroughPlayer(Actor other) const;
native void AdjustFloorClip();
native clearscope DropItem GetDropItems() const;
native void CopyFriendliness (Actor other, bool changeTarget, bool resetHealth = true);
@ -851,6 +852,7 @@ class Actor : Thinker native
native bool LookForTid(bool allaround, LookExParams params = null);
native bool LookForEnemies(bool allaround, LookExParams params = null);
native bool LookForPlayers(bool allaround, LookExParams params = null);
native int LookForEnemiesEx(out Array<Actor> targets, double range = -1, bool noPlayers = true, bool allaround = false, LookExParams params = null);
native bool TeleportMove(Vector3 pos, bool telefrag, bool modifyactor = true);
native clearscope double DistanceBySpeed(Actor other, double speed) const;
native name GetSpecies();

View file

@ -139,6 +139,11 @@ extend class Actor
{ // Must be monster, player, missile, touchy or vulnerable
continue;
}
if (player && ShouldPassThroughPlayer(mo))
{
// Don't blast friendly players if collision is disabled.
continue;
}
if (Distance2D(mo) > radius)
{ // Out of range
continue;

View file

@ -468,11 +468,12 @@ class HexenArmor : Armor
override Inventory CreateCopy (Actor other)
{
// Like BasicArmor, HexenArmor is used in the inventory but not the map.
// health is the slot this armor occupies.
// Unlike BasicArmor, Hexen's armor pieces directly inherit from this class.
// Health is the slot this armor occupies.
// Amount is the quantity to give (0 = normal max).
let copy = HexenArmor(Spawn(GetHexenArmorClass()));
copy.AddArmorToSlot (health, Amount);
let copy = HexenArmor(Spawn(GetClass()));
copy.Health = Health;
copy.Amount = Amount;
GoAwayAndDie ();
return copy;
}

View file

@ -59,6 +59,13 @@ class WeaponPiece : Inventory
property number: PieceValue;
property weapon: WeaponClass;
// Account for weapon replacers, but make sure it's still a Weapon
clearscope class<Weapon> GetWeaponClass() const
{
class<Weapon> type = WeaponClass ? (class<Weapon>)(GetReplacement(WeaponClass)) : null;
return type ? type : WeaponClass;
}
//==========================================================================
//
@ -74,7 +81,11 @@ class WeaponPiece : Inventory
return false;
}
let Defaults = GetDefaultByType(WeaponClass);
class<Weapon> type = GetWeaponClass();
if (!type)
return false;
let Defaults = GetDefaultByType(type);
bool gaveSome = !!(toucher.GiveAmmo (Defaults.AmmoType1, Defaults.AmmoGive1) +
toucher.GiveAmmo (Defaults.AmmoType2, Defaults.AmmoGive2));
@ -94,11 +105,15 @@ class WeaponPiece : Inventory
override bool TryPickup (in out Actor toucher)
{
class<Weapon> type = GetWeaponClass();
if (!type)
return false;
Inventory item;
WeaponHolder hold = NULL;
bool shouldStay = ShouldStay ();
int gaveAmmo;
let Defaults = GetDefaultByType(WeaponClass);
let Defaults = GetDefaultByType(type);
FullWeapon = NULL;
for(item=toucher.Inv; item; item=item.Inv)
@ -106,6 +121,7 @@ class WeaponPiece : Inventory
hold = WeaponHolder(item);
if (hold != null)
{
// Intentionally check against the unreplaced class
if (hold.PieceWeapon == WeaponClass)
{
break;
@ -153,9 +169,9 @@ class WeaponPiece : Inventory
// Check if weapon assembled
if (hold.PieceMask == (1 << Defaults.health) - 1)
{
if (!toucher.FindInventory (WeaponClass))
if (!toucher.FindInventory (type))
{
FullWeapon= Weapon(Spawn(WeaponClass));
FullWeapon= Weapon(Spawn(type));
// The weapon itself should not give more ammo to the player.
FullWeapon.AmmoGive1 = 0;

View file

@ -87,6 +87,8 @@ class PlayerPawn : Actor
flagdef CanSuperMorph: PlayerFlags, 1;
flagdef CrouchableMorph: PlayerFlags, 2;
flagdef WeaponLevel2Ended: PlayerFlags, 3;
//PF_VOODOO_ZOMBIE
flagdef MakeFootsteps: PlayerFlags, 5; //[inkoalawetrust] Use footstep system virtual.
enum EPrivatePlayerFlags
{
@ -1690,6 +1692,7 @@ class PlayerPawn : Actor
CheckPitch();
HandleMovement();
CalcHeight ();
if (bMakeFootsteps) MakeFootsteps();
if (!(player.cheats & CF_PREDICTING))
{
@ -1718,6 +1721,106 @@ class PlayerPawn : Actor
}
}
//---------------------------------------------------------------------------
//
// Handle player footstep sounds.
// Default footstep handling.
//
//---------------------------------------------------------------------------
int footstepCounter;
double footstepLength;
bool footstepFoot;
void DoFootstep(TerrainDef Ground)
{
Sound Step = Ground.StepSound;
//Generic foot-agnostic sound takes precedence.
if(!Step)
{
//Apparently most people walk with their right foot first, so assume that here.
if (!footstepFoot)
{
Step = Ground.LeftStepSound;
}
else
{
Step = Ground.RightStepSound;
}
footstepFoot = !footstepFoot;
}
if(Step)
{
A_StartSound(Step, flags: CHANF_OVERLAP, volume: Ground.StepVolume * snd_footstepvolume);
}
//Steps make splashes regardless.
bool Heavy = (Mass >= 200) ? 0 : THW_SMALL; //Big player makes big splash.
HitWater(CurSector, (Pos.XY, CurSector.FloorPlane.ZatPoint(Pos.XY)), true, false, flags: Heavy | THW_NOVEL);
}
virtual void MakeFootsteps()
{
if(pos.z > floorz) return;
let Ground = GetFloorTerrain();
if(Ground && (player.cmd.forwardMove != 0 || player.cmd.sideMove != 0))
{
int Delay = (player.cmd.buttons & BT_RUN) ? Ground.RunStepTics : Ground.WalkStepTics;
if((player.cmd.buttons ^ player.oldbuttons) & BT_RUN)
{ // zero out counters when starting/stopping a run
footstepCounter = 0;
footstepLength = Ground.StepDistance;
}
if(Ground.StepDistance > 0)
{ // distance-based terrain
footstepCounter = 0;
double moveVel = vel.xy.length();
if(moveVel > Ground.StepDistanceMinVel)
{
footstepLength += moveVel;
while(footstepLength > Ground.StepDistance)
{
footstepLength -= Ground.StepDistance;
DoFootstep(Ground);
}
}
else
{
footstepLength = Ground.StepDistance;
}
}
else if(Delay > 0)
{ // delay-based terrain
footstepLength = 0;
if(footstepCounter % Delay == 0)
{
DoFootstep(Ground);
}
footstepCounter = (footstepCounter + 1) % Delay;
}
}
else
{
footstepCounter = 0;
footstepLength = Ground.StepDistance;
footstepFoot = false;
}
}
//---------------------------------------------------------------------------
//
// PROC P_BringUpWeapon

View file

@ -212,3 +212,35 @@ class SpectatorCamera : Actor
}
}
}
Class OrthographicCamera : Actor
{
Default
{
+NOBLOCKMAP
+NOINTERACTION
CameraHeight 0;
RenderStyle "None";
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
UpdateViewPos();
}
override void Tick()
{
if (current != args[0])
UpdateViewPos();
Super.Tick();
}
protected int current;
protected void UpdateViewPos()
{
current = args[0];
SetViewPos((-abs(max(1.0, double(current))), 0, 0), VPSF_ORTHOGRAPHIC|VPSF_ALLOWOUTOFBOUNDS);
}
}

View file

@ -1410,6 +1410,8 @@ enum ELevelFlags
LEVEL3_AVOIDMELEE = 0x00020000, // global flag needed for proper MBF support.
LEVEL3_NOJUMPDOWN = 0x00040000, // only for MBF21. Inverse of MBF's dog_jumping flag.
LEVEL3_LIGHTCREATED = 0x00080000, // a light had been created in the last frame
LEVEL3_NOFOGOFWAR = 0x00100000, // disables effect of r_radarclipper CVAR on this map
LEVEL3_SECRET = 0x00200000, // level is a secret level
VKDLEVELFLAG_NOUSERSAVE = 0x00000001,
VKDLEVELFLAG_NOAUTOMAP = 0x00000002,
@ -1471,6 +1473,12 @@ enum ECompatFlags
COMPATF2_VOODOO_ZOMBIES = 1 << 15, // allow playerinfo, playerpawn, and voodoo health to all be different, and allow monster targetting of 'dead' players that have positive health
};
enum HitWaterFlags
{
THW_SMALL = 1 << 0,
THW_NOVEL = 1 << 1,
};
const M_E = 2.7182818284590452354; // e
const M_LOG2E = 1.4426950408889634074; // log_2 e
const M_LOG10E = 0.43429448190325182765; // log_10 e
@ -1513,3 +1521,13 @@ enum EModelFlags
MDL_CORRECTPIXELSTRETCH = 1<<13, // ensure model does not distort with pixel stretch when pitch/roll is applied
MDL_FORCECULLBACKFACES = 1<<14,
};
enum EVisualThinkerFlags
{
VTF_FlipOffsetX = 1 << 0,
VTF_FlipOffsetY = 1 << 1,
VTF_FlipX = 1 << 2,
VTF_FlipY = 1 << 3, // flip the sprite on the x/y axis.
VTF_DontInterpolate = 1 << 4, // disable all interpolation
VTF_AddLightLevel = 1 << 5, // adds sector light level to 'LightLevel'
};

View file

@ -621,6 +621,9 @@ struct TerrainDef native
native bool DamageOnLand;
native double Friction;
native double MoveFactor;
native Sound StepSound;
native double StepDistance;
native double StepDistanceMinVel;
};
enum EPickStart

View file

@ -15,27 +15,29 @@ enum ESoundFlags
// modifier flags
CHAN_LISTENERZ = 8,
CHAN_MAYBE_LOCAL = 16,
CHAN_UI = 32,
CHAN_NOPAUSE = 64,
CHAN_UI = 32, // Do not record sound in savegames.
CHAN_NOPAUSE = 64, // Do not pause this sound in menus.
CHAN_LOOP = 256,
CHAN_PICKUP = (CHAN_ITEM|CHAN_MAYBE_LOCAL), // Do not use this with A_StartSound! It would not do what is expected.
CHAN_NOSTOP = 4096,
CHAN_OVERLAP = 8192,
CHAN_NOSTOP = 4096, // only for A_PlaySound. Does not start if channel is playing something.
CHAN_OVERLAP = 8192, // [MK] Does not stop any sounds in the channel and instead plays over them.
// Same as above, with an F appended to allow better distinction of channel and channel flags.
CHANF_DEFAULT = 0, // just to make the code look better and avoid literal 0's.
CHANF_DEFAULT = 0, // just to make the code look better and avoid literal 0's.
CHANF_LISTENERZ = 8,
CHANF_MAYBE_LOCAL = 16,
CHANF_UI = 32,
CHANF_NOPAUSE = 64,
CHANF_UI = 32, // Do not record sound in savegames.
CHANF_NOPAUSE = 64, // Do not pause this sound in menus.
CHANF_LOOP = 256,
CHANF_NOSTOP = 4096,
CHANF_OVERLAP = 8192,
CHANF_LOCAL = 16384,
CHANF_NOSTOP = 4096, // only for A_PlaySound. Does not start if channel is playing something.
CHANF_OVERLAP = 8192, // [MK] Does not stop any sounds in the channel and instead plays over them.
CHANF_LOCAL = 16384, // only plays locally for the calling actor
CHANF_TRANSIENT = 32768, // Do not record in savegames - used for sounds that get restarted outside the sound system (e.g. ambients in SW and Blood)
CHANF_FORCE = 65536, // Start, even if sound is paused.
CHANF_SINGULAR = 0x20000, // Only start if no sound of this name is already playing.
CHANF_LOOPING = CHANF_LOOP | CHANF_NOSTOP, // convenience value for replicating the old 'looping' boolean.
};
// sound attenuation values
@ -689,6 +691,7 @@ struct CVar native
};
native static CVar FindCVar(Name name);
native static bool SaveConfig();
bool GetBool() { return GetInt(); }
native int GetInt();
native double GetFloat();
@ -785,7 +788,9 @@ class Object native
//
// Intrinsic random number generation functions. Note that the square
// bracket syntax for specifying an RNG ID is only available for these
// functions.
// functions. If the function is prefixed with a C, this is a client-side RNG
// call that isn't backed up while predicting and has a unique name space from
// regular RNG calls. This should be used for things like HUD elements.
// clearscope void SetRandomSeed[Name rngId = 'None'](int seed); // Set the seed for the given RNG.
// clearscope int Random[Name rngId = 'None'](int min, int max); // Use the given RNG to generate a random integer number in the range (min, max) inclusive.
// clearscope int Random2[Name rngId = 'None'](int mask); // Use the given RNG to generate a random integer number, and do a "union" (bitwise AND, AKA &) operation with the bits in the mask integer.

View file

@ -97,6 +97,14 @@ struct WorldEvent native play version("2.4")
native readonly bool DamageIsRadius;
native int NewDamage;
native readonly State CrushedState;
native readonly double AttackAngle;
native readonly double AttackPitch;
native readonly double AttackDistance;
native readonly vector3 AttackPos;
native readonly double AttackOffsetForward;
native readonly double AttackOffsetSide;
native readonly double AttackZ;
native readonly class<Actor> AttackPuffType;
}
struct PlayerEvent native play version("2.4")
@ -155,6 +163,8 @@ class StaticEventHandler : Object native play version("2.4")
virtual void WorldThingRevived(WorldEvent e) {}
virtual void WorldThingDamaged(WorldEvent e) {}
virtual void WorldThingDestroyed(WorldEvent e) {}
virtual bool WorldHitscanPreFired(WorldEvent e) { return false; }
virtual void WorldHitscanFired(WorldEvent e) {}
virtual void WorldLinePreActivated(WorldEvent e) {}
virtual void WorldLineActivated(WorldEvent e) {}
virtual void WorldSectorDamaged(WorldEvent e) {}

View file

@ -435,6 +435,11 @@ struct Sector native play
SECMF_UNDERWATERMASK = 32+64,
SECMF_DRAWN = 128, // sector has been drawn at least once
SECMF_HIDDEN = 256, // Do not draw on textured automap
SECMF_OVERLAPPING = 512, // floor and ceiling overlap and require special renderer action.
SECMF_NOSKYWALLS = 1024, // Do not draw "sky walls"
SECMF_LIFT = 2048, // For MBF monster AI
SECMF_HURTMONSTERS = 4096, // Monsters in this sector are hurt like players.
SECMF_HARMINAIR = 8192, // Actors in this sector are also hurt mid-air.
}
native uint16 MoreFlags;
@ -452,6 +457,9 @@ struct Sector native play
SECF_ENDLEVEL = 512, // ends level when health goes below 10
SECF_HAZARD = 1024, // Change to Strife's delayed damage handling.
SECF_NOATTACK = 2048, // monsters cannot start attacks in this sector.
SECF_EXIT1 = 4096,
SECF_EXIT2 = 8192,
SECF_KILLMONSTERS = 16384,// Monsters in this sector are instantly killed.
SECF_WASSECRET = 1 << 30, // a secret that was discovered
SECF_SECRET = 1 << 31, // a secret sector
@ -545,6 +553,8 @@ struct Sector native play
native void ChangeLightLevel(int newval);
native void SetLightLevel(int newval);
native clearscope int GetLightLevel() const;
native void SetPlaneReflectivity(int pos, double val);
native clearscope double GetPlaneReflectivity(int pos);
native void AdjustFloorClip();
native clearscope bool IsLinked(Sector other, bool ceiling) const;

View file

@ -216,11 +216,11 @@ class ConversationMenu : Menu
let goodbyestr = mCurNode.Goodbye;
if (goodbyestr.Length() == 0)
{
goodbyestr = String.Format("$TXT_RANDOMGOODBYE_%d", Random[RandomSpeech](1, NUM_RANDOM_GOODBYES));
goodbyestr = String.Format("$TXT_RANDOMGOODBYE_%d", CRandom[RandomSpeech](1, NUM_RANDOM_GOODBYES));
}
else if (goodbyestr.Left(7) == "RANDOM_")
{
goodbyestr = String.Format("$TXT_%s_%02d", goodbyestr, Random[RandomSpeech](1, NUM_RANDOM_LINES));
goodbyestr = String.Format("$TXT_%s_%02d", goodbyestr, CRandom[RandomSpeech](1, NUM_RANDOM_LINES));
}
goodbyestr = Stringtable.Localize(goodbyestr);
if (goodbyestr.Length() == 0 || goodbyestr.Left(1) == "$") goodbyestr = "Bye.";
@ -254,7 +254,7 @@ class ConversationMenu : Menu
String toSay = mCurNode.Dialogue;
if (toSay.Left(7) == "RANDOM_")
{
let dlgtext = String.Format("$TXT_%s_%02d", toSay, random[RandomSpeech](1, NUM_RANDOM_LINES));
let dlgtext = String.Format("$TXT_%s_%02d", toSay, crandom[RandomSpeech](1, NUM_RANDOM_LINES));
toSay = Stringtable.Localize(dlgtext);
if (toSay.Left(1) == "$") toSay = Stringtable.Localize("$TXT_GOAWAY");
}

View file

@ -45,7 +45,7 @@ class HereticStatusBar : BaseStatusBar
// wiggle the chain if it moves
if (Level.time & 1)
{
wiggle = (mHealthInterpolator.GetValue() != CPlayer.health) && Random[ChainWiggle](0, 1);
wiggle = (mHealthInterpolator.GetValue() != CPlayer.health) && CRandom[ChainWiggle](0, 1);
}
}

View file

@ -10,14 +10,18 @@ Class VisualThinker : Thinker native
Alpha;
native TextureID Texture;
native TranslationID Translation;
native uint16 Flags;
native int16 LightLevel;
native bool bFlipOffsetX,
bFlipOffsetY,
bXFlip,
bYFlip,
bDontInterpolate,
bAddLightLevel;
native uint16 Flags;
native int VisualThinkerFlags;
FlagDef FlipOffsetX : VisualThinkerFlags, 0;
FlagDef FlipOffsetY : VisualThinkerFlags, 1;
FlagDef XFlip : VisualThinkerFlags, 2;
FlagDef YFlip : VisualThinkerFlags, 3;
FlagDef DontInterpolate : VisualThinkerFlags, 4;
FlagDef AddLightLevel : VisualThinkerFlags, 5;
native Color scolor;
native Sector CurSector; // can be null!
@ -26,8 +30,11 @@ Class VisualThinker : Thinker native
native void SetRenderStyle(int mode); // see ERenderStyle
native bool IsFrozen();
native protected void UpdateSector(); // needs to be called if the thinker is set to a non-ticking statnum and the position is modified (or if Tick is overriden and doesn't call Super.Tick())
native protected void UpdateSpriteInfo(); // needs to be called every time the texture is updated if the thinker uses SPF_LOCAL_ANIM and is set to a non-ticking statnum (or if Tick is overriden and doesn't call Super.Tick())
static VisualThinker Spawn(Class<VisualThinker> type, TextureID tex, Vector3 pos, Vector3 vel, double alpha = 1.0, int flags = 0,
double roll = 0.0, Vector2 scale = (1,1), Vector2 offset = (0,0), int style = STYLE_Normal, TranslationID trans = 0)
double roll = 0.0, Vector2 scale = (1,1), Vector2 offset = (0,0), int style = STYLE_Normal, TranslationID trans = 0, int VisualThinkerFlags = 0)
{
if (!Level) return null;
@ -44,6 +51,9 @@ Class VisualThinker : Thinker native
p.SetRenderStyle(style);
p.Translation = trans;
p.Flags = flags;
p.VisualThinkerFlags = VisualThinkerFlags;
p.UpdateSector();
p.UpdateSpriteInfo();
}
return p;
}

View file

@ -709,15 +709,15 @@ Order // Order in the IWAD selection box
"DOOM 2: L'Enfer sur Terre"
"DOOM 2: BFG Edition"
"DOOM 2: XBox Edition"
"DOOM 2: KEX Edition"
"DOOM 2: Unity Edition"
"DOOM 2: KEX Edition"
"The Ultimate DOOM"
"DOOM Registered"
"DOOM Shareware"
"DOOM: BFG Edition"
"DOOM: XBox Edition"
"DOOM: KEX Edition"
"DOOM: Unity Edition"
"DOOM: KEX Edition"
"Final Doom: Plutonia Experiment"
"Final Doom: Plutonia Experiment: Unity Edition"
"Final Doom: Plutonia Experiment: KEX Edition"