diff --git a/src/am_map.cpp b/src/am_map.cpp index 2f78b2081..91ed928d9 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -2630,7 +2630,7 @@ void DAutomap::drawWalls (bool allmap) } else if (line.flags & ML_SECRET) { // secret door - if (am_cheat != 0 && line.backsector != nullptr) + if (am_cheat != 0 && am_cheat < 4 && line.backsector != nullptr) drawMline(&l, AMColors.SecretWallColor); else drawMline(&l, AMColors.WallColor); diff --git a/src/common/audio/music/i_music.cpp b/src/common/audio/music/i_music.cpp index b30a27f13..4570de82e 100644 --- a/src/common/audio/music/i_music.cpp +++ b/src/common/audio/music/i_music.cpp @@ -70,7 +70,7 @@ int nomusic = 0; // Maximum volume of MOD/stream music. //========================================================================== -CUSTOM_CVARD(Float, snd_musicvolume, 1.0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG, "controls music volume") +CUSTOM_CVARD(Float, snd_musicvolume, 0.65, CVAR_ARCHIVE|CVAR_GLOBALCONFIG, "controls music volume") { if (self < 0.f) self = 0.f; diff --git a/src/common/engine/i_interface.cpp b/src/common/engine/i_interface.cpp index c6e8e97eb..780bdc269 100644 --- a/src/common/engine/i_interface.cpp +++ b/src/common/engine/i_interface.cpp @@ -4,8 +4,15 @@ #include "startupinfo.h" #include "c_cvars.h" #include "gstrings.h" +#include "version.h" -static_assert(sizeof(void*) == 8, "32 builds are not supported"); +static_assert(sizeof(void*) == 8, + "Only LP64/LLP64 builds are officially supported. " + "Please do not attempt to build for other platforms; " + "even if the program succeeds in a MAP01 smoke test, " + "there are e.g. known visual artifacts " + " " + "that lead to a bad user experience."); // Some global engine variables taken out of the backend code. FStartupScreen* StartWindow; @@ -23,7 +30,7 @@ bool pauseext; FStartupInfo GameStartupInfo; -CVAR(Bool, queryiwad, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); +CVAR(Bool, queryiwad, QUERYIWADDEFAULT, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR(String, defaultiwad, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR(Bool, vid_fps, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) diff --git a/src/common/engine/i_net.cpp b/src/common/engine/i_net.cpp index 65dc60cb1..33b4c1267 100644 --- a/src/common/engine/i_net.cpp +++ b/src/common/engine/i_net.cpp @@ -903,11 +903,11 @@ bool JoinGame (int i) static int PrivateNetOf(in_addr in) { int addr = ntohl(in.s_addr); - if ((addr & 0xFFFF0000) == 0xC0A80000) // 192.168.0.0 + if ((addr & 0xFFFF0000) == 0xC0A80000) // 192.168.0.0 { return 0xC0A80000; } - else if ((addr & 0xFFF00000) == 0xAC100000) // 172.16.0.0 + else if ((addr & 0xFFFF0000) >= 0xAC100000 && (addr & 0xFFFF0000) <= 0xAC1F0000) // 172.16.0.0 - 172.31.0.0 { return 0xAC100000; } diff --git a/src/common/engine/stringtable.cpp b/src/common/engine/stringtable.cpp index 707b59b69..d9d739b57 100644 --- a/src/common/engine/stringtable.cpp +++ b/src/common/engine/stringtable.cpp @@ -254,6 +254,7 @@ bool FStringTable::ParseLanguageCSV(int filenum, const char* buffer, size_t size } } + row[labelcol].StripLeftRight(); FName strName = row[labelcol].GetChars(); if (hasDefaultEntry) { diff --git a/src/common/filesystem/source/file_directory.cpp b/src/common/filesystem/source/file_directory.cpp index 3193e00b1..1999e9c4c 100644 --- a/src/common/filesystem/source/file_directory.cpp +++ b/src/common/filesystem/source/file_directory.cpp @@ -106,7 +106,7 @@ int FDirectory::AddDirectory(const char *dirpath, LumpFilterInfo* filter, FileSy if (mBasePath == nullptr) { // extract the base path from the first entry to cover changes made in ScanDirectory. - auto full = entry.FilePath.find(entry.FilePathRel); + auto full = entry.FilePath.rfind(entry.FilePathRel); std::string path(entry.FilePath, 0, full); mBasePath = stringpool->Strdup(path.c_str()); } diff --git a/src/common/menu/menu.cpp b/src/common/menu/menu.cpp index ee9a1277e..1cff6a8b5 100644 --- a/src/common/menu/menu.cpp +++ b/src/common/menu/menu.cpp @@ -63,7 +63,7 @@ static ScaleOverrider *CurrentScaleOverrider; CVAR (Int, m_showinputgrid, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Bool, m_blockcontrollers, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -CVAR (Float, snd_menuvolume, 0.6f, CVAR_ARCHIVE) +CVAR (Float, snd_menuvolume, 0.8f, CVAR_ARCHIVE) CVAR(Int, m_use_mouse, 1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(Int, m_show_backbutton, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(Bool, m_cleanscale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) diff --git a/src/common/menu/savegamemanager.cpp b/src/common/menu/savegamemanager.cpp index 83865cd1d..135093021 100644 --- a/src/common/menu/savegamemanager.cpp +++ b/src/common/menu/savegamemanager.cpp @@ -84,7 +84,7 @@ int FSavegameManagerBase::RemoveSaveSlot(int index) int listindex = SaveGames[0]->bNoDelete ? index - 1 : index; if (listindex < 0) return index; - remove(SaveGames[index]->Filename.GetChars()); + RemoveFile(SaveGames[index]->Filename.GetChars()); UnloadSaveData(); FSaveGameNode *file = SaveGames[index]; @@ -274,7 +274,7 @@ DEFINE_ACTION_FUNCTION(FSavegameManager, DoSave) unsigned FSavegameManagerBase::ExtractSaveData(int index) { - FResourceFile *resf; + std::unique_ptr resf; FSaveGameNode *node; if (index == -1) @@ -295,7 +295,7 @@ unsigned FSavegameManagerBase::ExtractSaveData(int index) (node = SaveGames[index]) && !node->Filename.IsEmpty() && !node->bOldVersion && - (resf = FResourceFile::OpenResourceFile(node->Filename.GetChars(), true)) != nullptr) + ( (resf.reset(FResourceFile::OpenResourceFile(node->Filename.GetChars(), true))), resf != nullptr)) { auto info = resf->FindEntry("info.json"); if (info < 0) @@ -316,7 +316,8 @@ unsigned FSavegameManagerBase::ExtractSaveData(int index) auto pic = resf->FindEntry("savepic.png"); if (pic >= 0) { - FileReader picreader = resf->GetEntryReader(pic, FileSys::READER_NEW, FileSys::READERFLAG_SEEKABLE); + // This must use READER_CACHED or it will lock the savegame file. + FileReader picreader = resf->GetEntryReader(pic, FileSys::READER_CACHED, FileSys::READERFLAG_SEEKABLE); PNGHandle *png = M_VerifyPNG(picreader); if (png != nullptr) { @@ -329,7 +330,6 @@ unsigned FSavegameManagerBase::ExtractSaveData(int index) } } } - delete resf; } return index; } diff --git a/src/common/models/voxels.h b/src/common/models/voxels.h index 52299434a..fe583fd93 100644 --- a/src/common/models/voxels.h +++ b/src/common/models/voxels.h @@ -73,6 +73,9 @@ struct FVoxelDef int VoxeldefIndex; // Needed by GZDoom double Scale; DAngle AngleOffset;// added to actor's angle to compensate for wrong-facing voxels + double xoffset; + double yoffset; + double zoffset; bool PitchFromMomentum; bool UseActorPitch; bool UseActorRoll; diff --git a/src/common/objects/dobject.cpp b/src/common/objects/dobject.cpp index b66029a53..e124a975c 100644 --- a/src/common/objects/dobject.cpp +++ b/src/common/objects/dobject.cpp @@ -330,6 +330,7 @@ void DObject::Destroy () } OnDestroy(); ObjectFlags = (ObjectFlags & ~OF_Fixed) | OF_EuthanizeMe; + GC::WriteBarrier(this); } DEFINE_ACTION_FUNCTION(DObject, Destroy) @@ -622,3 +623,165 @@ void *DObject::ScriptVar(FName field, PType *type) // This is only for internal use so I_Error is fine. I_Error("Variable %s not found in %s\n", field.GetChars(), cls->TypeName.GetChars()); } + + +//========================================================================== +// +// +// +//========================================================================== + +void NetworkEntityManager::InitializeNetworkEntities() +{ + if (!s_netEntities.Size()) + s_netEntities.AppendFill(nullptr, NetIDStart); // Allocate the first 0-8 slots for the world and clients. +} + +// Clients need special handling since they always go in slots 1 - MAXPLAYERS. +void NetworkEntityManager::SetClientNetworkEntity(DObject* mo, const unsigned int playNum) +{ + // If resurrecting, we need to swap the corpse's position with the new pawn's + // position so it's no longer considered the client's body. + const uint32_t id = ClientNetIDStart + playNum; + DObject* const oldBody = s_netEntities[id]; + if (oldBody != nullptr) + { + if (oldBody == mo) + return; + + const uint32_t curID = mo->GetNetworkID(); + + s_netEntities[curID] = oldBody; + oldBody->ClearNetworkID(); + oldBody->SetNetworkID(curID); + + mo->ClearNetworkID(); + } + else + { + RemoveNetworkEntity(mo); // Free up its current id. + } + + s_netEntities[id] = mo; + mo->SetNetworkID(id); +} + +void NetworkEntityManager::AddNetworkEntity(DObject* const ent) +{ + if (ent->IsNetworked()) + return; + + // Slot 0 is reserved for the world. + // Clients go in the first 1 - MAXPLAYERS slots + // Everything else is first come first serve. + uint32_t id = WorldNetID; + if (s_openNetIDs.Size()) + { + s_openNetIDs.Pop(id); + s_netEntities[id] = ent; + } + else + { + id = s_netEntities.Push(ent); + } + + ent->SetNetworkID(id); +} + +void NetworkEntityManager::RemoveNetworkEntity(DObject* const ent) +{ + if (!ent->IsNetworked()) + return; + + const uint32_t id = ent->GetNetworkID(); + if (id == WorldNetID) + return; + + assert(s_netEntities[id] == ent); + if (id >= NetIDStart) + s_openNetIDs.Push(id); + s_netEntities[id] = nullptr; + ent->ClearNetworkID(); +} + +DObject* NetworkEntityManager::GetNetworkEntity(const uint32_t id) +{ + if (id == WorldNetID || id >= s_netEntities.Size()) + return nullptr; + + return s_netEntities[id]; +} + +//========================================================================== +// +// +// +//========================================================================== + +void DObject::SetNetworkID(const uint32_t id) +{ + if (!IsNetworked()) + { + ObjectFlags |= OF_Networked; + _networkID = id; + } +} + +void DObject::ClearNetworkID() +{ + ObjectFlags &= ~OF_Networked; + _networkID = NetworkEntityManager::WorldNetID; +} + +void DObject::EnableNetworking(const bool enable) +{ + if (enable) + NetworkEntityManager::AddNetworkEntity(this); + else + NetworkEntityManager::RemoveNetworkEntity(this); +} + +void DObject::RemoveFromNetwork() +{ + NetworkEntityManager::RemoveNetworkEntity(this); +} + +static unsigned int GetNetworkID(DObject* const self) +{ + return self->GetNetworkID(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DObject, GetNetworkID, GetNetworkID) +{ + PARAM_SELF_PROLOGUE(DObject); + + ACTION_RETURN_INT(self->GetNetworkID()); +} + +static void EnableNetworking(DObject* const self, const bool enable) +{ + self->EnableNetworking(enable); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DObject, EnableNetworking, EnableNetworking) +{ + PARAM_SELF_PROLOGUE(DObject); + PARAM_BOOL(enable); + + self->EnableNetworking(enable); + return 0; +} + +static DObject* GetNetworkEntity(const unsigned int id) +{ + return NetworkEntityManager::GetNetworkEntity(id); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DObject, GetNetworkEntity, GetNetworkEntity) +{ + PARAM_PROLOGUE; + PARAM_UINT(id); + + ACTION_RETURN_OBJECT(NetworkEntityManager::GetNetworkEntity(id)); +} + diff --git a/src/common/objects/dobject.h b/src/common/objects/dobject.h index f193379c1..c0b6aecb5 100644 --- a/src/common/objects/dobject.h +++ b/src/common/objects/dobject.h @@ -487,4 +487,25 @@ inline T *&DObject::PointerVar(FName field) return *(T**)ScriptVar(field, nullptr); // pointer check is more tricky and for the handful of uses in the DECORATE parser not worth the hassle. } + +class NetworkEntityManager +{ +private: + inline static TArray s_netEntities = {}; + inline static TArray s_openNetIDs = {}; + +public: + NetworkEntityManager() = delete; + + static constexpr uint32_t WorldNetID = 0u; + static constexpr uint32_t ClientNetIDStart = 1u; + inline static uint32_t NetIDStart;// = MAXPLAYERS + 1u; + + static void InitializeNetworkEntities(); + static void SetClientNetworkEntity(DObject* mo, const unsigned int playNum); + static void AddNetworkEntity(DObject* const ent); + static void RemoveNetworkEntity(DObject* const ent); + static DObject* GetNetworkEntity(const uint32_t id); +}; + #endif //__DOBJECT_H__ diff --git a/src/common/platform/posix/cocoa/i_system.mm b/src/common/platform/posix/cocoa/i_system.mm index cef2d177b..267bac31d 100644 --- a/src/common/platform/posix/cocoa/i_system.mm +++ b/src/common/platform/posix/cocoa/i_system.mm @@ -122,7 +122,7 @@ void I_ShowFatalError(const char *message) } -int I_PickIWad(WadStuff* const wads, const int numwads, const bool showwin, const int defaultiwad, int&) +int I_PickIWad(WadStuff* const wads, const int numwads, const bool showwin, const int defaultiwad, int&, FString&) { if (!showwin) { diff --git a/src/common/platform/posix/i_system.h b/src/common/platform/posix/i_system.h index 02acf0e3e..4d800d53b 100644 --- a/src/common/platform/posix/i_system.h +++ b/src/common/platform/posix/i_system.h @@ -38,7 +38,7 @@ void I_PrintStr (const char *str); void I_SetIWADInfo (); // Pick from multiple IWADs to use -int I_PickIWad (WadStuff *wads, int numwads, bool queryiwad, int defaultiwad, int&); +int I_PickIWad (WadStuff *wads, int numwads, bool queryiwad, int defaultiwad, int&, FString &); // [RH] Checks the registry for Steam's install path, so we can scan its // directories for IWADs if the user purchased any through Steam. diff --git a/src/common/platform/posix/sdl/i_system.cpp b/src/common/platform/posix/sdl/i_system.cpp index 7fa41b2bc..2c28368b0 100644 --- a/src/common/platform/posix/sdl/i_system.cpp +++ b/src/common/platform/posix/sdl/i_system.cpp @@ -298,7 +298,7 @@ void I_PrintStr(const char *cp) if (StartWindow) RedrawProgressBar(ProgressBarCurPos,ProgressBarMaxPos); } -int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags) +int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags, FString &extraArgs) { if (!showwin) { @@ -308,7 +308,7 @@ int I_PickIWad (WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& #ifdef __APPLE__ return I_PickIWad_Cocoa (wads, numwads, showwin, defaultiwad); #else - return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags); + return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags, &extraArgs); #endif } diff --git a/src/common/platform/win32/i_system.cpp b/src/common/platform/win32/i_system.cpp index 0b8ea53ac..ace9c9ef2 100644 --- a/src/common/platform/win32/i_system.cpp +++ b/src/common/platform/win32/i_system.cpp @@ -352,7 +352,7 @@ static void SetQueryIWad(HWND dialog) // //========================================================================== -int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags) +int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& autoloadflags, FString &extraArgs) { int vkey; if (stricmp(queryiwad_key, "shift") == 0) @@ -369,7 +369,7 @@ int I_PickIWad(WadStuff *wads, int numwads, bool showwin, int defaultiwad, int& } if (showwin || (vkey != 0 && GetAsyncKeyState(vkey))) { - return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags); + return LauncherWindow::ExecModal(wads, numwads, defaultiwad, &autoloadflags, &extraArgs); } return defaultiwad; } diff --git a/src/common/platform/win32/i_system.h b/src/common/platform/win32/i_system.h index 9d360b55a..b70efb78b 100644 --- a/src/common/platform/win32/i_system.h +++ b/src/common/platform/win32/i_system.h @@ -38,7 +38,7 @@ void I_PrintStr (const char *cp); void I_SetIWADInfo (); // Pick from multiple IWADs to use -int I_PickIWad(WadStuff* wads, int numwads, bool queryiwad, int defaultiwad, int& autoloadflags); +int I_PickIWad(WadStuff* wads, int numwads, bool queryiwad, int defaultiwad, int& autoloadflags, FString &extraArgs); // The ini could not be saved at exit bool I_WriteIniFailed (const char* filename); diff --git a/src/common/scripting/backend/codegen.cpp b/src/common/scripting/backend/codegen.cpp index 8b3ee306a..5b1a975a6 100644 --- a/src/common/scripting/backend/codegen.cpp +++ b/src/common/scripting/backend/codegen.cpp @@ -9544,7 +9544,9 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) if(FnPtrCall) static_cast(ctx.Function->Variants[0].Implementation)->blockJit = true; - int implicit = Function->GetImplicitArgs(); + unsigned implicit = Function->GetImplicitArgs(); + + bool relaxed_named_arugments = (ctx.Version >= MakeVersion(4, 13)); if (!CheckAccessibility(ctx.Version)) { @@ -9576,21 +9578,128 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) CallingFunction = ctx.Function; if (ArgList.Size() > 0) { - if (argtypes.Size() == 0) + if ((argtypes.Size() == 0) || (argtypes.Last() != nullptr && ArgList.Size() + implicit > argtypes.Size())) { ScriptPosition.Message(MSG_ERROR, "Too many arguments in call to %s", Function->SymbolName.GetChars()); delete this; return nullptr; } + bool isvararg = (argtypes.Last() == nullptr); + + { + TDeletingArray OrderedArgs; + const unsigned count = (argtypes.Size() - implicit) - isvararg; + + OrderedArgs.Resize(count); + memset(OrderedArgs.Data(), 0, sizeof(FxExpression*) * count); + + unsigned index = 0; + unsigned n = ArgList.Size(); + + for(unsigned i = 0; i < n; i++) + { + if(ArgList[i]->ExprType == EFX_NamedNode) + { + if(FnPtrCall) + { + ScriptPosition.Message(MSG_ERROR, "Named arguments not supported in function pointer calls"); + delete this; + return nullptr; + } + else if((index >= count) && isvararg) + { + ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument in the varargs part of the parameter list."); + delete this; + return nullptr; + } + else + { + FName name = static_cast(ArgList[i])->name; + if(argnames[index + implicit] != name) + { + unsigned j; + + for (j = 0; j < count; j++) + { + if (argnames[j + implicit] == name) + { + if(!relaxed_named_arugments && !(argflags[j + implicit] & VARF_Optional)) + { + ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument here - not all required arguments have been passed."); + } + else if(!relaxed_named_arugments && j < index) + { + ScriptPosition.Message(MSG_ERROR, "Named argument %s comes before current position in argument list.", name.GetChars()); + } + + // i don't think this needs any further optimization? + // O(N^2) complexity technically but N isn't likely to be large, + // and the check itself is just an int comparison, so it should be fine + index = j; + + break; + } + } + + if(j == count) + { + ScriptPosition.Message(MSG_ERROR, "Named argument %s not found.", name.GetChars()); + delete this; + return nullptr; + } + } + else if(!relaxed_named_arugments && !(argflags[index + implicit] & VARF_Optional)) + { + ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument here - not all required arguments have been passed."); + } + } + } + + if(index >= count) + { + if(isvararg) + { + OrderedArgs.Push(ArgList[i]); + ArgList[i] = nullptr; + index++; + } + else + { + ScriptPosition.Message(MSG_ERROR, "Too many arguments in call to %s", Function->SymbolName.GetChars()); + delete this; + return nullptr; + } + } + else + { + if(ArgList[i]->ExprType == EFX_NamedNode) + { + auto * node = static_cast(ArgList[i]); + OrderedArgs[index] = node->value; + node->value = nullptr; + } + else + { + OrderedArgs[index] = ArgList[i]; + } + ArgList[i] = nullptr; + index++; + } + } + + ArgList = std::move(OrderedArgs); + } + bool foundvarargs = false; PType * type = nullptr; int flag = 0; - if (argtypes.Size() > 0 && argtypes.Last() != nullptr && ArgList.Size() + implicit > argtypes.Size()) + + int defaults_index = 0; + + for(unsigned i = 0; i < implicit; i++) { - ScriptPosition.Message(MSG_ERROR, "Too many arguments in call to %s", Function->SymbolName.GetChars()); - delete this; - return nullptr; + defaults_index += argtypes[i]->GetRegCount(); } for (unsigned i = 0; i < ArgList.Size(); i++) @@ -9608,94 +9717,45 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx) } assert(type != nullptr); - if (ArgList[i]->ExprType == EFX_NamedNode) + if(!foundvarargs) { - if(FnPtrCall) - { - ScriptPosition.Message(MSG_ERROR, "Named arguments not supported in function pointer calls"); - delete this; - return nullptr; - } - if (!(flag & VARF_Optional)) + if(ArgList[i] == nullptr) { - ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument here - not all required arguments have been passed."); - delete this; - return nullptr; - } - if (foundvarargs) - { - ScriptPosition.Message(MSG_ERROR, "Cannot use a named argument in the varargs part of the parameter list."); - delete this; - return nullptr; - } - unsigned j; - bool done = false; - FName name = static_cast(ArgList[i])->name; - for (j = 0; j < argnames.Size() - implicit; j++) - { - if (argnames[j + implicit] == name) + if(!(flag & VARF_Optional)) { - if (j < i) - { - ScriptPosition.Message(MSG_ERROR, "Named argument %s comes before current position in argument list.", name.GetChars()); - delete this; - return nullptr; - } - // copy the original argument into the list - auto old = static_cast(ArgList[i]); - ArgList[i] = old->value; - old->value = nullptr; - delete old; - // now fill the gap with constants created from the default list so that we got a full list of arguments. - int insert = j - i; - int skipdefs = 0; - // Defaults contain multiple entries for pointers so we need to calculate how much additional defaults we need to skip - for (unsigned k = 0; k < i + implicit; k++) - { - skipdefs += argtypes[k]->GetRegCount() - 1; - } - for (int k = 0; k < insert; k++) + ScriptPosition.Message(MSG_ERROR, "Required argument %s has not been passed in call to %s", argnames[i + implicit].GetChars(), Function->SymbolName.GetChars()); + delete this; + return nullptr; + } + + auto ntype = argtypes[i + implicit]; + // If this is a reference argument, the pointer type must be undone because the code below expects the pointed type as value type. + if (argflags[i + implicit] & VARF_Ref) + { + assert(ntype->isPointer()); + ntype = TypeNullPtr; // the default of a reference type can only be a null pointer + } + if (ntype->GetRegCount() == 1) + { + ArgList[i] = new FxConstant(ntype, (*defaults)[defaults_index], ScriptPosition); + } + else + { + // Vectors need special treatment because they are not normal constants + FxConstant *cs[4] = { nullptr }; + for (int l = 0; l < ntype->GetRegCount(); l++) { - auto ntype = argtypes[i + k + implicit]; - // If this is a reference argument, the pointer type must be undone because the code below expects the pointed type as value type. - if (argflags[i + k + implicit] & VARF_Ref) - { - assert(ntype->isPointer()); - ntype = TypeNullPtr; // the default of a reference type can only be a null pointer - } - if (ntype->GetRegCount() == 1) - { - auto x = new FxConstant(ntype, (*defaults)[i + k + skipdefs + implicit], ScriptPosition); - ArgList.Insert(i + k, x); - } - else - { - // Vectors need special treatment because they are not normal constants - FxConstant *cs[4] = { nullptr }; - for (int l = 0; l < ntype->GetRegCount(); l++) - { - cs[l] = new FxConstant(TypeFloat64, (*defaults)[l + i + k + skipdefs + implicit], ScriptPosition); - } - FxExpression *x = new FxVectorValue(cs[0], cs[1], cs[2], cs[3], ScriptPosition); - ArgList.Insert(i + k, x); - skipdefs += ntype->GetRegCount() - 1; - } + cs[l] = new FxConstant(TypeFloat64, (*defaults)[l + defaults_index], ScriptPosition); } - done = true; - break; + ArgList[i] = new FxVectorValue(cs[0], cs[1], cs[2], cs[3], ScriptPosition); } } - if (!done) - { - ScriptPosition.Message(MSG_ERROR, "Named argument %s not found.", name.GetChars()); - delete this; - return nullptr; - } - // re-get the proper info for the inserted node. - type = argtypes[i + implicit]; - flag = argflags[i + implicit]; + + defaults_index += argtypes[i + implicit]->GetRegCount(); } + assert(ArgList[i]); + FxExpression *x = nullptr; if (foundvarargs && (Function->Variants[0].Flags & VARF_VarArg)) { diff --git a/src/common/utility/cmdlib.cpp b/src/common/utility/cmdlib.cpp index 5933a91d5..3b8dc9981 100644 --- a/src/common/utility/cmdlib.cpp +++ b/src/common/utility/cmdlib.cpp @@ -593,6 +593,26 @@ void CreatePath(const char *fn) } #endif +void RemoveFile(const char* file) +{ +#ifndef _WIN32 + remove(file); +#else + auto wpath = WideString(file); + _wremove(wpath.c_str()); +#endif +} + +int RemoveDir(const char* file) +{ +#ifndef _WIN32 + return rmdir(file); +#else + auto wpath = WideString(file); + return _wrmdir(wpath.c_str()); +#endif +} + //========================================================================== // // strbin -- In-place version diff --git a/src/common/utility/cmdlib.h b/src/common/utility/cmdlib.h index df779a975..88027c156 100644 --- a/src/common/utility/cmdlib.h +++ b/src/common/utility/cmdlib.h @@ -71,6 +71,8 @@ int strbin (char *str); FString strbin1 (const char *start); void CreatePath(const char * fn); +void RemoveFile(const char* file); +int RemoveDir(const char* file); FString ExpandEnvVars(const char *searchpathstring); FString NicePath(const char *path); diff --git a/src/common/utility/m_argv.cpp b/src/common/utility/m_argv.cpp index b9b05c3e9..0e8946058 100644 --- a/src/common/utility/m_argv.cpp +++ b/src/common/utility/m_argv.cpp @@ -189,7 +189,7 @@ int FArgs::CheckParmList(const char *check, FString **strings, int start) const } for (i = ++parmat; i < Argv.Size(); ++i) { - if (Argv[i][0] == '-' || Argv[i][1] == '+') + if (Argv[i][0] == '-' || Argv[i][0] == '+') { break; } @@ -345,6 +345,114 @@ void FArgs::AppendArgs(int argc, const FString *argv) } } +//=========================================================================== +// +// FArgs :: AppendArgsString +// +// Adds extra args as a space-separated string, supporting simple quoting, and inserting -file args into the right place +// +//=========================================================================== + + +void FArgs::AppendArgsString(FString argv) +{ + auto file_index = Argv.Find("-file"); + auto files_end = file_index + 1; + + for (; files_end < Argv.Size() && Argv[files_end][0] != '-' && Argv[files_end][0] != '+'; ++files_end); + + if(file_index == Argv.Size()) + { + Argv.Push("-file"); + } + + bool inserting_file = true; + + argv.StripLeftRight(); + + size_t i = 0; + size_t lastSection = 0; + size_t lastStart = 0; + char lastQuoteType = 0; + + FString tmp; + bool has_tmp = false; + + for(i = 0; i < argv.Len(); i++) + { + if(argv[i] == ' ') + { + FString arg = tmp + argv.Mid(lastSection, i - lastSection); + + if(arg[0] == '-' || arg[0] == '+') inserting_file = false; + + if(inserting_file) + { + Argv.Insert(files_end++, arg); + } + else if(arg.Compare("-file") == 0) + { + inserting_file = true; + } + else + { + files_end++; + Argv.Insert(file_index++, arg); + } + + lastSection = i + 1; + tmp = ""; + has_tmp = false; + for(;(i + 1) < argv.Len() && argv[i + 1] == ' '; i++, lastSection++); + lastStart = i + 1; + } + else if(argv[i] == '\'' || argv[i] == '"') + { + lastQuoteType = argv[i]; + tmp += argv.Mid(lastSection, i - lastSection); + has_tmp = true; + bool wasSlash = false; + + for(i++; (argv[i] != lastQuoteType || wasSlash) && i < argv.Len(); i++) + { + if(i == '\\' && !wasSlash) + { + wasSlash = true; + } + else + { + tmp += argv[i]; + wasSlash = false; + } + } + lastSection = i + 1; + } + } + + if(lastSection != i) + { // ended on an unquoted section + FString arg = tmp + argv.Mid(lastSection); + if(inserting_file) + { + Argv.Insert(files_end, arg); + } + else if(arg.Compare("-file") != 0) + { + Argv.Insert(file_index, arg); + } + } + else if(has_tmp) + { // ended on a quote + if(inserting_file) + { + Argv.Insert(files_end, tmp); + } + else if(tmp.Compare("-file") != 0) + { + Argv.Insert(file_index, tmp); + } + } +} //=========================================================================== // // FArgs :: RemoveArg diff --git a/src/common/utility/m_argv.h b/src/common/utility/m_argv.h index 953769703..d12da9a5f 100644 --- a/src/common/utility/m_argv.h +++ b/src/common/utility/m_argv.h @@ -85,6 +85,7 @@ class FArgs void AppendArg(FString arg); void AppendArgs(int argc, const FString *argv); + void AppendArgsString(FString argv); void RemoveArg(int argindex); void RemoveArgs(const char *check); void SetArgs(int argc, char **argv); diff --git a/src/common/utility/tarray.h b/src/common/utility/tarray.h index bce3ba3ea..3445bd484 100644 --- a/src/common/utility/tarray.h +++ b/src/common/utility/tarray.h @@ -919,6 +919,7 @@ class TDeletingArray : public TArray TDeletingArray(TDeletingArray &&other) : TArray(std::move(other)) {} TDeletingArray &operator=(TDeletingArray &&other) { + DeleteAndClear(); TArray::operator=(std::move(other)); return *this; } diff --git a/src/common/utility/vectors.h b/src/common/utility/vectors.h index 77e41d2a9..47eb59793 100644 --- a/src/common/utility/vectors.h +++ b/src/common/utility/vectors.h @@ -1019,6 +1019,45 @@ struct TVector4 } }; +inline void ZeroSubnormalsF(double& num) +{ + if (fabs(num) < FLT_MIN) num = 0; +} + +inline void ZeroSubnormals(double& num) +{ + if (fabs(num) < DBL_MIN) num = 0; +} + +inline void ZeroSubnormals(float& num) +{ + if (fabsf(num) < FLT_MIN) num = 0; +} + +template +inline void ZeroSubnormals(TVector2& vec) +{ + ZeroSubnormals(vec.X); + ZeroSubnormals(vec.Y); +} + +template +inline void ZeroSubnormals(TVector3& vec) +{ + ZeroSubnormals(vec.X); + ZeroSubnormals(vec.Y); + ZeroSubnormals(vec.Z); +} + +template +inline void ZeroSubnormals(TVector4& vec) +{ + ZeroSubnormals(vec.X); + ZeroSubnormals(vec.Y); + ZeroSubnormals(vec.Z); + ZeroSubnormals(vec.W); +} + template struct TMatrix3x3 { diff --git a/src/common/utility/writezip.cpp b/src/common/utility/writezip.cpp index 2f3507e6f..de89779c0 100644 --- a/src/common/utility/writezip.cpp +++ b/src/common/utility/writezip.cpp @@ -40,6 +40,7 @@ #include "m_swap.h" #include "w_zip.h" #include "fs_decompress.h" +#include "cmdlib.h" using FileSys::FCompressedBuffer; @@ -201,7 +202,7 @@ bool WriteZip(const char* filename, const FCompressedBuffer* content, size_t con if (pos == -1) { delete f; - remove(filename); + RemoveFile(filename); return false; } positions.Push(pos); @@ -213,7 +214,7 @@ bool WriteZip(const char* filename, const FCompressedBuffer* content, size_t con if (AppendCentralDirectory(f, content[i], dostime, positions[i]) < 0) { delete f; - remove(filename); + RemoveFile(filename); return false; } } @@ -230,7 +231,7 @@ bool WriteZip(const char* filename, const FCompressedBuffer* content, size_t con if (f->Write(&dirend, sizeof(dirend)) != sizeof(dirend)) { delete f; - remove(filename); + RemoveFile(filename); return false; } delete f; diff --git a/src/common/utility/zstring.cpp b/src/common/utility/zstring.cpp index ecd0dc7e6..488c09eb6 100644 --- a/src/common/utility/zstring.cpp +++ b/src/common/utility/zstring.cpp @@ -1089,6 +1089,12 @@ octdigits = [0-7]; ("0" octdigits+ | "0" [xX] hexdigits+ | (digits \ '0') digits*) { return true; } [\000-\377] { return false; }*/ + + //FIX for "0" returning false, doesn't fix 0 with whitespace, but that isn't necessary for savegame loading, so it'll need to be fixed later + if(Len() == 1 && Chars[0] == '0') return true; + + + const char *YYCURSOR = Chars; char yych; diff --git a/src/common/utility/zstring.h b/src/common/utility/zstring.h index 93d679021..284027712 100644 --- a/src/common/utility/zstring.h +++ b/src/common/utility/zstring.h @@ -128,6 +128,7 @@ class FString FString (FString &&other) noexcept : Chars(other.Chars) { other.ResetToNull(); } FString (const char *copyStr); FString (const char *copyStr, size_t copyLen); + FString (const std::string &s) : FString(s.c_str(), s.length()) {} FString (char oneChar); FString(const TArray & source) : FString(source.Data(), source.Size()) {} FString(const TArray & source) : FString((char*)source.Data(), source.Size()) {} diff --git a/src/d_iwad.cpp b/src/d_iwad.cpp index f1495febc..4758064a3 100644 --- a/src/d_iwad.cpp +++ b/src/d_iwad.cpp @@ -727,8 +727,10 @@ int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char } int pick = 0; - // We got more than one so present the IWAD selection box. - if (picks.Size() > 1) + // Present the IWAD selection box. + bool alwaysshow = (queryiwad && !Args->CheckParm("-iwad")); + + if (alwaysshow || picks.Size() > 1) { // Locate the user's prefered IWAD, if it was found. if (defaultiwad[0] != '\0') @@ -743,7 +745,7 @@ int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char } } } - if (picks.Size() > 1) + if (alwaysshow || picks.Size() > 1) { if (!havepicked) { @@ -762,9 +764,18 @@ int FIWadManager::IdentifyVersion (std::vector&wadfiles, const char if (autoloadbrightmaps) flags |= 4; if (autoloadwidescreen) flags |= 8; - pick = I_PickIWad(&wads[0], (int)wads.Size(), queryiwad, pick, flags); + FString extraArgs; + + pick = I_PickIWad(&wads[0], (int)wads.Size(), queryiwad, pick, flags, extraArgs); if (pick >= 0) { + extraArgs.StripLeftRight(); + + if(extraArgs.Len() > 0) + { + Args->AppendArgsString(extraArgs); + } + disableautoload = !!(flags & 1); autoloadlights = !!(flags & 2); autoloadbrightmaps = !!(flags & 4); diff --git a/src/d_main.cpp b/src/d_main.cpp index 4c0f33749..bde784b13 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1,4 +1,5 @@ //----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // Copyright 1993-1996 id Software // Copyright 1999-2016 Randy Heit // Copyright 2002-2016 Christoph Oelckers @@ -664,6 +665,19 @@ CUSTOM_CVAR(Int, compatmode, 0, CVAR_ARCHIVE|CVAR_NOINITCALL) COMPATF_TRACE | COMPATF_HITSCAN | COMPATF_MISSILECLIP | COMPATF_MASKEDMIDTEX | COMPATF_SOUNDTARGET; w = COMPATF2_POINTONLINE | COMPATF2_EXPLODE1 | COMPATF2_EXPLODE2 | COMPATF2_AVOID_HAZARDS | COMPATF2_STAYONLIFT | COMPATF2_NOMBF21; break; + + case 8: // MBF21 compat mode + v = COMPATF_TRACE | COMPATF_SOUNDTARGET | COMPATF_BOOMSCROLL | COMPATF_MISSILECLIP | COMPATF_CROSSDROPOFF | + COMPATF_MUSHROOM | COMPATF_MBFMONSTERMOVE | COMPATF_NOBLOCKFRIENDS | COMPATF_MASKEDMIDTEX; + w = COMPATF2_EXPLODE1 | COMPATF2_AVOID_HAZARDS | COMPATF2_STAYONLIFT; + break; + + case 9: // Stricter MBF21 compatibility + v = COMPATF_NOBLOCKFRIENDS | COMPATF_MBFMONSTERMOVE | COMPATF_INVISIBILITY | + COMPATF_NOTOSSDROPS | COMPATF_MUSHROOM | COMPATF_NO_PASSMOBJ | COMPATF_BOOMSCROLL | COMPATF_WALLRUN | + COMPATF_TRACE | COMPATF_HITSCAN | COMPATF_MISSILECLIP | COMPATF_CROSSDROPOFF | COMPATF_MASKEDMIDTEX | COMPATF_SOUNDTARGET; + w = COMPATF2_POINTONLINE | COMPATF2_EXPLODE1 | COMPATF2_EXPLODE2 | COMPATF2_AVOID_HAZARDS | COMPATF2_STAYONLIFT; + break; } compatflags = v; compatflags2 = w; @@ -1816,7 +1830,9 @@ static void GetCmdLineFiles(std::vector& wadfiles) int i, argc; argc = Args->CheckParmList("-file", &args); - for (i = 0; i < argc; ++i) + + // [RL0] Check for array size to only add new wads + for (i = wadfiles.size(); i < argc; ++i) { D_AddWildFile(wadfiles, args[i].GetChars(), ".wad", GameConfig); } @@ -3385,7 +3401,7 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw // [CW] Parse any TEAMINFO lumps. if (!batchrun) Printf ("ParseTeamInfo: Load team definitions.\n"); - TeamLibrary.ParseTeamInfo (); + FTeam::ParseTeamInfo (); R_ParseTrnslate(); PClassActor::StaticInit (); @@ -3660,6 +3676,7 @@ static int D_DoomMain_Internal (void) const char *wad; FIWadManager *iwad_man; + NetworkEntityManager::NetIDStart = MAXPLAYERS + 1; GC::AddMarkerFunc(GC_MarkGameRoots); VM_CastSpriteIDToString = Doom_CastSpriteIDToString; @@ -3833,6 +3850,9 @@ static int D_DoomMain_Internal (void) std::vector allwads; const FIWADInfo *iwad_info = iwad_man->FindIWAD(allwads, iwad.GetChars(), basewad.GetChars(), optionalwad.GetChars()); + + GetCmdLineFiles(pwads); // [RL0] Update with files passed on the launcher extra args + if (!iwad_info) return 0; // user exited the selection popup via cancel button. if ((iwad_info->flags & GI_SHAREWARE) && pwads.size() > 0) { diff --git a/src/d_net.cpp b/src/d_net.cpp index a4d8dab28..b785b52e2 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -2976,168 +2976,6 @@ int Net_GetLatency(int *ld, int *ad) // //========================================================================== -void NetworkEntityManager::InitializeNetworkEntities() -{ - if (!s_netEntities.Size()) - s_netEntities.AppendFill(nullptr, NetIDStart); // Allocate the first 0-8 slots for the world and clients. -} - -// Clients need special handling since they always go in slots 1 - MAXPLAYERS. -void NetworkEntityManager::SetClientNetworkEntity(player_t* const client) -{ - AActor* const mo = client->mo; - const uint32_t id = ClientNetIDStart + mo->Level->PlayerNum(client); - - // If resurrecting, we need to swap the corpse's position with the new pawn's - // position so it's no longer considered the client's body. - DObject* const oldBody = s_netEntities[id]; - if (oldBody != nullptr) - { - if (oldBody == mo) - return; - - const uint32_t curID = mo->GetNetworkID(); - - s_netEntities[curID] = oldBody; - oldBody->ClearNetworkID(); - oldBody->SetNetworkID(curID); - - mo->ClearNetworkID(); - } - else - { - RemoveNetworkEntity(mo); // Free up its current id. - } - - s_netEntities[id] = mo; - mo->SetNetworkID(id); -} - -void NetworkEntityManager::AddNetworkEntity(DObject* const ent) -{ - if (ent->IsNetworked()) - return; - - // Slot 0 is reserved for the world. - // Clients go in the first 1 - MAXPLAYERS slots - // Everything else is first come first serve. - uint32_t id = WorldNetID; - if (s_openNetIDs.Size()) - { - s_openNetIDs.Pop(id); - s_netEntities[id] = ent; - } - else - { - id = s_netEntities.Push(ent); - } - - ent->SetNetworkID(id); -} - -void NetworkEntityManager::RemoveNetworkEntity(DObject* const ent) -{ - if (!ent->IsNetworked()) - return; - - const uint32_t id = ent->GetNetworkID(); - if (id == WorldNetID) - return; - - assert(s_netEntities[id] == ent); - if (id >= NetIDStart) - s_openNetIDs.Push(id); - s_netEntities[id] = nullptr; - ent->ClearNetworkID(); -} - -DObject* NetworkEntityManager::GetNetworkEntity(const uint32_t id) -{ - if (id == WorldNetID || id >= s_netEntities.Size()) - return nullptr; - - return s_netEntities[id]; -} - -//========================================================================== -// -// -// -//========================================================================== - -void DObject::SetNetworkID(const uint32_t id) -{ - if (!IsNetworked()) - { - ObjectFlags |= OF_Networked; - _networkID = id; - } -} - -void DObject::ClearNetworkID() -{ - ObjectFlags &= ~OF_Networked; - _networkID = NetworkEntityManager::WorldNetID; -} - -void DObject::EnableNetworking(const bool enable) -{ - if (enable) - NetworkEntityManager::AddNetworkEntity(this); - else - NetworkEntityManager::RemoveNetworkEntity(this); -} - -void DObject::RemoveFromNetwork() -{ - NetworkEntityManager::RemoveNetworkEntity(this); -} - -static unsigned int GetNetworkID(DObject* const self) -{ - return self->GetNetworkID(); -} - -DEFINE_ACTION_FUNCTION_NATIVE(DObject, GetNetworkID, GetNetworkID) -{ - PARAM_SELF_PROLOGUE(DObject); - - ACTION_RETURN_INT(self->GetNetworkID()); -} - -static void EnableNetworking(DObject* const self, const bool enable) -{ - self->EnableNetworking(enable); -} - -DEFINE_ACTION_FUNCTION_NATIVE(DObject, EnableNetworking, EnableNetworking) -{ - PARAM_SELF_PROLOGUE(DObject); - PARAM_BOOL(enable); - - self->EnableNetworking(enable); - return 0; -} - -static DObject* GetNetworkEntity(const unsigned int id) -{ - return NetworkEntityManager::GetNetworkEntity(id); -} - -DEFINE_ACTION_FUNCTION_NATIVE(DObject, GetNetworkEntity, GetNetworkEntity) -{ - PARAM_PROLOGUE; - PARAM_UINT(id); - - ACTION_RETURN_OBJECT(NetworkEntityManager::GetNetworkEntity(id)); -} - -//========================================================================== -// -// -// -//========================================================================== - // [RH] List "ping" times CCMD (pings) { diff --git a/src/d_net.h b/src/d_net.h index e4029bd66..e6a6a32b9 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -98,25 +98,6 @@ extern int ticdup; class player_t; class DObject; -class NetworkEntityManager -{ -private: - inline static TArray s_netEntities = {}; - inline static TArray s_openNetIDs = {}; - -public: - NetworkEntityManager() = delete; - - inline static uint32_t WorldNetID = 0u; - inline static uint32_t ClientNetIDStart = 1u; - inline static uint32_t NetIDStart = MAXPLAYERS + 1u; - - static void InitializeNetworkEntities(); - static void SetClientNetworkEntity(player_t* const client); - static void AddNetworkEntity(DObject* const ent); - static void RemoveNetworkEntity(DObject* const ent); - static DObject* GetNetworkEntity(const uint32_t id); -}; // [RH] // New generic packet structure: diff --git a/src/d_netinfo.cpp b/src/d_netinfo.cpp index ee004505e..bac6eddfe 100644 --- a/src/d_netinfo.cpp +++ b/src/d_netinfo.cpp @@ -197,7 +197,7 @@ void D_GetPlayerColor (int player, float *h, float *s, float *v, FPlayerColorSet RGBtoHSV (RPART(color)/255.f, GPART(color)/255.f, BPART(color)/255.f, h, s, v); - if (teamplay && TeamLibrary.IsValidTeam((team = info->GetTeam())) && !Teams[team].GetAllowCustomPlayerColor()) + if (teamplay && FTeam::IsValid((team = info->GetTeam())) && !Teams[team].GetAllowCustomPlayerColor()) { // In team play, force the player to use the team's hue // and adjust the saturation and value so that the team @@ -264,7 +264,7 @@ int D_PickRandomTeam () if (playeringame[i]) { team = players[i].userinfo.GetTeam(); - if (TeamLibrary.IsValidTeam(team)) + if (FTeam::IsValid(team)) { if (Teams[team].m_iPresent++ == 0) { @@ -319,7 +319,7 @@ static void UpdateTeam (int pnum, int team, bool update) { userinfo_t *info = &players[pnum].userinfo; - if ((dmflags2 & DF2_NO_TEAM_SWITCH) && (alwaysapplydmflags || deathmatch) && TeamLibrary.IsValidTeam (info->GetTeam())) + if ((dmflags2 & DF2_NO_TEAM_SWITCH) && (alwaysapplydmflags || deathmatch) && FTeam::IsValid (info->GetTeam())) { Printf ("%s\n", GStrings.GetString("TXT_NO_TEAM_CHANGE")); return; @@ -327,7 +327,7 @@ static void UpdateTeam (int pnum, int team, bool update) int oldteam; - if (!TeamLibrary.IsValidTeam (team)) + if (!FTeam::IsValid (team)) { team = TEAM_NONE; } @@ -337,7 +337,7 @@ static void UpdateTeam (int pnum, int team, bool update) if (update && oldteam != team) { FString message; - if (TeamLibrary.IsValidTeam (team)) + if (FTeam::IsValid (team)) { message = GStrings.GetString("TXT_JOINED_TEAM"); message.Substitute("%t", Teams[team].GetName()); @@ -356,7 +356,7 @@ static void UpdateTeam (int pnum, int team, bool update) StatusBar->AttachToPlayer (&players[pnum]); } // Double-check - if (!TeamLibrary.IsValidTeam (team)) + if (!FTeam::IsValid (team)) { *static_cast((*info)[NAME_Team]) = TEAM_NONE; } @@ -365,7 +365,7 @@ static void UpdateTeam (int pnum, int team, bool update) int D_GetFragCount (player_t *player) { const int team = player->userinfo.GetTeam(); - if (!teamplay || !TeamLibrary.IsValidTeam(team)) + if (!teamplay || !FTeam::IsValid(team)) { return player->fragcount; } @@ -479,7 +479,7 @@ void userinfo_t::Reset(int pnum) int userinfo_t::TeamChanged(int team) { - if (teamplay && !TeamLibrary.IsValidTeam(team)) + if (teamplay && !FTeam::IsValid(team)) { // Force players onto teams in teamplay mode team = D_PickRandomTeam(); } diff --git a/src/doomdef.h b/src/doomdef.h index 811b4fa5e..f1f4f352e 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -242,6 +242,7 @@ enum : unsigned int COMPATF2_NOMBF21 = 1 << 14, // disable MBF21 features that may clash with certain maps COMPATF2_VOODOO_ZOMBIES = 1 << 15, // [RL0] allow playerinfo, playerpawn, and voodoo health to all be different, and skip killing the player's mobj if a voodoo doll dies to allow voodoo zombies COMPATF2_FDTELEPORT = 1 << 16, // Emulate Final Doom's teleporter z glitch. + COMPATF2_NOACSARGCHECK = 1 << 17, // Disable arg count checking for ACS }; diff --git a/src/g_game.cpp b/src/g_game.cpp index 5ad06b775..dc73465d8 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -476,11 +476,17 @@ CCMD(invquery) } } +constexpr char True[] = "true"; + CCMD (use) { if (argv.argc() > 1 && players[consoleplayer].mo != NULL) { - SendItemUse = players[consoleplayer].mo->FindInventory(argv[1]); + bool subclass = false; + if (argv.argc() > 2) + subclass = !stricmp(argv[2], True) || atoi(argv[2]); + + SendItemUse = players[consoleplayer].mo->FindInventory(argv[1], subclass); } } @@ -503,7 +509,11 @@ CCMD (drop) { if (argv.argc() > 1 && players[consoleplayer].mo != NULL) { - SendItemDrop = players[consoleplayer].mo->FindInventory(argv[1]); + bool subclass = false; + if (argv.argc() > 3) + subclass = !stricmp(argv[3], True) || atoi(argv[3]); + + SendItemDrop = players[consoleplayer].mo->FindInventory(argv[1], subclass); SendItemDropAmount = argv.argc() > 2 ? atoi(argv[2]) : -1; } } @@ -528,7 +538,11 @@ CCMD (select) auto user = players[consoleplayer].mo; if (argv.argc() > 1) { - auto item = user->FindInventory(argv[1]); + bool subclass = false; + if (argv.argc() > 2) + subclass = !stricmp(argv[2], True) || atoi(argv[2]); + + auto item = user->FindInventory(argv[1], subclass); if (item != NULL) { user->PointerVar(NAME_InvSel) = item; @@ -1425,7 +1439,7 @@ void FLevelLocals::PlayerReborn (int player) p->oldbuttons = ~0, p->attackdown = true; p->usedown = true; // don't do anything immediately p->original_oldbuttons = ~0; p->playerstate = PST_LIVE; - NetworkEntityManager::SetClientNetworkEntity(p); + NetworkEntityManager::SetClientNetworkEntity(p->mo, p - players); if (gamestate != GS_TITLELEVEL) { @@ -3082,7 +3096,7 @@ bool G_CheckDemoStatus (void) const size_t size = demo_p - demobuffer; saved = fw->Write(demobuffer, size) == size; delete fw; - if (!saved) remove(demoname.GetChars()); + if (!saved) RemoveFile(demoname.GetChars()); } M_Free (demobuffer); demorecording = false; diff --git a/src/g_statusbar/sbarinfo_commands.cpp b/src/g_statusbar/sbarinfo_commands.cpp index bbb3bc90f..73462f384 100644 --- a/src/g_statusbar/sbarinfo_commands.cpp +++ b/src/g_statusbar/sbarinfo_commands.cpp @@ -924,6 +924,7 @@ class CommandDrawString : public SBarInfoCommand break; default: str = GStrings.localize(label.GetChars()); + RealignString(); break; } } diff --git a/src/g_statusbar/shared_sbar.cpp b/src/g_statusbar/shared_sbar.cpp index fc8060d6a..2d878ab80 100644 --- a/src/g_statusbar/shared_sbar.cpp +++ b/src/g_statusbar/shared_sbar.cpp @@ -1233,76 +1233,63 @@ void DBaseStatusBar::DrawTopStuff (EHudState state) void DBaseStatusBar::DrawConsistancy () const { - static bool firsttime = true; - int i; - char conbuff[64], *buff_p; - if (!netgame) return; - buff_p = NULL; - for (i = 0; i < MAXPLAYERS; i++) + bool desync = false; + FString text = "Out of sync with:"; + for (int i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && players[i].inconsistant) { - if (buff_p == NULL) - { - strcpy (conbuff, "Out of sync with:"); - buff_p = conbuff + 17; - } - *buff_p++ = ' '; - *buff_p++ = '1' + i; - *buff_p = 0; + desync = true; + text.AppendFormat(" %s (%d)", players[i].userinfo.GetName(10u), i + 1); } } - if (buff_p != NULL) + if (desync) { - if (firsttime) + auto lines = V_BreakLines(SmallFont, twod->GetWidth() / CleanXfac - 40, text.GetChars()); + const int height = SmallFont->GetHeight() * CleanYfac; + double y = 0.0; + for (auto& line : lines) { - firsttime = false; - if (debugfile) - { - fprintf (debugfile, "%s as of tic %d (%d)\n", conbuff, - players[1-consoleplayer].inconsistant, - players[1-consoleplayer].inconsistant/ticdup); - } + DrawText(twod, SmallFont, CR_GREEN, + (twod->GetWidth() - SmallFont->StringWidth(line.Text) * CleanXfac) * 0.5, + y, line.Text.GetChars(), DTA_CleanNoMove, true, TAG_DONE); + y += height; } - DrawText(twod, SmallFont, CR_GREEN, - (twod->GetWidth() - SmallFont->StringWidth (conbuff)*CleanXfac) / 2, - 0, conbuff, DTA_CleanNoMove, true, TAG_DONE); } } void DBaseStatusBar::DrawWaiting () const { - int i; - char conbuff[64], *buff_p; - if (!netgame) return; - buff_p = NULL; - for (i = 0; i < MAXPLAYERS; i++) + FString text = "Waiting for:"; + bool isWaiting = false; + for (int i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && players[i].waiting) { - if (buff_p == NULL) - { - strcpy (conbuff, "Waiting for:"); - buff_p = conbuff + 12; - } - *buff_p++ = ' '; - *buff_p++ = '1' + i; - *buff_p = 0; + isWaiting = true; + text.AppendFormat(" %s (%d)", players[i].userinfo.GetName(10u), i + 1); } } - if (buff_p != NULL) + if (isWaiting) { - DrawText(twod, SmallFont, CR_ORANGE, - (twod->GetWidth() - SmallFont->StringWidth (conbuff)*CleanXfac) / 2, - SmallFont->GetHeight()*CleanYfac, conbuff, DTA_CleanNoMove, true, TAG_DONE); + auto lines = V_BreakLines(SmallFont, twod->GetWidth() / CleanXfac - 40, text.GetChars()); + const int height = SmallFont->GetHeight() * CleanYfac; + double y = 0.0; + for (auto& line : lines) + { + DrawText(twod, SmallFont, CR_ORANGE, + (twod->GetWidth() - SmallFont->StringWidth(line.Text) * CleanXfac) * 0.5, + y, line.Text.GetChars(), DTA_CleanNoMove, true, TAG_DONE); + y += height; + } } } diff --git a/src/gamedata/g_mapinfo.cpp b/src/gamedata/g_mapinfo.cpp index 2fdf197a2..4822f35a8 100644 --- a/src/gamedata/g_mapinfo.cpp +++ b/src/gamedata/g_mapinfo.cpp @@ -1875,6 +1875,7 @@ MapFlagHandlers[] = { "compat_stayonlift", MITYPE_COMPATFLAG, 0, COMPATF2_STAYONLIFT }, { "compat_nombf21", MITYPE_COMPATFLAG, 0, COMPATF2_NOMBF21 }, { "compat_voodoozombies", MITYPE_COMPATFLAG, 0, COMPATF2_VOODOO_ZOMBIES }, + { "compat_noacsargcheck", MITYPE_COMPATFLAG, 0, COMPATF2_NOACSARGCHECK }, { "cd_start_track", MITYPE_EATNEXT, 0, 0 }, { "cd_end1_track", MITYPE_EATNEXT, 0, 0 }, { "cd_end2_track", MITYPE_EATNEXT, 0, 0 }, diff --git a/src/gamedata/teaminfo.cpp b/src/gamedata/teaminfo.cpp index 1838d1b39..44e8c0e37 100644 --- a/src/gamedata/teaminfo.cpp +++ b/src/gamedata/teaminfo.cpp @@ -43,6 +43,7 @@ #include "v_video.h" #include "filesystem.h" #include "vm.h" +#include "d_player.h" // MACROS ------------------------------------------------------------------ @@ -56,9 +57,11 @@ // EXTERNAL DATA DECLARATIONS ---------------------------------------------- +extern bool playeringame[MAXPLAYERS]; +extern player_t players[MAXPLAYERS]; + // PUBLIC DATA DEFINITIONS ------------------------------------------------- -FTeam TeamLibrary; TArray Teams; // PRIVATE DATA DEFINITIONS ------------------------------------------------ @@ -245,7 +248,7 @@ void FTeam::ClearTeams () // //========================================================================== -bool FTeam::IsValidTeam (unsigned int uiTeam) const +bool FTeam::IsValid (unsigned int uiTeam) { if (uiTeam >= Teams.Size ()) return false; @@ -253,6 +256,16 @@ bool FTeam::IsValidTeam (unsigned int uiTeam) const return true; } +bool FTeam::ChangeTeam(unsigned int pNum, unsigned int newTeam) +{ + if (!multiplayer || !teamplay || pNum >= MAXPLAYERS || !playeringame[pNum] || !FTeam::IsValid(newTeam) || players[pNum].userinfo.GetTeam() == newTeam) + return false; + + players[pNum].userinfo.TeamChanged(newTeam); + R_BuildPlayerTranslation(pNum); + return true; +} + //========================================================================== // // FTeam :: GetName @@ -342,7 +355,7 @@ DEFINE_FIELD_NAMED(FTeam, m_Name, mName) static int IsValid(unsigned int id) { - return TeamLibrary.IsValidTeam(id); + return FTeam::IsValid(id); } DEFINE_ACTION_FUNCTION_NATIVE(FTeam, IsValid, IsValid) @@ -350,7 +363,21 @@ DEFINE_ACTION_FUNCTION_NATIVE(FTeam, IsValid, IsValid) PARAM_PROLOGUE; PARAM_UINT(id); - ACTION_RETURN_BOOL(TeamLibrary.IsValidTeam(id)); + ACTION_RETURN_BOOL(FTeam::IsValid(id)); +} + +static int ChangeTeam(unsigned int pNum, unsigned int newTeam) +{ + return FTeam::ChangeTeam(pNum, newTeam); +} + +DEFINE_ACTION_FUNCTION_NATIVE(FTeam, ChangeTeam, ChangeTeam) +{ + PARAM_PROLOGUE; + PARAM_UINT(pNum); + PARAM_UINT(newTeam); + + ACTION_RETURN_BOOL(FTeam::ChangeTeam(pNum, newTeam)); } static int GetPlayerColor(FTeam* self) diff --git a/src/gamedata/teaminfo.h b/src/gamedata/teaminfo.h index 1a3db705c..17be9dd46 100644 --- a/src/gamedata/teaminfo.h +++ b/src/gamedata/teaminfo.h @@ -45,8 +45,9 @@ class FTeam { public: FTeam (); - void ParseTeamInfo (); - bool IsValidTeam (unsigned int uiTeam) const; + static void ParseTeamInfo (); + static bool IsValid (unsigned int uiTeam); + static bool ChangeTeam(unsigned int pNum, unsigned int newTeam); const char *GetName () const; int GetPlayerColor () const; @@ -60,8 +61,8 @@ class FTeam int m_iTies; private: - void ParseTeamDefinition (FScanner &Scan); - void ClearTeams (); + static void ParseTeamDefinition (FScanner &Scan); + static void ClearTeams (); public: // needed for script access. FString m_Name; @@ -72,7 +73,6 @@ class FTeam bool m_bAllowCustomPlayerColor; }; -extern FTeam TeamLibrary; extern TArray Teams; #endif diff --git a/src/hu_scores.cpp b/src/hu_scores.cpp index 797e222ef..d7cde85fc 100644 --- a/src/hu_scores.cpp +++ b/src/hu_scores.cpp @@ -293,7 +293,7 @@ static void HU_DoDrawScores (player_t *player, player_t *sortedplayers[MAXPLAYER for (i = 0; i < MAXPLAYERS; ++i) { - if (playeringame[sortedplayers[i]-players] && TeamLibrary.IsValidTeam (sortedplayers[i]->userinfo.GetTeam())) + if (playeringame[sortedplayers[i]-players] && FTeam::IsValid (sortedplayers[i]->userinfo.GetTeam())) { if (Teams[sortedplayers[i]->userinfo.GetTeam()].m_iPlayerCount++ == 0) { @@ -485,7 +485,7 @@ int HU_GetRowColor(player_t *player, bool highlight) { if (teamplay && deathmatch) { - if (TeamLibrary.IsValidTeam (player->userinfo.GetTeam())) + if (FTeam::IsValid (player->userinfo.GetTeam())) return Teams[player->userinfo.GetTeam()].GetTextColor(); else return CR_GREY; diff --git a/src/launcher/launcherwindow.cpp b/src/launcher/launcherwindow.cpp index 768872fda..68142536c 100644 --- a/src/launcher/launcherwindow.cpp +++ b/src/launcher/launcherwindow.cpp @@ -12,11 +12,7 @@ #include #include -#if defined(EXTRAARGS) -CVAR(String, additional_parameters, "", CVAR_ARCHIVE | CVAR_NOSET | CVAR_GLOBALCONFIG); -#endif - -int LauncherWindow::ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags) +int LauncherWindow::ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags, FString * extraArgs) { Size screenSize = GetScreenSize(); double windowWidth = 615.0; @@ -28,6 +24,8 @@ int LauncherWindow::ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* DisplayWindow::RunLoop(); + if(extraArgs) *extraArgs = launcher->PlayGame->GetExtraArgs(); + return launcher->ExecResult; } @@ -47,10 +45,6 @@ LauncherWindow::LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int UpdateLanguage(); -#if defined(EXTRAARGS) - PlayGame->SetExtraArgs(static_cast(additional_parameters).GetChars()); -#endif - Pages->SetCurrentWidget(PlayGame); PlayGame->SetFocus(); } @@ -59,16 +53,6 @@ void LauncherWindow::Start() { Settings->Save(); -#if defined(EXTRAARGS) - std::string extraargs = PlayGame->GetExtraArgs(); - if (extraargs != static_cast(additional_parameters).GetChars()) - { - additional_parameters = extraargs.c_str(); - - // To do: restart the process like the cocoa backend is doing? - } -#endif - ExecResult = PlayGame->GetSelectedGame(); DisplayWindow::ExitLoop(); } diff --git a/src/launcher/launcherwindow.h b/src/launcher/launcherwindow.h index 8cdbc6ce7..8e7c8bbe6 100644 --- a/src/launcher/launcherwindow.h +++ b/src/launcher/launcherwindow.h @@ -14,7 +14,7 @@ struct WadStuff; class LauncherWindow : public Widget { public: - static int ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags); + static int ExecModal(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags, FString * extraArgs = nullptr); LauncherWindow(WadStuff* wads, int numwads, int defaultiwad, int* autoloadflags); void UpdateLanguage(); diff --git a/src/launcher/playgamepage.cpp b/src/launcher/playgamepage.cpp index 411d2fb9f..84ac64ad1 100644 --- a/src/launcher/playgamepage.cpp +++ b/src/launcher/playgamepage.cpp @@ -12,13 +12,9 @@ PlayGamePage::PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads { WelcomeLabel = new TextLabel(this); SelectLabel = new TextLabel(this); -#if defined(EXTRAARGS) ParametersLabel = new TextLabel(this); -#endif GamesList = new ListView(this); -#if defined(EXTRAARGS) ParametersEdit = new LineEdit(this); -#endif for (int i = 0; i < numwads; i++) { @@ -44,7 +40,6 @@ PlayGamePage::PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads GamesList->OnActivated = [=]() { OnGamesListActivated(); }; } -#if defined(EXTRAARGS) void PlayGamePage::SetExtraArgs(const std::string& args) { ParametersEdit->SetText(args); @@ -54,7 +49,6 @@ std::string PlayGamePage::GetExtraArgs() { return ParametersEdit->GetText(); } -#endif int PlayGamePage::GetSelectedGame() { @@ -64,9 +58,7 @@ int PlayGamePage::GetSelectedGame() void PlayGamePage::UpdateLanguage() { SelectLabel->SetText(GStrings.GetString("PICKER_SELECT")); -#if defined(EXTRAARGS) ParametersLabel->SetText(GStrings.GetString("PICKER_ADDPARM")); -#endif FString welcomeText = GStrings.GetString("PICKER_WELCOME"); welcomeText.Substitute("%s", GAMENAME); WelcomeLabel->SetText(welcomeText.GetChars()); @@ -98,7 +90,6 @@ void PlayGamePage::OnGeometryChanged() y = GetHeight() - 10.0; -#if 0 // NYI: Additional Parameters double editHeight = 24.0; y -= editHeight; ParametersEdit->SetFrameGeometry(0.0, y, GetWidth(), editHeight); @@ -108,7 +99,6 @@ void PlayGamePage::OnGeometryChanged() y -= labelHeight; ParametersLabel->SetFrameGeometry(0.0, y, GetWidth(), labelHeight); y -= 10.0; -#endif double listViewBottom = y - 10.0; GamesList->SetFrameGeometry(0.0, listViewTop, GetWidth(), std::max(listViewBottom - listViewTop, 0.0)); diff --git a/src/launcher/playgamepage.h b/src/launcher/playgamepage.h index 9378c3798..79e92e67b 100644 --- a/src/launcher/playgamepage.h +++ b/src/launcher/playgamepage.h @@ -2,8 +2,6 @@ #include -// #define EXTRAARGS - class LauncherWindow; class TextLabel; class ListView; @@ -16,10 +14,8 @@ class PlayGamePage : public Widget PlayGamePage(LauncherWindow* launcher, WadStuff* wads, int numwads, int defaultiwad); void UpdateLanguage(); -#if defined(EXTRAARGS) void SetExtraArgs(const std::string& args); std::string GetExtraArgs(); -#endif int GetSelectedGame(); @@ -32,11 +28,7 @@ class PlayGamePage : public Widget TextLabel* WelcomeLabel = nullptr; TextLabel* SelectLabel = nullptr; -#if defined(EXTRAARGS) TextLabel* ParametersLabel = nullptr; -#endif ListView* GamesList = nullptr; -#if defined(EXTRAARGS) LineEdit* ParametersEdit = nullptr; -#endif }; diff --git a/src/launcher/settingspage.cpp b/src/launcher/settingspage.cpp index 9cd89bcd0..1bfe54d22 100644 --- a/src/launcher/settingspage.cpp +++ b/src/launcher/settingspage.cpp @@ -161,7 +161,7 @@ void SettingsPage::OnLanguageChanged(int i) void SettingsPage::OnGeometryChanged() { - double panelWidth = 200.0; + double panelWidth = 160.0; double y = 0.0; double w = GetWidth(); double h = GetHeight(); diff --git a/src/maploader/compatibility.cpp b/src/maploader/compatibility.cpp index 7caddc90a..1ba18d444 100644 --- a/src/maploader/compatibility.cpp +++ b/src/maploader/compatibility.cpp @@ -174,6 +174,7 @@ static FCompatOption Options[] = { "nombf21", COMPATF2_NOMBF21, SLOT_COMPAT2 }, { "voodoozombies", COMPATF2_VOODOO_ZOMBIES, SLOT_COMPAT2 }, { "fdteleport", COMPATF2_FDTELEPORT, SLOT_COMPAT2 }, + { "noacsargcheck", COMPATF2_NOACSARGCHECK, SLOT_COMPAT2 }, { NULL, 0, 0 } }; diff --git a/src/maploader/glnodes.cpp b/src/maploader/glnodes.cpp index b41b22c0b..33051b6c1 100644 --- a/src/maploader/glnodes.cpp +++ b/src/maploader/glnodes.cpp @@ -34,12 +34,6 @@ #ifndef _WIN32 #include - -#else -#include - -#define rmdir _rmdir - #endif #include @@ -906,7 +900,8 @@ bool MapLoader::LoadGLNodes(MapData * map) break; } else - gwalumps[i] = f_gwa->GetEntryReader(li + i + 1, FileSys::READER_NEW, FileSys::READERFLAG_SEEKABLE); + // This is a special case. The container for the map WAD is not set up to create new file handles for itself so this needs to cache the content here. + gwalumps[i] = f_gwa->GetEntryReader(li + i + 1, FileSys::READER_CACHED, FileSys::READERFLAG_SEEKABLE); } if (result) result = DoLoadGLNodes(gwalumps); } @@ -1215,11 +1210,11 @@ UNSAFE_CCMD(clearnodecache) { if (list[i].isDirectory) { - rmdir(list[i].FilePath.c_str()); + RemoveDir(list[i].FilePath.c_str()); } else { - remove(list[i].FilePath.c_str()); + RemoveFile(list[i].FilePath.c_str()); } } diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index 6c72d3a52..76b9c3862 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -660,7 +660,7 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload) for (unsigned int i = 0u; i < MAXPLAYERS; ++i) { if (PlayerInGame(i) && Players[i]->mo != nullptr) - NetworkEntityManager::SetClientNetworkEntity(Players[i]); + NetworkEntityManager::SetClientNetworkEntity(Players[i]->mo, i); } } } diff --git a/src/playsim/a_dynlight.cpp b/src/playsim/a_dynlight.cpp index 98d105acb..b274d63c6 100644 --- a/src/playsim/a_dynlight.cpp +++ b/src/playsim/a_dynlight.cpp @@ -256,7 +256,7 @@ void FDynamicLight::Tick() if (owned) { - if (!target->state) + if (!target->state || !target->ShouldRenderLocally()) { Deactivate(); return; diff --git a/src/playsim/actor.h b/src/playsim/actor.h index de1582ca7..986e3e11a 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -501,7 +501,8 @@ enum ActorRenderFlag2 RF2_FLIPSPRITEOFFSETX = 0x0010, RF2_FLIPSPRITEOFFSETY = 0x0020, RF2_CAMFOLLOWSPLAYER = 0x0040, // Matches the cam's base position and angles to the main viewpoint. - RF2_ISOMETRICSPRITES = 0x0080, + RF2_NOMIPMAP = 0x0080, // [Nash] forces no mipmapping on sprites. Useful for tiny sprites that need to remain visually crisp + RF2_ISOMETRICSPRITES = 0x0100, }; // This translucency value produces the closest match to Heretic's TINTTAB. @@ -713,7 +714,7 @@ struct AnimOverride int flags = ANIMOVERRIDE_NONE; float framerate; double startTic; // when the current animation started (changing framerates counts as restarting) (or when animation starts if interpolating from previous animation) - double switchTic; // when the animation was changed -- where to interpolate the switch from + double switchOffset; // when the animation was changed -- where to interpolate the switch from }; struct ModelOverride @@ -777,6 +778,9 @@ class DViewPosition : public DObject void Set(DVector3 &off, int f = -1) { + ZeroSubnormalsF(off.X); + ZeroSubnormalsF(off.Y); + ZeroSubnormalsF(off.Z); Offset = off; if (f > -1) diff --git a/src/playsim/bots/b_game.cpp b/src/playsim/bots/b_game.cpp index 198692c1f..3863ac923 100644 --- a/src/playsim/bots/b_game.cpp +++ b/src/playsim/bots/b_game.cpp @@ -305,7 +305,7 @@ bool FCajunMaster::SpawnBot (const char *name, int color) { concat << colors[bot_next_color]; } - if (TeamLibrary.IsValidTeam (thebot->lastteam)) + if (FTeam::IsValid (thebot->lastteam)) { // Keep the bot on the same team when switching levels concat.AppendFormat("\\team\\%d\n", thebot->lastteam); } @@ -587,7 +587,7 @@ bool FCajunMaster::LoadBots () if (IsNum (sc.String)) { teamnum = atoi (sc.String); - if (!TeamLibrary.IsValidTeam (teamnum)) + if (!FTeam::IsValid (teamnum)) { teamnum = TEAM_NONE; } diff --git a/src/playsim/d_player.h b/src/playsim/d_player.h index 34ac55da6..a717bb42c 100644 --- a/src/playsim/d_player.h +++ b/src/playsim/d_player.h @@ -204,9 +204,25 @@ struct userinfo_t : TMap { return *static_cast(*CheckKey(NAME_Autoaim)); } - const char *GetName() const + const char *GetName(unsigned int charLimit = 0u) const { - return *static_cast(*CheckKey(NAME_Name)); + const char* name = *static_cast(*CheckKey(NAME_Name)); + if (charLimit) + { + FString temp = name; + if (temp.CharacterCount() > charLimit) + { + int next = 0; + for (unsigned int i = 0u; i < charLimit; ++i) + temp.GetNextCharacter(next); + + temp.Truncate(next); + temp += "..."; + name = temp.GetChars(); + } + } + + return name; } int GetTeam() const { diff --git a/src/playsim/dthinker.cpp b/src/playsim/dthinker.cpp index 3214decf3..4229de792 100644 --- a/src/playsim/dthinker.cpp +++ b/src/playsim/dthinker.cpp @@ -132,7 +132,7 @@ void FThinkerCollection::RunThinkers(FLevelLocals *Level) if (dolights) ac->SetDynamicLights(); } // This was merged from P_RunEffects to eliminate the costly duplicate ThinkerIterator loop. - if ((ac->effects || ac->fountaincolor) && !Level->isFrozen()) + if ((ac->effects || ac->fountaincolor) && ac->ShouldRenderLocally() && !Level->isFrozen()) { P_RunEffect(ac, ac->effects); } diff --git a/src/playsim/p_acs.cpp b/src/playsim/p_acs.cpp index b464ed2a5..1d4e90d1e 100644 --- a/src/playsim/p_acs.cpp +++ b/src/playsim/p_acs.cpp @@ -5323,7 +5323,13 @@ int DLevelScript::SwapActorTeleFog(AActor *activator, int tid) } // Macro for CallFunction. Checks passed number of arguments with minimum required. Sets needCount and returns if not enough. -#define MIN_ARG_COUNT(minCount) do { if (argCount < minCount) { needCount = minCount; return 0; } } while(0) +#define MIN_ARG_COUNT(minCount) \ + do { \ + if (argCount < minCount && !(Level->i_compatflags2 & COMPATF2_NOACSARGCHECK)) { \ + needCount = minCount; \ + return 0; \ + } \ + } while(0) int DLevelScript::CallFunction(int argCount, int funcIndex, int32_t *args, int &needCount) { diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index b6090213b..4a647af4e 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -5214,18 +5214,20 @@ void SetAnimationInternal(AActor * self, FName animName, double framerate, int s self->modelData->curAnim.startFrame = startFrame < 0 ? animStart : animStart + startFrame; self->modelData->curAnim.loopFrame = loopFrame < 0 ? animStart : animStart + loopFrame; self->modelData->curAnim.flags = (flags&SAF_LOOP) ? ANIMOVERRIDE_LOOP : 0; - self->modelData->curAnim.switchTic = tic; self->modelData->curAnim.framerate = (float)framerate; if(!(flags & SAF_INSTANT)) { self->modelData->prevAnim.startFrame = getCurrentFrame(self->modelData->prevAnim, tic); - self->modelData->curAnim.startTic = floor(tic) + interpolateTics; + int startTic = floor(tic) + interpolateTics; + self->modelData->curAnim.startTic = startTic; + self->modelData->curAnim.switchOffset = startTic - tic; } else { self->modelData->curAnim.startTic = tic; + self->modelData->curAnim.switchOffset = 0; } } @@ -5274,7 +5276,7 @@ void SetAnimationFrameRateInternal(AActor * self, double framerate, double ticFr self->modelData->curAnim.startFrame = frame; self->modelData->curAnim.startTic = tic; - self->modelData->curAnim.switchTic = tic; + self->modelData->curAnim.switchOffset = 0; self->modelData->curAnim.framerate = (float)framerate; } diff --git a/src/playsim/p_effect.cpp b/src/playsim/p_effect.cpp index 5ee7e9f3c..459d267be 100644 --- a/src/playsim/p_effect.cpp +++ b/src/playsim/p_effect.cpp @@ -374,7 +374,7 @@ void P_SpawnParticle(FLevelLocals *Level, const DVector3 &pos, const DVector3 &v particle->Acc = FVector3(accel); particle->color = ParticleColor(color); particle->alpha = float(startalpha); - if (fadestep <= -1.0) particle->fadestep = FADEFROMTTL(lifetime); + if ((fadestep < 0 && !(flags & SPF_NEGATIVE_FADESTEP)) || fadestep <= -1.0) particle->fadestep = FADEFROMTTL(lifetime); else particle->fadestep = float(fadestep); particle->ttl = lifetime; particle->size = size; diff --git a/src/playsim/p_effect.h b/src/playsim/p_effect.h index 0f439785c..5eafdcd4b 100644 --- a/src/playsim/p_effect.h +++ b/src/playsim/p_effect.h @@ -65,6 +65,11 @@ enum EParticleFlags SPF_REPLACE = 1 << 7, SPF_NO_XY_BILLBOARD = 1 << 8, SPF_LOCAL_ANIM = 1 << 9, + SPF_NEGATIVE_FADESTEP = 1 << 10, + SPF_FACECAMERA = 1 << 11, + SPF_NOFACECAMERA = 1 << 12, + SPF_ROLLCENTER = 1 << 13, + SPF_NOMIPMAP = 1 << 14, }; class DVisualThinker; @@ -87,7 +92,7 @@ struct particle_t FStandaloneAnimation animData; //+16 = 128 }; -static_assert(sizeof(particle_t) == 128); +static_assert(sizeof(particle_t) == 128, "Only LP64/LLP64 is supported"); const uint16_t NO_PARTICLE = 0xffff; diff --git a/src/playsim/p_enemy.cpp b/src/playsim/p_enemy.cpp index 50ed1a74d..cf15cbf38 100644 --- a/src/playsim/p_enemy.cpp +++ b/src/playsim/p_enemy.cpp @@ -2139,7 +2139,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_LookEx) if (!(flags & LOF_NOSIGHTCHECK)) { - if (!P_LookForPlayers(self, true, ¶ms)) + if (!P_LookForPlayers(self, (self->flags4 & MF4_LOOKALLAROUND), ¶ms)) // [RK] Account for the flag being set. return 0; } else diff --git a/src/playsim/p_map.cpp b/src/playsim/p_map.cpp index d2a7da76f..af8df92a7 100644 --- a/src/playsim/p_map.cpp +++ b/src/playsim/p_map.cpp @@ -6179,7 +6179,7 @@ int P_RadiusAttack(AActor *bombspot, AActor *bombsource, int bombdamage, double continue; //[inkoalawetrust] Don't harm actors friendly to the explosions' source. But do harm the source. - if ((flags & RADF_NOALLIES) && bombsource->IsFriend(thing) && !(thing == bombsource || thing == bombspot)) + if ((flags & RADF_NOALLIES) && bombsource && bombsource->IsFriend(thing) && !(thing == bombsource || thing == bombspot)) continue; if (bombsource && thing != bombsource && bombsource->player && P_ShouldPassThroughPlayer(bombsource, thing)) diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 5c274eeeb..7aa37e458 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -1453,7 +1453,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimOverride &a arc("flags", ao.flags); arc("framerate", ao.framerate); arc("startTic", ao.startTic); - arc("switchTic", ao.switchTic); + arc("switchOffset", ao.switchOffset); arc.EndObject(); return arc; } @@ -2811,15 +2811,18 @@ static void PlayerLandedMakeGruntSound(AActor* self, AActor *onmobj) } } -static void PlayerLandedOnThing (AActor *mo, AActor *onmobj) +static void PlayerSquatView(AActor *self, AActor *onmobj) { - if (!mo->player) - return; - - if (mo->player->mo == mo) + IFVIRTUALPTR(self, AActor, PlayerSquatView) { - mo->player->deltaviewheight = mo->Vel.Z / 8.; + VMValue params[2] = { self, onmobj }; + VMCall(func, params, 2, nullptr, 0); } +} + +static void PlayerLandedOnThing (AActor *mo, AActor *onmobj) +{ + PlayerSquatView(mo, onmobj); if (mo->player->cheats & CF_PREDICTING) return; @@ -3810,8 +3813,11 @@ void AActor::Tick () // Check for Actor unmorphing, but only on the thing that is the morphed Actor. // Players do their own special checking for this. - if (alternative != nullptr && !(flags & MF_UNMORPHED) && player == nullptr) + if (alternative != nullptr && player == nullptr) { + if (flags & MF_UNMORPHED) + return; + int res = false; IFVIRTUAL(AActor, CheckUnmorph) { @@ -3855,6 +3861,12 @@ void AActor::Tick () { special2++; } + + if(flags9 & MF9_DECOUPLEDANIMATIONS && modelData && !(modelData->curAnim.flags & ANIMOVERRIDE_NONE)) + { + modelData->curAnim.startTic += 1; + } + return; } @@ -3902,6 +3914,12 @@ void AActor::Tick () { special2++; } + + if(flags9 & MF9_DECOUPLEDANIMATIONS && modelData && !(modelData->curAnim.flags & ANIMOVERRIDE_NONE)) + { + modelData->curAnim.startTic += 1; + } + return; } @@ -5339,6 +5357,42 @@ int MorphPointerSubstitution(AActor* from, AActor* to) return false; } + // [MC] Had to move this here since ObtainInventory was also moved as well. Should be called + // before any transference of items since that's what was intended when introduced. + if (!from->alternative) // Morphing into + { + { + IFVIRTUALPTR(from, AActor, PreMorph) + { + VMValue params[] = { from, to, false }; + VMCall(func, params, 3, nullptr, 0); + } + } + { + IFVIRTUALPTR(to, AActor, PreMorph) + { + VMValue params[] = { to, from, true }; + VMCall(func, params, 3, nullptr, 0); + } + } + } + else // Unmorphing back + { + { + IFVIRTUALPTR(from, AActor, PreUnmorph) + { + VMValue params[] = { from, to, false }; + VMCall(func, params, 3, nullptr, 0); + } + } + { + IFVIRTUALPTR(to, AActor, PreUnmorph) + { + VMValue params[] = { to, from, true }; + VMCall(func, params, 3, nullptr, 0); + } + } + } // Since the check is good, move the inventory items over. This should always be done when // morphing to emulate Heretic/Hexen's behavior since those stored the inventory in their // player structs. @@ -5386,6 +5440,10 @@ int MorphPointerSubstitution(AActor* from, AActor* to) { to->player = from->player; from->player = nullptr; + + // Swap the new body into the right network slot if it's a client (this doesn't + // really matter for regular Actors since they grab any ID they can get anyway). + NetworkEntityManager::SetClientNetworkEntity(to, to->player - players); } if (from->alternative != nullptr) diff --git a/src/playsim/p_spec.cpp b/src/playsim/p_spec.cpp index c0bfca34a..ba2036971 100644 --- a/src/playsim/p_spec.cpp +++ b/src/playsim/p_spec.cpp @@ -384,7 +384,9 @@ bool P_PredictLine(line_t *line, AActor *mo, int side, int activationType) // Only predict a very specifc section of specials if (line->special != Teleport_Line && - line->special != Teleport) + line->special != Teleport && + line->special != Teleport_NoFog && + line->special != Teleport_NoStop) { return false; } diff --git a/src/playsim/p_user.cpp b/src/playsim/p_user.cpp index d00f7571b..fefb0f943 100644 --- a/src/playsim/p_user.cpp +++ b/src/playsim/p_user.cpp @@ -101,9 +101,9 @@ static FRandom pr_skullpop ("SkullPop"); CVAR(Bool, sv_singleplayerrespawn, false, CVAR_SERVERINFO | CVAR_CHEAT) // Variables for prediction -CVAR (Bool, cl_noprediction, false, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR(Bool, cl_predict_specials, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // Deprecated +CVAR(Bool, cl_noprediction, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Float, cl_predict_lerpscale, 0.05f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Float, cl_predict_lerpthreshold, 2.00f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) @@ -124,6 +124,11 @@ CUSTOM_CVAR(Float, cl_rubberband_minmove, 20.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFI if (self < 0.1f) self = 0.1f; } +CUSTOM_CVAR(Float, cl_rubberband_limit, 756.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 0.0f) + self = 0.0f; +} ColorSetList ColorSets; PainFlashList PainFlashes; @@ -156,6 +161,12 @@ static TArray PredictionPortalSectors_sprev_Backup; static TArray PredictionPortalLinesBackup; static TArray PredictionPortalLines_sprev_Backup; +struct +{ + DVector3 Pos = {}; + int Flags = 0; +} static PredictionViewPosBackup; + // [GRB] Custom player classes TArray PlayerClasses; @@ -347,6 +358,7 @@ void player_t::CopyFrom(player_t &p, bool copyPSP) MUSINFOactor = p.MUSINFOactor; MUSINFOtics = p.MUSINFOtics; SoundClass = p.SoundClass; + angleOffsetTargets = p.angleOffsetTargets; if (copyPSP) { // This needs to transfer ownership completely. @@ -721,7 +733,8 @@ DEFINE_ACTION_FUNCTION(_PlayerInfo, Resurrect) DEFINE_ACTION_FUNCTION(_PlayerInfo, GetUserName) { PARAM_SELF_STRUCT_PROLOGUE(player_t); - ACTION_RETURN_STRING(self->userinfo.GetName()); + PARAM_UINT(charLimit); + ACTION_RETURN_STRING(self->userinfo.GetName(charLimit)); } DEFINE_ACTION_FUNCTION(_PlayerInfo, GetNeverSwitch) @@ -1415,8 +1428,7 @@ void P_PredictPlayer (player_t *player) { int maxtic; - if (cl_noprediction || - singletics || + if (singletics || demoplayback || player->mo == NULL || player != player->mo->Level->GetConsolePlayer() || @@ -1443,6 +1455,14 @@ void P_PredictPlayer (player_t *player) PredictionActorBackupArray.Resize(act->GetClass()->Size); memcpy(PredictionActorBackupArray.Data(), &act->snext, act->GetClass()->Size - ((uint8_t *)&act->snext - (uint8_t *)act)); + // Since this is a DObject it needs to have its fields backed up manually for restore, otherwise any changes + // to it will be permanent while predicting. This is now auto-created on pawns to prevent creation spam. + if (act->ViewPos != nullptr) + { + PredictionViewPosBackup.Pos = act->ViewPos->Offset; + PredictionViewPosBackup.Flags = act->ViewPos->Flags; + } + act->flags &= ~MF_PICKUP; act->flags2 &= ~MF2_PUSHWALL; player->cheats |= CF_PREDICTING; @@ -1482,7 +1502,7 @@ void P_PredictPlayer (player_t *player) // This essentially acts like a mini P_Ticker where only the stuff relevant to the client is actually // called. Call order is preserved. - bool rubberband = false; + bool rubberband = false, rubberbandLimit = false; DVector3 rubberbandPos = {}; const bool canRubberband = LastPredictedTic >= 0 && cl_rubberband_scale > 0.0f && cl_rubberband_scale < 1.0f; const double rubberbandThreshold = max(cl_rubberband_minmove, cl_rubberband_threshold); @@ -1506,33 +1526,43 @@ void P_PredictPlayer (player_t *player) { rubberband = true; rubberbandPos = player->mo->Pos(); + rubberbandLimit = cl_rubberband_limit > 0.0f && dist > cl_rubberband_limit * cl_rubberband_limit; } } + player->oldbuttons = player->cmd.ucmd.buttons; player->cmd = localcmds[i % LOCALCMDTICS]; player->mo->ClearInterpolation(); player->mo->ClearFOVInterpolation(); - P_PlayerThink (player); - player->mo->Tick (); + P_PlayerThink(player); + player->mo->CallTick(); } if (rubberband) { - R_ClearInterpolationPath(); - player->mo->renderflags &= ~RF_NOINTERPOLATEVIEW; - DPrintf(DMSG_NOTIFY, "Prediction mismatch at (%.3f, %.3f, %.3f)\nExpected: (%.3f, %.3f, %.3f)\nCorrecting to (%.3f, %.3f, %.3f)\n", LastPredictedPosition.X, LastPredictedPosition.Y, LastPredictedPosition.Z, rubberbandPos.X, rubberbandPos.Y, rubberbandPos.Z, player->mo->X(), player->mo->Y(), player->mo->Z()); - DVector3 snapPos = {}; - P_LerpCalculate(player->mo, LastPredictedPosition, snapPos, cl_rubberband_scale, cl_rubberband_threshold, cl_rubberband_minmove); - player->mo->PrevPortalGroup = LastPredictedPortalGroup; - player->mo->Prev = LastPredictedPosition; - const double zOfs = player->viewz - player->mo->Z(); - player->mo->SetXYZ(snapPos); - player->viewz = snapPos.Z + zOfs; + if (rubberbandLimit) + { + // If too far away, instantly snap the player's view to their correct position. + player->mo->renderflags |= RF_NOINTERPOLATEVIEW; + } + else + { + R_ClearInterpolationPath(); + player->mo->renderflags &= ~RF_NOINTERPOLATEVIEW; + + DVector3 snapPos = {}; + P_LerpCalculate(player->mo, LastPredictedPosition, snapPos, cl_rubberband_scale, cl_rubberband_threshold, cl_rubberband_minmove); + player->mo->PrevPortalGroup = LastPredictedPortalGroup; + player->mo->Prev = LastPredictedPosition; + const double zOfs = player->viewz - player->mo->Z(); + player->mo->SetXYZ(snapPos); + player->viewz = snapPos.Z + zOfs; + } } // This is intentionally done after rubberbanding starts since it'll automatically smooth itself towards @@ -1580,6 +1610,12 @@ void P_UnPredictPlayer () act->UnlinkFromWorld(&ctx); memcpy(&act->snext, PredictionActorBackupArray.Data(), PredictionActorBackupArray.Size() - ((uint8_t *)&act->snext - (uint8_t *)act)); + if (act->ViewPos != nullptr) + { + act->ViewPos->Offset = PredictionViewPosBackup.Pos; + act->ViewPos->Flags = PredictionViewPosBackup.Flags; + } + // The blockmap ordering needs to remain unchanged, too. // Restore sector links and refrences. // [ED850] This is somewhat of a duplicate of LinkToWorld(), but we need to keep every thing the same, diff --git a/src/playsim/shadowinlines.h b/src/playsim/shadowinlines.h index 60caa9506..574abd251 100644 --- a/src/playsim/shadowinlines.h +++ b/src/playsim/shadowinlines.h @@ -85,7 +85,12 @@ inline bool AffectedByShadows(AActor* self) inline AActor* CheckForShadows(AActor* self, AActor* other, DVector3 pos, double& penaltyFactor) { - return ((other && (other->flags & MF_SHADOW)) || (self->flags9 & MF9_DOSHADOWBLOCK)) ? P_CheckForShadowBlock(self, other, pos, penaltyFactor) : nullptr; + if ((other && (other->flags & MF_SHADOW)) || (self->flags9 & MF9_DOSHADOWBLOCK)) + { + AActor* shadowBlock = P_CheckForShadowBlock(self, other, pos, penaltyFactor); + return shadowBlock ? shadowBlock : other; + } + return nullptr; } inline AActor* PerformShadowChecks(AActor* self, AActor* other, DVector3 pos, double& penaltyFactor) diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index df5c41e71..bb2493018 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -46,6 +46,7 @@ #include "i_interface.h" #include "p_tick.h" #include "actor.h" +#include "actorinlines.h" #ifdef _MSC_VER @@ -333,13 +334,13 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr if(actor->modelData && !(actor->modelData->curAnim.flags & ANIMOVERRIDE_NONE)) { double tic = actor->Level->totaltime; - if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !Level->isFrozen()) + if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !actor->isFrozen()) { tic += I_GetTimeFrac(); } if(actor->modelData->curAnim.startTic > tic) { - inter = (tic - actor->modelData->curAnim.switchTic) / (actor->modelData->curAnim.startTic - actor->modelData->curAnim.switchTic); + inter = (tic - (actor->modelData->curAnim.startTic - actor->modelData->curAnim.switchOffset)) / actor->modelData->curAnim.switchOffset; double nextFrame = actor->modelData->curAnim.startFrame; @@ -633,6 +634,9 @@ void InitModels() smf.animationIDs[0] = -1; smf.xscale = smf.yscale = smf.zscale = VoxelDefs[i]->Scale; smf.angleoffset = VoxelDefs[i]->AngleOffset.Degrees(); + smf.xoffset = VoxelDefs[i]->xoffset; + smf.yoffset = VoxelDefs[i]->yoffset; + smf.zoffset = VoxelDefs[i]->zoffset; // this helps catching uninitialized data. assert(VoxelDefs[i]->PitchFromMomentum == true || VoxelDefs[i]->PitchFromMomentum == false); if (VoxelDefs[i]->PitchFromMomentum) smf.flags |= MDL_PITCHFROMMOMENTUM; diff --git a/src/r_data/voxeldef.cpp b/src/r_data/voxeldef.cpp index 35037158b..a2eee3c35 100644 --- a/src/r_data/voxeldef.cpp +++ b/src/r_data/voxeldef.cpp @@ -58,6 +58,9 @@ struct VoxelOptions int PlacedSpin = 0; double Scale = 1; DAngle AngleOffset = DAngle90; + double xoffset = 0.0; + double yoffset = 0.0; + double zoffset = 0.0; bool OverridePalette = false; bool PitchFromMomentum = false; bool UseActorPitch = false; @@ -177,6 +180,54 @@ static void VOX_ReadOptions(FScanner &sc, VoxelOptions &opts) } opts.AngleOffset = DAngle::fromDeg(mul * sc.Float + 90.); } + else if (sc.Compare("xoffset")) + { + int mul = 1; + sc.MustGetToken('='); + if (sc.CheckToken('-')) mul = -1; + sc.MustGetAnyToken(); + if (sc.TokenType == TK_IntConst) + { + sc.Float = sc.Number; + } + else + { + sc.TokenMustBe(TK_FloatConst); + } + opts.xoffset = sc.Float * mul; + } + else if (sc.Compare("yoffset")) + { + int mul = 1; + sc.MustGetToken('='); + if (sc.CheckToken('-')) mul = -1; + sc.MustGetAnyToken(); + if (sc.TokenType == TK_IntConst) + { + sc.Float = sc.Number; + } + else + { + sc.TokenMustBe(TK_FloatConst); + } + opts.yoffset = sc.Float * mul; + } + else if (sc.Compare("zoffset")) + { + int mul = 1; + sc.MustGetToken('='); + if (sc.CheckToken('-')) mul = -1; + sc.MustGetAnyToken(); + if (sc.TokenType == TK_IntConst) + { + sc.Float = sc.Number; + } + else + { + sc.TokenMustBe(TK_FloatConst); + } + opts.zoffset = sc.Float * mul; + } else if (sc.Compare("overridepalette")) { opts.OverridePalette = true; @@ -262,6 +313,9 @@ void R_InitVoxels() def->DroppedSpin = opts.DroppedSpin; def->PlacedSpin = opts.PlacedSpin; def->AngleOffset = opts.AngleOffset; + def->xoffset = opts.xoffset; + def->yoffset = opts.yoffset; + def->zoffset = opts.zoffset; def->PitchFromMomentum = opts.PitchFromMomentum; def->UseActorPitch = opts.UseActorPitch; def->UseActorRoll = opts.UseActorRoll; diff --git a/src/rendering/hwrenderer/doom_levelmesh.cpp b/src/rendering/hwrenderer/doom_levelmesh.cpp index 34b2952f7..a1e4fc0db 100644 --- a/src/rendering/hwrenderer/doom_levelmesh.cpp +++ b/src/rendering/hwrenderer/doom_levelmesh.cpp @@ -339,7 +339,7 @@ void DoomLevelMesh::CreateSurfaces(FLevelLocals& doomMap) for (unsigned int i = 0; i < doomMap.sectors.Size(); i++) { sector_t* sector = &doomMap.sectors[i]; - if (sector->subsectors[0]->flags & SSECF_POLYORG) + if (sector->subsectorcount == 0 || sector->subsectors[0]->flags & SSECF_POLYORG) continue; UpdateFlat(doomMap, i); } @@ -618,6 +618,10 @@ void DoomLevelMesh::CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& state { for (HWFlat& flatpart : list) { + state.mSortedLists.clear(); + state.mVertices.Clear(); + state.mIndexes.Clear(); + if (isSky) { state.SetEffect(EFF_PORTAL); @@ -661,9 +665,6 @@ void DoomLevelMesh::CreateFlatSurface(HWFlatDispatcher& disp, MeshBuilder& state foundDraw = true; break; } - state.mSortedLists.clear(); - state.mVertices.Clear(); - state.mIndexes.Clear(); if (!foundDraw) continue; diff --git a/src/rendering/hwrenderer/scene/hw_drawstructs.h b/src/rendering/hwrenderer/scene/hw_drawstructs.h index 00706f81e..fa0f098ea 100644 --- a/src/rendering/hwrenderer/scene/hw_drawstructs.h +++ b/src/rendering/hwrenderer/scene/hw_drawstructs.h @@ -411,6 +411,7 @@ class HWSprite TArray *lightlist; DRotator Angles; + bool nomipmap; // force the sprite to have no mipmaps (ensures tiny sprites in the distance stay crisp) void SplitSprite(HWDrawInfo *di, FRenderState& state, sector_t * frontsector, bool translucent); void PerformSpriteClipAdjustment(AActor *thing, const DVector2 &thingpos, float spriteheight); diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index 0baff8e4b..9304e09c3 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -225,8 +225,10 @@ void HWSprite::DrawSprite(HWDrawInfo *di, FRenderState &state, bool translucent) state.SetFog(0, 0); } + int clampmode = nomipmap ? CLAMP_XY_NOMIP : CLAMP_XY; + uint32_t spritetype = actor? uint32_t(actor->renderflags & RF_SPRITETYPEMASK) : 0; - if (texture) state.SetMaterial(texture, UF_Sprite, (spritetype == RF_FACESPRITE) ? CTF_Expand : 0, CLAMP_XY, translation, OverrideShader); + if (texture) state.SetMaterial(texture, UF_Sprite, (spritetype == RF_FACESPRITE) ? CTF_Expand : 0, clampmode, translation, OverrideShader); else if (!modelframe) state.EnableTexture(false); //SetColor(lightlevel, rel, Colormap, trans); @@ -412,6 +414,7 @@ bool HWSprite::CalculateVertices(HWDrawInfo* di, FVector3* v, DVector3* vp) pixelstretch = actor->Level->pixelstretch; else if (particle && particle->subsector && particle->subsector->sector && particle->subsector->sector->Level) pixelstretch = particle->subsector->sector->Level->pixelstretch; + // float pixelstretch = di->Level->pixelstretch; FVector3 center = FVector3((x1 + x2) * 0.5, (y1 + y2) * 0.5, (z1 + z2) * 0.5); const auto& HWAngles = di->Viewpoint.HWAngles; @@ -456,8 +459,9 @@ bool HWSprite::CalculateVertices(HWDrawInfo* di, FVector3* v, DVector3* vp) && (gl_billboard_mode == 1 || (actor && actor->renderflags & RF_FORCEXYBILLBOARD)))); const bool drawBillboardFacingCamera = hw_force_cambbpref ? gl_billboard_faces_camera : - (gl_billboard_faces_camera && (actor && !(actor->renderflags2 & RF2_BILLBOARDNOFACECAMERA))) - || !!(actor && actor->renderflags2 & RF2_BILLBOARDFACECAMERA); + gl_billboard_faces_camera + && ((actor && (!(actor->renderflags2 & RF2_BILLBOARDNOFACECAMERA) || (actor->renderflags2 & RF2_BILLBOARDFACECAMERA))) + || (particle && particle->texture.isValid() && (!(particle->flags & SPF_NOFACECAMERA) || (particle->flags & SPF_FACECAMERA)))); // [Nash] has +ROLLSPRITE const bool drawRollSpriteActor = (actor != nullptr && actor->renderflags & RF_ROLLSPRITE); @@ -470,7 +474,7 @@ bool HWSprite::CalculateVertices(HWDrawInfo* di, FVector3* v, DVector3* vp) // [Nash] is a flat sprite const bool isWallSprite = (actor != nullptr) && (spritetype == RF_WALLSPRITE); - const bool useOffsets = (actor != nullptr) && !(actor->renderflags & RF_ROLLCENTER); + const bool useOffsets = ((actor != nullptr) && !(actor->renderflags & RF_ROLLCENTER)) || (particle && !(particle->flags & SPF_ROLLCENTER)); FVector2 offset = FVector2( offx, offy ); float xx = -center.X + x; @@ -843,6 +847,8 @@ void HWSprite::Process(HWDrawInfo *di, FRenderState& state, AActor* thing, secto return; } + nomipmap = (thing->renderflags2 & RF2_NOMIPMAP); + // check renderrequired vs ~r_rendercaps, if anything matches we don't support that feature, // check renderhidden vs r_rendercaps, if anything matches we do support that feature and should hide it. if ((!r_debug_disable_vis_filter && !!(thing->RenderRequired & ~r_renderercaps)) || @@ -1093,6 +1099,13 @@ void HWSprite::Process(HWDrawInfo *di, FRenderState& state, AActor* thing, secto r.Scale(sprscale.X, isSpriteShadow ? sprscale.Y * 0.15 * thing->isoscaleY : sprscale.Y * thing->isoscaleY); + if (thing->renderflags & (RF_ROLLSPRITE|RF_FLATSPRITE)) + { + double ps = di->Level->pixelstretch; + double mult = 1.0 / sqrt(ps); // shrink slightly + r.Scale(mult * ps, mult); + } + float rightfac = -r.left; float leftfac = rightfac - r.width; z1 = z - r.top; @@ -1406,6 +1419,7 @@ void HWSprite::ProcessParticle(HWDrawInfo *di, FRenderState& state, particle_t * actor = nullptr; this->particle = particle; fullbright = particle->flags & SPF_FULLBRIGHT; + nomipmap = particle->flags & SPF_NOMIPMAP; if (di->isFullbrightScene()) { @@ -1532,7 +1546,11 @@ void HWSprite::ProcessParticle(HWDrawInfo *di, FRenderState& state, particle_t * else factor = 1 / 7.f; float scalefac=particle->size * factor; - float viewvecX = vp.ViewVector.X * scalefac; + float ps = di->Level->pixelstretch; + + scalefac /= sqrt(ps); // shrink it slightly to account for the stretch + + float viewvecX = vp.ViewVector.X * scalefac * ps; float viewvecY = vp.ViewVector.Y * scalefac; x1=x+viewvecY; @@ -1602,6 +1620,13 @@ void HWSprite::AdjustVisualThinker(HWDrawInfo* di, DVisualThinker* spr, sector_t auto r = spi.GetSpriteRect(); r.Scale(spr->Scale.X, spr->Scale.Y); + if (spr->PT.flags & SPF_ROLL) + { + double ps = di->Level->pixelstretch; + double mult = 1.0 / sqrt(ps); // shrink slightly + r.Scale(mult * ps, mult); + } + if (spr->bXFlip) { std::swap(ul,ur); diff --git a/src/rendering/r_utility.cpp b/src/rendering/r_utility.cpp index 8fcd0c5f3..3e40c3a0c 100644 --- a/src/rendering/r_utility.cpp +++ b/src/rendering/r_utility.cpp @@ -450,7 +450,6 @@ void R_Shutdown () //========================================================================== //CVAR (Int, tf, 0, 0) -EXTERN_CVAR (Bool, cl_noprediction) bool P_NoInterpolation(player_t const *player, AActor const *actor) { @@ -464,7 +463,6 @@ bool P_NoInterpolation(player_t const *player, AActor const *actor) && player->mo->reactiontime == 0 && !NoInterpolateView && !paused - && (!netgame || !cl_noprediction) && !LocalKeyboardTurner; } diff --git a/src/rendering/swrenderer/things/r_voxel.cpp b/src/rendering/swrenderer/things/r_voxel.cpp index 28d8921f0..a75b66e31 100644 --- a/src/rendering/swrenderer/things/r_voxel.cpp +++ b/src/rendering/swrenderer/things/r_voxel.cpp @@ -148,6 +148,9 @@ namespace swrenderer vis->depth = (float)tz; vis->gpos = { (float)pos.X, (float)pos.Y, (float)pos.Z }; + vis->gpos.X += (float)voxel->xoffset; + vis->gpos.Y += (float)voxel->yoffset; + vis->gpos.Z += (float)voxel->zoffset; vis->gzb = (float)gzb; // [RH] use gzb, not thing->z vis->gzt = (float)gzt; // killough 3/27/98 vis->deltax = float(pos.X - thread->Viewport->viewpoint.Pos.X); diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index d30ced745..3993cd745 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -384,6 +384,7 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(RF2, FLIPSPRITEOFFSETX, AActor, renderflags2), DEFINE_FLAG(RF2, FLIPSPRITEOFFSETY, AActor, renderflags2), DEFINE_FLAG(RF2, CAMFOLLOWSPLAYER, AActor, renderflags2), + DEFINE_FLAG(RF2, NOMIPMAP, AActor, renderflags2), DEFINE_FLAG(RF2, ISOMETRICSPRITES, AActor, renderflags2), // Bounce flags diff --git a/src/version.h b/src/version.h index d4257ef08..be420e5ed 100644 --- a/src/version.h +++ b/src/version.h @@ -107,8 +107,11 @@ const char *GetVersionString(); #define GAMENAME "VKDoom" #define WGAMENAME L"VKDoom" #define GAMENAMELOWERCASE "vkdoom" +#define QUERYIWADDEFAULT true //#define FORUM_URL "http://forum.zdoom.org/" //#define BUGS_FORUM_URL "http://forum.zdoom.org/viewforum.php?f=2" +// For QUERYIWADDEFAULT: Set to 'true' to always show dialog box on startup by default, 'false' to disable +// Should set to 'false' for standalone games, and set to 'true' for regular source port forks that are meant to run any game. #if defined(__APPLE__) || defined(_WIN32) #define GAME_DIR GAMENAME diff --git a/src/win32/i_steam.cpp b/src/win32/i_steam.cpp index f8bd74bff..2cf536475 100644 --- a/src/win32/i_steam.cpp +++ b/src/win32/i_steam.cpp @@ -61,6 +61,7 @@ #include "printf.h" +#include "engineerrors.h" #include "version.h" #include "i_sound.h" #include "stats.h" @@ -307,23 +308,30 @@ TArray I_GetSteamPath() return result; } - TArray paths = ParseSteamRegistry((steamPath + "/config/libraryfolders.vdf").GetChars()); - - for(FString &path : paths) + try { - path.ReplaceChars('\\','/'); - path+="/"; - } + TArray paths = ParseSteamRegistry((steamPath + "/config/libraryfolders.vdf").GetChars()); - paths.Push(steamPath + "/steamapps/common/"); + for (FString& path : paths) + { + path.ReplaceChars('\\', '/'); + path += "/"; + } - for(unsigned int i = 0; i < countof(steam_dirs); ++i) - { - for(const FString &path : paths) + paths.Push(steamPath + "/steamapps/common/"); + + for (unsigned int i = 0; i < countof(steam_dirs); ++i) { - result.Push(path + steam_dirs[i]); + for (const FString& path : paths) + { + result.Push(path + steam_dirs[i]); + } } } + catch (const CRecoverableError& err) + { + // don't abort on errors in here. Just return an empty path. + } return result; } diff --git a/wadsrc/static/compatibility.txt b/wadsrc/static/compatibility.txt index f8c3fe453..21946aa17 100644 --- a/wadsrc/static/compatibility.txt +++ b/wadsrc/static/compatibility.txt @@ -337,3 +337,50 @@ BA2247ED1465C8107192AC4215B2A5F3 // saturnia.wad map10 { fdteleport } + +// Total Chaos Directors Cut 1.40 +1C9D1306F76EA85558C4BD3BC4C7363C // TITLEMAP - retro & full +5138C10892976F7F8D58E73DF9BD1D35 // MADNESS - retro +EA96F1D692B354B2388F34CD9F0E4CDC // MADNESS - full +F7658930D904959FF43BE0A374E1E356 // MAP01 - retro & full +7C5508701E4AC3D1A00985409EC45EFD // NG1 - retro +C4C2CBACCF08F68AFC341557AEE6A727 // NG2 - retro +37853C2D108A56241E51C785CF0857D4 // NG3 - retro +0B14A4C8223F410816292E7EC578C625 // NG4 - retro +04F3D083D787DE15ACE8661163A6CA14 // NG5 - retro +F52D8B176EEFD374F342F3D9B3DEFC61 // NG6 - retro +A4D6321677AA6E7BA5CD93B39D1D063B // NG1 - full +3B4BAD71CFD7C9C1B860FB66363AEDF2 // NG2 - full +DD660C6E6E3F6F7A615D7234B7A56F59 // NG3 - full +74C68D18E6BD94B386163C4F672D3BFD // NG4 - full +835052B4078B33206675A4BB240333C8 // NG5 - full +0D3A15105CC16BCA0D06CA864AF0B58D // NG6 - full +0E186784223C02A19420BB49081FB734 // NG7 - retro & full +B57F6841E4E5C3989AA58ADDE9812762 // NGD1 - retro +A979225DED3F99E22B50FC6D9CE0355F // NGD2 - retro +95CE0EA31B85BA4D65A189B2D86D69A3 // NGD3 - retro +C1DB8E3B014A548383FDF95D999DF948 // NGD1 - full +6DB1AF715DA54226DC8D958B07FEC7DA // NGD2 - full +E6386D98F733E8D965865859E6056AFA // NGD3 - full +62958B31FB86C109B91BF2DDB9E93D2E // REGRESS - retro +84B499C6179E845265DF4BBF0D3AD361 // RESOLVE - retro +CC4D0C838C0EC5F710E220F3D77A1509 // SURFACE - retro +9FB4164A268007D7D1A630A4460C1CBB // ARRIVAL - retro +0F6AFE40771DCF448C77B6028CB53336 // REGRESS - full +B6E841EC4E5B45D90E3D4CFF805D192E // RESOLVE - full +80098CA75B3FCDE714E10832A8A08185 // SURFACE - full +EF8AA935168FED4393D68DAE04613106 // ARRIVAL - full +60C9D10D3FFDEF25E3131839FC50E1AD // CHAOS - retro +37325C08B6466A1760CA25D0D4176A64 // CROSS - retro +4F032EA6B61A5A42B1B6A7EDC8D5667B // CHAOS - full +D08C3FED35FE0489CFFAB6088FE79B3A // CROSS - full +859662ADA3A4CF634E8B7CE32B8ACB80 // CTEST - retro & full +1A3D2981D32783FED2BE7BA1587809E5 // DECAY - retro +2903FAAB2FBCD516F0559F5799EEEF14 // DECAY - full +A7898E72ED8D2C679DF18D6BC5DCC485 // FINALE - retro +EBD9BB5E7719F6E5042F12B3AE670104 // FORGOT - retro +A318D63145AA0A454475FBE426567896 // FINALE - full +C58AA89A5ACDC7F16BDA68428E31C555 // FORGOT - full +{ + noacsargcheck +} diff --git a/wadsrc/static/language.csv b/wadsrc/static/language.csv index b588c7c66..81a41967e 100644 --- a/wadsrc/static/language.csv +++ b/wadsrc/static/language.csv @@ -3146,6 +3146,8 @@ Boom,OPTVAL_BOOM,,,,,,,,,,,,,,,,붐,,,,,,,,,,, Boom (strict),OPTVAL_BOOMSTRICT,,,,Boom (striktní),Boom (streng),Boom (strikt),,Boom (severa),Boom (estricto),,Boom (tiukka),,Boom (szigorú),Boom (rigoroso),Boom(厳密),붐 (엄격하게),Boom (streng),Boom (streng),Boom (ścisły),Boom (estritamente),Boom (estrito),,Boom (строгий),Boom (строг),Boom (strikt),Boom (katı), MBF,OPTVAL_MBF,,,,,,,,,,,,,,,,마린의 절친한 친구,,,,,,,,,,, MBF (strict),OPTVAL_MBFSTRICT,,,,MBF (striktní),MBF (streng),MBF (strikt),,MBF (severa),MBF (estricto),,MBF (tiukka),,MBF (szigorú),MBF (rigoroso),MBF(厳密),마린의 절친한 친구 (엄격하게),MBF (streng),MBF (streng),MBF (ścisły),MBF (estritamente),MBF (estrito),,MBF (строгий),MBF (строг),MBF (strikt),MBF (katı), +MBF21,OPTVAL_MBF21,,,,,,,,,,,,,,,,,,,,,,,,,,, +MBF21 (strict),OPTVAL_MBF21STRICT,,,,MBF21 (striktní),MBF21 (streng),MBF21 (strikt),,MBF21 (severa),MBF21 (estricto),,MBF21 (tiukka),,MBF21 (szigorú),MBF21 (rigoroso),MBF21(厳密),MBF21 (엄격하게),MBF21 (streng),MBF21 (streng),MBF21 (ścisły),MBF21 (estritamente),MBF21 (estrito),,MBF21 (строгий),MBF21 (строг),MBF21 (strikt),MBF21 (katı), ZDoom 2.0.63,OPTVAL_ZDOOM2063,,,,,,,,,,,,,,,,Z둠 2.0.63,,,,,,,,,,, All unacknowledged,OPTVAL_ALLUNACKNOWLEDGED,,,,Všechny nepotrvzené,Alle ubekræftede,Alle unbestätigten,,Ĉiuj neagnoskitaj,Todos no reconocidos,,Kaikki kuittaamattomat,Tout non-acknowledged,Minden visszaigazolatlan,Tutti non riconosciuti,未確認全て,모두 미확인함,Allemaal onbekend,Alle ubekreftet,Wszystkie niepotwierdzone,Todos não-reconhecidos,,Toate necunoscute,Всё неопознанное,Све неусвојено,Alla obeaktade,Tüm onaylanmamış, Errors,OPTVAL_ERRORS,,,,Chyby,Fejl,Fehler,,Eraroj,Errores,,Virheet,Erreurs,Hibák,Errori,エラー,에러,Fouten,Feil,Błędy,Erros,,Erori,Ошибки,Грешка,Fel,Hatalar, diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index a253f9933..83b74a1b7 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -1224,9 +1224,11 @@ OptionMenu "ScalingOptions" protected StaticText "$SCALEMNU_OVERRIDE", 1 ScaleSlider "$SCALEMNU_MESSAGES", "con_scaletext", 0.0, 8.0, 1.0, "$SCALEMNU_USEUI" ScaleSlider "$SCALEMNU_CONSOLE", "con_scale", 0.0, 8.0, 1.0, "$SCALEMNU_USEUI" - ScaleSlider "$SCALEMNU_STATBAR", "st_scale", -1.0, 8.0, 1.0, "$SCALEMNU_USEUI", "$SCALEMNU_USEFS" - ScaleSlider "$SCALEMNU_HUD", "hud_scale", -1.0, 8.0, 1.0, "$SCALEMNU_USEUI", "$SCALEMNU_USEFS" - ScaleSlider "$SCALEMNU_ALTHUD", "hud_althudscale", 0.0, 8.0, 1.0, "$SCALEMNU_USEUI" + Option "$SCALEMNU_HUD_OLDSCALE", "hud_oldscale", "OnOff" + Slider "$SCALEMNU_HUD_SCALEFACTOR", "hud_scalefactor", 0.0, 1.0, 0.025, 3, "hud_oldscale", 1 + ScaleSlider "$SCALEMNU_STATBAR", "st_scale", -1.0, 8.0, 1.0, "$SCALEMNU_USEUI", "$SCALEMNU_USEFS", "hud_oldscale" + ScaleSlider "$SCALEMNU_HUD", "hud_scale", -1.0, 8.0, 1.0, "$SCALEMNU_USEUI", "$SCALEMNU_USEFS", "hud_oldscale" + ScaleSlider "$SCALEMNU_ALTHUD", "hud_althudscale", 0.0, 8.0, 1.0, "$SCALEMNU_USEUI", "", "hud_oldscale" StaticText " " Option "$SCALEMNU_HUDASPECT", "hud_aspectscale", "OnOff" StaticText " " @@ -1855,6 +1857,8 @@ OptionValue CompatModes 6, "$OPTVAL_BOOMSTRICT" 5, "$OPTVAL_MBF" 7, "$OPTVAL_MBFSTRICT" + 8, "$OPTVAL_MBF21" + 9, "$OPTVAL_MBF21STRICT" 4, "$OPTVAL_ZDOOM2063" } @@ -2483,8 +2487,9 @@ OptionMenu NetworkOptions protected { Title "$NETMNU_TITLE" StaticText "$NETMNU_LOCALOPTIONS", 1 - Slider "$NETMNU_PREDICTIONLERPSCALE", "cl_predict_lerpscale", 0.0, 0.5, 0.05, 2 - Slider "$NETMNU_LERPTHRESHOLD", "cl_predict_lerpthreshold", 0.1, 16.0, 0.1 + Option "$NETMNU_LINESPECIALPREDICTION", "cl_predict_specials", "OnOff" + Slider "$NETMNU_PREDICTIONLERPSCALE", "cl_rubberband_scale", 0.0, 1.0, 0.05, 2 + Slider "$NETMNU_LERPTHRESHOLD", "cl_rubberband_threshold", 0.0, 256.0, 8.0 StaticText " " StaticText "$NETMNU_HOSTOPTIONS", 1 Option "$NETMNU_EXTRATICS", "net_extratic", "ExtraTicMode" diff --git a/wadsrc/static/shaders/scene/lightmodel_specular.glsl b/wadsrc/static/shaders/scene/lightmodel_specular.glsl index b889034ab..a9d99c779 100644 --- a/wadsrc/static/shaders/scene/lightmodel_specular.glsl +++ b/wadsrc/static/shaders/scene/lightmodel_specular.glsl @@ -1,5 +1,5 @@ -vec2 lightAttenuation(int i, vec3 normal, vec3 viewdir, float lightcolorA) +vec2 lightAttenuation(int i, vec3 normal, vec3 viewdir, float lightcolorA, float glossiness, float specularLevel) { vec4 lightpos = lights[i]; vec4 lightspot1 = lights[i+2]; @@ -25,9 +25,6 @@ vec2 lightAttenuation(int i, vec3 normal, vec3 viewdir, float lightcolorA) if (attenuation <= 0.0) return vec2(0.0); - float glossiness = uSpecularMaterial.x; - float specularLevel = uSpecularMaterial.y; - vec3 halfdir = normalize(viewdir + lightdir); float specAngle = clamp(dot(halfdir, normal), 0.0f, 1.0f); float phExp = glossiness * 4.0f; @@ -51,7 +48,7 @@ vec3 ProcessMaterialLight(Material material, vec3 color) for(int i=lightRange.x; i GetKeyType(int index); + + override bool ShouldShareItem(Actor giver) + { + return sv_coopsharekeys; + } override bool HandlePickup (Inventory item) { @@ -113,6 +118,11 @@ class PuzzleItem : Inventory PuzzleItem.FailMessage("$TXT_USEPUZZLEFAILED"); PuzzleItem.FailSound "*puzzfail"; } + + override bool ShouldShareItem(Actor giver) + { + return sv_coopsharekeys; + } override bool HandlePickup (Inventory item) { diff --git a/wadsrc/static/zscript/actors/inventory/inventory.zs b/wadsrc/static/zscript/actors/inventory/inventory.zs index a2af10398..2e6544093 100644 --- a/wadsrc/static/zscript/actors/inventory/inventory.zs +++ b/wadsrc/static/zscript/actors/inventory/inventory.zs @@ -261,24 +261,28 @@ class Inventory : Actor } } + virtual bool ShouldShareItem(Actor giver) + { + return false; + } + protected void ShareItemWithPlayers(Actor giver) { if (bSharingItem) return; - class type = GetClass(); int skip = giver && giver.player ? giver.PlayerNumber() : -1; - for (int i; i < MAXPLAYERS; ++i) { if (!playerInGame[i] || i == skip) continue; - let item = Inventory(Spawn(type)); - if (!item) + let item = CreateLocalCopy(players[i].mo); + if (!item || item == self) continue; item.bSharingItem = true; + item.bDropped = item.bNeverLocal = true; if (!item.CallTryPickup(players[i].mo)) { item.Destroy(); @@ -288,14 +292,13 @@ class Inventory : Actor if (!bQuiet) { - PlayPickupSound(players[i].mo); + PrintPickupMessage(i == consoleplayer, item.PickupMessage()); + + item.PlayPickupSound(players[i].mo); if (!bNoScreenFlash && players[i].PlayerState != PST_DEAD) players[i].BonusCount = BONUSADD; } } - - if (!bQuiet && consoleplayer != skip) - PrintPickupMessage(true, PickupMessage()); } //=========================================================================== @@ -695,7 +698,7 @@ class Inventory : Actor toucher.HasReceived(self); // If the item can be shared, make sure every player gets a copy. - if (multiplayer && !deathmatch && sv_coopsharekeys && bIsKeyItem) + if (multiplayer && !deathmatch && !bDropped && ShouldShareItem(toucher)) ShareItemWithPlayers(toucher); } return res, toucher; @@ -864,13 +867,13 @@ class Inventory : Actor if (!bQuiet) { - PrintPickupMessage(localview, PickupMessage ()); + PrintPickupMessage(localview, give.PickupMessage ()); // Special check so voodoo dolls picking up items cause the // real player to make noise. if (player != NULL) { - PlayPickupSound (player.mo); + give.PlayPickupSound (player.mo); if (!bNoScreenFlash && player.playerstate != PST_DEAD) { player.bonuscount = BONUSADD; @@ -878,7 +881,7 @@ class Inventory : Actor } else { - PlayPickupSound (toucher); + give.PlayPickupSound (toucher); } } diff --git a/wadsrc/static/zscript/actors/morph.zs b/wadsrc/static/zscript/actors/morph.zs index 9cd21287a..5ebbb59f0 100644 --- a/wadsrc/static/zscript/actors/morph.zs +++ b/wadsrc/static/zscript/actors/morph.zs @@ -99,6 +99,7 @@ extend class Actor } // [MC] Called when an actor morphs, on both the previous form (!current) and present form (current). + // Due to recent changes, these are now called internally instead of within the virtuals. virtual void PreMorph(Actor mo, bool current) {} virtual void PostMorph(Actor mo, bool current) {} virtual void PreUnmorph(Actor mo, bool current) {} diff --git a/wadsrc/static/zscript/actors/player/player.zs b/wadsrc/static/zscript/actors/player/player.zs index 0cee7a7f3..b2252c16e 100644 --- a/wadsrc/static/zscript/actors/player/player.zs +++ b/wadsrc/static/zscript/actors/player/player.zs @@ -154,7 +154,7 @@ class PlayerPawn : Actor if (health > 0) Height = FullHeight; } - if (player && bWeaponLevel2Ended) + if (player && bWeaponLevel2Ended && !(player.cheats & CF_PREDICTING)) { bWeaponLevel2Ended = false; if (player.ReadyWeapon != NULL && player.ReadyWeapon.bPowered_Up) @@ -179,6 +179,9 @@ class PlayerPawn : Actor override void BeginPlay() { + // Force create this since players can predict. + SetViewPos((0.0, 0.0, 0.0)); + Super.BeginPlay (); ChangeStatNum (STAT_PLAYER); FullHeight = Height; @@ -1942,6 +1945,7 @@ class PlayerPawn : Actor { item = Inventory(Spawn(ti)); item.bIgnoreSkill = true; // no skill multipliers here + item.bDropped = item.bNeverLocal = true; // Avoid possible copies. item.Amount = di.Amount; let weap = Weapon(item); if (weap) @@ -2857,7 +2861,7 @@ struct PlayerInfo native play // self is what internally is known as player_t native void SetSubtitleNumber (int text, Sound sound_id = 0); native bool Resurrect(); - native clearscope String GetUserName() const; + native clearscope String GetUserName(uint charLimit = 0u) const; native clearscope Color GetColor() const; native clearscope Color GetDisplayColor() const; native clearscope int GetColorSet() const; @@ -2985,6 +2989,7 @@ struct Team native native String mName; native static bool IsValid(uint teamIndex); + native play static bool ChangeTeam(uint playerNumber, uint newTeamIndex); native Color GetPlayerColor() const; native int GetTextColor() const; diff --git a/wadsrc/static/zscript/actors/player/player_morph.zs b/wadsrc/static/zscript/actors/player/player_morph.zs index ff84246a1..435d61cdf 100644 --- a/wadsrc/static/zscript/actors/player/player_morph.zs +++ b/wadsrc/static/zscript/actors/player/player_morph.zs @@ -129,9 +129,6 @@ extend class PlayerPawn return false; } - PreMorph(morphed, false); - morphed.PreMorph(self, true); - morphed.EndAllPowerupEffects(); if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate) @@ -259,9 +256,6 @@ extend class PlayerPawn if (!MorphInto(alt)) return false; - PreUnmorph(alt, false); // This body's about to be left. - alt.PreUnmorph(self, true); // This one's about to become current. - alt.EndAllPowerupEffects(); // Remove the morph power if the morph is being undone prematurely. @@ -323,6 +317,7 @@ extend class PlayerPawn if (level2) level2.Destroy(); + WeaponSlots.SetupWeaponSlots(alt); let morphWeap = p.ReadyWeapon; if (premorphWeap) { diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index 2bfee47b1..d99daa9e2 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -716,6 +716,11 @@ enum EParticleFlags SPF_REPLACE = 1 << 7, SPF_NO_XY_BILLBOARD = 1 << 8, SPF_LOCAL_ANIM = 1 << 9, + SPF_NEGATIVE_FADESTEP = 1 << 10, + SPF_FACECAMERA = 1 << 11, + SPF_NOFACECAMERA = 1 << 12, + SPF_ROLLCENTER = 1 << 13, + SPF_NOMIPMAP = 1 << 14, SPF_RELATIVE = SPF_RELPOS|SPF_RELVEL|SPF_RELACCEL|SPF_RELANG }; diff --git a/wadsrc/static/zscript/engine/ui/menu/optionmenuitems.zs b/wadsrc/static/zscript/engine/ui/menu/optionmenuitems.zs index 8276dda3e..9fc0d66ff 100644 --- a/wadsrc/static/zscript/engine/ui/menu/optionmenuitems.zs +++ b/wadsrc/static/zscript/engine/ui/menu/optionmenuitems.zs @@ -37,13 +37,15 @@ class OptionMenuItem : MenuItemBase String mLabel; bool mCentered; CVar mGrayCheck; + int mGrayCheckVal; - protected void Init(String label, String command, bool center = false, CVar graycheck = null) + protected void Init(String label, String command, bool center = false, CVar graycheck = null, int graycheckVal = 0) { Super.Init(0, 0, command); mLabel = label; mCentered = center; mGrayCheck = graycheck; + mGrayCheckVal = graycheckVal; } protected void drawText(int x, int y, int color, String text, bool grayed = false) @@ -75,7 +77,7 @@ class OptionMenuItem : MenuItemBase virtual bool IsGrayed() { - return mGrayCheck != null && !mGrayCheck.GetInt(); + return mGrayCheck != null && mGrayCheck.GetInt() == mGrayCheckVal; } override bool Selectable() @@ -263,9 +265,9 @@ class OptionMenuItemOptionBase : OptionMenuItem const OP_VALUES = 0x11001; - protected void Init(String label, Name command, Name values, CVar graycheck, int center) + protected void Init(String label, Name command, Name values, CVar graycheck, int center, int graycheckVal = 0) { - Super.Init(label, command, false, graycheck); + Super.Init(label, command, false, graycheck, graycheckVal); mValues = values; mCenter = center; } @@ -361,9 +363,9 @@ class OptionMenuItemOption : OptionMenuItemOptionBase private static native void SetCVarDescription(CVar cv, String label); - OptionMenuItemOption Init(String label, Name command, Name values, CVar graycheck = null, int center = 0) + OptionMenuItemOption Init(String label, Name command, Name values, CVar graycheck = null, int center = 0, int graycheckVal = 0) { - Super.Init(label, command, values, graycheck, center); + Super.Init(label, command, values, graycheck, center, graycheckVal); mCVar = CVar.FindCVar(mAction); if (mCVar) SetCVarDescription(mCVar, label); return self; @@ -699,9 +701,9 @@ class OptionMenuSliderBase : OptionMenuItem int mDrawX; int mSliderShort; - protected void Init(String label, double min, double max, double step, int showval, Name command = 'none', CVar graycheck = NULL) + protected void Init(String label, double min, double max, double step, int showval, Name command = 'none', CVar graycheck = NULL, int graycheckVal = 0) { - Super.Init(label, command, false, graycheck); + Super.Init(label, command, false, graycheck, graycheckVal); mMin = min; mMax = max; mStep = step; @@ -855,9 +857,9 @@ class OptionMenuItemSlider : OptionMenuSliderBase { CVar mCVar; - OptionMenuItemSlider Init(String label, Name command, double min, double max, double step, int showval = 1, CVar graycheck = NULL) + OptionMenuItemSlider Init(String label, Name command, double min, double max, double step, int showval = 1, CVar graycheck = NULL, int graycheckVal = 0) { - Super.Init(label, min, max, step, showval, command, graycheck); + Super.Init(label, min, max, step, showval, command, graycheck, graycheckVal); mCVar =CVar.FindCVar(command); return self; } @@ -895,9 +897,9 @@ class OptionMenuItemColorPicker : OptionMenuItem const CPF_RESET = 0x20001; - OptionMenuItemColorPicker Init(String label, Name command, CVar graycheck = null) + OptionMenuItemColorPicker Init(String label, Name command, CVar graycheck = null, int graycheckVal = 0) { - Super.Init(label, command, false, graycheck); + Super.Init(label, command, false, graycheck, graycheckVal); CVar cv = CVar.FindCVar(command); if (cv != null && cv.GetRealType() != CVar.CVAR_Color) cv = null; mCVar = cv; @@ -974,9 +976,9 @@ class OptionMenuFieldBase : OptionMenuItem { CVar mCVar; - void Init (String label, Name command, CVar graycheck = null) + void Init (String label, Name command, CVar graycheck = null, int graycheckVal = 0) { - Super.Init(label, command, false, graycheck); + Super.Init(label, command, false, graycheck, graycheckVal); mCVar = CVar.FindCVar(mAction); } @@ -1037,9 +1039,9 @@ class OptionMenuItemTextField : OptionMenuFieldBase { TextEnterMenu mEnter; - OptionMenuItemTextField Init (String label, Name command, CVar graycheck = null) + OptionMenuItemTextField Init (String label, Name command, CVar graycheck = null, int graycheckVal = 0) { - Super.Init(label, command, graycheck); + Super.Init(label, command, graycheck, graycheckVal); mEnter = null; return self; } @@ -1109,9 +1111,9 @@ class OptionMenuItemTextField : OptionMenuFieldBase class OptionMenuItemNumberField : OptionMenuFieldBase { - OptionMenuItemNumberField Init (String label, Name command, float minimum = 0, float maximum = 100, float step = 1, CVar graycheck = null) + OptionMenuItemNumberField Init (String label, Name command, float minimum = 0, float maximum = 100, float step = 1, CVar graycheck = null, int graycheckVal = 0) { - Super.Init(label, command, graycheck); + Super.Init(label, command, graycheck, graycheckVal); mMinimum = min(minimum, maximum); mMaximum = max(minimum, maximum); mStep = max(1, step); @@ -1169,9 +1171,9 @@ class OptionMenuItemScaleSlider : OptionMenuItemSlider String TextNegOne; int mClickVal; - OptionMenuItemScaleSlider Init(String label, Name command, double min, double max, double step, String zero, String negone = "", CVar graycheck = null) + OptionMenuItemScaleSlider Init(String label, Name command, double min, double max, double step, String zero, String negone = "", CVar graycheck = null, int graycheckVal = 0) { - Super.Init(label, command, min, max, step, 0, graycheck); + Super.Init(label, command, min, max, step, 0, graycheck, graycheckVal); mCVar =CVar.FindCVar(command); TextZero = zero; TextNEgOne = negone; @@ -1237,9 +1239,9 @@ class OptionMenuItemFlagOption : OptionMenuItemOption { int mBitShift; - OptionMenuItemFlagOption Init(String label, Name command, Name values, int bitShift, CVar greycheck = null, int center = 0) + OptionMenuItemFlagOption Init(String label, Name command, Name values, int bitShift, CVar greycheck = null, int center = 0, int graycheckVal = 0) { - Super.Init(label, command, values, greycheck, center); + Super.Init(label, command, values, greycheck, center, graycheckVal); mBitShift = bitShift; return self;