diff --git a/specs/udmf_zdoom.txt b/specs/udmf_zdoom.txt index 4388487a5..25c206db5 100644 --- a/specs/udmf_zdoom.txt +++ b/specs/udmf_zdoom.txt @@ -203,21 +203,21 @@ Note: All fields default to false unless mentioned otherwise. nogradient_top = ; // disables color gradient on upper tier. (Hardware rendering only.) flipgradient_top = ; // flips gradient colors on upper tier. (Hardware rendering only.) clampgradient_top = ; // clamps gradient on upper tier to actual bounds (default is the entire front sector height, hardware rendering only.) - useowncolors_top = ; // Set to 1 to use the colors set in the sidedef. Default is using the colors from the owning sector. + useowncolors_top = ; // 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 = ; // Material color of the top of the upper tier. lowercolor_top = ; // Material color of the bottom of the upper tier. (Hardware rendering only.) nogradient_mid = ; // disables color gradient on middle tier. (Hardware rendering only.) flipgradient_mid = ; // flips gradient colors on middle tier. (Hardware rendering only.) clampgradient_mid = ; // clamps gradient on middle tier to actual bounds (default is the entire front sector height, hardware rendering only.) - useowncolors_mid = ; // Set to 1 to use the colors set in the sidedef. Default is using the colors from the owning sector. + useowncolors_mid = ; // 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 = ; // Material color of the top of the middle tier. lowercolor_mid = ; // Material color of the bottom of the middle tier. (Hardware rendering only.) nogradient_bottom = ; // disables color gradient on lower tier. (Hardware rendering only.) flipgradient_bottom = ; // flips gradient colors on lower tier. (Hardware rendering only.) clampgradient_bottom = ;// clamps gradient on lower tier to actual bounds (default is the entire front sector height, hardware rendering only.) - useowncolors_bottom = ; // Set to 1 to use the colors set in the sidedef. Default is using the colors from the owning sector. + useowncolors_bottom = ; // 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 = ; // Material color of the top of the lower tier. lowercolor_bottom = ; // Material color of the bottom of the lower tier. (Hardware rendering only.) @@ -307,6 +307,8 @@ Note: All fields default to false unless mentioned otherwise. leakiness = ; // Probability of leaking through radiation suit (0 = never, 256 = always), default = 0. damageterraineffect = ; // Will spawn a terrain splash when damage is inflicted. Default = false. damagehazard = ; // Changes damage model to Strife's delayed damage for the given sector. Default = false. + hurtmonsters = ; // Non-players like monsters and decorations are hurt by this sector in the same manner as player. Doesn't work with damagehazard. + harminair = ; // Actors in this sector are harmed by the damage effects of the floor even if they aren't touching it. floorterrain = ; // Sets the terrain for the sector's floor. Default = 'use the flat texture's terrain definition.' ceilingterrain = ; // Sets the terrain for the sector's ceiling. Default = 'use the flat texture's terrain definition.' floor_reflect = ; // reflectiveness of floor (OpenGL only, not functional on sloped sectors) diff --git a/src/bbannouncer.cpp b/src/bbannouncer.cpp index 07bc2305e..f52b5344b 100644 --- a/src/bbannouncer.cpp +++ b/src/bbannouncer.cpp @@ -169,7 +169,7 @@ static const char *TelefragSounds[] = #endif static int LastAnnounceTime; -static FRandom pr_bbannounce ("BBAnnounce"); +static FCRandom pr_bbannounce ("BBAnnounce"); // CODE -------------------------------------------------------------------- diff --git a/src/common/2d/wipe.cpp b/src/common/2d/wipe.cpp index f359e3d18..3682070db 100644 --- a/src/common/2d/wipe.cpp +++ b/src/common/2d/wipe.cpp @@ -43,6 +43,8 @@ #include "s_soundinternal.h" #include "i_time.h" +EXTERN_CVAR(Bool, cl_capfps) + class FBurnTexture : public FTexture { TArray 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(y[i] + 8, HEIGHT); + y[i] = min(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(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(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); diff --git a/src/common/audio/sound/i_sound.cpp b/src/common/audio/sound/i_sound.cpp index f859ae0a8..2f40333c0 100644 --- a/src/common/audio/sound/i_sound.cpp +++ b/src/common/audio/sound/i_sound.cpp @@ -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 diff --git a/src/common/audio/sound/s_sound.cpp b/src/common/audio/sound/s_sound.cpp index 852d389f5..d8ae7fe00 100644 --- a/src/common/audio/sound/s_sound.cpp +++ b/src/common/audio/sound/s_sound.cpp @@ -61,7 +61,7 @@ enum { DEFAULT_PITCH = 128, }; -static FRandom pr_soundpitch ("SoundPitch"); +static FCRandom pr_soundpitch ("SoundPitch"); SoundEngine* soundEngine; //========================================================================== diff --git a/src/common/engine/i_net.cpp b/src/common/engine/i_net.cpp index 33b4c1267..ad106bbc4 100644 --- a/src/common/engine/i_net.cpp +++ b/src/common/engine/i_net.cpp @@ -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) { diff --git a/src/common/engine/i_net.h b/src/common/engine/i_net.h index c52072c86..15720374d 100644 --- a/src/common/engine/i_net.h +++ b/src/common/engine/i_net.h @@ -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); diff --git a/src/common/engine/m_random.cpp b/src/common/engine/m_random.cpp index 3af14526b..11c4350b7 100644 --- a/src/common/engine/m_random.cpp +++ b/src/common/engine/m_random.cpp @@ -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 NewRNGs; +FRandom *FRandom::RNGList, *FRandom::CRNGList; +static TDeletingArray NewRNGs, NewCRNGs; // CODE -------------------------------------------------------------------- @@ -139,14 +139,22 @@ static TDeletingArray 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& backups) +{ + for (auto cur = RNGList; cur != nullptr; cur = cur->Next) + backups.Push(*cur); +} + +void FRandom::RestoreRNGState(TArray& backups) +{ + unsigned int i = 0u; + for (auto cur = RNGList; cur != nullptr; cur = cur->Next) + *cur = backups[i++]; + + backups.Clear(); +} + //========================================================================== // // FRandom :: StaticPrintSeeds diff --git a/src/common/engine/m_random.h b/src/common/engine/m_random.h index d9eec6c44..ba0bcaf62 100644 --- a/src/common/engine/m_random.h +++ b/src/common/engine/m_random.h @@ -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& backups); + static void RestoreRNGState(TArray& 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 diff --git a/src/common/engine/namedef.h b/src/common/engine/namedef.h index 26c7943df..89f5b16a6 100644 --- a/src/common/engine/namedef.h +++ b/src/common/engine/namedef.h @@ -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) diff --git a/src/common/engine/serializer.h b/src/common/engine/serializer.h index 40b6ab706..902d4226c 100644 --- a/src/common/engine/serializer.h +++ b/src/common/engine/serializer.h @@ -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); diff --git a/src/common/engine/st_start.h b/src/common/engine/st_start.h index a8b78aaa8..b09991d04 100644 --- a/src/common/engine/st_start.h +++ b/src/common/engine/st_start.h @@ -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; diff --git a/src/common/objects/dobjtype.cpp b/src/common/objects/dobjtype.cpp index 0d1bfdf9a..a3172c5e5 100644 --- a/src/common/objects/dobjtype.cpp +++ b/src/common/objects/dobjtype.cpp @@ -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(this)); InitializeSpecials(mem, Defaults, &PClass::SpecialInits); + + if(statnum && ((DObject *)mem)->IsKindOf(RUNTIME_CLASS(DVisualThinker))) + { + *statnum = STAT_VISUALTHINKER; + } + return (DObject *)mem; } diff --git a/src/common/objects/dobjtype.h b/src/common/objects/dobjtype.h index 62e6f5ccc..17ad29862 100644 --- a/src/common/objects/dobjtype.h +++ b/src/common/objects/dobjtype.h @@ -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(); diff --git a/src/common/platform/posix/cocoa/st_console.h b/src/common/platform/posix/cocoa/st_console.h index b2af7bade..91f77d124 100644 --- a/src/common/platform/posix/cocoa/st_console.h +++ b/src/common/platform/posix/cocoa/st_console.h @@ -66,6 +66,7 @@ public: void NetInit(const char* message, int playerCount); void NetProgress(int count); void NetDone(); + void NetClose(); private: NSWindow* m_window; diff --git a/src/common/platform/posix/cocoa/st_console.mm b/src/common/platform/posix/cocoa/st_console.mm index 1c45776e6..95d0ee11e 100644 --- a/src/common/platform/posix/cocoa/st_console.mm +++ b/src/common/platform/posix/cocoa/st_console.mm @@ -531,3 +531,8 @@ void FConsoleWindow::NetDone() m_netAbortButton = nil; } } + +void FConsoleWindow::NetClose() +{ + // TODO: Implement this +} diff --git a/src/common/platform/posix/cocoa/st_start.mm b/src/common/platform/posix/cocoa/st_start.mm index ee6ea2627..2cd73104d 100644 --- a/src/common/platform/posix/cocoa/st_start.mm +++ b/src/common/platform/posix/cocoa/st_start.mm @@ -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) diff --git a/src/common/platform/posix/i_system.h b/src/common/platform/posix/i_system.h index e9e3f2a97..5b145eb8c 100644 --- a/src/common/platform/posix/i_system.h +++ b/src/common/platform/posix/i_system.h @@ -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(std::thread::hardware_concurrency(), 1); } inline void I_SetThreadNumaNode(std::thread &thread, int numaNode) { } diff --git a/src/common/platform/posix/sdl/st_start.cpp b/src/common/platform/posix/sdl/st_start.cpp index 019a66122..5bd88684b 100644 --- a/src/common/platform/posix/sdl/st_start.cpp +++ b/src/common/platform/posix/sdl/st_start.cpp @@ -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 diff --git a/src/common/platform/win32/st_start.cpp b/src/common/platform/win32/st_start.cpp index b24fee867..b066df07d 100644 --- a/src/common/platform/win32/st_start.cpp +++ b/src/common/platform/win32/st_start.cpp @@ -201,3 +201,8 @@ bool FBasicStartupScreen::NetLoop(bool (*timer_callback)(void *), void *userdata { return NetStartWindow::RunMessageLoop(timer_callback, userdata); } + +void FBasicStartupScreen::NetClose() +{ + NetStartWindow::CloseNetStartPane(); +} diff --git a/src/common/scripting/backend/codegen.cpp b/src/common/scripting/backend/codegen.cpp index df1c48c22..86bd606f9 100644 --- a/src/common/scripting/backend/codegen.cpp +++ b/src/common/scripting/backend/codegen.cpp @@ -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; diff --git a/src/common/scripting/backend/vmbuilder.cpp b/src/common/scripting/backend/vmbuilder.cpp index 71a766d9a..5437ca848 100644 --- a/src/common/scripting/backend/vmbuilder.cpp +++ b/src/common/scripting/backend/vmbuilder.cpp @@ -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; diff --git a/src/common/scripting/interface/vmnatives.cpp b/src/common/scripting/interface/vmnatives.cpp index 2b57ceb25..c778364ed 100644 --- a/src/common/scripting/interface/vmnatives.cpp +++ b/src/common/scripting/interface/vmnatives.cpp @@ -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)); +} + //============================================================================= // // diff --git a/src/common/widgets/netstartwindow.cpp b/src/common/widgets/netstartwindow.cpp index 69c83e565..558a47e9e 100644 --- a/src/common/widgets/netstartwindow.cpp +++ b/src/common/widgets/netstartwindow.cpp @@ -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)); diff --git a/src/common/widgets/netstartwindow.h b/src/common/widgets/netstartwindow.h index 5d27b8ebf..6df8801fb 100644 --- a/src/common/widgets/netstartwindow.h +++ b/src/common/widgets/netstartwindow.h @@ -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(); diff --git a/src/events.cpp b/src/events.cpp index a4773d133..2f7b12091 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -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) diff --git a/src/events.h b/src/events.h old mode 100755 new mode 100644 index d71922b9a..c3d05d485 --- a/src/events.h +++ b/src/events.h @@ -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. diff --git a/src/g_game.cpp b/src/g_game.cpp index ab3cac323..8a189bf17 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -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); diff --git a/src/g_level.cpp b/src/g_level.cpp index ab4fa15c0..fbd500008 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -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 // diff --git a/src/g_levellocals.h b/src/g_levellocals.h index 277849111..a1d6c94fa 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -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(cls->CreateNew()); + DThinker *thinker = static_cast(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; diff --git a/src/g_statusbar/sbarinfo_commands.cpp b/src/g_statusbar/sbarinfo_commands.cpp index 749294adc..a28d43e19 100644 --- a/src/g_statusbar/sbarinfo_commands.cpp +++ b/src/g_statusbar/sbarinfo_commands.cpp @@ -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. //////////////////////////////////////////////////////////////////////////////// diff --git a/src/gamedata/decallib.cpp b/src/gamedata/decallib.cpp index ae9d96d76..f5c5614d7 100644 --- a/src/gamedata/decallib.cpp +++ b/src/gamedata/decallib.cpp @@ -60,8 +60,8 @@ static TArray 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 { diff --git a/src/gamedata/p_terrain.cpp b/src/gamedata/p_terrain.cpp index 4d0efcff9..0bb11237f 100644 --- a/src/gamedata/p_terrain.cpp +++ b/src/gamedata/p_terrain.cpp @@ -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) \ No newline at end of file diff --git a/src/gamedata/p_terrain.h b/src/gamedata/p_terrain.h index a225b8a73..6890a38cf 100644 --- a/src/gamedata/p_terrain.h +++ b/src/gamedata/p_terrain.h @@ -118,6 +118,9 @@ struct FTerrainDef bool DamageOnLand; double Friction; double MoveFactor; + FSoundID StepSound; + double StepDistance; + double StepDistanceMinVel; }; extern TArray Splashes; diff --git a/src/gamedata/r_defs.h b/src/gamedata/r_defs.h index c83ed9eef..b235292a9 100644 --- a/src/gamedata/r_defs.h +++ b/src/gamedata/r_defs.h @@ -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); diff --git a/src/gamedata/textures/animations.cpp b/src/gamedata/textures/animations.cpp index b0dce0a55..619375c46 100644 --- a/src/gamedata/textures/animations.cpp +++ b/src/gamedata/textures/animations.cpp @@ -56,7 +56,7 @@ FTextureAnimator TexAnim; // PRIVATE DATA DEFINITIONS ------------------------------------------------ -static FRandom pr_animatepictures ("AnimatePics"); +static FCRandom pr_animatepictures ("AnimatePics"); // CODE -------------------------------------------------------------------- diff --git a/src/gamedata/umapinfo.cpp b/src/gamedata/umapinfo.cpp index 9ba72e61b..d574bdcb9 100644 --- a/src/gamedata/umapinfo.cpp +++ b/src/gamedata/umapinfo.cpp @@ -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")) { diff --git a/src/maploader/postprocessor.cpp b/src/maploader/postprocessor.cpp index e94e7d4d7..c430ce6a5 100644 --- a/src/maploader/postprocessor.cpp +++ b/src/maploader/postprocessor.cpp @@ -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(); - 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(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(); } } } diff --git a/src/maploader/udmf.cpp b/src/maploader/udmf.cpp index d604d6685..d2a415340 100644 --- a/src/maploader/udmf.cpp +++ b/src/maploader/udmf.cpp @@ -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; diff --git a/src/namedef_custom.h b/src/namedef_custom.h index eebb8ca3a..661de6665 100644 --- a/src/namedef_custom.h +++ b/src/namedef_custom.h @@ -812,6 +812,8 @@ xx(damageinterval) xx(leakiness) xx(damageterraineffect) xx(damagehazard) +xx(hurtmonsters) +xx(harminair) xx(floorterrain) xx(ceilingterrain) xx(floor_reflect) diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index f8ee0a8e5..356192ae6 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -62,7 +62,7 @@ #include "doommenu.h" #include "g_game.h" -static FRandom pr_randomspeech("RandomSpeech"); +static FCRandom pr_randomspeech("RandomSpeech"); static int ConversationMenuY; diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 339c1d437..b975e3398 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -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 tempPlayers = {}; + tempPlayers.Reserve(numPlayers); + TArray 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(); + } } //========================================================================== diff --git a/src/playsim/a_dynlight.cpp b/src/playsim/a_dynlight.cpp index 4a4cb46fd..b8168e0a6 100644 --- a/src/playsim/a_dynlight.cpp +++ b/src/playsim/a_dynlight.cpp @@ -67,7 +67,7 @@ static FMemArena DynLightArena(sizeof(FDynamicLight) * 200); static TArray FreeList; -static FRandom randLight; +static FCRandom randLight; extern TArray 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 diff --git a/src/playsim/actor.h b/src/playsim/actor.h index 96d97b392..4c5ca980b 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -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 --- diff --git a/src/playsim/dthinker.cpp b/src/playsim/dthinker.cpp index 4ee97062d..cb6906d09 100644 --- a/src/playsim/dthinker.cpp +++ b/src/playsim/dthinker.cpp @@ -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; diff --git a/src/playsim/mapthinkers/a_lights.cpp b/src/playsim/mapthinkers/a_lights.cpp index 692e2dbdc..3b8202361 100644 --- a/src/playsim/mapthinkers/a_lights.cpp +++ b/src/playsim/mapthinkers/a_lights.cpp @@ -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"); //----------------------------------------------------------------------------- diff --git a/src/playsim/mapthinkers/a_quake.cpp b/src/playsim/mapthinkers/a_quake.cpp index b21951562..d2529a5f8 100644 --- a/src/playsim/mapthinkers/a_quake.cpp +++ b/src/playsim/mapthinkers/a_quake.cpp @@ -36,7 +36,7 @@ #include "actorinlines.h" #include -static FRandom pr_quake ("Quake"); +static FCRandom pr_quake ("Quake"); IMPLEMENT_CLASS(DEarthquake, false, true) diff --git a/src/playsim/p_3dfloors.cpp b/src/playsim/p_3dfloors.cpp index dd5a2bb0a..cce58326c 100644 --- a/src/playsim/p_3dfloors.cpp +++ b/src/playsim/p_3dfloors.cpp @@ -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; } diff --git a/src/playsim/p_3dfloors.h b/src/playsim/p_3dfloors.h index 05d222137..2dba7f713 100644 --- a/src/playsim/p_3dfloors.h +++ b/src/playsim/p_3dfloors.h @@ -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); diff --git a/src/playsim/p_acs.cpp b/src/playsim/p_acs.cpp index d05f5dae3..89aee643d 100644 --- a/src/playsim/p_acs.cpp +++ b/src/playsim/p_acs.cpp @@ -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(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); diff --git a/src/playsim/p_effect.cpp b/src/playsim/p_effect.cpp index 459d267be..a2d42f7c5 100644 --- a/src/playsim/p_effect.cpp +++ b/src/playsim/p_effect.cpp @@ -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); diff --git a/src/playsim/p_effect.h b/src/playsim/p_effect.h index 735a87e81..1e5ba2705 100644 --- a/src/playsim/p_effect.h +++ b/src/playsim/p_effect.h @@ -147,56 +147,4 @@ struct SPortalHit void P_DrawRailTrail(AActor *source, TArray &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; -}; \ No newline at end of file +void P_DisconnectEffect (AActor *actor); \ No newline at end of file diff --git a/src/playsim/p_enemy.cpp b/src/playsim/p_enemy.cpp index f87c28654..120f37231 100644 --- a/src/playsim/p_enemy.cpp +++ b/src/playsim/p_enemy.cpp @@ -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); + 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. diff --git a/src/playsim/p_local.h b/src/playsim/p_local.h index 6d02d2a56..584cfc43f 100644 --- a/src/playsim/p_local.h +++ b/src/playsim/p_local.h @@ -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 diff --git a/src/playsim/p_map.cpp b/src/playsim/p_map.cpp index 6126a5059..2aea97407 100644 --- a/src/playsim/p_map.cpp +++ b/src/playsim/p_map.cpp @@ -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 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) diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 1b9422baf..adb770c71 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -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()) { diff --git a/src/playsim/p_spec.cpp b/src/playsim/p_spec.cpp index 6ffc2d9af..3851bd748 100644 --- a/src/playsim/p_spec.cpp +++ b/src/playsim/p_spec.cpp @@ -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. diff --git a/src/playsim/p_spec.h b/src/playsim/p_spec.h index cc7d990a9..3ef1aef9e 100644 --- a/src/playsim/p_spec.h +++ b/src/playsim/p_spec.h @@ -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. diff --git a/src/playsim/p_switch.cpp b/src/playsim/p_switch.cpp index e89f03011..59b70ec83 100644 --- a/src/playsim/p_switch.cpp +++ b/src/playsim/p_switch.cpp @@ -49,7 +49,7 @@ #include "actorinlines.h" #include "animations.h" -static FRandom pr_switchanim ("AnimSwitch"); +static FCRandom pr_switchanim ("AnimSwitch"); class DActiveButton : public DThinker { diff --git a/src/playsim/p_teleport.cpp b/src/playsim/p_teleport.cpp index a69d3654c..a198f2b27 100644 --- a/src/playsim/p_teleport.cpp +++ b/src/playsim/p_teleport.cpp @@ -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; diff --git a/src/playsim/p_user.cpp b/src/playsim/p_user.cpp index d3294ffd6..4ed0c0bfd 100644 --- a/src/playsim/p_user.cpp +++ b/src/playsim/p_user.cpp @@ -144,6 +144,8 @@ static DVector3 LastPredictedPosition; static int LastPredictedPortalGroup; static int LastPredictedTic; +static TArray PredictionRNG; + static player_t PredictionPlayerBackup; static AActor *PredictionActor; static TArray 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(NAME_InvSel); diff --git a/src/playsim/p_visualthinker.h b/src/playsim/p_visualthinker.h new file mode 100644 index 000000000..d4b1e262d --- /dev/null +++ b/src/playsim/p_visualthinker.h @@ -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; +}; \ No newline at end of file diff --git a/src/rendering/hwrenderer/scene/hw_bsp.cpp b/src/rendering/hwrenderer/scene/hw_bsp.cpp index cbf6a4bd6..7fb1cbe01 100644 --- a/src/rendering/hwrenderer/scene/hw_bsp.cpp +++ b/src/rendering/hwrenderer/scene/hw_bsp.cpp @@ -45,6 +45,8 @@ #include "hw_walldispatcher.h" #include "hw_flatdispatcher.h" +#include "p_visualthinker.h" + #ifdef ARCH_IA32 #include #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) { diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index d1bc36289..78d706e4d 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -59,6 +59,8 @@ #include "hw_drawcontext.h" #include "quaternion.h" +#include "p_visualthinker.h" + extern TArray sprites; extern TArray 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; diff --git a/src/rendering/r_utility.cpp b/src/rendering/r_utility.cpp index 5313f738c..3a3fdd70e 100644 --- a/src/rendering/r_utility.cpp +++ b/src/rendering/r_utility.cpp @@ -94,8 +94,8 @@ struct InterpolationViewer // PRIVATE DATA DECLARATIONS ----------------------------------------------- static TArray 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 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(); } //========================================================================== diff --git a/src/scripting/decorate/thingdef_exp.cpp b/src/scripting/decorate/thingdef_exp.cpp index 1b27342be..f8ac9e91a 100644 --- a/src/scripting/decorate/thingdef_exp.cpp +++ b/src/scripting/decorate/thingdef_exp.cpp @@ -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('('); diff --git a/src/scripting/decorate/thingdef_parse.cpp b/src/scripting/decorate/thingdef_parse.cpp index 19c8f38ab..03a249814 100644 --- a/src/scripting/decorate/thingdef_parse.cpp +++ b/src/scripting/decorate/thingdef_parse.cpp @@ -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 diff --git a/src/scripting/decorate/thingdef_states.cpp b/src/scripting/decorate/thingdef_states.cpp index 570905741..d518d6769 100644 --- a/src/scripting/decorate/thingdef_states.cpp +++ b/src/scripting/decorate/thingdef_states.cpp @@ -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; } diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index cf1d0eca1..fa8d66674 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -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), diff --git a/src/scripting/vmthunks.cpp b/src/scripting/vmthunks.cpp index 9229a8dc3..690511865 100644 --- a/src/scripting/vmthunks.cpp +++ b/src/scripting/vmthunks.cpp @@ -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)) { diff --git a/src/scripting/zscript/zcc_compile_doom.cpp b/src/scripting/zscript/zcc_compile_doom.cpp index a86ea9a0e..bd7ba9048 100644 --- a/src/scripting/zscript/zcc_compile_doom.cpp +++ b/src/scripting/zscript/zcc_compile_doom.cpp @@ -161,17 +161,14 @@ bool ZCCDoomCompiler::CompileProperties(PClass *type, TArray &Pr bool ZCCDoomCompiler::CompileFlagDefs(PClass *type, TArray &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 &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(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(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()); + } } } diff --git a/src/sound/s_doomsound.cpp b/src/sound/s_doomsound.cpp index 33604ce61..749b1f5e7 100644 --- a/src/sound/s_doomsound.cpp +++ b/src/sound/s_doomsound.cpp @@ -1163,7 +1163,7 @@ TArray 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) { diff --git a/src/sound/s_sndseq.cpp b/src/sound/s_sndseq.cpp index 99b58a8f9..266447773 100644 --- a/src/sound/s_sndseq.cpp +++ b/src/sound/s_sndseq.cpp @@ -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 -------------------------------------------------------------------- diff --git a/src/version.h b/src/version.h index da6ab9cde..dee38c893 100644 --- a/src/version.h +++ b/src/version.h @@ -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! diff --git a/wadsrc/static/graphics/AMMNUM0.png b/wadsrc/static/graphics/AMMNUM0.png new file mode 100644 index 000000000..6924f3956 Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM0.png differ diff --git a/wadsrc/static/graphics/AMMNUM1.png b/wadsrc/static/graphics/AMMNUM1.png new file mode 100644 index 000000000..14dab57fe Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM1.png differ diff --git a/wadsrc/static/graphics/AMMNUM2.png b/wadsrc/static/graphics/AMMNUM2.png new file mode 100644 index 000000000..4c6514893 Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM2.png differ diff --git a/wadsrc/static/graphics/AMMNUM3.png b/wadsrc/static/graphics/AMMNUM3.png new file mode 100644 index 000000000..bcd495a68 Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM3.png differ diff --git a/wadsrc/static/graphics/AMMNUM4.png b/wadsrc/static/graphics/AMMNUM4.png new file mode 100644 index 000000000..6d373156c Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM4.png differ diff --git a/wadsrc/static/graphics/AMMNUM5.png b/wadsrc/static/graphics/AMMNUM5.png new file mode 100644 index 000000000..c840a0a00 Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM5.png differ diff --git a/wadsrc/static/graphics/AMMNUM6.png b/wadsrc/static/graphics/AMMNUM6.png new file mode 100644 index 000000000..2c0f3b101 Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM6.png differ diff --git a/wadsrc/static/graphics/AMMNUM7.png b/wadsrc/static/graphics/AMMNUM7.png new file mode 100644 index 000000000..1739247cd Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM7.png differ diff --git a/wadsrc/static/graphics/AMMNUM8.png b/wadsrc/static/graphics/AMMNUM8.png new file mode 100644 index 000000000..0bb5499fa Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM8.png differ diff --git a/wadsrc/static/graphics/AMMNUM9.png b/wadsrc/static/graphics/AMMNUM9.png new file mode 100644 index 000000000..fa9232fa4 Binary files /dev/null and b/wadsrc/static/graphics/AMMNUM9.png differ diff --git a/wadsrc/static/mapinfo/common.txt b/wadsrc/static/mapinfo/common.txt index e80a50105..51b2f12de 100644 --- a/wadsrc/static/mapinfo/common.txt +++ b/wadsrc/static/mapinfo/common.txt @@ -78,6 +78,7 @@ DoomEdNums 9081 = SkyPicker 9082 = SectorSilencer 9083 = SkyCamCompat + 9084 = OrthographicCamera 9200 = Decal 9300 = "$PolyAnchor" 9301 = "$PolySpawn" diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index e7cdf917b..8c50fa738 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -773,7 +773,7 @@ class Actor : Thinker native native Actor SpawnPuff(class 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 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(); diff --git a/wadsrc/static/zscript/actors/hexen/blastradius.zs b/wadsrc/static/zscript/actors/hexen/blastradius.zs index 271f2bcfc..b66ca1418 100644 --- a/wadsrc/static/zscript/actors/hexen/blastradius.zs +++ b/wadsrc/static/zscript/actors/hexen/blastradius.zs @@ -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; diff --git a/wadsrc/static/zscript/actors/inventory/armor.zs b/wadsrc/static/zscript/actors/inventory/armor.zs index 8509c5bf2..29c82a1fc 100644 --- a/wadsrc/static/zscript/actors/inventory/armor.zs +++ b/wadsrc/static/zscript/actors/inventory/armor.zs @@ -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; } diff --git a/wadsrc/static/zscript/actors/inventory/weaponpiece.zs b/wadsrc/static/zscript/actors/inventory/weaponpiece.zs index 2f2434d6c..2a2cd9fe6 100644 --- a/wadsrc/static/zscript/actors/inventory/weaponpiece.zs +++ b/wadsrc/static/zscript/actors/inventory/weaponpiece.zs @@ -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 GetWeaponClass() const + { + class type = WeaponClass ? (class)(GetReplacement(WeaponClass)) : null; + return type ? type : WeaponClass; + } //========================================================================== // @@ -74,7 +81,11 @@ class WeaponPiece : Inventory return false; } - let Defaults = GetDefaultByType(WeaponClass); + class 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 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; diff --git a/wadsrc/static/zscript/actors/player/player.zs b/wadsrc/static/zscript/actors/player/player.zs index c2da5f46c..b320b6253 100644 --- a/wadsrc/static/zscript/actors/player/player.zs +++ b/wadsrc/static/zscript/actors/player/player.zs @@ -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 diff --git a/wadsrc/static/zscript/actors/shared/camera.zs b/wadsrc/static/zscript/actors/shared/camera.zs index 463ad6178..11b036162 100644 --- a/wadsrc/static/zscript/actors/shared/camera.zs +++ b/wadsrc/static/zscript/actors/shared/camera.zs @@ -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); + } +} diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index ebcb85f39..e56b826c3 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -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' +}; diff --git a/wadsrc/static/zscript/doombase.zs b/wadsrc/static/zscript/doombase.zs index 8f055e1ed..2d3d5a132 100644 --- a/wadsrc/static/zscript/doombase.zs +++ b/wadsrc/static/zscript/doombase.zs @@ -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 diff --git a/wadsrc/static/zscript/engine/base.zs b/wadsrc/static/zscript/engine/base.zs index 18fe1b363..8abd04ee3 100644 --- a/wadsrc/static/zscript/engine/base.zs +++ b/wadsrc/static/zscript/engine/base.zs @@ -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. diff --git a/wadsrc/static/zscript/events.zs b/wadsrc/static/zscript/events.zs index 52cf3a7eb..72cf23a51 100644 --- a/wadsrc/static/zscript/events.zs +++ b/wadsrc/static/zscript/events.zs @@ -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 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) {} diff --git a/wadsrc/static/zscript/mapdata.zs b/wadsrc/static/zscript/mapdata.zs index 5d45563eb..9031b3158 100644 --- a/wadsrc/static/zscript/mapdata.zs +++ b/wadsrc/static/zscript/mapdata.zs @@ -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; diff --git a/wadsrc/static/zscript/ui/menu/conversationmenu.zs b/wadsrc/static/zscript/ui/menu/conversationmenu.zs index 1b1d3c011..4e8210b1a 100644 --- a/wadsrc/static/zscript/ui/menu/conversationmenu.zs +++ b/wadsrc/static/zscript/ui/menu/conversationmenu.zs @@ -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"); } diff --git a/wadsrc/static/zscript/ui/statusbar/heretic_sbar.zs b/wadsrc/static/zscript/ui/statusbar/heretic_sbar.zs index 62cc27efa..135b711c2 100644 --- a/wadsrc/static/zscript/ui/statusbar/heretic_sbar.zs +++ b/wadsrc/static/zscript/ui/statusbar/heretic_sbar.zs @@ -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); } } diff --git a/wadsrc/static/zscript/visualthinker.zs b/wadsrc/static/zscript/visualthinker.zs index 12a8c41d6..45b32865c 100644 --- a/wadsrc/static/zscript/visualthinker.zs +++ b/wadsrc/static/zscript/visualthinker.zs @@ -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 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; } diff --git a/wadsrc_extra/static/iwadinfo.txt b/wadsrc_extra/static/iwadinfo.txt index 985df8b5d..98cf868f6 100644 --- a/wadsrc_extra/static/iwadinfo.txt +++ b/wadsrc_extra/static/iwadinfo.txt @@ -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"