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/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..fb906aee8 100644 --- a/src/common/engine/serializer.h +++ b/src/common/engine/serializer.h @@ -252,7 +252,7 @@ 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); 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/filesystem/include/fs_filesystem.h b/src/common/filesystem/include/fs_filesystem.h index 858b74837..e817bc12d 100644 --- a/src/common/filesystem/include/fs_filesystem.h +++ b/src/common/filesystem/include/fs_filesystem.h @@ -42,8 +42,8 @@ public: void SetMaxIwadNum(int x) { MaxIwadIndex = x; } bool InitSingleFile(const char *filename, FileSystemMessageFunc Printf = nullptr); - bool InitMultipleFiles (std::vector& filenames, LumpFilterInfo* filter = nullptr, FileSystemMessageFunc Printf = nullptr, bool allowduplicates = false, FILE* hashfile = nullptr); - void AddFile (const char *filename, FileReader *wadinfo, LumpFilterInfo* filter, FileSystemMessageFunc Printf, FILE* hashfile); + bool InitMultipleFiles (std::vector& filenames, LumpFilterInfo* filter = nullptr, FileSystemMessageFunc Printf = nullptr, bool allowduplicates = false); + void AddFile (const char *filename, FileReader *wadinfo, LumpFilterInfo* filter, FileSystemMessageFunc Printf); int CheckIfResourceFileLoaded (const char *name) noexcept; void AddAdditionalFile(const char* filename, FileReader* wadinfo = NULL) {} diff --git a/src/common/filesystem/source/filesystem.cpp b/src/common/filesystem/source/filesystem.cpp index fee5f1011..0bc0dd5fe 100644 --- a/src/common/filesystem/source/filesystem.cpp +++ b/src/common/filesystem/source/filesystem.cpp @@ -238,7 +238,7 @@ bool FileSystem::InitSingleFile(const char* filename, FileSystemMessageFunc Prin return InitMultipleFiles(filenames, nullptr, Printf); } -bool FileSystem::InitMultipleFiles (std::vector& filenames, LumpFilterInfo* filter, FileSystemMessageFunc Printf, bool allowduplicates, FILE* hashfile) +bool FileSystem::InitMultipleFiles (std::vector& filenames, LumpFilterInfo* filter, FileSystemMessageFunc Printf, bool allowduplicates) { int numfiles; @@ -269,7 +269,7 @@ bool FileSystem::InitMultipleFiles (std::vector& filenames, LumpFil for(size_t i=0;igetName(i); auto embedded = resfile->GetEntryReader(i, READER_CACHED); - AddFile(path.c_str(), &embedded, filter, Printf, hashfile); + AddFile(path.c_str(), &embedded, filter, Printf); } } - if (hashfile) - { - uint8_t cksum[16]; - char cksumout[33]; - memset(cksumout, 0, sizeof(cksumout)); - - if (filereader.isOpen()) - { - filereader.Seek(0, FileReader::SeekSet); - md5Hash(filereader, cksum); - - for (size_t j = 0; j < sizeof(cksum); ++j) - { - snprintf(cksumout + (j * 2), 3, "%02X", cksum[j]); - } - - fprintf(hashfile, "file: %s, hash: %s, size: %td\n", filename, cksumout, filereader.GetLength()); - } - - else - fprintf(hashfile, "file: %s, Directory structure\n", filename); - - for (int i = 0; i < resfile->EntryCount(); i++) - { - int flags = resfile->GetEntryFlags(i); - if (!(flags & RESFF_EMBEDDED)) - { - auto reader = resfile->GetEntryReader(i, READER_SHARED, 0); - md5Hash(filereader, cksum); - - for (size_t j = 0; j < sizeof(cksum); ++j) - { - snprintf(cksumout + (j * 2), 3, "%02X", cksum[j]); - } - - fprintf(hashfile, "file: %s, lump: %s, hash: %s, size: %zu\n", filename, resfile->getName(i), cksumout, (uint64_t)resfile->Length(i)); - } - } - } return; } } 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..03a41f3a8 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; @@ -8967,7 +8993,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) } else { - if (PFunction **Override; ctx.Version >= MakeVersion(4, 11, 0) && (Override = static_cast(Self->ValueType)->FnOverrides.CheckKey(MethodName))) + if (PFunction **Override; (Override = static_cast(Self->ValueType)->FnOverrides.CheckKey(MethodName))) { afd_override = *Override; } diff --git a/src/common/scripting/backend/vmbuilder.cpp b/src/common/scripting/backend/vmbuilder.cpp index 71a766d9a..c9bfc63af 100644 --- a/src/common/scripting/backend/vmbuilder.cpp +++ b/src/common/scripting/backend/vmbuilder.cpp @@ -40,6 +40,7 @@ #include "filesystem.h" CVAR(Bool, strictdecorate, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE) +CVAR(Bool, warningstoerrors, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE) EXTERN_CVAR(Bool, vm_jit) EXTERN_CVAR(Bool, vm_jit_aot) @@ -879,10 +880,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/textures/m_png.cpp b/src/common/textures/m_png.cpp index 331b35e20..83714a6ac 100644 --- a/src/common/textures/m_png.cpp +++ b/src/common/textures/m_png.cpp @@ -54,7 +54,7 @@ // MACROS ------------------------------------------------------------------ // The maximum size of an IDAT chunk ZDoom will write. This is also the -// size of the compression buffer it allocates on the stack. +// size of the compression buffer it allocates on the heap. #define PNG_WRITE_SIZE 32768 // Set this to 1 to use a simple heuristic to select the filter to apply @@ -926,8 +926,7 @@ bool M_SaveBitmap(const uint8_t *from, ESSType color_type, int width, int height temprow[i] = &temprow_storage[temprow_size * i]; } - TArray array(PNG_WRITE_SIZE, true); - auto buffer = array.data(); + TArray buffer(PNG_WRITE_SIZE, true); z_stream stream; int err; int y; @@ -944,8 +943,8 @@ bool M_SaveBitmap(const uint8_t *from, ESSType color_type, int width, int height } y = height; - stream.next_out = buffer; - stream.avail_out = sizeof(buffer); + stream.next_out = buffer.data(); + stream.avail_out = buffer.size(); temprow[0][0] = 0; #if USE_FILTER_HEURISTIC @@ -1007,12 +1006,12 @@ bool M_SaveBitmap(const uint8_t *from, ESSType color_type, int width, int height } while (stream.avail_out == 0) { - if (!WriteIDAT (file, buffer, sizeof(buffer))) + if (!WriteIDAT (file, buffer.data(), buffer.size())) { return false; } - stream.next_out = buffer; - stream.avail_out = sizeof(buffer); + stream.next_out = buffer.data(); + stream.avail_out = buffer.size(); if (stream.avail_in != 0) { err = deflate (&stream, (y == 0) ? Z_FINISH : 0); @@ -1033,12 +1032,12 @@ bool M_SaveBitmap(const uint8_t *from, ESSType color_type, int width, int height } if (stream.avail_out == 0) { - if (!WriteIDAT (file, buffer, sizeof(buffer))) + if (!WriteIDAT (file, buffer.data(), buffer.size())) { return false; } - stream.next_out = buffer; - stream.avail_out = sizeof(buffer); + stream.next_out = buffer.data(); + stream.avail_out = buffer.size(); } } @@ -1048,7 +1047,7 @@ bool M_SaveBitmap(const uint8_t *from, ESSType color_type, int width, int height { return false; } - return WriteIDAT (file, buffer, sizeof(buffer)-stream.avail_out); + return WriteIDAT (file, buffer.data(), buffer.size() - stream.avail_out); } //========================================================================== 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/d_iwad.cpp b/src/d_iwad.cpp index 1eb0c0ea0..6ce60d30b 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -824,7 +824,7 @@ int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char // [SP] Load non-free assets if available. This must be done before the IWAD. int iwadnum = 1; - if (D_AddFile(wadfiles, optional_wad, true, -1, GameConfig)) + if (optional_wad && D_AddFile(wadfiles, optional_wad, true, -1, GameConfig)) { iwadnum++; } diff --git a/src/d_main.cpp b/src/d_main.cpp index d5eb38180..63ed53fba 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -3071,39 +3071,6 @@ static void System_HudScaleChanged() bool CheckSkipGameOptionBlock(const char* str); -//========================================================================== -// -// -// -//========================================================================== - -static FILE* D_GetHashFile() -{ - FILE *hashfile = nullptr; - - if (Args->CheckParm("-hashfiles")) - { - const char *filename = "fileinfo.txt"; - Printf("Hashing loaded content to: %s\n", filename); - hashfile = fopen(filename, "w"); - if (hashfile) - { - Printf("Notice: File hashing is incredibly verbose. Expect loading files to take much longer than usual.\n"); - fprintf(hashfile, "%s version %s (%s)\n", GAMENAME, GetVersionString(), GetGitHash()); -#ifdef __VERSION__ - fprintf(hashfile, "Compiler version: %s\n", __VERSION__); -#endif - fprintf(hashfile, "Command line:"); - for (int i = 0; i < Args->NumArgs(); ++i) - { - fprintf(hashfile, " %s", Args->GetArg(i)); - } - fprintf(hashfile, "\n"); - } - } - return hashfile; -} - // checks if a file within a directory is allowed to be added to the file system. static bool FileNameCheck(const char* base, const char* path) { @@ -3260,8 +3227,7 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw ); bool allowduplicates = Args->CheckParm("-allowduplicates"); - auto hashfile = D_GetHashFile(); - if (!fileSystem.InitMultipleFiles(allwads, &lfi, FileSystemPrintf, allowduplicates, hashfile)) + if (!fileSystem.InitMultipleFiles(allwads, &lfi, FileSystemPrintf, allowduplicates)) { I_FatalError("FileSystem: no files found"); } diff --git a/src/events.cpp b/src/events.cpp index a4773d133..e4d7d1d1a 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -701,6 +701,69 @@ 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; +} + +bool EventManager::WorldRailgunPreFired(FName damageType, PClassActor* pufftype, FRailParams* param) +{ + auto actor = param->source; + + // 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.WorldRailgunPreFired(damageType, pufftype, param); + + if (!ret) + { + for (DStaticEventHandler* handler = FirstEventHandler; handler && ret == false; handler = handler->next) + ret = handler->WorldRailgunPreFired(damageType, pufftype, param); + } + + 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::WorldRailgunFired(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.WorldRailgunFired(actor, AttackPos, DamagePosition, Inflictor, flags); + + for (DStaticEventHandler* handler = FirstEventHandler; handler; handler = handler->next) + handler->WorldRailgunFired(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 +1089,16 @@ 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(WorldEvent, FWorldEvent, RailParams); +DEFINE_FIELD_X(WorldEvent, FWorldEvent, AttackLineFlags); DEFINE_FIELD_X(PlayerEvent, FPlayerEvent, PlayerNumber); DEFINE_FIELD_X(PlayerEvent, FPlayerEvent, IsReturn); @@ -1729,6 +1802,98 @@ 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.AttackLineFlags = flags; + int processed; + VMReturn results[1] = { &processed }; + VMValue params[2] = { (DStaticEventHandler*)this, &e }; + VMCall(func, params, 2, results, 1); + return !!processed; + } + + return false; +} + +bool DStaticEventHandler::WorldRailgunPreFired(FName damageType, PClassActor* pufftype, FRailParams* param) +{ + IFVIRTUAL(DStaticEventHandler, WorldRailgunPreFired) + { + // don't create excessive DObjects if not going to be processed anyway + if (isEmpty(func)) return false; + FWorldEvent e = owner->SetupWorldEvent(); + e.Thing = param->source; + e.AttackPuffType = pufftype; + e.DamageType = damageType; + e.Damage = param->damage; + e.AttackOffsetForward = 0; + e.AttackOffsetSide = param->offset_xy; + e.AttackDistance = param->distance; + e.AttackZ = param->offset_z; + e.AttackAngle = e.Thing->Angles.Yaw + param->angleoffset; + e.AttackPitch = e.Thing->Angles.Pitch + param->pitchoffset; + e.RailParams = *param; + e.RailParams.puff = pufftype; + + 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.AttackLineFlags = flags; + VMValue params[2] = { (DStaticEventHandler*)this, &e }; + VMCall(func, params, 2, nullptr, 0); + } +} + +void DStaticEventHandler::WorldRailgunFired(AActor* actor, const DVector3& AttackPos, const DVector3& DamagePosition, AActor* Inflictor, int flags) +{ + IFVIRTUAL(DStaticEventHandler, WorldRailgunFired) + { + // 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.AttackLineFlags = 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 +1908,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..4a73406f8 --- a/src/events.h +++ b/src/events.h @@ -4,6 +4,7 @@ #include "dobject.h" #include "serializer.h" #include "d_event.h" +#include "p_local.h" #include "sbar.h" #include "info.h" #include "vm.h" @@ -311,6 +312,10 @@ 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); + bool WorldRailgunPreFired(FName damageType, PClassActor* pufftype, FRailParams* param); + void WorldHitscanFired(AActor* actor, const DVector3& AttackPos, const DVector3& DamagePosition, AActor* Inflictor, int flags); + void WorldRailgunFired(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 +398,16 @@ 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; + FRailParams RailParams; + int AttackLineFlags = 0; }; struct FPlayerEvent @@ -467,6 +482,14 @@ 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 when a railgun attack has been fired + void WorldRailgunFired(AActor* actor, const DVector3& AttackPos, const DVector3& DamagePosition, AActor* Inflictor, int flags); + // called when a railgun attack has been fired (can be overridden to block it) + bool WorldRailgunPreFired(FName damageType, PClassActor* pufftype, FRailParams* param); // 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..70110aca1 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); @@ -1436,6 +1438,7 @@ void FLevelLocals::PlayerReborn (int player) p->cheats |= chasecam; p->Bot = Bot; //Added by MC: p->settings_controller = settings_controller; + p->LastSafePos = p->mo->Pos(); p->oldbuttons = ~0, p->attackdown = true; p->usedown = true; // don't do anything immediately p->original_oldbuttons = ~0; @@ -2146,7 +2149,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 +2442,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..9ae2997eb 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -56,6 +56,7 @@ #include "r_data/r_interpolate.h" #include "doom_aabbtree.h" #include "doom_levelmesh.h" +#include "p_visualthinker.h" //============================================================================ // @@ -149,6 +150,7 @@ struct FLevelLocals int GetCompatibility2(int mask); void ApplyCompatibility(); void ApplyCompatibility2(); + AActor* SelectActorFromTID(int tid, size_t index, AActor* defactor); void Init(); @@ -166,8 +168,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 +200,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); @@ -425,10 +427,13 @@ public: DThinker *CreateThinker(PClass *cls, int statnum = STAT_DEFAULT) { DThinker *thinker = static_cast(cls->CreateNew()); + if (statnum && thinker->IsKindOf(RUNTIME_CLASS(DVisualThinker))) + { + statnum = STAT_VISUALTHINKER; + } + 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.cpp b/src/g_statusbar/sbarinfo.cpp index 6bb27d179..82aba4263 100644 --- a/src/g_statusbar/sbarinfo.cpp +++ b/src/g_statusbar/sbarinfo.cpp @@ -381,6 +381,7 @@ enum //Key words SBARINFO_MUGSHOT, SBARINFO_CREATEPOPUP, SBARINFO_PROTRUSION, + SBARINFO_APPENDSTATUSBAR, }; enum //Bar types @@ -410,6 +411,7 @@ static const char *SBarInfoTopLevel[] = "mugshot", "createpopup", "protrusion", + "appendstatusbar", NULL }; @@ -488,7 +490,9 @@ void SBarInfo::ParseSBarInfo(int lump) continue; } int baselump = -2; - switch(sc.MustMatchString(SBarInfoTopLevel)) + // Store the command, used for the switch statement and case SBARINFO_APPENDSTATUSBAR. + const int command = sc.MustMatchString(SBarInfoTopLevel); + switch(command) { case SBARINFO_BASE: baseSet = true; @@ -629,6 +633,7 @@ void SBarInfo::ParseSBarInfo(int lump) sc.MustGetToken(';'); break; case SBARINFO_STATUSBAR: + case SBARINFO_APPENDSTATUSBAR: { if(!baseSet) //If the user didn't explicitly define a base, do so now. gameType = GAME_Any; @@ -638,11 +643,19 @@ void SBarInfo::ParseSBarInfo(int lump) sc.MustGetToken(TK_Identifier); barNum = sc.MustMatchString(StatusBars); } - if (this->huds[barNum] != NULL) + // SBARINFO_APPENDSTATUSBAR shouldn't delete the old HUD if it exists. + if(command != SBARINFO_APPENDSTATUSBAR) { - delete this->huds[barNum]; + if (this->huds[barNum] != NULL) + { + delete this->huds[barNum]; + } + this->huds[barNum] = new SBarInfoMainBlock(this); + } + else if(this->huds[barNum] == NULL) + { + sc.ScriptError("Status bar '%s' has not been created and cannot be appended to. Use 'StatusBar' instead.", StatusBars[barNum]); } - this->huds[barNum] = new SBarInfoMainBlock(this); if(barNum == STBAR_AUTOMAP) { automapbar = true; 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..b94ec5cc3 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 @@ -806,6 +808,8 @@ public: void RemoveForceField(); int Index() const { return sectornum; } + bool IsDangerous(const DVector3& pos, double height) const; + void AdjustFloorClip () const; void SetColor(PalEntry pe, int desat); void SetFade(PalEntry pe); @@ -1062,6 +1066,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/m_misc.cpp b/src/m_misc.cpp index 1b451486b..f8bafabad 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -66,6 +66,7 @@ #include "gameconfigfile.h" #include "gstrings.h" +#include "vm.h" FGameConfigFile *GameConfig; @@ -691,3 +692,14 @@ CCMD(openscreenshots) I_OpenShellFolder(autoname.GetChars()); } +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/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_decals.cpp b/src/playsim/a_decals.cpp index 758b16a5d..40468ade0 100644 --- a/src/playsim/a_decals.cpp +++ b/src/playsim/a_decals.cpp @@ -849,7 +849,7 @@ void SprayDecal(AActor *shooter, const char *name, double distance, DVector3 off { if (trace.HitType == TRACE_HitWall) { - DImpactDecal::StaticCreate(shooter->Level, name, trace.HitPos, trace.Line->sidedef[trace.Side], NULL, entry, bloodTrans); + DImpactDecal::StaticCreate(shooter->Level, name, trace.HitPos, trace.Line->sidedef[trace.Side], trace.ffloor, entry, bloodTrans); } } } 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..c4669afdd 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 --- @@ -754,12 +756,6 @@ public: int Flags; // Functions - DViewPosition() - { - Offset = { 0,0,0 }; - Flags = 0; - } - void Set(DVector3 &off, int f = -1) { ZeroSubnormalsF(off.X); @@ -775,6 +771,8 @@ public: { return Offset.isZero(); } + + void Serialize(FSerializer& arc) override; }; const double MinVel = EQUAL_EPSILON; @@ -871,7 +869,7 @@ public: void PlayPushSound(); // Called when an actor with MF_MISSILE and MF2_FLOORBOUNCE hits the floor - bool FloorBounceMissile (secplane_t &plane); + bool FloorBounceMissile (secplane_t &plane, bool is3DFloor); // Called by RoughBlockCheck bool IsOkayToAttack (AActor *target); diff --git a/src/playsim/d_player.h b/src/playsim/d_player.h index a717bb42c..862890f07 100644 --- a/src/playsim/d_player.h +++ b/src/playsim/d_player.h @@ -306,7 +306,7 @@ void WriteUserInfo(FSerializer &arc, userinfo_t &info); class player_t { public: - player_t() = default; + player_t() { angleOffsetTargets.Zero(); } ~player_t(); player_t &operator= (const player_t &p) = delete; void CopyFrom(player_t &src, bool copyPSP); @@ -322,8 +322,8 @@ public: AActor *mo = nullptr; uint8_t playerstate = 0; ticcmd_t cmd = {}; - usercmd_t original_cmd; - uint32_t original_oldbuttons; + usercmd_t original_cmd = {}; + uint32_t original_oldbuttons = 0; userinfo_t userinfo; // [RH] who is this? @@ -340,7 +340,7 @@ public: // mo->velx and mo->vely represent true velocity experienced by player. // This only represents the thrust that the player applies himself. // This avoids anomalies with such things as Boom ice and conveyors. - DVector2 Vel = { 0,0 }; + DVector2 Vel = { 0.0, 0.0 }; bool centering = false; uint8_t turnticks = 0; @@ -419,7 +419,7 @@ public: FString SoundClass; FString LogText; // [RH] Log for Strife FString SubtitleText; - int SubtitleCounter; + int SubtitleCounter = 0; DAngle MinPitch = nullAngle; // Viewpitch limits (negative is up, positive is down) DAngle MaxPitch = nullAngle; @@ -435,6 +435,8 @@ public: DAngle ConversationNPCAngle = nullAngle; bool ConversationFaceTalker = false; + DVector3 LastSafePos = {}; // Mark the last known safe location the player was standing. + double GetDeltaViewHeight() const { return (mo->FloatVar(NAME_ViewHeight) + crouchviewdelta - viewheight) / 8; 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_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index ea7090c1d..8d50993d9 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -5130,6 +5130,7 @@ enum ESetAnimationFlags void SetAnimationInternal(AActor * self, FName animName, double framerate, int startFrame, int loopFrame, int endFrame, int interpolateTics, int flags, double ticFrac) { + if(!self) ThrowAbortException(X_READ_NIL, "In function parameter self"); if(!(self->flags9 & MF9_DECOUPLEDANIMATIONS)) @@ -5176,43 +5177,46 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s if(!(flags & SAF_INSTANT)) { - if(self->modelData->curAnim.startTic > tic) - { - ModelAnimFrameInterp to; - float inter; - - calcFrames(self->modelData->curAnim, tic, to, inter); - - const TArray* animationData = nullptr; - - int animationid = -1; - - const FSpriteModelFrame * smf = &BaseSpriteModelFrames[self->GetClass()]; - - if (self->modelData->animationIDs.Size() > 0 && self->modelData->animationIDs[0] >= 0) + if((self->modelData->curAnim.startTic - self->modelData->curAnim.switchOffset) != int(floor(tic))) + { // don't change interpolation data if animation switch happened in the same tic + if(self->modelData->curAnim.startTic > tic) { - animationid = self->modelData->animationIDs[0]; + ModelAnimFrameInterp to; + float inter; + + calcFrames(self->modelData->curAnim, tic, to, inter); + + const TArray* animationData = nullptr; + + int animationid = -1; + + const FSpriteModelFrame * smf = &BaseSpriteModelFrames[self->GetClass()]; + + if (self->modelData->animationIDs.Size() > 0 && self->modelData->animationIDs[0] >= 0) + { + animationid = self->modelData->animationIDs[0]; + } + else if(smf->modelsAmount > 0) + { + animationid = smf->animationIDs[0]; + } + + FModel* animation = mdl; + + if (animationid >= 0) + { + animation = Models[animationid]; + animationData = animation->AttachAnimationData(); + } + + self->modelData->prevAnim = animation->PrecalculateFrame(self->modelData->prevAnim, to, inter, animationData, self->boneComponentData, 0); } - else if(smf->modelsAmount > 0) + else { - animationid = smf->animationIDs[0]; + self->modelData->prevAnim = ModelAnimFrameInterp{}; + + calcFrame(self->modelData->curAnim, tic, std::get(self->modelData->prevAnim)); } - - FModel* animation = mdl; - - if (animationid >= 0) - { - animation = Models[animationid]; - animationData = animation->AttachAnimationData(); - } - - self->modelData->prevAnim = animation->PrecalculateFrame(self->modelData->prevAnim, to, inter, animationData, self->boneComponentData, 0); - } - else - { - self->modelData->prevAnim = ModelAnimFrameInterp{}; - - calcFrame(self->modelData->curAnim, tic, std::get(self->modelData->prevAnim)); } } else @@ -5524,7 +5528,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_ChangeModel, ChangeModelNative) DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimation, SetAnimationNative) { - PARAM_ACTION_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AActor); PARAM_NAME(animName); PARAM_FLOAT(framerate); PARAM_INT(startFrame); @@ -5540,7 +5544,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimation, SetAnimationNative) DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationUI, SetAnimationUINative) { - PARAM_ACTION_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AActor); PARAM_NAME(animName); PARAM_FLOAT(framerate); PARAM_INT(startFrame); @@ -5556,7 +5560,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationUI, SetAnimationUINative) DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationFrameRate, SetAnimationFrameRateNative) { - PARAM_ACTION_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(framerate); SetAnimationFrameRateInternal(self, framerate, 1); @@ -5566,7 +5570,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationFrameRate, SetAnimationFrameRa DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationFrameRateUI, SetAnimationFrameRateUINative) { - PARAM_ACTION_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AActor); PARAM_FLOAT(framerate); SetAnimationFrameRateInternal(self, framerate, I_GetTimeFrac()); @@ -5576,7 +5580,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationFrameRateUI, SetAnimationFrame DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetModelFlag, SetModelFlag) { - PARAM_ACTION_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AActor); PARAM_INT(flag); SetModelFlag(self, flag); @@ -5586,7 +5590,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetModelFlag, SetModelFlag) DEFINE_ACTION_FUNCTION_NATIVE(AActor, ClearModelFlag, ClearModelFlag) { - PARAM_ACTION_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AActor); PARAM_INT(flag); ClearModelFlag(self, flag); @@ -5596,7 +5600,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, ClearModelFlag, ClearModelFlag) DEFINE_ACTION_FUNCTION_NATIVE(AActor, ResetModelFlags, ResetModelFlags) { - PARAM_ACTION_PROLOGUE(AActor); + PARAM_SELF_PROLOGUE(AActor); ResetModelFlags(self); 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_interaction.cpp b/src/playsim/p_interaction.cpp index 0ae77e914..60b6f3059 100644 --- a/src/playsim/p_interaction.cpp +++ b/src/playsim/p_interaction.cpp @@ -1158,7 +1158,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da // Special damage types if (inflictor) { - if (inflictor->flags4 & MF4_SPECTRAL) + if (inflictor->flags4 & MF4_SPECTRAL && !(inflictor->flags9 & MF9_ISPUFF)) { if (player != NULL) { 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..0cde7aac9 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 @@ -1335,46 +1343,44 @@ static bool CanAttackHurt(AActor *victim, AActor *shooter) // //========================================================================== -void P_DoMissileDamage(AActor* inflictor, AActor* target) +void P_DoMissileDamage(AActor* self, AActor* victim) { + const bool ripper = (self->flags2 & MF2_RIP); + // Do poisoning (if using new style poison) - if (inflictor->PoisonDamage > 0 && inflictor->PoisonDuration != INT_MIN) - { - P_PoisonMobj(target, inflictor, inflictor->target, inflictor->PoisonDamage, inflictor->PoisonDuration, inflictor->PoisonPeriod, inflictor->PoisonDamageType); - } + if (self->PoisonDamage > 0 && self->PoisonDuration != INT_MIN) + P_PoisonMobj(victim, self, self->target, self->PoisonDamage, self->PoisonDuration, self->PoisonPeriod, self->PoisonDamageType); // Do damage - int damage = inflictor->GetMissileDamage((inflictor->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1); - if ((damage > 0) || (inflictor->flags6 & MF6_FORCEPAIN) || (inflictor->flags7 & MF7_CAUSEPAIN)) + int damage = ripper ? self->GetMissileDamage(3, 2) : self->GetMissileDamage((self->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1); + if (damage > 0 || (self->flags6 & MF6_FORCEPAIN) || (self->flags7 & MF7_CAUSEPAIN)) { - int newdam = P_DamageMobj(target, inflictor, inflictor->target, damage, inflictor->DamageType); - if (damage > 0) + if (ripper) + S_Sound(self, CHAN_BODY, 0, self->SoundVar(NAME_RipSound), 1.0f, ATTN_IDLE); + + int dealt = P_DamageMobj(victim, self, self->target, damage, self->DamageType); + if (damage > 0 && !(self->flags3 & MF3_BLOODLESSIMPACT) + && !(victim->flags & MF_NOBLOOD) + && !(victim->flags2 & (MF2_INVULNERABLE | MF2_DORMANT | MF2_REFLECTIVE))) { - if ((inflictor->flags5 & MF5_BLOODSPLATTER) && - !(target->flags & MF_NOBLOOD) && - !(target->flags2 & MF2_REFLECTIVE) && - !(target->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)) && - !(inflictor->flags3 & MF3_BLOODLESSIMPACT) && - (pr_checkthing() < 192)) - { - P_BloodSplatter(inflictor->Pos(), target, inflictor->AngleTo(target)); - } - if (!(inflictor->flags3 & MF3_BLOODLESSIMPACT)) - { - P_TraceBleed(newdam > 0 ? newdam : damage, target, inflictor); - } + if (ripper) + P_RipperBlood(self, victim); + else if ((self->flags5 & MF5_BLOODSPLATTER) && pr_checkthing() < 192) + P_BloodSplatter(self->Pos(), victim, self->AngleTo(victim)); + + P_TraceBleed(dealt > 0 ? dealt : damage, victim, self); } } else { - P_GiveBody(target, -damage); + P_GiveBody(victim, -damage); } } -DEFINE_ACTION_FUNCTION(AActor, DoMissileDamage) +DEFINE_ACTION_FUNCTION_NATIVE(AActor, DoMissileDamage, P_DoMissileDamage) { PARAM_SELF_PROLOGUE(AActor); - PARAM_OBJECT_NOT_NULL(target, AActor); - P_DoMissileDamage(self, target); + PARAM_OBJECT_NOT_NULL(victim, AActor); + P_DoMissileDamage(self, victim); return 0; } //========================================================================== @@ -1679,27 +1685,7 @@ bool PIT_CheckThing(FMultiBlockThingsIterator &it, FMultiBlockThingsIterator::Ch if (check == NULL || !*check) { tm.LastRipped[thing] = true; - if (!(thing->flags & MF_NOBLOOD) && - !(thing->flags2 & MF2_REFLECTIVE) && - !(tm.thing->flags3 & MF3_BLOODLESSIMPACT) && - !(thing->flags2 & (MF2_INVULNERABLE | MF2_DORMANT))) - { // Ok to spawn blood - P_RipperBlood(tm.thing, thing); - } - S_Sound(tm.thing, CHAN_BODY, 0, tm.thing->SoundVar(NAME_RipSound), 1, ATTN_IDLE); - - // Do poisoning (if using new style poison) - if (tm.thing->PoisonDamage > 0 && tm.thing->PoisonDuration != INT_MIN) - { - P_PoisonMobj(thing, tm.thing, tm.thing->target, tm.thing->PoisonDamage, tm.thing->PoisonDuration, tm.thing->PoisonPeriod, tm.thing->PoisonDamageType); - } - - damage = tm.thing->GetMissileDamage(3, 2); - int newdam = P_DamageMobj(thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType); - if (!(tm.thing->flags3 & MF3_BLOODLESSIMPACT)) - { - P_TraceBleed(newdam > 0 ? newdam : damage, thing, tm.thing); - } + P_DoMissileDamage(tm.thing, thing); if (thing->flags2 & MF2_PUSHABLE && !(tm.thing->flags2 & MF2_CANNOTPUSH)) { // Push thing @@ -2100,10 +2086,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 +2103,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 +2129,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; @@ -2425,7 +2464,9 @@ bool P_TryMove(AActor *thing, const DVector2 &pos, // If it's a bouncer, let it bounce off its new floor, too. if (thing->BounceFlags & BOUNCE_Floors) { - thing->FloorBounceMissile(tm.floorsector->floorplane); + F3DFloor* ff = nullptr; + NextLowestFloorAt(tm.sector, tm.pos.X, tm.pos.Y, tm.pos.Z, 0, thing->MaxStepHeight, nullptr, &ff); + thing->FloorBounceMissile(ff != nullptr ? *ff->top.plane : tm.floorsector->floorplane, ff != nullptr); } else { @@ -3511,7 +3552,9 @@ bool FSlide::BounceWall(AActor *mo) double movelen; line_t *line; - if (!(mo->BounceFlags & BOUNCE_Walls)) + // The plane bounce flags need to be checked here in case it hit a ramp while + // moving along the xy axes. + if (!(mo->BounceFlags & (BOUNCE_Walls | BOUNCE_Ceilings | BOUNCE_Floors))) { return false; } @@ -3542,17 +3585,22 @@ bool FSlide::BounceWall(AActor *mo) { // Could not find a wall, so bounce off the floor/ceiling instead. double floordist = mo->Z() - mo->floorz; double ceildist = mo->ceilingz - mo->Z(); + F3DFloor* ff = nullptr; if (floordist <= ceildist) { - mo->FloorBounceMissile(mo->Sector->floorplane); - return true; + NextLowestFloorAt(mo->Sector, mo->X(), mo->Y(), mo->Z(), 0, mo->MaxStepHeight, nullptr, &ff); + return !mo->FloorBounceMissile(ff != nullptr ? *ff->top.plane : mo->floorsector->floorplane, ff != nullptr); } else { - mo->FloorBounceMissile(mo->Sector->ceilingplane); - return true; + NextHighestCeilingAt(mo->Sector, mo->X(), mo->Y(), mo->Z(), mo->Top(), 0, nullptr, &ff); + return !mo->FloorBounceMissile(ff != nullptr ? *ff->bottom.plane : mo->ceilingsector->ceilingplane, ff != nullptr); } } + + if (!(mo->BounceFlags & BOUNCE_Walls)) + return false; + line = bestslideline; if (mo->flags & MF_MISSILE) @@ -3726,7 +3774,8 @@ bool P_BounceActor(AActor *mo, AActor *BlockingMobj, bool ontop) else // Don't run through this for MBF-style bounces { // The reflected velocity keeps only about 70% of its original speed - mo->Vel.Z = (mo->Vel.Z - 2. * dot) * mo->bouncefactor; + mo->Vel.Z -= 2. * dot; + mo->Vel *= mo->bouncefactor; } mo->PlayBounceSound(true); @@ -3737,8 +3786,11 @@ bool P_BounceActor(AActor *mo, AActor *BlockingMobj, bool ontop) } else if (mo->BounceFlags & (BOUNCE_AutoOff | BOUNCE_AutoOffFloorOnly)) { - if (!(mo->flags & MF_NOGRAVITY) && (mo->Vel.Z < 3.)) - mo->BounceFlags &= ~BOUNCE_TypeMask; + if (mo->Vel.Z >= 0 || (mo->BounceFlags & BOUNCE_AutoOff)) + { + if (!(mo->flags & MF_NOGRAVITY) && (mo->Vel.Z < 3.)) + mo->BounceFlags &= ~BOUNCE_TypeMask; + } } } if (mo->BounceFlags & BOUNCE_UseBounceState) @@ -4620,6 +4672,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 +4692,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 +4783,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 +4819,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 +4850,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 +4898,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 +4991,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 +5444,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->WorldRailgunPreFired(damagetype, puffclass, p)) + { + 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 +5496,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 +5532,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 +5620,9 @@ void P_RailAttack(FRailParams *p) } } } + + source->Level->localEventManager->WorldRailgunFired(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..1a400a65a 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -1545,6 +1545,13 @@ void DActorModelData::OnDestroy() animationIDs.Reset(); } +void DViewPosition::Serialize(FSerializer& arc) +{ + Super::Serialize(arc); + arc("offset", Offset) + ("flags", Flags); +} + //---------------------------------------------------------------------------- // // PROC P_ExplodeMissile @@ -1732,7 +1739,7 @@ void AActor::PlayBounceSound(bool onfloor) // Returns true if the missile was destroyed //---------------------------------------------------------------------------- -bool AActor::FloorBounceMissile (secplane_t &plane) +bool AActor::FloorBounceMissile (secplane_t &plane, bool is3DFloor) { if (flags & MF_MISSILE) { @@ -1775,9 +1782,13 @@ bool AActor::FloorBounceMissile (secplane_t &plane) } } + DVector3 norm = plane.Normal(); + if (is3DFloor) + norm = -norm; + bool onsky; - if (plane.fC() < 0) + if (norm.Z < 0) { // on ceiling if (!(BounceFlags & BOUNCE_Ceilings)) return true; @@ -1808,11 +1819,11 @@ bool AActor::FloorBounceMissile (secplane_t &plane) return true; } - double dot = (Vel | plane.Normal()) * 2; + double dot = (Vel | norm) * 2; if (BounceFlags & (BOUNCE_HereticType | BOUNCE_MBF)) { - Vel -= plane.Normal() * dot; + Vel -= norm * dot; AngleFromVel(); if (!(BounceFlags & BOUNCE_MBF)) // Heretic projectiles die, MBF projectiles don't. { @@ -1826,7 +1837,7 @@ bool AActor::FloorBounceMissile (secplane_t &plane) else // Don't run through this for MBF-style bounces { // The reflected velocity keeps only about 70% of its original speed - Vel = (Vel - plane.Normal() * dot) * bouncefactor; + Vel = (Vel - norm * dot) * bouncefactor; AngleFromVel(); } @@ -1835,7 +1846,7 @@ bool AActor::FloorBounceMissile (secplane_t &plane) // Set bounce state if (BounceFlags & BOUNCE_UseBounceState) { - FName names[2] = { NAME_Bounce, plane.fC() < 0 ? NAME_Ceiling : NAME_Floor }; + FName names[2] = { NAME_Bounce, norm.Z < 0 ? NAME_Ceiling : NAME_Floor }; FState *bouncestate = FindState(2, names); if (bouncestate != nullptr) { @@ -1850,10 +1861,10 @@ bool AActor::FloorBounceMissile (secplane_t &plane) } else if (BounceFlags & (BOUNCE_AutoOff|BOUNCE_AutoOffFloorOnly)) { - if (plane.fC() > 0 || (BounceFlags & BOUNCE_AutoOff)) + if (norm.Z > 0 || (BounceFlags & BOUNCE_AutoOff)) { // AutoOff only works when bouncing off a floor, not a ceiling (or in compatibility mode.) - if (!(flags & MF_NOGRAVITY) && (Vel.Z < 3)) + if (!(flags & MF_NOGRAVITY) && (norm.Z < 0 || ((Vel | norm) < 3))) BounceFlags &= ~BOUNCE_TypeMask; } } @@ -2646,7 +2657,9 @@ static void P_ZMovement (AActor *mo, double oldfloorz) mo->SetZ(mo->floorz); if (mo->BounceFlags & BOUNCE_Floors) { - mo->FloorBounceMissile (mo->floorsector->floorplane); + F3DFloor* ff = nullptr; + NextLowestFloorAt(mo->Sector, mo->X(), mo->Y(), mo->Z(), 0, mo->MaxStepHeight, nullptr, &ff); + mo->FloorBounceMissile (ff != nullptr ? *ff->top.plane : mo->floorsector->floorplane, ff != nullptr); /* if (!CanJump(mo)) */ return; } else if (mo->flags3 & MF3_NOEXPLODEFLOOR) @@ -2682,7 +2695,9 @@ static void P_ZMovement (AActor *mo, double oldfloorz) } else if (mo->BounceFlags & BOUNCE_MBF && mo->Vel.Z) // check for MBF-like bounce on non-missiles { - mo->FloorBounceMissile(mo->floorsector->floorplane); + F3DFloor* ff = nullptr; + NextLowestFloorAt(mo->Sector, mo->X(), mo->Y(), mo->Z(), 0, mo->MaxStepHeight, nullptr, &ff); + mo->FloorBounceMissile(ff != nullptr ? *ff->top.plane : mo->floorsector->floorplane, ff != nullptr); } if (mo->flags3 & MF3_ISMONSTER) // Blasted mobj falling { @@ -2753,7 +2768,9 @@ static void P_ZMovement (AActor *mo, double oldfloorz) mo->SetZ(mo->ceilingz - mo->Height); if (mo->BounceFlags & BOUNCE_Ceilings) { // ceiling bounce - mo->FloorBounceMissile (mo->ceilingsector->ceilingplane); + F3DFloor* ff = nullptr; + NextHighestCeilingAt(mo->Sector, mo->X(), mo->Y(), mo->Z(), mo->Top(), 0, nullptr, &ff); + mo->FloorBounceMissile(ff != nullptr ? *ff->bottom.plane : mo->ceilingsector->ceilingplane, ff != nullptr); /* if (!CanJump(mo)) */ return; } if (mo->flags & MF_SKULLFLY) @@ -4362,14 +4379,13 @@ void AActor::Tick () } if (Vel.Z != 0 && (BounceFlags & BOUNCE_Actors)) { - bool res = P_BounceActor(this, onmo, true); + if (flags & MF_MISSILE) + P_DoMissileDamage(this, onmo); + // If the bouncer is a missile and has hit the other actor it needs to be exploded here // to be in line with the case when an actor's side is hit. - if (!res && (flags & MF_MISSILE)) - { - P_DoMissileDamage(this, onmo); + if (!P_BounceActor(this, onmo, true) && (flags & MF_MISSILE)) P_ExplodeMissile(this, nullptr, onmo); - } } else { @@ -4435,6 +4451,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) { @@ -5586,16 +5610,13 @@ AActor *FLevelLocals::SpawnPlayer (FPlayerStart *mthing, int playernum, int flag PlayerSpawnPickClass(playernum); - if (( dmflags2 & DF2_SAME_SPAWN_SPOT ) && - ( p->playerstate == PST_REBORN ) && - ( deathmatch == false ) && - ( gameaction != ga_worlddone ) && - ( p->mo != NULL ) && - ( !(p->mo->Sector->Flags & SECF_NORESPAWN) ) && - ( NULL != p->attacker ) && // don't respawn on damaging floors - ( p->mo->Sector->damageamount < TELEFRAG_DAMAGE )) // this really should be a bit smarter... + if ((dmflags2 & DF2_SAME_SPAWN_SPOT) && !deathmatch + && p->mo != nullptr && p->playerstate == PST_REBORN + && gameaction != ga_worlddone + && !(p->mo->Sector->Flags & SECF_NORESPAWN) + && p->LastDamageType != NAME_Suicide) { - spawn = p->mo->Pos(); + spawn = p->LastSafePos; SpawnAngle = p->mo->Angles.Yaw; } else @@ -6652,7 +6673,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 +6767,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 +6838,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 +7848,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_sectors.cpp b/src/playsim/p_sectors.cpp index a9014c2ce..581a5350a 100644 --- a/src/playsim/p_sectors.cpp +++ b/src/playsim/p_sectors.cpp @@ -129,6 +129,28 @@ DEFINE_ACTION_FUNCTION_NATIVE(_Sector, NextSpecialSector, P_NextSpecialSector) ACTION_RETURN_POINTER(P_NextSpecialSector(self, type, nogood)); } +bool sector_t::IsDangerous(const DVector3& pos, double height) const +{ + if (damageamount > 0) + return true; + + auto cl = dyn_cast(ceilingdata.Get()); + if (cl != nullptr && cl->getCrush() > 0) + return true; + + for (auto rover : e->XFloor.ffloors) + { + if ((rover->flags & FF_EXISTS) && rover->model->damageamount > 0 + && pos.Z <= rover->top.plane->ZatPoint(pos) + && pos.Z + height >= rover->bottom.plane->ZatPoint(pos)) + { + return true; + } + } + + return false; +} + // // P_FindLowestFloorSurrounding() // FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS 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_trace.cpp b/src/playsim/p_trace.cpp index b0873c552..cf9f20793 100644 --- a/src/playsim/p_trace.cpp +++ b/src/playsim/p_trace.cpp @@ -496,6 +496,10 @@ bool FTraceInfo::LineCheck(intercept_t *in, double dist, DVector3 hit, bool spec entersector = &DummySector[sectorsel]; sectorsel ^= 1; + // We need to make sure that 3D floors clipping underneath the ground/above the ceiling don't + // accidentally get ignored. + bool setFloor = false, setCeiling = false; + for (auto rover : entersector->e->XFloor.ffloors) { int entershootthrough = !!(rover->flags&FF_SHOOTTHROUGH); @@ -508,8 +512,8 @@ bool FTraceInfo::LineCheck(intercept_t *in, double dist, DVector3 hit, bool spec // clip to the part of the sector we are in if (hit.Z > ff_top) { - // 3D floor height is the same as the floor height. We need to test a second spot to see if it is above or below - if (fabs(bf - ff_top) < EQUAL_EPSILON) + // 3D floor height is the same as the floor height or underground. We need to test a second spot to see if it is above or below + if ((ff_top < bf && !setFloor) || fabs(bf - ff_top) < EQUAL_EPSILON) { double cf = entersector->floorplane.ZatPoint(entersector->centerspot); double ffc = rover->top.plane->ZatPoint(entersector->centerspot); @@ -522,6 +526,7 @@ bool FTraceInfo::LineCheck(intercept_t *in, double dist, DVector3 hit, bool spec // above if (bf < ff_top) { + setFloor = true; entersector->floorplane = *rover->top.plane; entersector->SetTexture(sector_t::floor, *rover->top.texture, false, false); entersector->ClearPortal(sector_t::floor); @@ -530,8 +535,8 @@ bool FTraceInfo::LineCheck(intercept_t *in, double dist, DVector3 hit, bool spec } else if (hit.Z < ff_bottom) { - // 3D floor height is the same as the ceiling height. We need to test a second spot to see if it is above or below - if (fabs(bc - ff_bottom) < EQUAL_EPSILON) + // 3D floor height is the same as the ceiling height or above it. We need to test a second spot to see if it is above or below + if ((ff_bottom > bc && !setCeiling) || fabs(bc - ff_bottom) < EQUAL_EPSILON) { double cc = entersector->ceilingplane.ZatPoint(entersector->centerspot); double fcc = rover->bottom.plane->ZatPoint(entersector->centerspot); @@ -544,6 +549,7 @@ bool FTraceInfo::LineCheck(intercept_t *in, double dist, DVector3 hit, bool spec //below if (bc > ff_bottom) { + setCeiling = true; entersector->ceilingplane = *rover->bottom.plane; entersector->SetTexture(sector_t::ceiling, *rover->bottom.texture, false, false); entersector->ClearPortal(sector_t::ceiling); diff --git a/src/playsim/p_user.cpp b/src/playsim/p_user.cpp index d3294ffd6..65f892903 100644 --- a/src/playsim/p_user.cpp +++ b/src/playsim/p_user.cpp @@ -99,6 +99,7 @@ static FRandom pr_skullpop ("SkullPop"); // [SP] Allows respawn in single player CVAR(Bool, sv_singleplayerrespawn, false, CVAR_SERVERINFO | CVAR_CHEAT) +CVAR(Float, snd_footstepvolume, 1.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // Variables for prediction CVAR(Bool, cl_predict_specials, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) @@ -144,6 +145,8 @@ static DVector3 LastPredictedPosition; static int LastPredictedPortalGroup; static int LastPredictedTic; +static TArray PredictionRNG; + static player_t PredictionPlayerBackup; static AActor *PredictionActor; static TArray PredictionActorBackupArray; @@ -358,6 +361,7 @@ void player_t::CopyFrom(player_t &p, bool copyPSP) MUSINFOactor = p.MUSINFOactor; MUSINFOtics = p.MUSINFOtics; SoundClass = p.SoundClass; + LastSafePos = p.LastSafePos; angleOffsetTargets = p.angleOffsetTargets; if (copyPSP) { @@ -1204,15 +1208,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) @@ -1297,6 +1292,13 @@ void P_PlayerThink (player_t *player) player->SubtitleCounter--; } + if (player->playerstate == PST_LIVE + && player->mo->Z() <= player->mo->floorz + && !player->mo->Sector->IsDangerous(player->mo->Pos(), player->mo->Height)) + { + player->LastSafePos = player->mo->Pos(); + } + // Bots do not think in freeze mode. if (player->mo->Level->isFrozen() && player->Bot != nullptr) { @@ -1470,6 +1472,8 @@ void P_PredictPlayer (player_t *player) return; } + FRandom::SaveRNGState(PredictionRNG); + // Save original values for restoration later PredictionPlayerBackup.CopyFrom(*player, false); @@ -1609,6 +1613,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); @@ -1781,7 +1787,9 @@ void player_t::Serialize(FSerializer &arc) ("onground", onground) ("musinfoactor", MUSINFOactor) ("musinfotics", MUSINFOtics) - ("soundclass", SoundClass); + ("soundclass", SoundClass) + ("angleoffsettargets", angleOffsetTargets) + ("lastsafepos", LastSafePos); if (arc.isWriting ()) { 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.cpp b/src/scripting/thingdef.cpp index 6affab4c3..bdec840f5 100644 --- a/src/scripting/thingdef.cpp +++ b/src/scripting/thingdef.cpp @@ -65,6 +65,7 @@ static TMap StateSourceLines; static FScriptPosition unknownstatesource("unknown file", 0); EXTERN_CVAR(Bool, strictdecorate); +EXTERN_CVAR(Bool, warningstoerrors); //========================================================================== // @@ -464,8 +465,27 @@ void LoadActors() if (FScriptPosition::ErrorCounter > 0) { - I_Error("%d errors while parsing DECORATE scripts", FScriptPosition::ErrorCounter); + if (FScriptPosition::WarnCounter > 0) + { + I_Error("%d errors, %d warnings while parsing scripts", FScriptPosition::ErrorCounter, FScriptPosition::WarnCounter); + } + else + { + I_Error("%d errors while parsing scripts", FScriptPosition::ErrorCounter); + } } + else if (FScriptPosition::WarnCounter > 0) + { + if(warningstoerrors) + { + I_Error("%d warnings while parsing scripts\n", FScriptPosition::WarnCounter); + } + else + { + Printf(TEXTCOLOR_ORANGE "%d warnings while parsing scripts\n", FScriptPosition::WarnCounter); + } + } + FScriptPosition::ResetErrorCounter(); // AllActorClasses hasn'T been set up yet. for (int i = PClass::AllClasses.Size() - 1; i >= 0; i--) 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/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index 18cd8aa32..2cf04fd80 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -1851,8 +1851,9 @@ DEFINE_ACTION_FUNCTION(AActor, BouncePlane) { PARAM_SELF_PROLOGUE(AActor); PARAM_POINTER(plane, secplane_t); + PARAM_BOOL(is3DFloor); - ACTION_RETURN_BOOL(self->FloorBounceMissile(*plane)); + ACTION_RETURN_BOOL(self->FloorBounceMissile(*plane, is3DFloor)); } DEFINE_ACTION_FUNCTION(AActor, PlayBounceSound) 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/serializer_doom.h b/src/serializer_doom.h index b399568c2..40b6710a1 100644 --- a/src/serializer_doom.h +++ b/src/serializer_doom.h @@ -42,6 +42,7 @@ FSerializer& Serialize(FSerializer& arc, const char* key, char& value, char* def FSerializer &Serialize(FSerializer &arc, const char *key, ticcmd_t &sid, ticcmd_t *def); FSerializer &Serialize(FSerializer &arc, const char *key, usercmd_t &cmd, usercmd_t *def); FSerializer &Serialize(FSerializer &arc, const char *key, FInterpolator &rs, FInterpolator *def); +FSerializer& Serialize(FSerializer& arc, const char* key, struct FStandaloneAnimation& value, struct FStandaloneAnimation* defval); template<> FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj *&value, FPolyObj **defval); template<> FSerializer &Serialize(FSerializer &arc, const char *key, sector_t *&value, sector_t **defval); 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..f63ca3fea 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! @@ -99,6 +99,7 @@ const char *GetVersionString(); // This is so that derivates can use the same savegame versions without worrying about engine compatibility #define GAMESIG "VKDOOM" #define BASEWAD "vkdoom.pk3" +// Set OPTIONALWAD to "" (null) to disable searching for it #define OPTIONALWAD "game_support.pk3" #define GZDOOM 1 #define VR3D_ENABLED 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/language.0 b/wadsrc/static/language.0 index cf004c828..3eefd93d7 100644 --- a/wadsrc/static/language.0 +++ b/wadsrc/static/language.0 @@ -873,12 +873,12 @@ Appearance,DSPLYMNU_APPEARANCE,,,,Vzhled,Udseende,Spieldarstellung,,Aspekto,Apar Advanced Display Options,DSPLYMNU_ADVANCED,,,,Grafika (pokročilé),Avancerede visningsindstillinger,Erweiterte Anzeigeoptionen,,Altnivelaj ekran-agordoj,Opciones avanzadas de visualización,,Näytön lisäasetukset,Options d'affichage avancées,Napredne postavke zaslona,Speciális megjelenítési beállítások,Opzioni di visualizzazione avanzate,高度なディスプレイオプション,고급 디스플레이 옵션,Geavanceerde Weergave Opties,Avanserte visningsalternativer,Zaawansowane Opcje Wyświetlania,Opções de vídeo avançadas,,Opțiuni avansate de afișare,Расширенные настройки экрана,Напредне опције приказа,Avancerade visningsalternativ,Gelişmiş Görüntüleme Seçenekleri,Додаткові параметри відображення, ,,IWAD/Game picker,,,,,,,,,,,,,,,,,,,,,,,,,,,, Select which game file to run.,PICKER_SELECT,,,,"Vyberte, jaký herní soubor spustit.","Vælg, hvilket spil du vil spille",Bitte wähle ein Spiel aus.,,Elektu kiun ludodosieron ruli.,,,Valitse suoritettava pelitiedosto.,Sélectionner le jeu à jouer,Odaberi koju igru želiš pokrenuti.,"Válassza ki, hogy melyik játékfájlt futtassa.",Selezionare il file di gioco da eseguire.,実行するゲームファイルを選択します。,실행할 게임 파일을 선택합니다.,Selecteer welk spel je wilt spelen,Velg hvilket spill du vil spille,"Wybierz, który plik gry uruchomić.",Selecione qual arquivo de jogo rodar.,,Selectați ce fișier de joc să rulați.,Выбор файла игры для запуска.,Изаберите коју датотеку игре желите да покренете.,Välj vilket spel du vill spela,Hangi oyunu oynayacağınızı seçin,Виберіть файл гри для запуску., -Play Game,PICKER_PLAY,,,,Hrát hru,Start spil,Spielen,,Ludi ludon,,,Pelin pelaaminen,Démarrer le jeu,Zaigraj igru,Játék lejátszása,Esegui gioco,ゲームをプレイする,게임 플레이,Spel starten,Start spill,Graj,Jogar,,Joacă jocul,Играть,Играј игру,Starta spel,Oyunu Başlat,Запустити гру, +Play Game,PICKER_PLAY,,,,Hrát hru,Start spil,Spielen,,Ludi ludon,,,Pelin pelaaminen,Démarrer le jeu,Zaigraj igru,Játék lejátszása,Esegui gioco,プレイ,게임 플레이,Spel starten,Start spill,Graj,Jogar,,Joacă jocul,Играть,Играј игру,Starta spel,Oyunu Başlat,Запустити гру, Exit,PICKER_EXIT,,,,Odejít,Afslut,Verlassen,,Eliri,,,Poistu,Quitter,Izlaz,Kilépés,Esci,終了,종료,Verlaten,Avslutt,Wyjdź,Sair,,Ieșire,Выход,Изађи,Avsluta,Çıkış,Вихід, General,PICKER_GENERAL,,,,Obecné,Generelt,Allgemein,,Ĝenerala,,,Yleistä,Général,Općenito,Általános,Generale,一般,일반,Algemeen,Generelt,Ogólne,Geral,,,Общее,Генерал,Allmänt,Genel,Загальні, Extra Graphics,PICKER_EXTRA,,,,Grafické doplňky,Ekstra grafik,Extragrafiken,,Ekstra grafiko,,,Extra Graphics,Graphiques supplémentaires,Dodatna grafika,Extra grafika,Grafica extra,追加グラフィックス,추가 그래픽,Extra afbeeldingen,Ekstra grafikk,Ekstra grafiki,Gráficos extras,,Grafică suplimentară,Доп. графика,Ектра Грапхицс,Extra grafik,Ekstra Grafikler,Додаткова графіка, Fullscreen,PICKER_FULLSCREEN,,,,Přes celou obrazovku,Fuld skærm,Vollbild,,Plena ekrano,,,Koko näyttö,Plein écran,Puni zaslon,Teljes képernyő,Schermo intero,フルスクリーン,전체 화면,Volledig scherm,Fullskjerm,Pełny ekran,Tela cheia,,Ecran complet,Полный экран,Цео екран,Fullskärm,Tam Ekran,Повноекранний режим, -Disable autoload,PICKER_NOAUTOLOAD,,,,Zakázat autoload,Deaktiver autoload,Autoload deaktivieren,,Malvalidigi aŭtoŝargon,,,Poista automaattinen lataus käytöstä,Désactiver le chargement automatique,Onemogući automatsko učitavanje,Automatikus betöltés kikapcsolása,Disabilita il caricamento automatico,オートロードを無効にする,자동 로드 비활성화,Autoload uitschakelen,Deaktiver autolading,Wyłącz auto-ładowanie,Desativar autocarregam.,,Dezactivați încărcarea automată,Откл. автозагрузку,Онемогући аутоматско учитавање,Inaktivera autoload,Otomatik yükleme yok,Вимкнути автозавантаження, +Disable autoload,PICKER_NOAUTOLOAD,,,,Zakázat autoload,Deaktiver autoload,Autoload deaktivieren,,Malvalidigi aŭtoŝargon,,,Poista automaattinen lataus käytöstä,Désactiver le chargement automatique,Onemogući automatsko učitavanje,Automatikus betöltés kikapcsolása,Disabilita il caricamento automatico,オートロード無効,자동 로드 비활성화,Autoload uitschakelen,Deaktiver autolading,Wyłącz auto-ładowanie,Desativar autocarregam.,,Dezactivați încărcarea automată,Откл. автозагрузку,Онемогући аутоматско учитавање,Inaktivera autoload,Otomatik yükleme yok,Вимкнути автозавантаження, Don't ask me again,PICKER_DONTASK,,,,Již se neptat,Spørg mig ikke igen,Nicht nochmal fragen,,Ne demandu min denove,,,Älä kysy uudestaan,Ne me demandez plus rien,Više me ne pitaj,Ne kérdezz újra,Non chiedermelo più,再度聞かない,다시 묻지 마세요,Vraag me niet opnieuw,Ikke spør meg igjen,Nie pytaj ponownie,Não perguntar de novo,,Nu mă mai întrebați din nou,Не спрашивать снова,Не питај ме поново,Fråga mig inte igen,Bir daha sorma.,Не запитуйте мене більше, Lights,PICKER_LIGHTS,,,,Světla,Lys,Lichtdefinitionen,,Lumoj,,,Valot,Lumières,Svjetla,Fények,Luci,ライト,조명,Verlichting,Lysdefinisjoner,Oświetlenie,Luzes,,Lumini,Освещение,Светла,Definitioner av ljus,Işık tanımları,Освітлення, Brightmaps,PICKER_BRIGHTMAPS,,,,,,,,Helomapoj,,,Brightmaps,Cartes lumineuses,Svijetle karte,Brightmaps,Mappe luminose,ブライトマップ,브라이트맵,Heldermaps,Lyskart,Mapowanie świateł,,,,Карты освещения,Бригхтмапс,Ljuskartor,Brightmaps,Яскраві карти, @@ -891,4 +891,4 @@ Game,PICKER_TAB_PLAY,,,,Hra,Spil,Spiel,,Ludo,Juego,,Peli,Jeu,Igra,Játék,Gioco, Enable this controller,JOYMNU_JOYENABLE,Option to enable or disable individual controllers/joysticks when configuring it in the Controller Options menu,,,Povolit tento ovladač,Aktivér denne controller,Diesen Controller aktivieren,,Ŝalti ĉi tiun ludregilon,Habilitar este controlador,,Aktivoi tämä ohjain,Activer ce contrôleur,Omogući ovaj kontroler,Engedélyezze ezt a vezérlőt,Attiva questo controller,このコントローラーを有効にする,이 컨트롤러 활성화,Deze controller inschakelen,Aktiver denne kontrolleren,Włącz ten kontroler,Habilitar este controle,,Activați acest controler,Включить этот контроллер,Омогућите овај контролер,Aktivera denna styrenhet,Bu denetleyiciyi etkinleştirin,Увімкніть цей контролер, Open Main Menu,CNTRLMNU_OPEN_MAIN,,,,Otevřít hlavní menu,Åbn hovedmenuen,Hauptmenü öffnen,,Malfermi ĉefan menuon,Abrir el menú principal,,Avaa päävalikko,Ouvrir le menu principal,Otvori glavni izbornik,Főmenü megnyitása,Apri il menu principale,メインメニューを開く,메인 메뉴 열기,Hoofdmenu openen,Åpne hovedmenyen,Otwórz meny główne,Abrir menu principal,,Deschideți meniul principal,Открыть главное меню,Отворите главни мени,Öppna huvudmenyn,Ana Menüyü Aç,Відкрити головне меню, ,,New content,,,,,,,,,,,,,,,,,,,,,,,,,,,, -Module replayer,MODMNU_REPLAYER,,,,Nastavení přehrávače modulů,Modulafspiller,Modul-Spieler,,Legilo de moduloj,Reproductor de módulos,,,Lecteur de module,,,Module replayer,,,Module replayer,Modulavspiller,,Reprodutor de módulos,,Redare a modulelor,Модуль-повторитель,,Modulåterspelare,,Module replayer, \ No newline at end of file +Module replayer,MODMNU_REPLAYER,,,,Nastavení přehrávače modulů,Modulafspiller,Modul-Spieler,,Legilo de moduloj,Reproductor de módulos,,,Lecteur de module,Ponavljač modula,,Module replayer,モジュール リプレイヤー,,Module replayer,Modulavspiller,,Reprodutor de módulos,,Redare a modulelor,Модуль-повторитель,,Modulåterspelare,,Module replayer, \ No newline at end of file diff --git a/wadsrc/static/language.csv b/wadsrc/static/language.csv index eaa712791..4661e4598 100644 --- a/wadsrc/static/language.csv +++ b/wadsrc/static/language.csv @@ -1911,7 +1911,7 @@ armură.",Игрока %o обслюнявила бронированная дв %o was hit by %k's propulsor.,OB_MPP_SPLASH,,,,%o byl@[ao_cs] zasažen@[ao_cs] propulzorem hráčem %k.,%o blev ramt af %k's propulsor.,%o wurde von %ks Propeller getroffen,,%o estas trafita de la propulsilo de %k.,%o fue impactad@[ao_esp] por el propulsor de %k.,,%k osui %o parkaan työntimellään.,%o à été frappé@[e_fr] par le propulseur de %k.,,%o elérte %k elhárítóját.,%o è stato colpito dal propulsore di %k.,%o に %k のロケットゾーチャーがとどいた。,%o 은(는) %k 의 저치 추진기의 방사 능력을 우습게 봤습니다.,%o werd geraakt door %k's propulsor.,%o ble truffet av %k's propulsor.,%o został@[ao_pl] trafion@[adj_pl] pędnikiem %k.,%o foi atingid@[ao_ptb] pelo propulsor de %k.,,%o a fost învins de acceleratorul lui %k.,Игрок %o подстрелен из ускорителя зорча %k.,%o је ударен %k погоном.,%o blev träffad av %ks propulsor.,"%o, %k tarafından yenildi.",%k врази@[adj_1_ua] %o з прискореного зорчера. %o was phase zorched by %k.,OB_MPPHASEZORCH,,,,%o byl@[ao_cs] bzukr-vyfázován@[ao_cs] hráčem %k.,%o blev fasezorched af %k.,%o wurde von %k wegsynchronisiert,,%o estas fluktuantzorĉita de %k.,%o fue electrizad@[ao_esp] en fase por %k.,,%k vaiheiszorchasi %o paran.,%o à été phasé@[e_fr] par %k.,,%o fázis zorker áldozata lett %k által.,%o è stato phase-zorchato da %k.,%o は %k によってスベスベにされた。,%o 은(는) %k 의 전자 자쳐에 의해 쓰러졌습니다.,%o was fase zorched door %k.,%o ble fasebrent av %k.,%o został@[ao_pl] został sfazowan@[adj_pl] przez %k.,%o foi atingid@[ao_ptb] pelo zorcher fásico por %k.,,%o a fost înfrânt de zorcherul fazat al lui %k.,Игрок %o получил заряд из фазерного зорчера %k.,%o је фазно торчован %k.,%o blev faszorchad av %k.,"%o, %k tarafından yenildi.",%k фазорчну@[adj_1_ua] %o. %o fell prey to %k's LAZ device.,OB_MPLAZ_BOOM,,,,%o padl@[ao_cs] velkodosahovému bzukru hráče %k.,%o blev offer for %k's LAZ-enhed.,%o fiel %ks Flächenzorcher zum Opfer,,%o iĝis predo de la LAZ-aparato de %k.,%o cayó pres@[ao_esp] ante el dispositivo LAZ de %k.,,%o lankesi pelaajan %k SAZ-laitteen uhriksi.,%o est devenu@[e_fr] la proie du ZZL de %k.,,%o prédául lett ejtve %k LAZ készülékével.,%o è stato preda del dispositivo LAZ di %k.,%o は %k のLAZデバイスのえじきになった。,%k 이(가) LAZ 장치를 들기 전에 %o 은(는) 도망쳐야 했습니다.,%o viel ten prooi aan %k's LAZ apparaat.,%o ble offer for %k's LAZ-enhet.,%o stał@[ao_pl] się ofiarą urządzenia LAZ %k.,%o foi vítima do dispositivo LAZ de %k.,,%o a căzut pradă dispozitivului LAZ al lui %k.,Игрок %o склонился перед «ЗБР» игрока %k.,%o је постао жртва %k ЛАЗ уређаја.,%o föll offer för %ks LAZ-enhet.,"%o, %k tarafından lazzlandı.",%o па@[adj_1_ua] жертвою LAZ девайсу %k. -%o was lazzed by %k.,OB_MPLAZ_SPLASH,,,,%o byl@[ao_cs] odbzukrován hráčem %k.,%o blev lazzed af %k.,%o wurde von %k weggebeamt.,,%o estas LAZ-ita de %k.,%o fue LAZeado por %k.,,%k sazzasi pelaajaan %o.,%o à été pris@[e_fr] par le ZZL de %k.,,%o el lett lazzázva %k által.,%o è stato lazzato da %k.,%o は %k にとかされた。,%o 은(는) %k 의 LAZ 장치 범위를 벗어날 수 없었습니다.,%o werd gelazed door %k.,%o ble lazzed av %k.,%o został@[ao_pl] zLAZowan@[adj_pl] przez %k.,%o foi LAZead@[ao_ptb] por %k.,,%o a fost stropit de dispozitivul LAZ al lui %k.,Игрок %o получил заряд из «ЗБР» игрока %k.,%o је ЛАЗ-овао %k.,%o blev lazzad av %k.,"%o, %k tarafından lazzlandı.",%k LAZну@[adj_1_ua] %o. +%o was lazzed by %k.,OB_MPBFG_SPLASH,,chex,,%o byl@[ao_cs] odbzukrován hráčem %k.,%o blev lazzed af %k.,%o wurde von %k weggebeamt.,,%o estas LAZ-ita de %k.,%o fue LAZeado por %k.,,%k sazzasi pelaajaan %o.,%o à été pris@[e_fr] par le ZZL de %k.,,%o el lett lazzázva %k által.,%o è stato lazzato da %k.,%o は %k にとかされた。,%o 은(는) %k 의 LAZ 장치 범위를 벗어날 수 없었습니다.,%o werd gelazed door %k.,%o ble lazzed av %k.,%o został@[ao_pl] zLAZowan@[adj_pl] przez %k.,%o foi LAZead@[ao_ptb] por %k.,,%o a fost stropit de dispozitivul LAZ al lui %k.,Игрок %o получил заряд из «ЗБР» игрока %k.,%o је ЛАЗ-овао %k.,%o blev lazzad av %k.,"%o, %k tarafından lazzlandı.",%k LAZну@[adj_1_ua] %o. ,,Miscellaneous,,,,,,,,,,,,,,,,,,,,,,,,,,, E1M1: Landing Zone,CHUSTR_E1M1,,,,E1M1: Přistávací zóna,E1M1: Landingszone,E1M1: Landezone,,E1M1: Surterejo,E1M1: Zona de Aterrizaje,,E1M1: Laskeutumisalue,E1M1: La piste d'atterissage,,E1M1: Leszállópálya,E1M1: Zona di Atterraggio,E1M1: ちゃくりく ちてん,E1M1: 착륙 지점,E1M1: Landingszone,E1M1: Landingssone,E1M1: Strefa Lądowania,E1M1: Zona de Pouso,E1M1: Zona de Aterragem,E1M1: Pista de Aterizare,E1M1: Зона приземления,Е1М1: Зона слетања,E1M1: Landningszon,E1M1: İniş Bölgesi,E1M1: Зона висадки E1M2: Storage Facility,CHUSTR_E1M2,,,,E1M2: Skladiště,E1M2: Opbevaringsanlæg,E1M2: Lagerhalle,,E1M2: Stokejo,E1M2: Instalación de Almacenamiento,,E1M2: Varastolaitos,E1M2: Le centre de stockage,,E1M2: Raktárépület,E1M2: Struttura di Immagazzinamento,E1M2: ほかん しせつ,E1M2: 저장 시설,E1M2: Opslagfaciliteit,E1M2: Lagringsanlegg,E1M2: Magazyn,E1M2: Depósito,E1M2: Armazém,E1M2: Depozitul,E1M2: Хранилище,Е1М2: Складиште,E1M2: Förvaringsanläggning,E1M2: Depolama Tesisi,E1M2: Сховище @@ -3415,4 +3415,5 @@ Restart level on Death,MISCMNU_RESTARTONDEATH,Disables autoloading save on death Pistol Start,GMPLYMNU_PISTOLSTART,Resets inventory on every map,,,Začít jen s pistolí,Start med pistol,Pistolenstart,,Komenci kun pistolo,,,,Démarrage du pistolet,,,Avvio pistola,ピストルスタート,,Start met pistool,Start med pistol,Start z pistoletem,Iniciar com a pistola,,,Пистолет в начале,,Starta med pistol,Tabanca ile başlayın, Allow creation of zombie players,CMPTMNU_VOODOOZOMBIES,,,,Povolit zombie hráče,,Erlaube Zombieplayer,,Permesi kreadon de zombiaj ludantoj,,,,,,,,ゾンビプレイヤーの生成許可,,,,Pozwól na tworzenie graczy-zombie,Permitir criação de jogadores zumbi,,,Разрешить создание игроков-зомби,,,, ignore floor z when teleporting,CMPTMNU_FDTELEPORT,,,,Při teleportu ignorovat výšku podlahy,,Ignoriere Fußbodenhöhe bem Teleportieren,,Ignori altecon de planko dum teleportado,,,,,,,,テレポート時にfloor z値を無視する,,,,Ignoruj wysokość podłogi podczas teleportacji,Ignorar altura do chão ao teletransportar,,,игнорирование диагонали пола (Z) при телепортации,,,, -Forced-Perspective,OPTVAL_ANAMORPHIC,"Hardware Sprite Clipping mode that gives the illusion of a sprite being in a different position than it really is, allowing it to be rendered under the floor despite clipping",,,,,Erzwungene Perspektive,,,,,,,,,,,,,,,,,,Перспективный (анаморфотный),,,, \ No newline at end of file +Forced-Perspective,OPTVAL_ANAMORPHIC,"Hardware Sprite Clipping mode that gives the illusion of a sprite being in a different position than it really is, allowing it to be rendered under the floor despite clipping",,,Vynucená perspektiva,,Erzwungene Perspektive,,Devigita perspektivo,,,,,,,,,,,,,Perspectiva forçada,,,Принудительно перспективный,,,, +Footstep Volume,SNDMENU_FOOTSTEPVOLUME,,,,Hlasitost kroků,,Schrittlautstärke,,,,,,,,,,,,,,,Volume dos passos,,,Громкость шагов,,,, \ No newline at end of file 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..47db8c4b5 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(); @@ -864,7 +866,7 @@ class Actor : Thinker native native void PlayPushSound(); native bool BounceActor(Actor blocking, bool onTop); native bool BounceWall(Line l = null); - native bool BouncePlane(readonly plane); + native bool BouncePlane(readonly plane, bool is3DFloor = false); native void PlayBounceSound(bool onFloor); native bool ReflectOffActor(Actor blocking); 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..b1aaf30c8 100644 --- a/wadsrc/static/zscript/events.zs +++ b/wadsrc/static/zscript/events.zs @@ -97,6 +97,16 @@ struct WorldEvent native play version("2.4") native readonly bool DamageIsRadius; native int NewDamage; native readonly State CrushedState; + native readonly int AttackLineFlags; + 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; + native readonly FRailParams RailParams; } struct PlayerEvent native play version("2.4") @@ -155,6 +165,10 @@ 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 bool WorldRailgunPreFired(WorldEvent e) { return false; } + virtual void WorldRailgunFired(WorldEvent e) {} + 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" diff --git a/wadsrc_extra/static/language.csv b/wadsrc_extra/static/language.csv index 2a62c0fcc..c2bd82f36 100644 --- a/wadsrc_extra/static/language.csv +++ b/wadsrc_extra/static/language.csv @@ -10701,8 +10701,8 @@ Where's the illegal tap?,TXT_RPLY2_SCRIPT02_D98540_WHERE,〃 (〃),,,Kde je nele どれをイジるかまでは知らん。 配電機はここの下水処理場にある。",그게 어딨는지 말해주지. 왜 그 동력선에 손을 대려고 하는지는 모르겠지마는. 저 앞 하수도 안에 있다네.,"Ik zal je vertellen waar hij is, maar ik weet niet met wie je met de koppeling zult knoeien. Het is hier in de rioolwaterzuiveringsinstallatie.","Jeg skal si deg hvor den er, men jeg vet ikke hvem sin kobling du skal tukle med. Den er her i kloakkanlegget.","Powiem ci gdzie jest, ale nie wiem czyj to odbierak, przy którym będziesz majstrować. Jest w oczyszczalni ścieków.","Vou te dizer onde está, mas não sei em qual ligação você vai acabar mexendo. Fica bem aqui na usina de tratamento de esgoto.",,"Îți voi spune unde este, dar nu știu de care cuplaj te vei lega. Este chiar aici, în sistemul de canalizare.","Я скажу тебе, где она, но не знаю, которая тебе нужна. Она прямо здесь, в очистной системе.",,"Jag ska berätta var den är, men jag vet inte vems koppling du kommer att mixtra med. Den finns här i reningsverket.","Nerede olduğunu söyleyeceğim, ama kimin bağlantılarını kurcalayacağınızı bilmiyorum. Tam burada, kanalizasyon tesisinde." Thanks,TXT_RPLY0_SCRIPT02_D100056_THANK,〃 (〃),,,Díky.,Tak,Danke,,Dankon.,Gracias.,,Kiitos,Merci.,,Kösz.,Grazie,ありがとう。,그렇군요.,Bedankt,Takk skal du ha.,Dzięki,Obrigado,,Mulțumesc,Спасибо.,,Tack,Teşekkürler. -That's right here in the sewage plant. But it's the Front's coupling. Whoever told you that it was the Order's was wrong.,TXT_DLG_SCRIPT02_D101572_THATS,〃 (〃),,,"Síť je přímo tady, v čističce. Ale je to spojka Fronty. Kdo ti řekl, že je Řádu, se mýlil.","Det er lige her i kloakanlægget. Men det er Fronts kobling. Den, der fortalte dig, at det var Ordenens, tog fejl.","Genau hier, in der Kläranlage. Aber sie gehört der Front. Wer auch immer dir gesagt hat, sie gehöre dem Orden, hat gelogen.",,"Ĝi estas ĉi tie, en la kloakpurigejo, sed ĝi estas la kuplado de la Fronto. Tiu ajn, kiu diris, ke ĝi estas de La Ordeno, eraris.","Está justo aquí en la planta de aguas residuales, pero es el acoplamiento del Frente. Quien te haya dicho que era de La Orden se ha equivocado. -","Está justo aquí en la planta de aguas residuales, pero es el acoplamiento del Frente. Quien te haya dicho que era de La Orden te dijo mal. +That's right here in the sewage plant. But it's the Front's coupling. Whoever told you that it was the Order's was wrong.,TXT_DLG_SCRIPT02_D101572_THATS,〃 (〃),,,"Síť je přímo tady, v čističce. Ale je to spojka Fronty. Kdo ti řekl, že je Řádu, se mýlil.","Det er lige her i kloakanlægget. Men det er Fronts kobling. Den, der fortalte dig, at det var Ordenens, tog fejl.","Genau hier, in der Kläranlage. Aber sie gehört der Front. Wer auch immer dir gesagt hat, sie gehöre dem Orden, hat gelogen.",,"Ĝi estas ĉi tie, en la kloakpurigejo, sed ĝi estas la kuplado de la Fronto. Kiu diris, ke ĝi estas de La Ordeno, tiu ajn eraris.","Está justo aquí en la planta de aguas residuales, aunque es el acoplamiento del Frente. Quien te haya dicho que era de La Orden se ha equivocado. +","Está justo aquí en la planta de aguas residuales, aunque es el acoplamiento del Frente. Quien te haya dicho que era de La Orden te dijo mal. ","Se on täällä jätelaitoksella. Mutta se on Rintaman kytkentä. Kuka sinulle kertoikaan sen kuuluvan Veljeskunnalle, oli väärässä.","Il se trouve ici dans le traitement des égouts, mais c'est le coupleur du Front . Celui qui vous a dit qu'il appartient à l'Ordre se plante.",,"Igen, jól hallottad, a szennyvíz tisztítóban. Ez azonban a Front kapcsolója. Akárki mondta, hogy a Rendé, hazudott neked.",È proprio qui nell'impianto fognario. Ma è il giunto del Fronte. Chiunque ti abbia detto che fosse il giunto dell'Ordine si sbagliava.,"正にここの下水処理場にあるさ。 だがそれはフロントにも繋いでいる。 そいつがオーダーの所有物と言われてたが、 @@ -12900,10 +12900,10 @@ What an up guy.,TXT_BBSUB_SCRIPT05_D4548_THEOR,MAP05: Prison Guard.,,,Takový ve Great. Now we're shopping for the Rat King.,TXT_BBSUB_SCRIPT06_D3032_BRING,〃,,,"Super, teď máme nakupovat pro Krysího krále.",Alle er så trængende. Nu skal vi købe ind til rottekongen.,"Klasse, jetzt machen wir die Arbeit für den Rattenkönig.",,,Genial. Ahora vamos de compras para el Rey de las Ratas.,,"Loistavaa, hoidamme nyt Rottakuninkaan ostoksia.","Parfait, maintenant on fait du shopping pour le roi des rats.",,Kiváló. A Patkány Király bevásárlói lettünk.,Grandioso. Adesso stiamo facendo shopping con il re dei ratti.,素敵ね。私達がラットキングのために買い物とか。,끝내준다. 이제 시궁쥐 왕의 심부름도 해야 한다니.,Geweldig. Nu zijn we aan het winkelen voor de rattenkoning.,Flott. Nå handler vi til Rottekongen.,Świetnie. Teraz robimy zakupy dla Króla Szczurów.,Que beleza. Agora estamos fazendo comprinhas pro Rato-Rei.,,Grozav. Acum facem cumpărături pentru Regele Șobolanilor.,Классно. Мы идём за покупками для Крысиного короля.,,Toppen. Nu ska vi shoppa för råttkungen.,Harika. Şimdi de Fare Kral için alışveriş yapıyoruz. "I bet this guy smells really bad, too.",TXT_BBSUB_SCRIPT06_D4548_OPENT,〃,,,"Hádám, že tenhle chlápek taky pěkně smrdí.","Jeg vil vædde med, at han også lugter rigtig dårligt.","Ich wette, dieser Kerl riecht auch übel.",,,"Apuesto a que este tipo huele muy mal, también.",,"Lyön vetoa, että tämä tyyppi myös haisee tosi pahalta.","Je parie que ce type sent salement mauvais, aussi.",,"Le merném fogadni, hogy ez a pacák is büdös.",Scommetto che questo tizio ha pure un cattivo odore.,この男も酷い悪臭がすると賭けるわ。,얘도 분명 냄새 하나는 죽여줄 거야.,Ik durf te wedden dat deze kerel ook heel erg stinkt.,Jeg vedder på at denne fyren også lukter fælt.,"Założę się, że ten facet też bardzo śmierdzi.",Aposto que esse cara fede pra caramba também.,,Pun pariu că tipul ăsta mai și pute.,"Уверена, этот парень воняет особенно ужасно.",,Jag slår vad om att den här killen luktar riktigt illa också.,Eminim bu adam da çok kötü kokuyordur. More sewers... Great. Wonder what lives down there.,TXT_BBSUB_SCRIPT06_D9096_YOUHA,〃,,,"Další stoky... skvělý. Zajímalo by mě, co tam dole žije.",Flere kloakker... Fedt. Gad vide hvad der bor dernede.,"Mehr Kanäle, toll... Ich frage mich, was da unten lebt.",,,Más alcantarillas... Genial. Me pregunto qué vivirá ahí abajo.,,Lisää viemäreitä... Mahtavaa. Kukakohan elää täällä alhaalla.,Plus d'égouts.. Youpi. Je me demande qui vit là dedans.,,Még több kanális... kiváló. Mi lakhat odalent?,Altre fogne... fantastico. Mi domando cosa vive qua giù.,更なる下水道...ヤッバ。下に何が住んでるのか不思議に思うわ。,또 하수도라... 끝내준다. 저 아래에 또 뭐가 살고 있을까?,Meer riolering.... Geweldig. Ik vraag me af wat daar beneden leeft.,Mer kloakk... Flott. Lurer på hva som bor der nede.,Więcej kanałów... Świetnie. Ciekawe co tam mieszka.,Mais esgotos... Que legal. O que será que vive lá embaixo?,,Mai multe canale... Grozav. Mă întreb ce-o mai trăi jos aici.,"Снова канализация... Замечательно. Интересно, кого мы там встретим?",,Mer kloaker... Toppen. Undrar vad som bor där nere.,Daha fazla kanalizasyon. Harika. Acaba orada ne yaşıyor? -Dead end here. We'd better keep looking.,TXT_BBSUB_SCRIPT07_D6064_PROGR,MAP07: Computer room.,,,Tudy cesta nevede. Měli bychom se podívat dál.,Det er en blindgyde her. Vi må hellere lede videre.,Sackgasse. Wir sollten es woanders versuchen.,,,Un callejón sin salida. Deberíamos seguir buscando.,,Umpikuja tässä. Parasta jatkaa etsintää.,"Une impasse. Bon, on continue de chercher.",,Zsákutca. Folytatnunk kell a keresést.,Qui c'è un vicolo cieco. Meglio continuare a guardare.,行き止まりだ。私達はまだこの景色を楽しめるわ。,여기가 아닌가 봐. 다른 곳을 찾아봐야겠어.,Doodlopend hier. We kunnen maar beter blijven zoeken.,Blindvei her. Vi får lete videre.,Tu jest ślepy zaułek. Lepiej szukajmy dalej.,Sem saída aqui. Melhor seguirmos procurando.,,Capăt de linie. Mai bine ne mai uităm împrejur.,Тупик. Давай продолжать поиски.,,Det är en återvändsgränd här. Det är bäst att fortsätta leta.,Burası çıkmaz sokak. Aramaya devam etsek iyi olur. +Dead end here. We'd better keep looking.,TXT_BBSUB_SCRIPT07_D6064_PROGR,MAP07: Computer room.,,,Tudy cesta nevede. Měli bychom se podívat dál.,Det er en blindgyde her. Vi må hellere lede videre.,Sackgasse. Wir sollten es woanders versuchen.,,,Un camino sin salida. Deberíamos seguir buscando.,,Umpikuja tässä. Parasta jatkaa etsintää.,"Une impasse. Bon, on continue de chercher.",,Zsákutca. Folytatnunk kell a keresést.,Qui c'è un vicolo cieco. Meglio continuare a guardare.,行き止まりだ。私達はまだこの景色を楽しめるわ。,여기가 아닌가 봐. 다른 곳을 찾아봐야겠어.,Doodlopend hier. We kunnen maar beter blijven zoeken.,Blindvei her. Vi får lete videre.,Tu jest ślepy zaułek. Lepiej szukajmy dalej.,Sem saída aqui. Melhor seguirmos procurando.,,Capăt de linie. Mai bine ne mai uităm împrejur.,Тупик. Давай продолжать поиски.,,Det är en återvändsgränd här. Det är bäst att fortsätta leta.,Burası çıkmaz sokak. Aramaya devam etsek iyi olur. "Onward, my champion. Let's make Mr. Ugly twist and shout. ",TXT_BBSUB_SCRIPT10_D10612_YOUHA,MAP10: Macil (Possible reference to a song of The Beatles).,,,"Kupředu, můj šampióne. Pojďme pana Ošklivu hezky nakopat.","Fremad, min mester. Lad os få hr. Grimme til at vride sig og skrige. -",Und weiter gehts. Lass die Hackfresse zittern!,,,"Adelante, mi campeón. Hagamos al Sr. Feo retorcerse y gritar.",,"Eteenpäin, sotaurhoni. Pannaan Herra Rumilus vääntelehtimään ja huutamaan.",Allons-y mon champion. Allons tordre de douleur Monsieur Sale-geule.,,Gyerünk bajnokom. Ne várakoztassuk meg Ocsmány urat.,"Vai avanti, mio campione. Facciamo agitare e urlare il signor Brutto.",進み出よ、わがチャンピオン。ミスター生塵を捻りつぶしてやれ。,"어서 가자, 나의 영웅. 그 못생긴 녀석에게 본때를 보여주자고.","Voorwaarts, mijn kampioen. Laten we Mr Lelijk laten draaien en schreeuwen.","Fremad, min mester. La oss få herr Stygg til å vri seg og rope.","Naprzód, mój mistrzu. Sprawmy, by Pan Brzydki skręcał się i krzyczał. +",Und weiter gehts. Lass die Hackfresse zittern!,,,"Adelante, campeón. Hagamos que el Sr. Feo se retuerza y grite.",,"Eteenpäin, sotaurhoni. Pannaan Herra Rumilus vääntelehtimään ja huutamaan.",Allons-y mon champion. Allons tordre de douleur Monsieur Sale-geule.,,Gyerünk bajnokom. Ne várakoztassuk meg Ocsmány urat.,"Vai avanti, mio campione. Facciamo agitare e urlare il signor Brutto.",進み出よ、わがチャンピオン。ミスター生塵を捻りつぶしてやれ。,"어서 가자, 나의 영웅. 그 못생긴 녀석에게 본때를 보여주자고.","Voorwaarts, mijn kampioen. Laten we Mr Lelijk laten draaien en schreeuwen.","Fremad, min mester. La oss få herr Stygg til å vri seg og rope.","Naprzód, mój mistrzu. Sprawmy, by Pan Brzydki skręcał się i krzyczał. ","Vai em frente, meu campeão. Vamos fazer o Sr. Feioso soltar a franga.",,"Drept în față, campionul meu. Să-l facem pe dl. Urât să țipe.","Вперёд, мой защитник. Заставим Г-на Урода сплясать и спеть.",,"Framåt, min mästare. Låt oss få herr Ful att vrida sig och skrika.","İleri, şampiyonum. Bay Çirkin'i döndürelim ve bağırtalım. " "Biomechanical? Well, that explains their seemingly endless supply of troops! And their bad attitude.