diff --git a/auto-setup-linux.sh b/auto-setup-linux.sh new file mode 100755 index 000000000..a54677478 --- /dev/null +++ b/auto-setup-linux.sh @@ -0,0 +1,104 @@ +#!/bin/bash +#** +#** auto-setup-linux.sh +#** Automatic (easy) setup and build script for Linux +#** +#** Note that this script assumes you have both 'git' and 'cmake' installed properly and in your PATH! +#** This script also assumes you have installed a build system that cmake can automatically detect. +#** Such as GCC or Clang. Requires appropriate supporting libraries installed too! +#** Without these items, this script will FAIL! So make sure you have your build environment properly +#** set up in order for this script to succeed. +#** +#** The purpose of this script is to get someone easily going with a full working compile of GZDoom. +#** This allows anyone to make simple changes or tweaks to the engine as they see fit and easily +#** compile their own copy without having to follow complex instructions to get it working. +#** Every build environment is different, and every computer system is different - this should work +#** in most typical systems under Windows but it may fail under certain types of systems or conditions. +#** Not guaranteed to work and your mileage will vary. +#** +#** Prerequisite Packages used in testing (from Linux Mint-XFCE): +#** nasm autoconf libtool libsystemd-dev clang-15 libx11-dev libsdl2-dev libgtk-3-dev +#** +#**--------------------------------------------------------------------------- +#** Copyright 2024 Rachael Alexanderson and the GZDoom team +#** All rights reserved. +#** +#** Redistribution and use in source and binary forms, with or without +#** modification, are permitted provided that the following conditions +#** are met: +#** +#** 1. Redistributions of source code must retain the above copyright +#** notice, this list of conditions and the following disclaimer. +#** 2. Redistributions in binary form must reproduce the above copyright +#** notice, this list of conditions and the following disclaimer in the +#** documentation and/or other materials provided with the distribution. +#** 3. The name of the author may not be used to endorse or promote products +#** derived from this software without specific prior written permission. +#** +#** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +#** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +#** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +#** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +#** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +#** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +#** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +#** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +#** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +#** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#**--------------------------------------------------------------------------- +#** + +# -- Always operate within the build folder +BUILDFOLDER=$(dirname "$0")/build + +if [ ! -d "$BUILDFOLDER" ]; then + mkdir "$BUILDFOLDER" +fi +cd "$BUILDFOLDER" + +if [ -d "vcpkg" ]; then + git -C ./vcpkg pull +fi +if [ ! -d "vcpkg" ]; then + git clone https://github.com/microsoft/vcpkg +fi +if [ -d "zmusic" ]; then + git -C ./zmusic fetch +fi +if [ ! -d "zmusic" ]; then + git clone https://github.com/zdoom/zmusic +fi +if [ -d "zmusic" ]; then + git -C ./zmusic checkout 1.1.12 +fi + +if [ ! -d "zmusic/build" ]; then + mkdir zmusic/build +fi +if [ ! -d "vcpkg_installed" ]; then + mkdir vcpkg_installed +fi + +cmake -S ./zmusic -B ./zmusic/build \ + -DCMAKE_TOOLCHAIN_FILE=../vcpkg/scripts/buildsystems/vcpkg.cmake \ + -DVCPKG_LIBSNDFILE=1 \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DVCPKG_INSTALLLED_DIR=../vcpkg_installed/ +pushd ./zmusic/build +make -j $(nproc) +popd + +cmake -S .. -B . \ + -DCMAKE_TOOLCHAIN_FILE=./vcpkg/scripts/buildsystems/vcpkg.cmake \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DVCPKG_INSTALLLED_DIR=./vcpkg_installed/ +make -j $(nproc); rc=$? + +# -- If successful, show the build +if [ $rc -eq 0 ]; then + if [ -f gzdoom ]; then + xdg-open . + fi +fi + +exit $rc diff --git a/auto-setup-windows.cmd b/auto-setup-windows.cmd index b20eb8375..38a16d8d7 100644 --- a/auto-setup-windows.cmd +++ b/auto-setup-windows.cmd @@ -19,7 +19,7 @@ goto aftercopyright ** Not guaranteed to work and your mileage will vary. ** **--------------------------------------------------------------------------- -** Copyright 2023 Rachael Alexanderson and the GZDoom team +** Copyright 2023-2024 Rachael Alexanderson and the GZDoom team ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without diff --git a/cmake/FindZMusic.cmake b/cmake/FindZMusic.cmake index dd0c9296d..43754a807 100644 --- a/cmake/FindZMusic.cmake +++ b/cmake/FindZMusic.cmake @@ -10,9 +10,15 @@ if(ZMUSIC_INCLUDE_DIR AND ZMUSIC_LIBRARIES) set(ZMUSIC_FIND_QUIETLY TRUE) endif() -find_path(ZMUSIC_INCLUDE_DIR zmusic.h) +find_path(ZMUSIC_INCLUDE_DIR zmusic.h + HINTS + ${CMAKE_SOURCE_DIR}/build/zmusic/include +) -find_library(ZMUSIC_LIBRARIES NAMES zmusic) +find_library(ZMUSIC_LIBRARIES NAMES zmusic + HINTS + ${CMAKE_SOURCE_DIR}/build/zmusic/build/source +) mark_as_advanced(ZMUSIC_LIBRARIES ZMUSIC_INCLUDE_DIR) # handle the QUIETLY and REQUIRED arguments and set ZMUSIC_FOUND to TRUE if diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7c9d31614..6d9941463 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1353,7 +1353,7 @@ if( NOT WIN32 AND NOT APPLE ) IF ("${INSTALL_RPATH}" STREQUAL "") set_target_properties(zdoom PROPERTIES #allow libzmusic.so.1 library in same folder as executable at runtime - INSTALL_RPATH "\$ORIGIN" + INSTALL_RPATH "\$ORIGIN:\$ORIGIN/zmusic/build/source" BUILD_WITH_INSTALL_RPATH ON ) endif() diff --git a/src/am_map.cpp b/src/am_map.cpp index 795756346..927b2b735 100644 --- a/src/am_map.cpp +++ b/src/am_map.cpp @@ -2958,7 +2958,8 @@ void DAutomap::drawThings () if (am_cheat > 0 || !(t->flags6 & MF6_NOTONAUTOMAP) || (am_thingrenderstyles && !(t->renderflags & RF_INVISIBLE) && !(t->flags6 & MF6_NOTONAUTOMAP))) { - DVector3 pos = t->InterpolatedPosition(r_viewpoint.TicFrac) + t->Level->Displacements.getOffset(sec.PortalGroup, MapPortalGroup); + DVector3 fracPos = t->InterpolatedPosition(r_viewpoint.TicFrac); + FVector2 pos = FVector2(float(fracPos.X),float(fracPos.Y)) + FVector2(t->Level->Displacements.getOffset(sec.PortalGroup, MapPortalGroup)) + FVector2(t->AutomapOffsets); p.x = pos.X; p.y = pos.Y; @@ -2968,14 +2969,14 @@ void DAutomap::drawThings () spriteframe_t *frame; int rotation = 0; - // try all modes backwards until a valid texture has been found. + // try all modes backwards until a valid texture has been found. for(int show = am_showthingsprites; show > 0 && texture == nullptr; show--) { const spritedef_t& sprite = sprites[t->sprite]; const size_t spriteIndex = sprite.spriteframes + (show > 1 ? t->frame : 0); frame = &SpriteFrames[spriteIndex]; - DAngle angle = DAngle::fromDeg(270. + 22.5) - t->InterpolatedAngles(r_viewpoint.TicFrac).Yaw; + DAngle angle = DAngle::fromDeg(270.) - t->InterpolatedAngles(r_viewpoint.TicFrac).Yaw - t->SpriteRotation; if (frame->Texture[0] != frame->Texture[1]) angle += DAngle::fromDeg(180. / 16); if (am_rotate == 1 || (am_rotate == 2 && viewactive)) { diff --git a/src/common/engine/namedef.h b/src/common/engine/namedef.h index ab280f5b4..a1f791f9d 100644 --- a/src/common/engine/namedef.h +++ b/src/common/engine/namedef.h @@ -80,7 +80,6 @@ xx(__decorate_internal_float__) // Per-actor sound channels (for deprecated PlaySoundEx function) Do not separate this block!!! xx(Auto) xx(Weapon) -xx(BobPivot3D) xx(Voice) xx(Item) xx(Body) diff --git a/src/common/engine/palettecontainer.cpp b/src/common/engine/palettecontainer.cpp index 23d26f88f..29ec3599f 100644 --- a/src/common/engine/palettecontainer.cpp +++ b/src/common/engine/palettecontainer.cpp @@ -32,6 +32,7 @@ ** */ +#include #include "palutil.h" #include "sc_man.h" #include "m_crc32.h" diff --git a/src/common/filesystem/source/file_zip.cpp b/src/common/filesystem/source/file_zip.cpp index 9f10d00a7..8351b7146 100644 --- a/src/common/filesystem/source/file_zip.cpp +++ b/src/common/filesystem/source/file_zip.cpp @@ -35,6 +35,7 @@ #include #include +#include #include "w_zip.h" #include "ancientzip.h" #include "resourcefile.h" diff --git a/src/common/filesystem/source/files_decompress.cpp b/src/common/filesystem/source/files_decompress.cpp index 95cce3c33..3f7dfb865 100644 --- a/src/common/filesystem/source/files_decompress.cpp +++ b/src/common/filesystem/source/files_decompress.cpp @@ -980,11 +980,12 @@ bool OpenDecompressor(FileReader& self, FileReader &parent, FileReader::Size len } dec->Length = length; } - if ((flags & DCF_CACHED)) + if ((flags & (DCF_CACHED| DCF_SEEKABLE))) // the buffering reader does not seem to be stable, so cache it instead until we find out what's wrong. { // read everything into a MemoryArrayReader. FileData data(nullptr, length); fr->Read(data.writable(), length); + delete fr; fr = new MemoryArrayReader(data); } else if ((flags & DCF_SEEKABLE)) diff --git a/src/common/filesystem/source/filesystem.cpp b/src/common/filesystem/source/filesystem.cpp index a8418956f..fee5f1011 100644 --- a/src/common/filesystem/source/filesystem.cpp +++ b/src/common/filesystem/source/filesystem.cpp @@ -395,7 +395,7 @@ void FileSystem::AddFile (const char *filename, FileReader *filer, LumpFilterInf std::string path = filename; path += ':'; path += resfile->getName(i); - auto embedded = resfile->GetEntryReader(i, READER_NEW, READERFLAG_SEEKABLE); + auto embedded = resfile->GetEntryReader(i, READER_CACHED); AddFile(path.c_str(), &embedded, filter, Printf, hashfile); } } diff --git a/src/common/menu/menu.cpp b/src/common/menu/menu.cpp index 82af8b2e4..26ad084c8 100644 --- a/src/common/menu/menu.cpp +++ b/src/common/menu/menu.cpp @@ -1039,6 +1039,7 @@ DEFINE_FIELD(DListMenuDescriptor, mFontColor2) DEFINE_FIELD(DListMenuDescriptor, mAnimatedTransition) DEFINE_FIELD(DListMenuDescriptor, mAnimated) DEFINE_FIELD(DListMenuDescriptor, mCenter) +DEFINE_FIELD(DListMenuDescriptor, mCenterText) DEFINE_FIELD(DListMenuDescriptor, mDontDim) DEFINE_FIELD(DListMenuDescriptor, mDontBlur) DEFINE_FIELD(DListMenuDescriptor, mVirtWidth) diff --git a/src/common/menu/menu.h b/src/common/menu/menu.h index 3bd23b3b8..a96e25929 100644 --- a/src/common/menu/menu.h +++ b/src/common/menu/menu.h @@ -88,6 +88,7 @@ public: EColorRange mFontColor; EColorRange mFontColor2; bool mCenter; + bool mCenterText; bool mFromEngine; bool mAnimated; bool mAnimatedTransition; diff --git a/src/common/menu/menudef.cpp b/src/common/menu/menudef.cpp index 31c0ba24a..0a46dfa35 100644 --- a/src/common/menu/menudef.cpp +++ b/src/common/menu/menudef.cpp @@ -431,6 +431,10 @@ static void DoParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc, bool &s { desc->mForceList = true; } + else if (sc.Compare("CenterText")) + { + desc->mCenterText = true; + } else { // all item classes from which we know that they support sized scaling. @@ -768,6 +772,7 @@ static void ParseListMenu(FScanner &sc) desc->mWLeft = 0; desc->mWRight = 0; desc->mCenter = false; + desc->mCenterText = false; desc->mFromEngine = fileSystem.GetFileContainer(sc.LumpNum) == 0; // flags menu if the definition is from the IWAD. desc->mVirtWidth = -2; desc->mCustomSizeSet = false; diff --git a/src/common/objects/autosegs.cpp b/src/common/objects/autosegs.cpp index 11fce1005..e9b3f935f 100644 --- a/src/common/objects/autosegs.cpp +++ b/src/common/objects/autosegs.cpp @@ -42,6 +42,7 @@ ** compile with something other than Visual C++ or GCC. */ +#include #include "autosegs.h" #ifdef _WIN32 diff --git a/src/common/objects/autosegs.h b/src/common/objects/autosegs.h index 838553dd4..e015ea9f5 100644 --- a/src/common/objects/autosegs.h +++ b/src/common/objects/autosegs.h @@ -36,6 +36,7 @@ #define AUTOSEGS_H #include +#include #if defined(__clang__) #if defined(__has_feature) && __has_feature(address_sanitizer) diff --git a/src/common/objects/dobject.cpp b/src/common/objects/dobject.cpp index ba4f0a0f1..09bea7999 100644 --- a/src/common/objects/dobject.cpp +++ b/src/common/objects/dobject.cpp @@ -218,6 +218,8 @@ CCMD (dumpclasses) // //========================================================================== +#include "d_net.h" + void DObject::InPlaceConstructor (void *mem) { new ((EInPlace *)mem) DObject; @@ -317,6 +319,8 @@ void DObject::Release() void DObject::Destroy () { + NetworkEntityManager::RemoveNetworkEntity(this); + // We cannot call the VM during shutdown because all the needed data has been or is in the process of being deleted. if (PClass::bVMOperational) { @@ -504,8 +508,15 @@ void DObject::Serialize(FSerializer &arc) SerializeFlag("justspawned", OF_JustSpawned); SerializeFlag("spawned", OF_Spawned); - + SerializeFlag("networked", OF_Networked); + ObjectFlags |= OF_SerialSuccess; + + if (arc.isReading() && (ObjectFlags & OF_Networked)) + { + ClearNetworkID(); + EnableNetworking(true); + } } void DObject::CheckIfSerialized () const @@ -520,6 +531,73 @@ void DObject::CheckIfSerialized () const } } +//========================================================================== +// +// +// +//========================================================================== + +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); +} + +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)); +} DEFINE_ACTION_FUNCTION(DObject, MSTime) { diff --git a/src/common/objects/dobject.h b/src/common/objects/dobject.h index aed223071..bb295fb78 100644 --- a/src/common/objects/dobject.h +++ b/src/common/objects/dobject.h @@ -351,6 +351,17 @@ protected: friend T* Create(Args&&... args); friend class JitCompiler; + +private: + // This is intentionally left unserialized. + uint32_t _networkID; + +public: + inline bool IsNetworked() const { return (ObjectFlags & OF_Networked); } + inline uint32_t GetNetworkID() const { return _networkID; } + void SetNetworkID(const uint32_t id); + void ClearNetworkID(); + virtual void EnableNetworking(const bool enable); }; // This is the only method aside from calling CreateNew that should be used for creating DObjects diff --git a/src/common/objects/dobjgc.h b/src/common/objects/dobjgc.h index 969551d37..280c86273 100644 --- a/src/common/objects/dobjgc.h +++ b/src/common/objects/dobjgc.h @@ -26,6 +26,7 @@ enum EObjectFlags OF_Transient = 1 << 11, // Object should not be archived (references to it will be nulled on disk) OF_Spawned = 1 << 12, // Thinker was spawned at all (some thinkers get deleted before spawning) OF_Released = 1 << 13, // Object was released from the GC system and should not be processed by GC function + OF_Networked = 1 << 14, // Object has a unique network identifier that makes it synchronizable between all clients. }; template class TObjPtr; @@ -214,6 +215,9 @@ class TObjPtr mutable DObject *o; }; public: + TObjPtr() = default; + + TObjPtr(T t) : pp(t) {} constexpr TObjPtr& operator=(T q) noexcept { diff --git a/src/common/objects/dobjtype.cpp b/src/common/objects/dobjtype.cpp index b6379106c..7b4463011 100644 --- a/src/common/objects/dobjtype.cpp +++ b/src/common/objects/dobjtype.cpp @@ -669,7 +669,9 @@ PClass *PClass::FindClassTentative(FName name) // //========================================================================== -int PClass::FindVirtualIndex(FName name, PFunction::Variant *variant, PFunction *parentfunc, bool exactReturnType) +bool ShouldAllowGameSpecificVirtual(FName name, unsigned index, PType* arg, PType* varg); + +int PClass::FindVirtualIndex(FName name, PFunction::Variant *variant, PFunction *parentfunc, bool exactReturnType, bool ignorePointerReadOnly) { auto proto = variant->Proto; for (unsigned i = 0; i < Virtuals.Size(); i++) @@ -689,8 +691,22 @@ int PClass::FindVirtualIndex(FName name, PFunction::Variant *variant, PFunction { if (proto->ArgumentTypes[a] != vproto->ArgumentTypes[a]) { - fail = true; - break; + if(ignorePointerReadOnly && proto->ArgumentTypes[a]->isPointer() && vproto->ArgumentTypes[a]->isPointer()) + { + PPointer *ppa = proto->ArgumentTypes[a]->toPointer(); + PPointer *ppb = vproto->ArgumentTypes[a]->toPointer(); + + if(ppa->PointedType != ppb->PointedType) + { + fail = true; + break; + } + } + else if(!ShouldAllowGameSpecificVirtual(name, a, proto->ArgumentTypes[a], vproto->ArgumentTypes[a])) + { + fail = true; + break; + } } } if (fail) continue; diff --git a/src/common/objects/dobjtype.h b/src/common/objects/dobjtype.h index 81fbe3aac..7e698a6bf 100644 --- a/src/common/objects/dobjtype.h +++ b/src/common/objects/dobjtype.h @@ -43,7 +43,7 @@ public: void InitializeSpecials(void* addr, void* defaults, TArray PClass::* Inits); void WriteAllFields(FSerializer &ar, const void *addr) const; bool ReadAllFields(FSerializer &ar, void *addr) const; - int FindVirtualIndex(FName name, PFunction::Variant *variant, PFunction *parentfunc, bool exactReturnType); + int FindVirtualIndex(FName name, PFunction::Variant *variant, PFunction *parentfunc, bool exactReturnType, bool ignorePointerReadOnly); PSymbol *FindSymbol(FName symname, bool searchparents) const; PField *AddField(FName name, PType *type, uint32_t flags, int fileno = 0); void InitializeDefaults(); diff --git a/src/common/platform/posix/sdl/base_sysfb.cpp b/src/common/platform/posix/sdl/base_sysfb.cpp index f99af048b..e0d70a847 100644 --- a/src/common/platform/posix/sdl/base_sysfb.cpp +++ b/src/common/platform/posix/sdl/base_sysfb.cpp @@ -87,8 +87,6 @@ CUSTOM_CVAR(Bool, gl_es, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCA Printf("This won't take effect until " GAMENAME " is restarted.\n"); } -CVAR (Int, vid_adapter, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) - CUSTOM_CVAR(String, vid_sdl_render_driver, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) { Printf("This won't take effect until " GAMENAME " is restarted.\n"); @@ -111,29 +109,61 @@ namespace Priv SDL_Window *window; bool softpolyEnabled; bool fullscreenSwitch; + int numberOfDisplays; + SDL_Rect* displayBounds = nullptr; + + void updateDisplayInfo() + { + Priv::numberOfDisplays = SDL_GetNumVideoDisplays(); + if (Priv::numberOfDisplays <= 0) { + Printf("%sWrong number of displays detected.\n", TEXTCOLOR_BOLD); + return; + } + Printf("Number of detected displays %d .\n", Priv::numberOfDisplays); + + if (Priv::displayBounds != nullptr) { + free(Priv::displayBounds); + } + Priv::displayBounds = (SDL_Rect*) calloc(Priv::numberOfDisplays, sizeof(SDL_Rect)); + + for (int i=0; i < Priv::numberOfDisplays; i++) { + if (0 != SDL_GetDisplayBounds(i, &Priv::displayBounds[i])) { + Printf("%sError getting display %d size: %s\n", TEXTCOLOR_BOLD, i, SDL_GetError()); + if (i == 0) { + free(Priv::displayBounds); + displayBounds = nullptr; + } + Priv::numberOfDisplays = i; + return; + } + } + } void CreateWindow(uint32_t extraFlags) { assert(Priv::window == nullptr); - // Set default size - SDL_Rect bounds; - SDL_GetDisplayBounds(vid_adapter, &bounds); + // Get displays and default display size + updateDisplayInfo(); + + // TODO control better when updateDisplayInfo fails + SDL_Rect* bounds = &displayBounds[vid_adapter % numberOfDisplays]; if (win_w <= 0 || win_h <= 0) { - win_w = bounds.w * 8 / 10; - win_h = bounds.h * 8 / 10; + win_w = bounds->w * 8 / 10; + win_h = bounds->h * 8 / 10; } + int xWindowPos = (win_x <= 0) ? SDL_WINDOWPOS_CENTERED_DISPLAY(vid_adapter) : win_x; + int yWindowPos = (win_y <= 0) ? SDL_WINDOWPOS_CENTERED_DISPLAY(vid_adapter) : win_y; + Printf("Creating window [%dx%d] on adapter %d\n", (*win_w), (*win_h), (*vid_adapter)); + FString caption; caption.Format(GAMENAME " %s (%s)", GetVersionString(), GetGitTime()); const uint32_t windowFlags = (win_maximized ? SDL_WINDOW_MAXIMIZED : 0) | SDL_WINDOW_RESIZABLE | extraFlags; - Priv::window = SDL_CreateWindow(caption.GetChars(), - (win_x <= 0) ? SDL_WINDOWPOS_CENTERED_DISPLAY(vid_adapter) : win_x, - (win_y <= 0) ? SDL_WINDOWPOS_CENTERED_DISPLAY(vid_adapter) : win_y, - win_w, win_h, windowFlags); + Priv::window = SDL_CreateWindow(caption.GetChars(), xWindowPos, yWindowPos, win_w, win_h, windowFlags); if (Priv::window != nullptr) { @@ -150,15 +180,78 @@ namespace Priv SDL_DestroyWindow(Priv::window); Priv::window = nullptr; + + if (Priv::displayBounds != nullptr) { + free(Priv::displayBounds); + Priv::displayBounds = nullptr; + } } } +CUSTOM_CVAR(Int, vid_adapter, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + if (Priv::window != nullptr) { + // Get displays and default display size + Priv::updateDisplayInfo(); + + int display = (*self) % Priv::numberOfDisplays; + + // TODO control better when updateDisplayInfo fails + SDL_Rect* bounds = &Priv::displayBounds[vid_adapter % Priv::numberOfDisplays]; + + if (win_w <= 0 || win_h <= 0) + { + win_w = bounds->w * 8 / 10; + win_h = bounds->h * 8 / 10; + } + // Forces to set to the ini this vars to -1, so +vid_adapter keeps working the next time that the game it's launched + win_x = -1; + win_y = -1; + + if ((SDL_GetWindowFlags(Priv::window) & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0) { + + // TODO This not works. For some reason keeps stuck on the previus screen + /* + SDL_DisplayMode currentDisplayMode; + SDL_GetWindowDisplayMode(Priv::window, ¤tDisplayMode); + currentDisplayMode.w = win_w; + currentDisplayMode.h = win_h; + if ( 0 != SDL_SetWindowDisplayMode(Priv::window, ¤tDisplayMode)) { + Printf("A problem occured trying to change of display %s\n", SDL_GetError()); + } + */ + + // TODO This workaround also isn't working + /* + SDL_SetWindowFullscreen(Priv::window, 0); + SDL_SetWindowSize(Priv::window, win_w, win_h); + SDL_SetWindowPosition(Priv::window, bounds->x , bounds->y); + SDL_SetWindowFullscreen(Priv::window, SDL_WINDOW_FULLSCREEN_DESKTOP); + */ + Printf("Changing adapter on fullscreen, isn't full supported by SDL. Instead try to switch to windowed mode, change the adapter and then switch again to fullscreen.\n"); + + } else { + SDL_SetWindowSize(Priv::window, win_w, win_h); + SDL_SetWindowPosition(Priv::window, SDL_WINDOWPOS_CENTERED_DISPLAY(display), SDL_WINDOWPOS_CENTERED_DISPLAY(display)); + } + + display = SDL_GetWindowDisplayIndex(Priv::window); + if (display >= 0) { + Printf("New display is %d\n", display ); + } else { + Printf("A problem occured trying to change of display %s\n", SDL_GetError()); + } + } +} + class SDLVideo : public IVideo { public: SDLVideo (); ~SDLVideo (); + void DumpAdapters(); + DFrameBuffer *CreateFrameBuffer (); private: @@ -216,6 +309,22 @@ SDLVideo::~SDLVideo () #endif } +void SDLVideo::DumpAdapters() +{ + Priv::updateDisplayInfo(); + for (int i=0; i < Priv::numberOfDisplays; i++) { + Printf("%s%d. [%dx%d @ (%d,%d)]\n", + vid_adapter == i ? TEXTCOLOR_BOLD : "", + i, + Priv::displayBounds[i].w, + Priv::displayBounds[i].h, + Priv::displayBounds[i].x, + Priv::displayBounds[i].y + ); + } +} + + DFrameBuffer *SDLVideo::CreateFrameBuffer () { SystemBaseFrameBuffer *fb = nullptr; @@ -334,6 +443,7 @@ void SystemBaseFrameBuffer::SetWindowSize(int w, int h) SDL_GetWindowPosition(Priv::window, &x, &y); win_x = x; win_y = y; + } } diff --git a/src/common/platform/posix/sdl/i_joystick.cpp b/src/common/platform/posix/sdl/i_joystick.cpp index 8f147112d..a7eb59568 100644 --- a/src/common/platform/posix/sdl/i_joystick.cpp +++ b/src/common/platform/posix/sdl/i_joystick.cpp @@ -38,6 +38,8 @@ #include "m_joy.h" #include "keydef.h" +#define DEFAULT_DEADZONE 0.25f; + // Very small deadzone so that floating point magic doesn't happen #define MIN_DEADZONE 0.000001f @@ -143,7 +145,7 @@ public: info.Name.Format("Axis %d", i+1); else info.Name.Format("Hat %d (%c)", (i-NumAxes)/2 + 1, (i-NumAxes)%2 == 0 ? 'x' : 'y'); - info.DeadZone = MIN_DEADZONE; + info.DeadZone = DEFAULT_DEADZONE; info.Multiplier = 1.0f; info.Value = 0.0; info.ButtonValue = 0; @@ -266,7 +268,9 @@ protected: friend class SDLInputJoystickManager; }; -const EJoyAxis SDLInputJoystick::DefaultAxes[5] = {JOYAXIS_Side, JOYAXIS_Forward, JOYAXIS_Pitch, JOYAXIS_Yaw, JOYAXIS_Up}; + +// [Nash 4 Feb 2024] seems like on Linux, the third axis is actually the Left Trigger, resulting in the player uncontrollably looking upwards. +const EJoyAxis SDLInputJoystick::DefaultAxes[5] = {JOYAXIS_Side, JOYAXIS_Forward, JOYAXIS_None, JOYAXIS_Yaw, JOYAXIS_Pitch}; class SDLInputJoystickManager { diff --git a/src/common/rendering/vulkan/pipelines/vk_renderpass.h b/src/common/rendering/vulkan/pipelines/vk_renderpass.h index 4c24f9449..aeba5adb7 100644 --- a/src/common/rendering/vulkan/pipelines/vk_renderpass.h +++ b/src/common/rendering/vulkan/pipelines/vk_renderpass.h @@ -15,6 +15,7 @@ class ColorBlendAttachmentBuilder; class VkPPShader; class VkPPRenderPassKey; class VkPPRenderPassSetup; +class ColorBlendAttachmentBuilder; class VkPipelineKey { diff --git a/src/common/scripting/backend/codegen.cpp b/src/common/scripting/backend/codegen.cpp index 66f99e8b4..0504fc160 100644 --- a/src/common/scripting/backend/codegen.cpp +++ b/src/common/scripting/backend/codegen.cpp @@ -291,23 +291,21 @@ bool AreCompatiblePointerTypes(PType *dest, PType *source, bool forcompare) if (!forcompare && fromtype->IsConst && !totype->IsConst) return false; // A type is always compatible to itself. if (fromtype == totype) return true; - // Pointers to different types are only compatible if both point to an object and the source type is a child of the destination type. if (source->isObjectPointer() && dest->isObjectPointer()) - { + { // Pointers to different types are only compatible if both point to an object and the source type is a child of the destination type. auto fromcls = static_cast(source)->PointedClass(); auto tocls = static_cast(dest)->PointedClass(); if (forcompare && tocls->IsDescendantOf(fromcls)) return true; return (fromcls->IsDescendantOf(tocls)); } - // The same rules apply to class pointers. A child type can be assigned to a variable of a parent type. - if (source->isClassPointer() && dest->isClassPointer()) - { + else if (source->isClassPointer() && dest->isClassPointer()) + { // The same rules apply to class pointers. A child type can be assigned to a variable of a parent type. auto fromcls = static_cast(source)->ClassRestriction; auto tocls = static_cast(dest)->ClassRestriction; if (forcompare && tocls->IsDescendantOf(fromcls)) return true; return (fromcls->IsDescendantOf(tocls)); } - if(source->isFunctionPointer() && dest->isFunctionPointer()) + else if(source->isFunctionPointer() && dest->isFunctionPointer()) { auto from = static_cast(source); auto to = static_cast(dest); @@ -315,6 +313,10 @@ bool AreCompatiblePointerTypes(PType *dest, PType *source, bool forcompare) return to->PointedType == TypeVoid || (AreCompatibleFnPtrTypes((PPrototype *)to->PointedType, (PPrototype *)from->PointedType) && from->ArgFlags == to->ArgFlags && FScopeBarrier::CheckSidesForFunctionPointer(from->Scope, to->Scope)); } + else if(source->isRealPointer() && dest->isRealPointer()) + { + return fromtype->PointedType == totype->PointedType; + } } return false; } @@ -1940,6 +1942,12 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx) { bool writable; basex->RequestAddress(ctx, &writable); + + if(!writable && !ValueType->toPointer()->IsConst && ctx.Version >= MakeVersion(4, 12)) + { + ScriptPosition.Message(MSG_ERROR, "Trying to assign readonly value to writable type."); + } + basex->ValueType = ValueType; auto x = basex; basex = nullptr; @@ -8739,6 +8747,12 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) if (Self->ValueType->isRealPointer()) { auto pointedType = Self->ValueType->toPointer()->PointedType; + + if(pointedType && pointedType->isStruct() && Self->ValueType->toPointer()->IsConst && ctx.Version >= MakeVersion(4, 12)) + { + isreadonly = true; + } + if (pointedType && (pointedType->isDynArray() || pointedType->isMap() || pointedType->isMapIterator())) { Self = new FxOutVarDereference(Self, Self->ScriptPosition); @@ -9268,7 +9282,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) return nullptr; } } - else if (Self->ValueType->isStruct()) + else if (Self->ValueType->isStruct() && !isreadonly) { bool writable; diff --git a/src/common/scripting/frontend/zcc_compile.cpp b/src/common/scripting/frontend/zcc_compile.cpp index 4c3f2bc38..14a0fd5fa 100644 --- a/src/common/scripting/frontend/zcc_compile.cpp +++ b/src/common/scripting/frontend/zcc_compile.cpp @@ -2764,7 +2764,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool // [ZZ] unspecified virtual function inherits old scope. virtual function scope can't be changed. sym->Variants[0].Implementation->VarFlags = sym->Variants[0].Flags; } - + bool exactReturnType = mVersion < MakeVersion(4, 4); PClass *clstype = forclass? static_cast(c->Type())->Descriptor : nullptr; if (varflags & VARF_Virtual) @@ -2785,7 +2785,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool auto parentfunc = clstype->ParentClass? dyn_cast(clstype->ParentClass->VMType->Symbols.FindSymbol(sym->SymbolName, true)) : nullptr; - int virtindex = clstype->FindVirtualIndex(sym->SymbolName, &sym->Variants[0], parentfunc, exactReturnType); + int virtindex = clstype->FindVirtualIndex(sym->SymbolName, &sym->Variants[0], parentfunc, exactReturnType, sym->SymbolName == FName("SpecialBounceHit") && mVersion < MakeVersion(4, 12)); // specifying 'override' is necessary to prevent one of the biggest problem spots with virtual inheritance: Mismatching argument types. if (varflags & VARF_Override) { @@ -2867,7 +2867,7 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool } else if (forclass) { - int virtindex = clstype->FindVirtualIndex(sym->SymbolName, &sym->Variants[0], nullptr, exactReturnType); + int virtindex = clstype->FindVirtualIndex(sym->SymbolName, &sym->Variants[0], nullptr, exactReturnType, sym->SymbolName == FName("SpecialBounceHit") && mVersion < MakeVersion(4, 12)); if (virtindex != -1) { Error(f, "Function %s attempts to override parent function without 'override' qualifier", FName(f->Name).GetChars()); diff --git a/src/common/scripting/frontend/zcc_parser.cpp b/src/common/scripting/frontend/zcc_parser.cpp index d125e8b29..a713f2052 100644 --- a/src/common/scripting/frontend/zcc_parser.cpp +++ b/src/common/scripting/frontend/zcc_parser.cpp @@ -71,9 +71,16 @@ static FString ResolveIncludePath(const FString &path,const FString &lumpname){ { relativePath = relativePath.Mid(3); auto slash_index = fullPath.LastIndexOf("/"); - if (slash_index != -1) { + if (slash_index != -1) + { fullPath = fullPath.Mid(0, slash_index); - } else { + } + else if (fullPath.IsNotEmpty()) + { + fullPath = ""; + } + else + { pathOk = false; break; } diff --git a/src/common/textures/bitmap.h b/src/common/textures/bitmap.h index 2828be356..418f256c2 100644 --- a/src/common/textures/bitmap.h +++ b/src/common/textures/bitmap.h @@ -36,6 +36,7 @@ #ifndef __BITMAP_H__ #define __BITMAP_H__ +#include #include "palentry.h" struct FCopyInfo; diff --git a/src/common/textures/textureid.h b/src/common/textures/textureid.h index 84ebb679e..2d3a226f5 100644 --- a/src/common/textures/textureid.h +++ b/src/common/textures/textureid.h @@ -1,5 +1,4 @@ #pragma once -#define TEXTUREID_H #include #include "tarray.h" diff --git a/src/common/thirdparty/md5.h b/src/common/thirdparty/md5.h index f3aab94d9..95ac9a50d 100644 --- a/src/common/thirdparty/md5.h +++ b/src/common/thirdparty/md5.h @@ -18,6 +18,8 @@ #ifndef MD5_H #define MD5_H +#include + struct MD5Context { MD5Context() { Init(); } diff --git a/src/common/utility/engineerrors.cpp b/src/common/utility/engineerrors.cpp index 41485778a..05897b786 100644 --- a/src/common/utility/engineerrors.cpp +++ b/src/common/utility/engineerrors.cpp @@ -35,8 +35,9 @@ bool gameisdead; -#ifdef _WIN32 #include + +#ifdef _WIN32 #include #include "zstring.h" void I_DebugPrint(const char *cp) diff --git a/src/common/utility/findfile.cpp b/src/common/utility/findfile.cpp index 86d7098ec..65090cc90 100644 --- a/src/common/utility/findfile.cpp +++ b/src/common/utility/findfile.cpp @@ -90,7 +90,7 @@ bool D_AddFile(std::vector& wadfiles, const char* file, bool check, closedir(d); if (!found) { - Printf("Can't find file '%s' in '%s'\n", filename.GetChars(), basepath.GetChars()); + //Printf("Can't find file '%s' in '%s'\n", filename.GetChars(), basepath.GetChars()); return false; } } diff --git a/src/common/utility/palette.cpp b/src/common/utility/palette.cpp index ea046a220..03517c1ba 100644 --- a/src/common/utility/palette.cpp +++ b/src/common/utility/palette.cpp @@ -33,6 +33,8 @@ */ #include +#include +#include #include "palutil.h" #include "palentry.h" #include "sc_man.h" diff --git a/src/common/utility/tarray.h b/src/common/utility/tarray.h index 90cec1e58..b642f3c87 100644 --- a/src/common/utility/tarray.h +++ b/src/common/utility/tarray.h @@ -1,5 +1,4 @@ #pragma once -#define TARRAY_H /* ** tarray.h ** Templated, automatically resizing array @@ -356,6 +355,105 @@ public: return i; } + // !!! THIS REQUIRES AN ELEMENT TYPE THAT'S COMPARABLE WITH THE LT OPERATOR !!! + bool IsSorted() + { + for(unsigned i = 1; i < Count; i++) + { + if(Array[i] < Array[i-1]) return false; + } + return true; + } + + template + bool IsSorted(Func lt) + { + for(unsigned i = 1; i < Count; i++) + { + if(lt(Array[i], Array[i-1])) return false; + } + return true; + } + + // !!! THIS REQUIRES A SORTED OR EMPTY ARRAY !!! + // !!! AND AN ELEMENT TYPE THAT'S COMPARABLE WITH THE LT OPERATOR !!! + // + // exact = false returns the closest match, to be used for, ex., insertions, exact = true returns Size() when no match, like Find does + unsigned int SortedFind(const T& item, bool exact = true) const + { + if(Count == 0) return 0; + if(Count == 1) return (item < Array[0]) ? 0 : 1; + + unsigned int lo = 0; + unsigned int hi = Count - 1; + + while(lo <= hi) + { + int mid = lo + ((hi - lo) / 2); + + if(Array[mid] < item) + { + lo = mid + 1; + } + else if(item < Array[mid]) + { + hi = mid - 1; + } + else + { + return mid; + } + } + if(exact) + { + return Count; + } + else + { + return (lo == Count || (item < Array[lo])) ? lo : lo + 1; + } + } + + // !!! THIS REQUIRES A SORTED OR EMPTY ARRAY !!! + // + // exact = false returns the closest match, to be used for, ex., insertions, exact = true returns Size() when no match, like Find does + template + unsigned int SortedFind(const T& item, Func lt, bool exact = true) const + { + if(Count == 0) return 0; + if(Count == 1) return lt(item, Array[0]) ? 0 : 1; + + unsigned int lo = 0; + unsigned int hi = Count - 1; + + while(lo <= hi) + { + int mid = lo + ((hi - lo) / 2); + + if(lt(Array[mid], item)) + { + lo = mid + 1; + } + else if(lt(item, Array[mid])) + { + if(mid == 0) break; // prevent negative overflow due to unsigned numbers + hi = mid - 1; + } + else + { + return mid; + } + } + if(exact) + { + return Count; + } + else + { + return (lo == Count || lt(item, Array[lo])) ? lo : lo + 1; + } + } + bool Contains(const T& item) const { unsigned int i; @@ -463,6 +561,50 @@ public: return f; } + unsigned SortedAddUnique(const T& obj) + { + auto f = SortedFind(obj, true); + if (f == Size()) Push(obj); + return f; + } + + template + unsigned SortedAddUnique(const T& obj, Func lt) + { + auto f = SortedFind(obj, lt, true); + if (f == Size()) Push(obj); + return f; + } + + bool SortedDelete(const T& obj) + { + auto f = SortedFind(obj, true); + if (f == Size()) + { + Delete(f); + return true; + } + else + { + return false; + } + } + + template + bool SortedDelete(const T& obj, Func lt) + { + auto f = SortedFind(obj, lt, true); + if (f == Size()) + { + Delete(f); + return true; + } + else + { + return false; + } + } + bool Pop () { if (Count > 0) @@ -543,6 +685,17 @@ public: } } + void SortedInsert (const T &item) + { + Insert (SortedFind (item, false), item); + } + + template + void SortedInsert (const T &item, Func lt) + { + Insert (SortedFind (item, lt, false), item); + } + void ShrinkToFit () { if (Most > Count) diff --git a/src/common/utility/writezip.cpp b/src/common/utility/writezip.cpp index a6f6f9a27..2f3507e6f 100644 --- a/src/common/utility/writezip.cpp +++ b/src/common/utility/writezip.cpp @@ -39,6 +39,7 @@ #include "files.h" #include "m_swap.h" #include "w_zip.h" +#include "fs_decompress.h" using FileSys::FCompressedBuffer; diff --git a/src/common/widgets/netstartwindow.cpp b/src/common/widgets/netstartwindow.cpp index c45f30098..69c83e565 100644 --- a/src/common/widgets/netstartwindow.cpp +++ b/src/common/widgets/netstartwindow.cpp @@ -2,6 +2,7 @@ #include "netstartwindow.h" #include "version.h" #include "engineerrors.h" +#include "gstrings.h" #include #include #include diff --git a/src/d_main.cpp b/src/d_main.cpp index 3fdb30c65..a3d1e3767 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -575,6 +575,10 @@ CUSTOM_CVAR(Int, dmflags3, 0, CVAR_SERVERINFO | CVAR_NOINITCALL) CVAR(Flag, sv_noplayerclip, dmflags3, DF3_NO_PLAYER_CLIP); CVAR(Flag, sv_coopsharekeys, dmflags3, DF3_COOP_SHARE_KEYS); +CVAR(Flag, sv_localitems, dmflags3, DF3_LOCAL_ITEMS); +CVAR(Flag, sv_nolocaldrops, dmflags3, DF3_NO_LOCAL_DROPS); +CVAR(Flag, sv_nocoopitems, dmflags3, DF3_NO_COOP_ONLY_ITEMS); +CVAR(Flag, sv_nocoopthings, dmflags3, DF3_NO_COOP_ONLY_THINGS); //========================================================================== // @@ -3110,6 +3114,8 @@ static int FileSystemPrintf(FSMessageLevel level, const char* fmt, ...) static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allwads, std::vector& pwads) { + NetworkEntityManager::InitializeNetworkEntities(); + if (!restart) { V_InitScreenSize(); @@ -3257,6 +3263,9 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector& allw exec = NULL; } + if (!restart) + V_Init2(); + // [RH] Initialize localizable strings. GStrings.LoadStrings(fileSystem, language); diff --git a/src/d_net.cpp b/src/d_net.cpp index c43ae158b..2536739cf 100644 --- a/src/d_net.cpp +++ b/src/d_net.cpp @@ -2965,6 +2965,89 @@ int Net_GetLatency(int *ld, int *ad) return severity; } +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]; +} + // [RH] List "ping" times CCMD (pings) { diff --git a/src/d_net.h b/src/d_net.h index f0c160683..e4029bd66 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -95,6 +95,29 @@ extern int nodeforplayer[MAXPLAYERS]; extern ticcmd_t netcmds[MAXPLAYERS][BACKUPTICS]; 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/doomdef.h b/src/doomdef.h index a2ae2f35e..668059909 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -180,7 +180,11 @@ enum : unsigned enum : unsigned { DF3_NO_PLAYER_CLIP = 1 << 0, // Players can walk through and shoot through each other - DF3_COOP_SHARE_KEYS = 1 << 1, // Keys will be given to all players in coop + DF3_COOP_SHARE_KEYS = 1 << 1, // Keys and other core items will be given to all players in coop + DF3_LOCAL_ITEMS = 1 << 2, // Items are picked up client-side rather than fully taken by the client who picked it up + DF3_NO_LOCAL_DROPS = 1 << 3, // Drops from Actors aren't picked up locally + DF3_NO_COOP_ONLY_ITEMS = 1 << 4, // Items that only appear in co-op are disabled + DF3_NO_COOP_ONLY_THINGS = 1 << 5, // Any Actor that only appears in co-op is disabled }; // [RH] Compatibility flags. diff --git a/src/events.cpp b/src/events.cpp index 1f212076c..a4773d133 100755 --- a/src/events.cpp +++ b/src/events.cpp @@ -131,7 +131,7 @@ void DNetworkBuffer::OnDestroy() { Super::OnDestroy(); - _buffer.Clear(); + _buffer.Reset(); } void DNetworkBuffer::Serialize(FSerializer& arc) @@ -1043,164 +1043,201 @@ DEFINE_FIELD_X(ReplacedEvent, FReplacedEvent, Replacee) DEFINE_FIELD_X(ReplacedEvent, FReplacedEvent, Replacement) DEFINE_FIELD_X(ReplacedEvent, FReplacedEvent, IsFinal) -DEFINE_FIELD_X(NetworkCommand, FNetworkCommand, Player) -DEFINE_FIELD_X(NetworkCommand, FNetworkCommand, Command) +DEFINE_FIELD(FNetworkCommand, Player) +DEFINE_FIELD(FNetworkCommand, Command) -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadInt8) +static int NativeReadInt8(FNetworkCommand* const self) { return self->ReadInt8(); } + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadInt8, NativeReadInt8) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); ACTION_RETURN_INT(self->ReadInt8()); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadInt16) +static int NativeReadInt16(FNetworkCommand* const self) { return self->ReadInt16(); } + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadInt16, NativeReadInt16) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); ACTION_RETURN_INT(self->ReadInt16()); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadInt) +static int NativeReadInt(FNetworkCommand* const self) { return self->ReadInt(); } + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadInt, NativeReadInt) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); ACTION_RETURN_INT(self->ReadInt()); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadFloat) +static double NativeReadFloat(FNetworkCommand* const self) { return self->ReadFloat(); } + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadFloat, NativeReadFloat) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); ACTION_RETURN_FLOAT(self->ReadFloat()); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadDouble) +static double NativeReadDouble(FNetworkCommand* const self) { return self->ReadDouble(); } + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadDouble, NativeReadDouble) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); ACTION_RETURN_FLOAT(self->ReadDouble()); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadString) +static void NativeReadString(FNetworkCommand* const self, FString* const result) +{ + const auto str = self->ReadString(); + if (str != nullptr) + *result = str; +} + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadString, NativeReadString) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); FString res = {}; - auto str = self->ReadString(); - if (str != nullptr) - res = str; - + NativeReadString(self, &res); ACTION_RETURN_STRING(res); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadName) +static int NativeReadName(FNetworkCommand* const self) { - PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); - FName res = NAME_None; - auto str = self->ReadString(); + const auto str = self->ReadString(); if (str != nullptr) res = str; - ACTION_RETURN_INT(res.GetIndex()); + return res.GetIndex(); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadMapUnit) +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadName, NativeReadName) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + ACTION_RETURN_INT(NativeReadName(self)); +} + +static double NativeReadMapUnit(FNetworkCommand* const self) +{ constexpr double FixedToFloat = 1.0 / (1 << 16); - ACTION_RETURN_FLOAT(self->ReadInt() * FixedToFloat); + return self->ReadInt() * FixedToFloat; } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadAngle) +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadMapUnit, NativeReadMapUnit) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); - const DAngle bam = DAngle::fromBam(self->ReadInt()); - ACTION_RETURN_FLOAT(bam.Degrees()); + ACTION_RETURN_FLOAT(NativeReadMapUnit(self)); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadVector2) +static double NativeReadAngle(FNetworkCommand* const self) { return DAngle::fromBam(self->ReadInt()).Degrees(); } + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadAngle, NativeReadAngle) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); - DVector2 vec = {}; - vec.X = self->ReadDouble(); - vec.Y = self->ReadDouble(); - ACTION_RETURN_VEC2(vec); + ACTION_RETURN_FLOAT(DAngle::fromBam(self->ReadInt()).Degrees()); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadVector3) +static void NativeReadVector2(FNetworkCommand* const self, DVector2* const result) +{ + result->X = self->ReadDouble(); + result->Y = self->ReadDouble(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadVector2, NativeReadVector2) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); - DVector3 vec = {}; - vec.X = self->ReadDouble(); - vec.Y = self->ReadDouble(); - vec.Z = self->ReadDouble(); - ACTION_RETURN_VEC3(vec); + ACTION_RETURN_VEC2(DVector2(self->ReadDouble(), self->ReadDouble())); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadVector4) +static void NativeReadVector3(FNetworkCommand* const self, DVector3* const result) +{ + result->X = self->ReadDouble(); + result->Y = self->ReadDouble(); + result->Z = self->ReadDouble(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadVector3, NativeReadVector3) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); - DVector4 vec = {}; - vec.X = self->ReadDouble(); - vec.Y = self->ReadDouble(); - vec.Z = self->ReadDouble(); - vec.W = self->ReadDouble(); - ACTION_RETURN_VEC4(vec); + ACTION_RETURN_VEC3(DVector3(self->ReadDouble(), self->ReadDouble(), self->ReadDouble())); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadQuat) +static void NativeReadVector4(FNetworkCommand* const self, DVector4* const result) +{ + result->X = self->ReadDouble(); + result->Y = self->ReadDouble(); + result->Z = self->ReadDouble(); + result->W = self->ReadDouble(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadVector4, NativeReadVector4) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); - DQuaternion quat = {}; - quat.X = self->ReadDouble(); - quat.Y = self->ReadDouble(); - quat.Z = self->ReadDouble(); - quat.W = self->ReadDouble(); - ACTION_RETURN_QUAT(quat); + ACTION_RETURN_VEC4(DVector4(self->ReadDouble(), self->ReadDouble(), self->ReadDouble(), self->ReadDouble())); } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadIntArray) +static void NativeReadQuat(FNetworkCommand* const self, DQuaternion* const result) +{ + result->X = self->ReadDouble(); + result->Y = self->ReadDouble(); + result->Z = self->ReadDouble(); + result->W = self->ReadDouble(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadQuat, NativeReadQuat) { PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); - PARAM_OUTPOINTER(values, TArray); - PARAM_INT(type) - unsigned int size = self->ReadInt(); + ACTION_RETURN_QUAT(DQuaternion(self->ReadDouble(), self->ReadDouble(), self->ReadDouble(), self->ReadDouble())); +} + +static void NativeReadIntArray(FNetworkCommand* const self, TArray* const values, const int type) +{ + const unsigned int size = self->ReadInt(); for (unsigned int i = 0u; i < size; ++i) { switch (type) { - case NET_INT8: - values->Push(self->ReadInt8()); - break; + case NET_INT8: + values->Push(self->ReadInt8()); + break; - case NET_INT16: - values->Push(self->ReadInt16()); - break; + case NET_INT16: + values->Push(self->ReadInt16()); + break; - default: - values->Push(self->ReadInt()); - break; + default: + values->Push(self->ReadInt()); + break; } } +} +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadIntArray, NativeReadIntArray) +{ + PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + PARAM_OUTPOINTER(values, TArray); + PARAM_INT(type); + + NativeReadIntArray(self, values, type); return 0; } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadDoubleArray) +static void NativeReadDoubleArray(FNetworkCommand* const self, TArray* const values, const bool doublePrecision) { - PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); - PARAM_OUTPOINTER(values, TArray); - PARAM_BOOL(doublePrecision); - - unsigned int size = self->ReadInt(); + const unsigned int size = self->ReadInt(); for (unsigned int i = 0u; i < size; ++i) { if (doublePrecision) @@ -1208,32 +1245,55 @@ DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadDoubleArray) else values->Push(self->ReadFloat()); } +} +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadDoubleArray, NativeReadDoubleArray) +{ + PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + PARAM_OUTPOINTER(values, TArray); + PARAM_BOOL(doublePrecision); + + NativeReadDoubleArray(self, values, doublePrecision); return 0; } -DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadStringArray) +static void NativeReadStringArray(FNetworkCommand* const self, TArray* const values, const bool skipEmpty) { - PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); - PARAM_OUTPOINTER(values, TArray); - PARAM_BOOL(skipEmpty); - - unsigned int size = self->ReadInt(); + const unsigned int size = self->ReadInt(); for (unsigned int i = 0u; i < size; ++i) { FString res = {}; - auto str = self->ReadString(); + const auto str = self->ReadString(); if (str != nullptr) res = str; if (!skipEmpty || !res.IsEmpty()) values->Push(res); } +} +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, ReadStringArray, NativeReadStringArray) +{ + PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + PARAM_OUTPOINTER(values, TArray); + PARAM_BOOL(skipEmpty); + + NativeReadStringArray(self, values, skipEmpty); return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddInt8) +static int EndOfStream(FNetworkCommand* const self) { return self->EndOfStream(); } + +DEFINE_ACTION_FUNCTION_NATIVE(FNetworkCommand, EndOfStream, EndOfStream) +{ + PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand); + + ACTION_RETURN_BOOL(self->EndOfStream()); +} + +static void AddInt8(DNetworkBuffer* const self, const int value) { self->AddInt8(value); } + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddInt8, AddInt8) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_INT(value); @@ -1241,7 +1301,9 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddInt8) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddInt16) +static void AddInt16(DNetworkBuffer* const self, const int value) { self->AddInt16(value); } + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddInt16, AddInt16) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_INT(value); @@ -1249,7 +1311,9 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddInt16) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddInt) +static void AddInt(DNetworkBuffer* const self, const int value) { self->AddInt(value); } + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddInt, AddInt) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_INT(value); @@ -1257,7 +1321,9 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddInt) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddFloat) +static void AddFloat(DNetworkBuffer* const self, const double value) { self->AddFloat(value); } + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddFloat, AddFloat) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_FLOAT(value); @@ -1265,7 +1331,9 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddFloat) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddDouble) +static void AddDouble(DNetworkBuffer* const self, const double value) { self->AddDouble(value); } + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddDouble, AddDouble) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_FLOAT(value); @@ -1273,7 +1341,9 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddDouble) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddString) +static void AddString(DNetworkBuffer* const self, const FString& value) { self->AddString(value); } + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddString, AddString) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_STRING(value); @@ -1281,7 +1351,9 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddString) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddName) +static void AddName(DNetworkBuffer* const self, const int value) { FName x = ENamedName(value); self->AddString(x.GetChars()); } + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddName, AddName) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_NAME(value); @@ -1289,17 +1361,24 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddName) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddMapUnit) +static void AddMapUnit(DNetworkBuffer* const self, const double value) +{ + constexpr int FloatToFixed = 1 << 16; + self->AddInt(static_cast(value * FloatToFixed)); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddMapUnit, AddMapUnit) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_FLOAT(value); - constexpr int FloatToFixed = 1 << 16; - self->AddInt(static_cast(value * FloatToFixed)); + AddMapUnit(self, value); return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddAngle) +static void AddAngle(DNetworkBuffer* const self, const double value) { self->AddInt(DAngle::fromDeg(value).BAMs()); } + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddAngle, AddAngle) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_ANGLE(value); @@ -1307,7 +1386,13 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddAngle) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddVector2) +static void AddVector2(DNetworkBuffer* const self, const double x, const double y) +{ + self->AddDouble(x); + self->AddDouble(y); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddVector2, AddVector2) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_FLOAT(x); @@ -1317,7 +1402,14 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddVector2) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddVector3) +static void AddVector3(DNetworkBuffer* const self, const double x, const double y, const double z) +{ + self->AddDouble(x); + self->AddDouble(y); + self->AddDouble(z); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddVector3, AddVector3) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_FLOAT(x); @@ -1329,7 +1421,15 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddVector3) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddVector4) +static void AddVector4(DNetworkBuffer* const self, const double x, const double y, const double z, const double w) +{ + self->AddDouble(x); + self->AddDouble(y); + self->AddDouble(z); + self->AddDouble(w); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddVector4, AddVector4) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_FLOAT(x); @@ -1343,7 +1443,15 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddVector4) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddQuat) +static void AddQuat(DNetworkBuffer* const self, const double x, const double y, const double z, const double w) +{ + self->AddDouble(x); + self->AddDouble(y); + self->AddDouble(z); + self->AddDouble(w); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddQuat, AddQuat) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_FLOAT(x); @@ -1357,42 +1465,42 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddQuat) return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddIntArray) +static void AddIntArray(DNetworkBuffer* const self, TArray* const values, const int type) { - PARAM_SELF_PROLOGUE(DNetworkBuffer); - PARAM_POINTER(values, TArray); - PARAM_INT(type); - - unsigned int size = values->Size(); + const unsigned int size = values->Size(); self->AddInt(size); for (unsigned int i = 0u; i < size; ++i) { switch (type) { - case NET_INT8: - self->AddInt8((*values)[i]); - break; + case NET_INT8: + self->AddInt8((*values)[i]); + break; - case NET_INT16: - self->AddInt16((*values)[i]); - break; + case NET_INT16: + self->AddInt16((*values)[i]); + break; - default: - self->AddInt((*values)[i]); - break; + default: + self->AddInt((*values)[i]); + break; } } +} +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddIntArray, AddIntArray) +{ + PARAM_SELF_PROLOGUE(DNetworkBuffer); + PARAM_POINTER(values, TArray); + PARAM_INT(type); + + AddIntArray(self, values, type); return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddDoubleArray) +static void AddDoubleArray(DNetworkBuffer* const self, TArray* const values, const bool doublePrecision) { - PARAM_SELF_PROLOGUE(DNetworkBuffer); - PARAM_POINTER(values, TArray); - PARAM_BOOL(doublePrecision); - - unsigned int size = values->Size(); + const unsigned int size = values->Size(); self->AddInt(size); for (unsigned int i = 0u; i < size; ++i) { @@ -1401,20 +1509,32 @@ DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddDoubleArray) else self->AddFloat((*values)[i]); } +} +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddDoubleArray, AddDoubleArray) +{ + PARAM_SELF_PROLOGUE(DNetworkBuffer); + PARAM_POINTER(values, TArray); + PARAM_BOOL(doublePrecision); + + AddDoubleArray(self, values, doublePrecision); return 0; } -DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddStringArray) +static void AddStringArray(DNetworkBuffer* const self, TArray* const values) +{ + const unsigned int size = values->Size(); + self->AddInt(size); + for (unsigned int i = 0u; i < size; ++i) + self->AddString((*values)[i]); +} + +DEFINE_ACTION_FUNCTION_NATIVE(DNetworkBuffer, AddStringArray, AddStringArray) { PARAM_SELF_PROLOGUE(DNetworkBuffer); PARAM_POINTER(values, TArray); - unsigned int size = values->Size(); - self->AddInt(size); - for (unsigned int i = 0u; i < size; ++i) - self->AddString((*values)[i]); - + AddStringArray(self, values); return 0; } diff --git a/src/events.h b/src/events.h index facad2b05..d71922b9a 100755 --- a/src/events.h +++ b/src/events.h @@ -36,11 +36,6 @@ private: size_t _index = 0; TArray _stream; - inline bool IsValid() const - { - return _index < _stream.Size(); - } - public: int Player; FName Command; @@ -50,6 +45,11 @@ public: _stream.Swap(stream); } + inline bool EndOfStream() const + { + return _index >= _stream.Size(); + } + inline void Reset() { _index = 0; @@ -57,7 +57,7 @@ public: int ReadInt8() { - if (!IsValid()) + if (EndOfStream()) return 0; return _stream[_index++]; @@ -66,11 +66,11 @@ public: // If a value has to cut off early, just treat the previous value as the full one. int ReadInt16() { - if (!IsValid()) + if (EndOfStream()) return 0; int value = _stream[_index++]; - if (IsValid()) + if (!EndOfStream()) value = (value << 8) | _stream[_index++]; return value; @@ -78,17 +78,17 @@ public: int ReadInt() { - if (!IsValid()) + if (EndOfStream()) return 0; int value = _stream[_index++]; - if (IsValid()) + if (!EndOfStream()) { value = (value << 8) | _stream[_index++]; - if (IsValid()) + if (!EndOfStream()) { value = (value << 8) | _stream[_index++]; - if (IsValid()) + if (!EndOfStream()) value = (value << 8) | _stream[_index++]; } } @@ -99,17 +99,17 @@ public: // Floats without their first bits are pretty meaningless so those are done first. double ReadFloat() { - if (!IsValid()) + if (EndOfStream()) return 0.0; int value = _stream[_index++] << 24; - if (IsValid()) + if (!EndOfStream()) { value |= _stream[_index++] << 16; - if (IsValid()) + if (!EndOfStream()) { value |= _stream[_index++] << 8; - if (IsValid()) + if (!EndOfStream()) value |= _stream[_index++]; } } @@ -125,29 +125,29 @@ public: double ReadDouble() { - if (!IsValid()) + if (EndOfStream()) return 0.0; int64_t value = int64_t(_stream[_index++]) << 56; - if (IsValid()) + if (!EndOfStream()) { value |= int64_t(_stream[_index++]) << 48; - if (IsValid()) + if (!EndOfStream()) { value |= int64_t(_stream[_index++]) << 40; - if (IsValid()) + if (!EndOfStream()) { value |= int64_t(_stream[_index++]) << 32; - if (IsValid()) + if (!EndOfStream()) { value |= int64_t(_stream[_index++]) << 24; - if (IsValid()) + if (!EndOfStream()) { value |= int64_t(_stream[_index++]) << 16; - if (IsValid()) + if (!EndOfStream()) { value |= int64_t(_stream[_index++]) << 8; - if (IsValid()) + if (!EndOfStream()) value |= int64_t(_stream[_index++]); } } @@ -167,7 +167,7 @@ public: const char* ReadString() { - if (!IsValid()) + if (EndOfStream()) return nullptr; const char* str = reinterpret_cast(&_stream[_index]); diff --git a/src/g_game.cpp b/src/g_game.cpp index 4f422c81d..4596212c0 100644 --- a/src/g_game.cpp +++ b/src/g_game.cpp @@ -1424,6 +1424,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); if (gamestate != GS_TITLELEVEL) { diff --git a/src/g_level.cpp b/src/g_level.cpp index ed0904ca3..c490cbbcd 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -98,6 +98,7 @@ #include "s_music.h" #include "fragglescript/t_script.h" + #include "texturemanager.h" void STAT_StartNewGame(const char *lev); @@ -1715,7 +1716,7 @@ int FLevelLocals::FinishTravel () pawn->flags2 &= ~MF2_BLASTED; if (oldpawn != nullptr) { - StaticPointerSubstitution (oldpawn, pawn); + PlayerPointerSubstitution (oldpawn, pawn); oldpawn->Destroy(); } if (pawndup != NULL) @@ -2472,3 +2473,188 @@ DEFINE_ACTION_FUNCTION(FLevelLocals, GetEpisodeName) ACTION_RETURN_STRING(GStrings.localize(STAT_EpisodeName().GetChars())); } + +//---------------------------------------------------------------------------- +// Pathfinding +//---------------------------------------------------------------------------- + +// Code by RicardoLuis0, modified by Major Cooke +static TArray>& GetPathNodeNeighbors(AActor * self) +{ + static PClass * nodeCls = PClass::FindClass(NAME_PathNode); + +#ifndef NDEBUG + if(!nodeCls->IsAncestorOf(self->GetClass())) + { + ThrowAbortException(X_BAD_SELF, "Invalid class passed to GetNeighbors (must be PathNode)"); + } +#endif + + static PField *var = dyn_cast(nodeCls->FindSymbol("neighbors", true)); + + assert(var); + assert(var->Type->isDynArray()); + assert(static_cast(var->Type)->ElementType == nodeCls->VMType); + + return *reinterpret_cast>*>(reinterpret_cast(self) + var->Offset); +} + +static void ReconstructPath(TMap &cameFrom, AActor* current, TArray> &path) +{ + path.Clear(); + path.Push(current); + AActor ** tmp = cameFrom.CheckKey(current); + + if(tmp) do + { + path.Insert(0, *tmp); + } + while(tmp = cameFrom.CheckKey(*tmp)); +} + +static AActor* FindClosestNode(AActor* from) +{ + static const PClass * nodeCls = PClass::FindClass(NAME_PathNode); + + AActor * closest = nullptr; + double closestDist = DBL_MAX; + + for (int i = 0; i < from->Level->PathNodes.Size(); i++) + { + AActor* node = from->Level->PathNodes[i]; + if(node && !(node->flags & MF_AMBUSH) && nodeCls->IsAncestorOf(node->GetClass())) + { + double dst = node->Distance3DSquared(from); + const bool mrange = (dst < closestDist && + (node->friendlyseeblocks <= 0 || dst < double(node->friendlyseeblocks * node->friendlyseeblocks))); + + if (mrange && !from->CallExcludeNode(node) && P_CheckSight(node, from)) + { + closestDist = dst; + closest = node; + } + } + } + + return closest; +} + +template +static V GetOr(TMap map, const K &key, V alt) +{ + V *k = map.CheckKey(key); + return k ? *k : alt; +} + +static bool FindPathAStar(AActor *chaser, AActor* startnode, AActor* goalnode, TArray> &path) +{ + TArray openSet; + TMap cameFrom; + TMap gScore; + TMap fScore; + + openSet.Push(startnode); + gScore.Insert(startnode, 0); + fScore.Insert(startnode, startnode->Distance3DSquared(goalnode)); + + auto lt_fScore = [&fScore](AActor* lhs, AActor* rhs) + { + return GetOr(fScore, lhs, DBL_MAX) < GetOr(fScore, rhs, DBL_MAX); + }; + + while(openSet.Size() > 0) + { + AActor * current = openSet[0]; + openSet.Delete(0); + if(current == goalnode) + { + ReconstructPath(cameFrom, current, path); + return true; + } + + double current_gScore = GetOr(gScore, current, DBL_MAX); + + for(AActor * neighbor : GetPathNodeNeighbors(current)) + { + double tentative_gScore = current_gScore + current->Distance3DSquared(neighbor); + + double neighbor_gScore = GetOr(gScore, neighbor, DBL_MAX); + + if (tentative_gScore < neighbor_gScore && !chaser->CallExcludeNode(neighbor)) + { + openSet.SortedDelete(neighbor, lt_fScore); + cameFrom.Insert(neighbor, current); + gScore.Insert(neighbor, tentative_gScore); + fScore.Insert(neighbor, tentative_gScore + neighbor->Distance3DSquared(goalnode)); + openSet.SortedInsert(neighbor, lt_fScore); + } + } + } + return false; +} + +bool FLevelLocals::FindPath(AActor* chaser, AActor* target, AActor* startNode, AActor* goalNode) +{ + if (!chaser || !target || PathNodes.Size() < 1) + { + return false; + } + + static PClass* nodeCls = PClass::FindClass(NAME_PathNode); + assert(startNode == nullptr || nodeCls->IsAncestorOf(startNode->GetClass())); + assert(goalNode == nullptr || nodeCls->IsAncestorOf(goalNode->GetClass())); + + if(startNode == nullptr) startNode = FindClosestNode(chaser); + if(goalNode == nullptr) goalNode = FindClosestNode(target); + + // Incomplete graph. + if (!startNode || !goalNode) + { + return false; + } + + if (startNode == goalNode) + { + chaser->ClearPath(); + chaser->Path.Push(MakeObjPtr(startNode)); + return true; + } + + if (FindPathAStar(chaser, startNode, goalNode, chaser->Path)) + { + if (chaser->goal && nodeCls->IsAncestorOf(chaser->goal->GetClass())) + { + chaser->goal = nullptr; + } + return true; + } + + return false; +} + +DEFINE_ACTION_FUNCTION(FLevelLocals, FindPath) +{ + PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals); + PARAM_OBJECT(chaser, AActor); + PARAM_OBJECT(target, AActor); + PARAM_OBJECT(startnode, AActor); + PARAM_OBJECT(goalnode, AActor); + return self->FindPath(chaser, target, startnode, goalnode); +} + +DEFINE_ACTION_FUNCTION(FLevelLocals, HandlePathNode) +{ + PARAM_SELF_STRUCT_PROLOGUE(FLevelLocals); + PARAM_OBJECT(node, AActor); + PARAM_BOOL(add); + if (node) + { + if (add) + { + if (self->PathNodes.Find(node) >= self->PathNodes.Size()) + self->PathNodes.Push(node); + } + else self->PathNodes.Delete(self->PathNodes.Find(node)); + } + return 0; +} diff --git a/src/g_levellocals.h b/src/g_levellocals.h index 2a9c41af6..de0483879 100644 --- a/src/g_levellocals.h +++ b/src/g_levellocals.h @@ -444,6 +444,9 @@ public: void SetMusic(); + bool FindPath(AActor *chaser, AActor *target, AActor *startnode = nullptr, AActor *goalnode = nullptr); + + TArray PathNodes; TArray vertexes; TArray sectors; TArray extsectors; // container for non-trivial sector information. sector_t must be trivially copyable for *_fakeflat to work as intended. diff --git a/src/g_statusbar/sbar_mugshot.cpp b/src/g_statusbar/sbar_mugshot.cpp index f1d43af28..493124389 100644 --- a/src/g_statusbar/sbar_mugshot.cpp +++ b/src/g_statusbar/sbar_mugshot.cpp @@ -479,7 +479,7 @@ FGameTexture *FMugShot::GetFace(player_t *player, const char *default_face, int if (CurrentState != NULL) { int skin = player->userinfo.GetSkin(); - const char *skin_face = (stateflags & FMugShot::CUSTOM) ? nullptr : (player->morphTics ? (GetDefaultByType(player->MorphedPlayerClass))->NameVar(NAME_Face).GetChars() : Skins[skin].Face.GetChars()); + const char *skin_face = (stateflags & FMugShot::CUSTOM) ? nullptr : (player->mo->alternative != nullptr ? (GetDefaultByType(player->MorphedPlayerClass))->NameVar(NAME_Face).GetChars() : Skins[skin].Face.GetChars()); return CurrentState->GetCurrentFrameTexture(default_face, skin_face, level, angle); } return NULL; diff --git a/src/g_statusbar/shared_hud.cpp b/src/g_statusbar/shared_hud.cpp index a6a6ea410..f53e93a74 100644 --- a/src/g_statusbar/shared_hud.cpp +++ b/src/g_statusbar/shared_hud.cpp @@ -74,6 +74,7 @@ CVAR (Int , hud_showlag, 0, CVAR_ARCHIVE); // Show input latency (maketic - g CVAR (Int, hud_ammo_order, 0, CVAR_ARCHIVE); // ammo image and text order CVAR (Int, hud_ammo_red, 25, CVAR_ARCHIVE) // ammo percent less than which status is red CVAR (Int, hud_ammo_yellow, 50, CVAR_ARCHIVE) // ammo percent less is yellow more green +CVAR (Bool, hud_swaphealtharmor, false, CVAR_ARCHIVE); // swap health and armor position on HUD CVAR (Int, hud_health_red, 25, CVAR_ARCHIVE) // health amount less than which status is red CVAR (Int, hud_health_yellow, 50, CVAR_ARCHIVE) // health amount less than which status is yellow CVAR (Int, hud_health_green, 100, CVAR_ARCHIVE) // health amount above is blue, below is green @@ -193,4 +194,3 @@ void DBaseStatusBar::DrawAltHUD() VMCall(func, params, countof(params), nullptr, 0); } } - diff --git a/src/gamedata/g_mapinfo.cpp b/src/gamedata/g_mapinfo.cpp index dde9d1cc4..76b5e7d00 100644 --- a/src/gamedata/g_mapinfo.cpp +++ b/src/gamedata/g_mapinfo.cpp @@ -1823,6 +1823,7 @@ MapFlagHandlers[] = { "disableskyboxao", MITYPE_CLRFLAG3, LEVEL3_SKYBOXAO, 0 }, { "avoidmelee", MITYPE_SETFLAG3, LEVEL3_AVOIDMELEE, 0 }, { "attenuatelights", MITYPE_SETFLAG3, LEVEL3_ATTENUATE, 0 }, + { "pathing", MITYPE_SETFLAG3, LEVEL3_PATHING, 0 }, { "nousersave", MITYPE_SETVKDFLAG, VKDLEVELFLAG_NOUSERSAVE, 0 }, { "noautomap", MITYPE_SETVKDFLAG, VKDLEVELFLAG_NOAUTOMAP, 0 }, { "noautosaveonenter", MITYPE_SETVKDFLAG, VKDLEVELFLAG_NOAUTOSAVEONENTER, 0 }, diff --git a/src/gamedata/g_mapinfo.h b/src/gamedata/g_mapinfo.h index 17fae6255..a2505e057 100644 --- a/src/gamedata/g_mapinfo.h +++ b/src/gamedata/g_mapinfo.h @@ -270,6 +270,7 @@ enum ELevelFlags : unsigned int LEVEL3_AVOIDMELEE = 0x00020000, // global flag needed for proper MBF support. LEVEL3_NOJUMPDOWN = 0x00040000, // only for MBF21. Inverse of MBF's dog_jumping flag. LEVEL3_LIGHTCREATED = 0x00080000, // a light had been created in the last frame + LEVEL3_PATHING = 0x00100000, // enable pathfinding by default // VKDoom custom flags VKDLEVELFLAG_NOUSERSAVE = 0x00000001, diff --git a/src/gamedata/r_defs.h b/src/gamedata/r_defs.h index efdbd4959..cc3a82aaa 100644 --- a/src/gamedata/r_defs.h +++ b/src/gamedata/r_defs.h @@ -503,6 +503,7 @@ enum SECMF_OVERLAPPING = 512, // floor and ceiling overlap and require special renderer action. SECMF_NOSKYWALLS = 1024, // Do not draw "sky walls" SECMF_LIFT = 2048, // For MBF monster AI + SECMF_NOPATHING = 4096, // monsters cannot path find in these areas, saves on time and resources }; enum diff --git a/src/launcher/settingspage.h b/src/launcher/settingspage.h index 246fe31c5..77a6ff048 100644 --- a/src/launcher/settingspage.h +++ b/src/launcher/settingspage.h @@ -1,6 +1,7 @@ #pragma once #include +#include "gstrings.h" // #define RENDER_BACKENDS diff --git a/src/m_cheat.cpp b/src/m_cheat.cpp index 2ed4ddf62..7834f92d4 100644 --- a/src/m_cheat.cpp +++ b/src/m_cheat.cpp @@ -626,10 +626,13 @@ public: double plyrdmgfact = Pawn->DamageFactor; Pawn->DamageFactor = 1.; P_DamageMobj (Pawn, Pawn, Pawn, TELEFRAG_DAMAGE, NAME_Suicide); - Pawn->DamageFactor = plyrdmgfact; - if (Pawn->health <= 0) + if (Pawn != nullptr) { - Pawn->flags &= ~MF_SHOOTABLE; + Pawn->DamageFactor = plyrdmgfact; + if (Pawn->health <= 0) + { + Pawn->flags &= ~MF_SHOOTABLE; + } } Destroy(); } diff --git a/src/maploader/maploader.h b/src/maploader/maploader.h index c6d9236a5..fa765b084 100644 --- a/src/maploader/maploader.h +++ b/src/maploader/maploader.h @@ -152,7 +152,7 @@ private: // Polyobjects void InitSideLists(); void IterFindPolySides(FPolyObj *po, side_t *side); - void SpawnPolyobj(int index, int tag, int type); + void SpawnPolyobj(int index, int tag, int type, int damage); void TranslateToStartSpot(int tag, const DVector2 &origin); void InitPolyBlockMap(void); diff --git a/src/maploader/polyobjects.cpp b/src/maploader/polyobjects.cpp index b16942429..1f662b134 100644 --- a/src/maploader/polyobjects.cpp +++ b/src/maploader/polyobjects.cpp @@ -148,7 +148,22 @@ static int posicmp(const void *a, const void *b) return (*(const side_t **)a)->linedef->args[1] - (*(const side_t **)b)->linedef->args[1]; } -void MapLoader::SpawnPolyobj (int index, int tag, int type) +int SetPolyobjDamage(int type, int damage) +{ + int dam; + if (type != SMT_PolySpawn) + { + if (damage == 1) + dam = 3; + else if (damage > 1) + dam = TELEFRAG_DAMAGE; + else if (damage < 0) + dam = abs(damage); + } + return (type != SMT_PolySpawn) ? dam : 0; +} + +void MapLoader::SpawnPolyobj (int index, int tag, int type, int damage) { unsigned int ii; int i; @@ -181,8 +196,9 @@ void MapLoader::SpawnPolyobj (int index, int tag, int type) sd->linedef->args[0] = 0; IterFindPolySides(&Level->Polyobjects[index], sd); po->MirrorNum = sd->linedef->args[1]; - po->crush = (type != SMT_PolySpawn) ? 3 : 0; + po->crush = SetPolyobjDamage(type,damage); po->bHurtOnTouch = (type == SMT_PolySpawnHurt); + po->tag = tag; po->seqType = sd->linedef->args[2]; if (po->seqType < 0 || po->seqType > (MAX_SNDSEQS - 1)) @@ -222,7 +238,7 @@ void MapLoader::SpawnPolyobj (int index, int tag, int type) qsort(&po->Sidedefs[0], po->Sidedefs.Size(), sizeof(po->Sidedefs[0]), posicmp); if (po->Sidedefs.Size() > 0) { - po->crush = (type != SMT_PolySpawn) ? 3 : 0; + po->crush = SetPolyobjDamage(type,damage); po->bHurtOnTouch = (type == SMT_PolySpawnHurt); po->tag = tag; po->seqType = po->Sidedefs[0]->linedef->args[3]; @@ -359,13 +375,13 @@ void MapLoader::PO_Init (void) // Find the startSpot points, and spawn each polyobj for (int i=polythings.Size()-1; i >= 0; i--) { - // 9301 (3001) = no crush, 9302 (3002) = crushing, 9303 = hurting touch + // 9301 (3001) = no crush, 9302 (3002) = crushing, 9303 = hurting touch, Health = crusher/hurter damage int type = polythings[i]->info->Special; if (type >= SMT_PolySpawn && type <= SMT_PolySpawnHurt) { // Polyobj StartSpot Pt. Level->Polyobjects[polyIndex].StartSpot.pos = polythings[i]->pos.XY(); - SpawnPolyobj(polyIndex, polythings[i]->angle, type); + SpawnPolyobj(polyIndex, polythings[i]->angle, type, polythings[i]->Health); polyIndex++; } } diff --git a/src/namedef_custom.h b/src/namedef_custom.h index 732076bb3..720546407 100644 --- a/src/namedef_custom.h +++ b/src/namedef_custom.h @@ -12,6 +12,9 @@ xx(BloodSplatter) xx(AxeBlood) xx(Spray) +// Actor properties +xx(BobPivot3D) + // Invulnerability types xx(Ghost) xx(Reflective) @@ -199,6 +202,7 @@ xx(Cast) // 'damage type' for the cast call xx(MapSpot) xx(PatrolPoint) xx(PatrolSpecial) +xx(PathNode) xx(Communicator) xx(PowerScanner) @@ -459,6 +463,7 @@ xx(WBobSpeed) xx(WBobFire) xx(PlayerClass) xx(MonsterClass) +xx(Morph) xx(MorphedMonster) xx(Wi_NoAutostartMap) diff --git a/src/p_saveg.cpp b/src/p_saveg.cpp index a3ad87f71..d05140ee8 100644 --- a/src/p_saveg.cpp +++ b/src/p_saveg.cpp @@ -62,6 +62,7 @@ #include "fragglescript/t_script.h" #include "s_music.h" #include "model.h" +#include "d_net.h" EXTERN_CVAR(Bool, save_formatted) @@ -653,6 +654,15 @@ void FLevelLocals::SerializePlayers(FSerializer &arc, bool skipload) ReadMultiplePlayers(arc, numPlayers, numPlayersNow, skipload); } arc.EndArray(); + + if (!skipload) + { + for (unsigned int i = 0u; i < MAXPLAYERS; ++i) + { + if (PlayerInGame(i) && Players[i]->mo != nullptr) + NetworkEntityManager::SetClientNetworkEntity(Players[i]); + } + } } if (!skipload && numPlayersNow > numPlayers) { @@ -1140,7 +1150,7 @@ void FLevelLocals::UnSnapshotLevel(bool hubLoad) // If this isn't the unmorphed original copy of a player, destroy it, because it's extra. for (i = 0; i < MAXPLAYERS; ++i) { - if (PlayerInGame(i) && Players[i]->morphTics && Players[i]->mo->alternative == pawn) + if (PlayerInGame(i) && Players[i]->mo->alternative == pawn) { break; } diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 53cbfce62..c2e32f4d8 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -326,6 +326,7 @@ void FLevelLocals::ClearLevelData(bool fullgc) } ClearPortals(); + PathNodes.Clear(); tagManager.Clear(); ClearTIDHashes(); if (SpotState) SpotState->Destroy(); diff --git a/src/p_states.cpp b/src/p_states.cpp index 49c3fe71f..da8ef7f96 100644 --- a/src/p_states.cpp +++ b/src/p_states.cpp @@ -387,7 +387,7 @@ FState *FStateLabelStorage::GetState(int pos, PClassActor *cls, bool exact) //========================================================================== // -// State label conversion function for scripts +// State label conversion functions for scripts // //========================================================================== @@ -395,7 +395,7 @@ DEFINE_ACTION_FUNCTION(AActor, FindState) { PARAM_SELF_PROLOGUE(AActor); PARAM_INT(newstate); - PARAM_BOOL(exact) + PARAM_BOOL(exact); ACTION_RETURN_STATE(StateLabels.GetState(newstate, self->GetClass(), exact)); } @@ -407,6 +407,15 @@ DEFINE_ACTION_FUNCTION(AActor, ResolveState) ACTION_RETURN_STATE(newstate); } +// find state by string instead of label +DEFINE_ACTION_FUNCTION(AActor, FindStateByString) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_STRING(newstate); + PARAM_BOOL(exact); + ACTION_RETURN_STATE(self->GetClass()->FindStateByString(newstate.GetChars(), exact)); +} + //========================================================================== // // Search one list of state definitions for the given name diff --git a/src/playsim/a_morph.cpp b/src/playsim/a_morph.cpp index 80e6807af..5f6c38b91 100644 --- a/src/playsim/a_morph.cpp +++ b/src/playsim/a_morph.cpp @@ -37,7 +37,7 @@ bool P_MorphActor(AActor *activator, AActor *victim, PClassActor *ptype, PClassA bool P_UnmorphActor(AActor *activator, AActor *morphed, int flags, bool force) { - IFVIRTUALPTR(morphed, AActor, UnMorph) + IFVIRTUALPTR(morphed, AActor, Unmorph) { VMValue params[] = { morphed, activator, flags, force }; int retval; diff --git a/src/playsim/a_morph.h b/src/playsim/a_morph.h index 00303533c..762cd01bb 100644 --- a/src/playsim/a_morph.h +++ b/src/playsim/a_morph.h @@ -26,6 +26,8 @@ enum MORPH_UNDOBYTIMEOUT = 0x00001000, // Player unmorphs once countdown expires MORPH_UNDOALWAYS = 0x00002000, // Powerups must always unmorph, no matter what. MORPH_TRANSFERTRANSLATION = 0x00004000, // Transfer translation from the original actor to the morphed one + MORPH_KEEPARMOR = 0x00008000, // Don't lose current armor value when morphing. + MORPH_IGNOREINVULN = 0x00010000, // Completely ignore invulnerability status on players. MORPH_STANDARDUNDOING = MORPH_UNDOBYTOMEOFPOWER | MORPH_UNDOBYCHAOSDEVICE | MORPH_UNDOBYTIMEOUT, }; diff --git a/src/playsim/a_sharedglobal.h b/src/playsim/a_sharedglobal.h index 83d3a471d..ecaa74b52 100644 --- a/src/playsim/a_sharedglobal.h +++ b/src/playsim/a_sharedglobal.h @@ -125,11 +125,11 @@ enum struct FQuakeJiggers { - DVector3 Intensity; - DVector3 RelIntensity; - DVector3 Offset; - DVector3 RelOffset; - double RollIntensity, RollWave; + DVector3 Intensity = {}; + DVector3 RelIntensity = {}; + DVector3 Offset = {}; + DVector3 RelOffset = {}; + double RollIntensity = 0.0, RollWave = 0.0; }; class DEarthquake : public DThinker diff --git a/src/playsim/actor.h b/src/playsim/actor.h index fec32882c..176848704 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -442,7 +442,10 @@ enum ActorFlag9 MF9_DOSHADOWBLOCK = 0x00000002, // [inkoalawetrust] Should the monster look for SHADOWBLOCK actors ? MF9_SHADOWBLOCK = 0x00000004, // [inkoalawetrust] Actors in the line of fire with this flag trigger the MF_SHADOW aiming penalty. MF9_SHADOWAIMVERT = 0x00000008, // [inkoalawetrust] Monster aim is also offset vertically when aiming at shadow actors. - MF9_DECOUPLEDANIMATIONS = 0x00000010, // [RL0] Decouple model animations from states + MF9_DECOUPLEDANIMATIONS = 0x00000010, // [RL0] Decouple model animations from states + MF9_PATHING = 0x00000020, // [MC] Enables monsters to do pathfinding, such as A*. + MF9_KEEPPATH = 0x00000040, // [MC] Forces monsters to keep to the path when target's in sight. + MF9_NOPATHING = 0x00000080, // [MC] override the mapinfo "pathfinding" }; // --- mobj.renderflags --- @@ -498,6 +501,9 @@ enum ActorRenderFlag2 RF2_ONLYVISIBLEINMIRRORS = 0x0002, // [Nash] only renders in mirrors RF2_BILLBOARDFACECAMERA = 0x0004, // Sprite billboard face camera (override gl_billboard_faces_camera) RF2_BILLBOARDNOFACECAMERA = 0x0008, // Sprite billboard face camera angle (override gl_billboard_faces_camera) + RF2_FLIPSPRITEOFFSETX = 0x0010, + RF2_FLIPSPRITEOFFSETY = 0x0020, + RF2_CAMFOLLOWSPLAYER = 0x0040, // Matches the cam's base position and angles to the main viewpoint. }; // This translucency value produces the closest match to Heretic's TINTTAB. @@ -767,7 +773,7 @@ public: Flags = f; } - bool isZero() + bool isZero() const { return Offset.isZero(); } @@ -791,6 +797,7 @@ public: virtual void PostSerialize() override; virtual void PostBeginPlay() override; // Called immediately before the actor's first tick virtual void Tick() override; + void EnableNetworking(const bool enable) override; static AActor *StaticSpawn (FLevelLocals *Level, PClassActor *type, const DVector3 &pos, replace_t allowreplacement, bool SpawningMapThing = false); @@ -899,6 +906,9 @@ public: // Returns true if this view is considered "local" for the player. bool CheckLocalView() const; + // Allows for enabling/disabling client-side rendering in a way the playsim can't access. + void DisableLocalRendering(const unsigned int pNum, const bool disable); + bool ShouldRenderLocally() const; // Finds the first item of a particular type. AActor *FindInventory (PClassActor *type, bool subclass=false); @@ -1094,6 +1104,11 @@ public: void AttachLight(unsigned int count, const FLightDefaults *lightdef); void SetDynamicLights(); + void ClearPath(); + bool CanPathfind(); + bool CallExcludeNode(AActor *node); + void CallReachedNode(AActor *node); + // info for drawing // NOTE: The first member variable *must* be snext. AActor *snext, **sprev; // links in sector (if needed) @@ -1101,6 +1116,7 @@ public: DAngle SpriteAngle; DAngle SpriteRotation; + DVector2 AutomapOffsets; // Offset the actors' sprite view on the automap by these coordinates. DRotator Angles; DRotator ViewAngles; // Angle offsets for cameras TObjPtr ViewPos; // Position offsets for cameras @@ -1119,6 +1135,7 @@ public: uint32_t RenderRequired; // current renderer must have this feature set uint32_t RenderHidden; // current renderer must *not* have any of these features + bool NoLocalRender; // DO NOT EXPORT THIS! This is a way to disable rendering such that the playsim cannot access it. ActorRenderFlags renderflags; // Different rendering flags ActorRenderFlags2 renderflags2; // More rendering flags... ActorFlags flags; @@ -1148,6 +1165,7 @@ public: TObjPtr boneComponentData; // interaction info + TArray > Path; FBlockNode *BlockNode; // links in blocks (if needed) struct sector_t *Sector; subsector_t * subsector; @@ -1473,6 +1491,11 @@ public: result.Roll = PrevAngles.Roll + deltaangle(PrevAngles.Roll, Angles.Roll) * ticFrac; return result; } + float GetSpriteOffset(bool y) const + { + if (y) return (float)(renderflags2 & RF2_FLIPSPRITEOFFSETY ? SpriteOffset.Y : -SpriteOffset.Y); + else return (float)(renderflags2 & RF2_FLIPSPRITEOFFSETX ? SpriteOffset.X : -SpriteOffset.X); + } DAngle GetSpriteAngle(DAngle viewangle, double ticFrac) { if (flags7 & MF7_SPRITEANGLE) @@ -1727,8 +1750,8 @@ struct FTranslatedLineTarget bool unlinked; // found by a trace that went through an unlinked portal. }; - -void StaticPointerSubstitution(AActor* old, AActor* notOld); +void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer); +int MorphPointerSubstitution(AActor* from, AActor* to); #define S_FREETARGMOBJ 1 diff --git a/src/playsim/d_player.h b/src/playsim/d_player.h index 675813873..49d8d54a1 100644 --- a/src/playsim/d_player.h +++ b/src/playsim/d_player.h @@ -458,8 +458,7 @@ public: bool Resurrect(); // Scaled angle adjustment info. Not for direct manipulation. - DRotator angleTargets; - DRotator angleAppliedAmounts; + DRotator angleOffsetTargets; }; // Bookkeeping on players - state. diff --git a/src/playsim/p_actionfunctions.cpp b/src/playsim/p_actionfunctions.cpp index 4c842d3bb..76d6be9fd 100644 --- a/src/playsim/p_actionfunctions.cpp +++ b/src/playsim/p_actionfunctions.cpp @@ -1691,7 +1691,8 @@ DEFINE_ACTION_FUNCTION(AActor, A_SpawnParticleEx) PARAM_FLOAT (rollacc) startalpha = clamp(startalpha, 0., 1.); - if (fadestep > 0) fadestep = clamp(fadestep, 0., 1.); + fadestep = clamp(fadestep, -1.0, 1.0); + size = fabs(size); if (lifetime != 0) { diff --git a/src/playsim/p_effect.cpp b/src/playsim/p_effect.cpp index 430265a75..5ee7e9f3c 100644 --- a/src/playsim/p_effect.cpp +++ b/src/playsim/p_effect.cpp @@ -306,10 +306,9 @@ void P_ThinkParticles (FLevelLocals *Level) continue; } - auto oldtrans = particle->alpha; particle->alpha -= particle->fadestep; particle->size += particle->sizestep; - if (particle->alpha <= 0 || oldtrans < particle->alpha || --particle->ttl <= 0 || (particle->size <= 0)) + if (particle->alpha <= 0 || --particle->ttl <= 0 || (particle->size <= 0)) { // The particle has expired, so free it *particle = {}; if (prev) @@ -375,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 < 0) particle->fadestep = FADEFROMTTL(lifetime); + if (fadestep <= -1.0) particle->fadestep = FADEFROMTTL(lifetime); else particle->fadestep = float(fadestep); particle->ttl = lifetime; particle->size = size; @@ -1206,7 +1205,7 @@ DEFINE_ACTION_FUNCTION_NATIVE(DVisualThinker, SetTranslation, SetTranslation) static int IsFrozen(DVisualThinker * self) { - return (self->Level->isFrozen() && !(self->PT.flags & SPF_NOTIMEFREEZE)); + return !!(self->Level->isFrozen() && !(self->PT.flags & SPF_NOTIMEFREEZE)); } bool DVisualThinker::isFrozen() @@ -1242,6 +1241,14 @@ int DVisualThinker::GetRenderStyle() return PT.style; } +float DVisualThinker::GetOffset(bool y) const // Needed for the renderer. +{ + if (y) + return (float)(bFlipOffsetY ? Offset.Y : -Offset.Y); + else + return (float)(bFlipOffsetX ? Offset.X : -Offset.X); +} + void DVisualThinker::Serialize(FSerializer& arc) { Super::Serialize(arc); @@ -1264,6 +1271,8 @@ void DVisualThinker::Serialize(FSerializer& arc) ("flipy", bYFlip) ("dontinterpolate", bDontInterpolate) ("addlightlevel", bAddLightLevel) + ("flipoffsetx", bFlipOffsetX) + ("flipoffsetY", bFlipOffsetY) ("lightlevel", LightLevel) ("flags", PT.flags); @@ -1289,3 +1298,5 @@ DEFINE_FIELD(DVisualThinker, bXFlip); DEFINE_FIELD(DVisualThinker, bYFlip); DEFINE_FIELD(DVisualThinker, bDontInterpolate); DEFINE_FIELD(DVisualThinker, bAddLightLevel); +DEFINE_FIELD(DVisualThinker, bFlipOffsetX); +DEFINE_FIELD(DVisualThinker, bFlipOffsetY); diff --git a/src/playsim/p_effect.h b/src/playsim/p_effect.h index 98f3d6789..0f439785c 100644 --- a/src/playsim/p_effect.h +++ b/src/playsim/p_effect.h @@ -166,7 +166,9 @@ public: FTextureID AnimatedTexture; sector_t *cursector; - bool bXFlip, + bool bFlipOffsetX, + bFlipOffsetY, + bXFlip, bYFlip, // flip the sprite on the x/y axis. bDontInterpolate, // disable all interpolation bAddLightLevel; // adds sector light level to 'LightLevel' @@ -191,4 +193,5 @@ public: void UpdateSpriteInfo(); void Serialize(FSerializer& arc) override; -}; + float GetOffset(bool y) const; +}; \ No newline at end of file diff --git a/src/playsim/p_enemy.cpp b/src/playsim/p_enemy.cpp index 58adfb737..0dc3fd0ef 100644 --- a/src/playsim/p_enemy.cpp +++ b/src/playsim/p_enemy.cpp @@ -318,13 +318,14 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, CheckMeleeRange, P_CheckMeleeRange) // //============================================================================= -static int P_CheckMissileRange (AActor *actor) +static int DoCheckMissileRange (AActor *actor, int &sight) { double dist; if ((actor->Sector->Flags & SECF_NOATTACK)) return false; - if (!P_CheckSight (actor, actor->target, SF_SEEPASTBLOCKEVERYTHING)) + sight = P_CheckSight(actor, actor->target, SF_SEEPASTBLOCKEVERYTHING); + if (!sight) return false; if (actor->flags & MF_JUSTHIT) @@ -380,6 +381,12 @@ static int P_CheckMissileRange (AActor *actor) return pr_checkmissilerange() >= min(int(dist), mmc); } +static int P_CheckMissileRange(AActor* actor) +{ + int n = -1; + return DoCheckMissileRange(actor, n); +} + DEFINE_ACTION_FUNCTION_NATIVE(AActor, CheckMissileRange, P_CheckMissileRange) { PARAM_SELF_PROLOGUE(AActor); @@ -944,13 +951,13 @@ void P_DoNewChaseDir (AActor *actor, double deltax, double deltay) // //============================================================================= -void P_NewChaseDir(AActor * actor) +void DoNewChaseDir(AActor * actor, bool node) { DVector2 delta; actor->strafecount = 0; - if ((actor->flags5&MF5_CHASEGOAL || actor->goal == actor->target) && actor->goal!=NULL) + if ((node || actor->flags5&MF5_CHASEGOAL || actor->goal == actor->target) && actor->goal != nullptr) { delta = actor->Vec2To(actor->goal); } @@ -1089,6 +1096,11 @@ void P_NewChaseDir(AActor * actor) } +void P_NewChaseDir(AActor* actor) +{ + DoNewChaseDir(actor, false); +} + //============================================================================= // // P_RandomChaseDir @@ -1823,7 +1835,7 @@ int P_LookForPlayers (AActor *actor, INTBOOL allaround, FLookExParams *params) if (!(player->mo->flags & MF_SHOOTABLE)) continue; // not shootable (observer or dead) - if (!actor->IsHostile(player->mo)) + if (actor->IsFriend(player->mo)) continue; // same +MF_FRIENDLY, ignore if (player->cheats & CF_NOTARGET) @@ -1917,7 +1929,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_Look) targ = NULL; } - if (targ && targ->player && ((targ->player->cheats & CF_NOTARGET) || !self->IsHostile(targ))) + if (targ && targ->player && ((targ->player->cheats & CF_NOTARGET) || !(targ->flags & MF_FRIENDLY))) { return 0; } @@ -1931,7 +1943,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_Look) if (targ && (targ->flags & MF_SHOOTABLE)) { - if (!self->IsHostile (targ)) // be a little more precise! + if (self->IsFriend (targ)) // be a little more precise! { // If we find a valid target here, the wandering logic should *not* // be activated! It would cause the seestate to be set twice. @@ -2199,6 +2211,81 @@ DEFINE_ACTION_FUNCTION(AActor, A_ClearLastHeard) return 0; } +//========================================================================== +// +// ClearPath +// +//========================================================================== + +void AActor::ClearPath() +{ + Path.Clear(); + if (goal) + { + static PClass* nodeCls = PClass::FindClass(NAME_PathNode); + if (nodeCls->IsAncestorOf(goal->GetClass())) + goal = nullptr; + } +} + +DEFINE_ACTION_FUNCTION(AActor, ClearPath) +{ + PARAM_SELF_PROLOGUE(AActor); + self->ClearPath(); + return 0; +} + +bool AActor::CanPathfind() +{ + if (Level->PathNodes.Size() > 0 && + (!(flags9 & MF9_NOPATHING) && !(Sector->MoreFlags & SECMF_NOPATHING)) && + (flags9 & MF9_PATHING || Level->flags3 & LEVEL3_PATHING)) + { + if ((flags6 & MF6_NOFEAR)) + return true; + + // Can't pathfind while feared. + if (!(flags4 & MF4_FRIGHTENED)) + { + if (!target) + return true; + + if (!target->flags8 & MF8_FRIGHTENING) + return (!target->player || !(target->player->cheats & CF_FRIGHTENING)); + } + } + return false; +} + +DEFINE_ACTION_FUNCTION(AActor, CanPathfind) +{ + PARAM_SELF_PROLOGUE(AActor); + return self->CanPathfind(); +} + +void AActor::CallReachedNode(AActor *node) +{ + IFVIRTUAL(AActor, ReachedNode) + { + VMValue params[2] = { this, node }; + VMCall(func, params, 2, nullptr, 0); + } +} + +// Called while scoring the path. +bool AActor::CallExcludeNode(AActor* node) +{ + IFVIRTUAL(AActor, ExcludeNode) + { + VMValue params[2] = { (DObject*)this, node }; + int retval = 0; + VMReturn ret(&retval); + VMCall(func, params, 2, &ret, 1); + return !!retval; + } + return false; +} + //========================================================================== // // A_Wander @@ -2333,6 +2420,7 @@ nosee: void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missilestate, bool playactive, bool nightmarefast, bool dontmove, int flags) { + int sight = -1; if (actor->flags5 & MF5_INCONVERSATION) return; @@ -2411,7 +2499,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi // [RH] Friendly monsters will consider chasing whoever hurts a player if they // don't already have a target. - if (actor->flags & MF_FRIENDLY && actor->target == NULL) + if (actor->flags & MF_FRIENDLY && actor->target == nullptr) { player_t *player; @@ -2443,7 +2531,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi } if (!actor->target || !(actor->target->flags & MF_SHOOTABLE)) { // look for a new target - if (actor->target != NULL && (actor->target->flags2 & MF2_NONSHOOTABLE)) + if (actor->target != nullptr && (actor->target->flags2 & MF2_NONSHOOTABLE)) { // Target is only temporarily unshootable, so remember it. actor->lastenemy = actor->target; @@ -2451,17 +2539,17 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi // hurt our old one temporarily. actor->threshold = 0; } - if (P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), NULL) && actor->target != actor->goal) + if (P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), nullptr) && actor->target != actor->goal) { // got a new target actor->flags7 &= ~MF7_INCHASE; return; } - if (actor->target == NULL) + if (actor->target == nullptr) { if (flags & CHF_DONTIDLE || actor->flags & MF_FRIENDLY) { //A_Look(actor); - if (actor->target == NULL) + if (actor->target == nullptr) { if (!dontmove) A_Wander(actor); actor->flags7 &= ~MF7_INCHASE; @@ -2470,6 +2558,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi } else { + actor->ClearPath(); actor->SetIdle(); actor->flags7 &= ~MF7_INCHASE; return; @@ -2493,9 +2582,13 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi actor->flags7 &= ~MF7_INCHASE; return; } - + // [RH] Don't attack if just moving toward goal - if (actor->target == actor->goal || (actor->flags5&MF5_CHASEGOAL && actor->goal != NULL)) + static const PClass* nodeCls = PClass::FindClass(NAME_PathNode); + bool pnode = (actor->goal && nodeCls->IsAncestorOf(actor->goal->GetClass())); + + if (!pnode && + (actor->target == actor->goal || (actor->flags5&MF5_CHASEGOAL && actor->goal))) { AActor * savedtarget = actor->target; actor->target = actor->goal; @@ -2513,7 +2606,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi // as the goal. while ( (spec = specit.Next()) ) { - P_ExecuteSpecial(actor->Level, spec->special, NULL, actor, false, spec->args[0], + P_ExecuteSpecial(actor->Level, spec->special, nullptr, actor, false, spec->args[0], spec->args[1], spec->args[2], spec->args[3], spec->args[4]); } @@ -2536,6 +2629,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi if (newgoal != NULL && delay != 0) { actor->flags4 |= MF4_INCOMBAT; + actor->ClearPath(); // [MC] Just to be safe. actor->SetIdle(); } actor->flags7 &= ~MF7_INCHASE; @@ -2573,11 +2667,10 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi } } } - } // [RH] Scared monsters attack less frequently - if (((actor->target->player == NULL || + if (((actor->target->player == nullptr || !((actor->target->player->cheats & CF_FRIGHTENING) || (actor->target->flags8 & MF8_FRIGHTENING))) && !(actor->flags4 & MF4_FRIGHTENED)) || pr_scaredycat() < 43) @@ -2594,17 +2687,9 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi } // check for missile attack - if (missilestate) + if (missilestate && (actor->isFast() || actor->movecount < 1) && DoCheckMissileRange(actor, sight)) { - if (!actor->isFast() && actor->movecount) - { - goto nomissile; - } - - if (!P_CheckMissileRange (actor)) - goto nomissile; - - actor->SetState (missilestate); + actor->SetState(missilestate); actor->flags |= MF_JUSTATTACKED; actor->flags4 |= MF4_INCOMBAT; actor->flags7 &= ~MF7_INCHASE; @@ -2626,7 +2711,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi lookForBetter = true; } AActor * oldtarget = actor->target; - gotNew = P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), NULL); + gotNew = P_LookForPlayers (actor, !(flags & CHF_DONTLOOKALLAROUND), nullptr); if (lookForBetter) { actor->flags3 |= MF3_NOSIGHTCHECK; @@ -2634,10 +2719,43 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi if (gotNew && actor->target != oldtarget) { actor->flags7 &= ~MF7_INCHASE; + actor->ClearPath(); return; // got a new target } } + if (!dontmove && actor->target && actor->CanPathfind()) + { + // Try to get sight checks from missile states if they have any, saving time. + if (!(actor->flags9 & MF9_KEEPPATH)) + { + if (sight < 0) + sight = P_CheckSight(actor, actor->target, SF_SEEPASTBLOCKEVERYTHING); + } + else sight = 0; + + if (sight == 0) + { + if (pnode && !(actor->goal->flags & MF_AMBUSH)) + { + AActor* temp = actor->target; + actor->target = actor->goal; + bool reached = (P_CheckMeleeRange(actor)); + actor->target = temp; + + if (reached) + actor->CallReachedNode(actor->goal); + + } + if (!actor->goal) + { + if (actor->Path.Size() > 0 || actor->Level->FindPath(actor, actor->target)) + actor->goal = actor->Path[0]; + } + } + else actor->ClearPath(); + } + // // chase towards player // @@ -2656,7 +2774,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi // chase towards player if ((--actor->movecount < 0 && !(flags & CHF_NORANDOMTURN)) || (!P_SmartMove(actor) && !(flags & CHF_STOPIFBLOCKED))) { - P_NewChaseDir(actor); + DoNewChaseDir(actor, pnode); } // if the move was illegal, reset it // (copied from A_SerpentChase - it applies to everything with CANTLEAVEFLOORPIC!) @@ -2672,7 +2790,7 @@ void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missi } } if (!(flags & CHF_STOPIFBLOCKED)) - P_NewChaseDir(actor); + DoNewChaseDir(actor, pnode); } } else if (dontmove && actor->movecount > 0) actor->movecount--; @@ -3115,7 +3233,7 @@ DEFINE_ACTION_FUNCTION(AActor, A_Pain) PARAM_SELF_PROLOGUE(AActor); // [RH] Vary player pain sounds depending on health (ala Quake2) - if (self->player && self->player->morphTics == 0) + if (self->player && self->alternative == nullptr) { const char *pain_amount; FSoundID sfx_id = NO_SOUND; diff --git a/src/playsim/p_interaction.cpp b/src/playsim/p_interaction.cpp index 88326acb5..86416f0c3 100644 --- a/src/playsim/p_interaction.cpp +++ b/src/playsim/p_interaction.cpp @@ -313,36 +313,54 @@ EXTERN_CVAR (Int, fraglimit) void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOfDeath) { - // Handle possible unmorph on death bool wasgibbed = (health < GetGibHealth()); + // Check to see if unmorph Actors need to be killed as well. Originally this was always + // called but that puts an unnecessary burden on the modder to determine whether it's + // a valid call or not. + if (alternative != nullptr && !(flags & MF_UNMORPHED)) { IFVIRTUAL(AActor, MorphedDeath) { - AActor *realthis = NULL; - int realstyle = 0; - int realhealth = 0; + // Return values are no longer used to ensure things stay properly managed. + AActor* const realMo = alternative; + int morphStyle = 0; VMValue params[] = { this }; - VMReturn returns[3]; - returns[0].PointerAt((void**)&realthis); - returns[1].IntAt(&realstyle); - returns[2].IntAt(&realhealth); - VMCall(func, params, 1, returns, 3); - if (realthis && !(realstyle & MORPH_UNDOBYDEATHSAVES)) + { + IFVM(Actor, GetMorphStyle) + { + VMReturn ret[] = { &morphStyle }; + VMCall(func, params, 1, ret, 1); + } + } + + VMCall(func, params, 1, nullptr, 0); + + // Kill the dummy Actor if it didn't unmorph, otherwise checking the morph flags. Player pawns need + // to stay, otherwise they won't respawn correctly. + if (realMo != nullptr && !(realMo->flags6 & MF6_KILLED) + && ((alternative != nullptr && player == nullptr) || (alternative == nullptr && !(morphStyle & MORPH_UNDOBYDEATHSAVES)))) { if (wasgibbed) { - int realgibhealth = realthis->GetGibHealth(); - if (realthis->health >= realgibhealth) - { - realthis->health = realgibhealth - 1; // if morphed was gibbed, so must original be (where allowed)l - } + const int realGibHealth = realMo->GetGibHealth(); + if (realMo->health >= realGibHealth) + realMo->health = realGibHealth - 1; // If morphed was gibbed, so must original be (where allowed). + } + else if (realMo->health > 0) + { + realMo->health = 0; } - realthis->CallDie(source, inflictor, dmgflags, MeansOfDeath); - } + // Pass appropriate damage information along when it's confirmed to die. + realMo->DamageTypeReceived = DamageTypeReceived; + realMo->DamageType = DamageType; + realMo->special1 = special1; + + realMo->CallDie(source, inflictor, dmgflags, MeansOfDeath); + } } } @@ -458,7 +476,7 @@ void AActor::Die (AActor *source, AActor *inflictor, int dmgflags, FName MeansOf ++source->player->spreecount; } - if (source->player->morphTics) + if (source->alternative != nullptr) { // Make a super chicken source->GiveInventoryType (PClass::FindActor(NAME_PowerWeaponLevel2)); } @@ -1329,7 +1347,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da if (damage >= player->health && !telefragDamage && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) - && !player->morphTics) + && target->alternative == nullptr) { // Try to use some inventory health P_AutoUseHealth (player, damage - player->health + 1); } @@ -1463,7 +1481,7 @@ static int DamageMobj (AActor *target, AActor *inflictor, AActor *source, int da // check for special fire damage or ice damage deaths if (mod == NAME_Fire) { - if (player && !player->morphTics) + if (player && target->alternative == nullptr) { // Check for flame death if (!inflictor || ((target->health > -50) && (damage > 25)) || @@ -1797,7 +1815,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain } if (damage >= player->health && (G_SkillProperty(SKILLP_AutoUseHealth) || deathmatch) - && !player->morphTics) + && target->alternative == nullptr) { // Try to use some inventory health P_AutoUseHealth(player, damage - player->health+1); } @@ -1827,7 +1845,7 @@ void P_PoisonDamage (player_t *player, AActor *source, int damage, bool playPain else { target->special1 = damage; - if (player && !player->morphTics) + if (player && target->alternative == nullptr) { // Check for flame death if ((player->poisontype == NAME_Fire) && (target->health > -50) && (damage > 25)) { diff --git a/src/playsim/p_local.h b/src/playsim/p_local.h index 468a19d8c..ea40d386a 100644 --- a/src/playsim/p_local.h +++ b/src/playsim/p_local.h @@ -393,11 +393,8 @@ bool P_CheckMissileSpawn(AActor *missile, double maxdist); void P_PlaySpawnSound(AActor *missile, AActor *spawner); -// [RH] Position the chasecam -void P_AimCamera (AActor *t1, DVector3 &, DAngle &, sector_t *&sec, bool &unlinked); - -// [MC] Aiming for ViewPos -void P_AdjustViewPos(AActor *t1, DVector3 orig, DVector3 &, sector_t *&sec, bool &unlinked, DViewPosition *VP, FRenderViewpoint *view); +// [RH] Position the cam's view offsets. +void R_OffsetView(FRenderViewpoint& viewPoint, const DVector3& dir, const double distance); // [RH] Means of death diff --git a/src/playsim/p_map.cpp b/src/playsim/p_map.cpp index 572ec6cc4..e3c0e16f5 100644 --- a/src/playsim/p_map.cpp +++ b/src/playsim/p_map.cpp @@ -592,7 +592,7 @@ bool P_TeleportMove(AActor* thing, const DVector3 &pos, bool telefrag, bool modi thing->CheckSectorTransition(oldsec); } } - + thing->ClearPath(); return true; } @@ -2536,8 +2536,8 @@ bool P_TryMove(AActor *thing, const DVector2 &pos, // Check for crossed portals - bool portalcrossed; - portalcrossed = false; + bool portalcrossed, nodecall; + nodecall = portalcrossed = false; while (true) { @@ -2582,6 +2582,7 @@ bool P_TryMove(AActor *thing, const DVector2 &pos, thing->Prev += port->mDisplacement; thing->LinkToWorld(&ctx); P_FindFloorCeiling(thing); + thing->ClearPath(); portalcrossed = true; tm.portalstep = false; tm.pos += port->mDisplacement; @@ -2611,7 +2612,8 @@ bool P_TryMove(AActor *thing, const DVector2 &pos, P_TranslatePortalVXVY(ld, thing->Vel.X, thing->Vel.Y); P_TranslatePortalAngle(ld, thing->Angles.Yaw); thing->LinkToWorld(&ctx); - P_FindFloorCeiling(thing); + P_FindFloorCeiling(thing); + thing->ClearPath(); thing->ClearInterpolation(); portalcrossed = true; tm.portalstep = false; @@ -2769,7 +2771,7 @@ bool P_TryMove(AActor *thing, const DVector2 &pos, thing->Sector = thing->Level->PointInSector(thing->Pos()); thing->PrevPortalGroup = thing->Sector->PortalGroup; thing->LinkToWorld(&ctx); - + thing->ClearPath(); P_FindFloorCeiling(thing); } @@ -5573,78 +5575,39 @@ void P_RailAttack(FRailParams *p) CVAR(Float, chase_height, -8.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) CVAR(Float, chase_dist, 90.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -void P_AimCamera(AActor *t1, DVector3 &campos, DAngle &camangle, sector_t *&CameraSector, bool &unlinked) +void R_OffsetView(FRenderViewpoint& viewPoint, const DVector3& dir, const double distance) { - double distance = clamp(chase_dist, 0, 30000); - DAngle angle = t1->Angles.Yaw - DAngle::fromDeg(180); - DAngle pitch = t1->Angles.Pitch; - FTraceResults trace; - DVector3 vvec; - double sz; - - double pc = pitch.Cos(); - - vvec = { pc * angle.Cos(), pc * angle.Sin(), pitch.Sin() }; - sz = t1->Top() - t1->Floorclip + clamp(chase_height, -1000, 1000); - - if (Trace(t1->PosAtZ(sz), t1->Sector, vvec, distance, 0, 0, NULL, trace) && - trace.Distance > 10) + const DAngle baseYaw = dir.Angle(); + FTraceResults trace = {}; + if (Trace(viewPoint.Pos, viewPoint.sector, dir, distance, 0u, 0u, nullptr, trace)) { - // Position camera slightly in front of hit thing - campos = t1->PosAtZ(sz) + vvec *(trace.Distance - 5); + viewPoint.Pos = trace.HitPos - trace.HitVector * min(5.0, trace.Distance); + viewPoint.sector = viewPoint.ViewLevel->PointInRenderSubsector(viewPoint.Pos)->sector; } else { - campos = trace.HitPos - trace.HitVector * 1/256.; + viewPoint.Pos = trace.HitPos; + viewPoint.sector = trace.Sector; } - CameraSector = trace.Sector; - unlinked = trace.unlinked; - camangle = trace.SrcAngleFromTarget - DAngle::fromDeg(180.); -} -struct ViewPosPortal -{ - int counter; -}; - -static ETraceStatus VPos_CheckPortal(FTraceResults &res, void *userdata) -{ - //[MC] Mirror how third person works. - ViewPosPortal *pc = (ViewPosPortal *)userdata; - - if (res.HitType == TRACE_CrossingPortal) + viewPoint.Angles.Yaw += deltaangle(baseYaw, trace.SrcAngleFromTarget); + // TODO: Why does this even need to be done? Please fix tracers already. + if (dir.Z < 0.0) { - res.HitType = TRACE_HitNone; // Needed to force the trace to continue appropriately. - pc->counter++; - return TRACE_Skip; + while (!viewPoint.sector->PortalBlocksMovement(sector_t::floor) && viewPoint.Pos.Z < viewPoint.sector->GetPortalPlaneZ(sector_t::floor)) + { + viewPoint.Pos += viewPoint.sector->GetPortalDisplacement(sector_t::floor); + viewPoint.sector = viewPoint.sector->GetPortal(sector_t::floor)->mDestination; + } } - if (res.HitType == TRACE_HitActor) + else if (dir.Z > 0.0) { - return TRACE_Skip; + while (!viewPoint.sector->PortalBlocksMovement(sector_t::ceiling) && viewPoint.Pos.Z > viewPoint.sector->GetPortalPlaneZ(sector_t::ceiling)) + { + viewPoint.Pos += viewPoint.sector->GetPortalDisplacement(sector_t::ceiling); + viewPoint.sector = viewPoint.sector->GetPortal(sector_t::ceiling)->mDestination; + } } - return TRACE_Stop; -} - -// [MC] Used for ViewPos. Uses code borrowed from P_AimCamera. -void P_AdjustViewPos(AActor *t1, DVector3 orig, DVector3 &campos, sector_t *&CameraSector, bool &unlinked, DViewPosition *VP, FRenderViewpoint *view) -{ - FTraceResults trace; - ViewPosPortal pc; - pc.counter = 0; - const DVector3 vvec = campos - orig; - const double distance = vvec.Length(); - - // Trace handles all of the portal crossing, which is why there is no usage of Vec#Offset(Z). - if (Trace(orig, CameraSector, vvec.Unit(), distance, 0, 0, t1, trace, TRACE_ReportPortals, VPos_CheckPortal, &pc) && - trace.Distance > 5) - campos = orig + vvec.Unit() * (trace.Distance - 5); - else - campos = trace.HitPos - trace.HitVector * 1 / 256.; - - - if (pc.counter > 2) view->noviewer = true; - CameraSector = trace.Sector; - unlinked = trace.unlinked; } //========================================================================== diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 5474fdb3e..0b78d0b90 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -100,6 +100,7 @@ #include "a_dynlight.h" #include "fragglescript/t_fs.h" #include "shadowinlines.h" +#include "d_net.h" // MACROS ------------------------------------------------------------------ @@ -178,6 +179,23 @@ IMPLEMENT_POINTERS_START(AActor) IMPLEMENT_POINTER(boneComponentData) IMPLEMENT_POINTERS_END +//========================================================================== +// +// Make sure Actors can never have their networking disabled. +// +//========================================================================== + +void AActor::EnableNetworking(const bool enable) +{ + if (!enable) + { + ThrowAbortException(X_OTHER, "Cannot disable networking on Actors. Consider a Thinker instead."); + return; + } + + Super::EnableNetworking(true); +} + //========================================================================== // // AActor :: Serialize @@ -817,7 +835,7 @@ int P_GetRealMaxHealth(AActor *actor, int max) { max = actor->GetMaxHealth(true); // [MH] First step in predictable generic morph effects - if (player->morphTics) + if (actor->alternative != nullptr) { if (player->MorphStyle & MORPH_FULLHEALTH) { @@ -839,7 +857,7 @@ int P_GetRealMaxHealth(AActor *actor, int max) else { // Bonus health should be added on top of the item's limit. - if (player->morphTics == 0 || (player->MorphStyle & MORPH_ADDSTAMINA)) + if (actor->alternative == nullptr || (player->MorphStyle & MORPH_ADDSTAMINA)) { max += actor->IntVar(NAME_BonusHealth); } @@ -970,6 +988,44 @@ DEFINE_ACTION_FUNCTION(AActor, CheckLocalView) ACTION_RETURN_BOOL(self->CheckLocalView()); } +void AActor::DisableLocalRendering(const unsigned int pNum, const bool disable) +{ + if (pNum == consoleplayer) + NoLocalRender = disable; +} + +static void DisableLocalRendering(AActor* const self, const unsigned int pNum, const int disable) +{ + self->DisableLocalRendering(pNum, disable); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, DisableLocalRendering, DisableLocalRendering) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_UINT(pNum); + PARAM_INT(disable); + + DisableLocalRendering(self, pNum, disable); + return 0; +} + +bool AActor::ShouldRenderLocally() const +{ + return !NoLocalRender; +} + +static int ShouldRenderLocally(const AActor* const self) +{ + return self->ShouldRenderLocally(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, ShouldRenderLocally, ShouldRenderLocally) +{ + PARAM_SELF_PROLOGUE(AActor); + + ACTION_RETURN_INT(ShouldRenderLocally(self)); +} + //============================================================================ // // AActor :: IsInsideVisibleAngles @@ -1049,6 +1105,9 @@ bool AActor::IsVisibleToPlayer() const // [BB] Safety check. This should never be NULL. Nevertheless, we return true to leave the default ZDoom behavior unaltered. if (p == nullptr || p->camera == nullptr ) return true; + + if (!ShouldRenderLocally()) + return false; if (VisibleToTeam != 0 && teamplay && (signed)(VisibleToTeam-1) != p->userinfo.GetTeam() ) @@ -3454,22 +3513,16 @@ void AActor::SetPitch(DAngle p, int fflags) { if (player != nullptr) { - const bool mustLerp = !P_NoInterpolation(player, this); - - if ((fflags & SPF_INTERPOLATE) || ((fflags & SPF_SCALEDNOLERP) && mustLerp)) + if (fflags & SPF_SCALEDNOLERP) { - Angles.Pitch = p; - player->cheats |= CF_INTERPVIEW; - } - else if ((fflags & SPF_SCALEDNOLERP) && !mustLerp) - { - player->angleTargets.Pitch = deltaangle(Angles.Pitch, p); - player->angleAppliedAmounts.Pitch = nullAngle; + player->angleOffsetTargets.Pitch = deltaangle(Angles.Pitch, p); player->cheats |= CF_SCALEDNOLERP; } else { Angles.Pitch = p; + if (fflags & SPF_INTERPOLATE) + player->cheats |= CF_INTERPVIEW; } } else @@ -3486,22 +3539,16 @@ void AActor::SetAngle(DAngle ang, int fflags) { if (player != nullptr) { - const bool mustLerp = !P_NoInterpolation(player, this); - - if ((fflags & SPF_INTERPOLATE) || ((fflags & SPF_SCALEDNOLERP) && mustLerp)) + if (fflags & SPF_SCALEDNOLERP) { - Angles.Yaw = ang; - player->cheats |= CF_INTERPVIEW; - } - else if ((fflags & SPF_SCALEDNOLERP) && !mustLerp) - { - player->angleTargets.Yaw = deltaangle(Angles.Yaw, ang); - player->angleAppliedAmounts.Yaw = nullAngle; + player->angleOffsetTargets.Yaw = deltaangle(Angles.Yaw, ang); player->cheats |= CF_SCALEDNOLERP; } else { Angles.Yaw = ang; + if (fflags & SPF_INTERPOLATE) + player->cheats |= CF_INTERPVIEW; } } else @@ -3518,22 +3565,16 @@ void AActor::SetRoll(DAngle r, int fflags) { if (player != nullptr) { - const bool mustLerp = !P_NoInterpolation(player, this); - - if ((fflags & SPF_INTERPOLATE) || ((fflags & SPF_SCALEDNOLERP) && mustLerp)) + if (fflags & SPF_SCALEDNOLERP) { - Angles.Roll = r; - player->cheats |= CF_INTERPVIEW; - } - else if ((fflags & SPF_SCALEDNOLERP) && !mustLerp) - { - player->angleTargets.Roll = deltaangle(Angles.Roll, r); - player->angleAppliedAmounts.Roll = nullAngle; + player->angleOffsetTargets.Roll = deltaangle(Angles.Roll, r); player->cheats |= CF_SCALEDNOLERP; } else { Angles.Roll = r; + if (fflags & SPF_INTERPOLATE) + player->cheats |= CF_INTERPVIEW; } } else @@ -3726,6 +3767,22 @@ void AActor::Tick () static const uint8_t HereticScrollDirs[4] = { 6, 9, 1, 4 }; static const uint8_t HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 }; + // 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) + { + int res = false; + IFVIRTUAL(AActor, CheckUnmorph) + { + VMValue params[] = { this }; + VMReturn ret[] = { &res }; + VMCall(func, params, 1, ret, 1); + } + + if (res) + return; + } + if (freezetics > 0) { freezetics--; @@ -4752,6 +4809,7 @@ AActor *AActor::StaticSpawn(FLevelLocals *Level, PClassActor *type, const DVecto AActor *actor; actor = static_cast(Level->CreateThinker(type)); + actor->EnableNetworking(true); ConstructActor(actor, pos, SpawningMapThing); return actor; @@ -5020,6 +5078,16 @@ void AActor::CallDeactivate(AActor *activator) void AActor::OnDestroy () { + // If the Actor is leaving behind a premorph Actor, make sure it gets cleaned up as + // well so it's not stuck in the map. + if (alternative != nullptr && !(flags & MF_UNMORPHED)) + { + alternative->ClearCounters(); + alternative->alternative = nullptr; + alternative->Destroy(); + alternative = nullptr; + } + // [ZZ] call destroy event hook. // note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the hook. // but you can't really do that without utterly breaking the game, so it's ok. @@ -5141,59 +5209,182 @@ extern bool demonew; //========================================================================== // -// This once was the main method for pointer cleanup, but -// nowadays its only use is swapping out PlayerPawns. -// This requires pointer fixing throughout all objects and a few -// global variables, but it only needs to look at pointers that -// can point to a player. +// This function is dangerous and only designed for swapping player pawns +// over to their new ones upon changing levels or respawning. It SHOULD NOT be +// used for anything else! Do not export this functionality as it's +// meant strictly for internal usage. Only swap pointers if the thing being swapped +// to is a type of the thing being swapped from. // //========================================================================== -void StaticPointerSubstitution(AActor* old, AActor* notOld) +void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer) { - DObject* probe; - size_t changed = 0; - int i; - - if (old == nullptr) return; - - // This is only allowed to replace players or swap out morphed monsters - if (!old->IsKindOf(NAME_PlayerPawn) || (notOld != nullptr && !notOld->IsKindOf(NAME_PlayerPawn))) + if (oldPlayer == nullptr || newPlayer == nullptr || oldPlayer == newPlayer + || !oldPlayer->IsKindOf(NAME_PlayerPawn) || !newPlayer->IsKindOf(NAME_PlayerPawn)) { - if (notOld == nullptr) return; - if (!old->IsKindOf(NAME_MorphedMonster) && !notOld->IsKindOf(NAME_MorphedMonster)) return; - } - // Go through all objects. - i = 0; DObject* last = 0; - for (probe = GC::Root; probe != NULL; probe = probe->ObjNext) - { - i++; - changed += probe->PointerSubstitution(old, notOld); - last = probe; + return; } - // Go through players. - for (i = 0; i < MAXPLAYERS; i++) + // Go through player infos. + for (int i = 0; i < MAXPLAYERS; ++i) { - if (playeringame[i]) - { - AActor* replacement = notOld; - auto& p = players[i]; + if (!oldPlayer->Level->PlayerInGame(i)) + continue; - if (p.mo == old) p.mo = replacement, changed++; - if (p.poisoner.ForceGet() == old) p.poisoner = replacement, changed++; - if (p.attacker.ForceGet() == old) p.attacker = replacement, changed++; - if (p.camera.ForceGet() == old) p.camera = replacement, changed++; - if (p.ConversationNPC.ForceGet() == old) p.ConversationNPC = replacement, changed++; - if (p.ConversationPC.ForceGet() == old) p.ConversationPC = replacement, changed++; - } + auto p = oldPlayer->Level->Players[i]; + + if (p->mo == oldPlayer) + p->mo = newPlayer; + if (p->poisoner == oldPlayer) + p->poisoner = newPlayer; + if (p->attacker == oldPlayer) + p->attacker = newPlayer; + if (p->camera == oldPlayer) + p->camera = newPlayer; + if (p->ConversationNPC == oldPlayer) + p->ConversationNPC = newPlayer; + if (p->ConversationPC == oldPlayer) + p->ConversationPC = newPlayer; } - // Go through sectors. Only the level this actor belongs to is relevant. - for (auto& sec : old->Level->sectors) + // Go through sectors. + for (auto& sec : oldPlayer->Level->sectors) { - if (sec.SoundTarget == old) sec.SoundTarget = notOld; + if (sec.SoundTarget == oldPlayer) + sec.SoundTarget = newPlayer; } + + // Update all the remaining object pointers. This is dangerous but needed to ensure + // everything functions correctly when respawning or changing levels. + for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext) + probe->PointerSubstitution(oldPlayer, newPlayer); +} + +//========================================================================== +// +// This function is much safer than PlayerPointerSubstition as it only truly +// swaps a few safe pointers. This has some extra barriers to it to allow +// Actors to freely morph into other Actors which is its main usage. +// Previously this used raw pointer substitutions but that's far too +// volatile to use with modder-provided information. It also allows morphing +// to be more extendable from ZScript. +// +//========================================================================== + +int MorphPointerSubstitution(AActor* from, AActor* to) +{ + // Special care is taken here to make sure things marked as a dummy Actor for a morphed thing aren't + // allowed to be changed into other things. Anything being morphed into that's considered a player + // is automatically out of the question to ensure modders aren't swapping clients around. + if (from == nullptr || to == nullptr || from == to || to->player != nullptr + || (from->flags & MF_UNMORPHED) // Another thing's dummy Actor, unmorphing the wrong way, etc. + || (from->alternative == nullptr && to->alternative != nullptr) // Morphing into something that's already morphed. + || (from->alternative != nullptr && from->alternative != to)) // Only allow something morphed to unmorph. + { + return false; + } + + const bool toIsPlayer = to->IsKindOf(NAME_PlayerPawn); + if (from->IsKindOf(NAME_PlayerPawn)) + { + // Players are only allowed to turn into other valid player pawns. For + // valid pawns, make sure an actual player is changing into an empty one. + // Voodoo dolls aren't allowed to morph since that should be passed to + // the main player directly. + if (!toIsPlayer || from->player == nullptr || from->player->mo != from) + return false; + } + else if (toIsPlayer || from->player != nullptr + || (from->IsKindOf(NAME_Inventory) && from->PointerVar(NAME_Owner) != nullptr) + || (to->IsKindOf(NAME_Inventory) && to->PointerVar(NAME_Owner) != nullptr)) + { + // Only allow items to be swapped around if they aren't currently owned. Also prevent non-players from + // turning into fake players. + return false; + } + + // 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. + IFVM(Actor, ObtainInventory) + { + VMValue params[] = { to, from }; + VMCall(func, params, 2, nullptr, 0); + } + + // Only change some gameplay-related pointers that we know we can safely swap to whatever + // new Actor class is present. + AActor* mo = nullptr; + auto it = from->Level->GetThinkerIterator(); + while ((mo = it.Next()) != nullptr) + { + if (mo->target == from) + mo->target = to; + if (mo->tracer == from) + mo->tracer = to; + if (mo->master == from) + mo->master = to; + if (mo->goal == from) + mo->goal = to; + if (mo->lastenemy == from) + mo->lastenemy = to; + if (mo->LastHeard == from) + mo->LastHeard = to; + if (mo->LastLookActor == from) + mo->LastLookActor = to; + if (mo->Poisoner == from) + mo->Poisoner = to; + } + + // Go through player infos. + for (int i = 0; i < MAXPLAYERS; ++i) + { + if (!from->Level->PlayerInGame(i)) + continue; + + auto p = from->Level->Players[i]; + + if (p->mo == from) + p->mo = to; + if (p->poisoner == from) + p->poisoner = to; + if (p->attacker == from) + p->attacker = to; + if (p->camera == from) + p->camera = to; + if (p->ConversationNPC == from) + p->ConversationNPC = to; + if (p->ConversationPC == from) + p->ConversationPC = to; + } + + // Go through sectors. + for (auto& sec : from->Level->sectors) + { + if (sec.SoundTarget == from) + sec.SoundTarget = to; + } + + // Remaining maintenance related to morphing. + if (from->player != nullptr) + { + to->player = from->player; + from->player = nullptr; + } + + if (from->alternative != nullptr) + { + to->flags &= ~MF_UNMORPHED; + to->alternative = from->alternative = nullptr; + } + else + { + from->flags |= MF_UNMORPHED; + from->alternative = to; + to->alternative = from; + } + + return true; } void FLevelLocals::PlayerSpawnPickClass (int playernum) @@ -5466,7 +5657,7 @@ AActor *FLevelLocals::SpawnPlayer (FPlayerStart *mthing, int playernum, int flag if (sec.SoundTarget == oldactor) sec.SoundTarget = nullptr; } - StaticPointerSubstitution (oldactor, p->mo); + PlayerPointerSubstitution (oldactor, p->mo); localEventManager->PlayerRespawned(PlayerNum(p)); Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true); @@ -5701,15 +5892,26 @@ AActor *FLevelLocals::SpawnMapThing (FMapThing *mthing, int position) const AActor *info = GetDefaultByType (i); - // don't spawn keycards and players in deathmatch - if (deathmatch && info->flags & MF_NOTDMATCH) - return NULL; + // Don't spawn keycards and players in deathmatch. + if (deathmatch && (info->flags & MF_NOTDMATCH)) + return nullptr; - // don't spawn extra things in coop if so desired - if (multiplayer && !deathmatch && (dmflags2 & DF2_NO_COOP_THING_SPAWN)) + // Don't spawn extra things in co-op if desired. + if (multiplayer && !deathmatch) { - if ((mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH) - return NULL; + // Don't spawn DM-only things in co-op. + if ((dmflags2 & DF2_NO_COOP_THING_SPAWN) && (mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH) + return nullptr; + // Having co-op only functionality is a bit odd, but you never know. + if (!mthing->special && !mthing->thingid && (mthing->flags & (MTF_COOPERATIVE | MTF_SINGLE)) == MTF_COOPERATIVE) + { + // Don't spawn co-op only things in general. + if (dmflags3 & DF3_NO_COOP_ONLY_THINGS) + return nullptr; + // Don't spawn co-op only items. + if ((dmflags3 & DF3_NO_COOP_ONLY_ITEMS) && i->IsDescendantOf(NAME_Inventory)) + return nullptr; + } } // [RH] don't spawn extra weapons in coop if so desired diff --git a/src/playsim/p_user.cpp b/src/playsim/p_user.cpp index a4e2c6485..a1888d5c1 100644 --- a/src/playsim/p_user.cpp +++ b/src/playsim/p_user.cpp @@ -693,7 +693,7 @@ bool player_t::Resurrect() P_BringUpWeapon(this); } - if (morphTics) + if (mo->alternative != nullptr) { P_UnmorphActor(mo, mo); } @@ -1172,7 +1172,7 @@ void P_CheckEnvironment(player_t *player) P_PlayerOnSpecialFlat(player, P_GetThingFloorType(player->mo)); } if (player->mo->Vel.Z <= -player->mo->FloatVar(NAME_FallingScreamMinSpeed) && - player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && !player->morphTics && + player->mo->Vel.Z >= -player->mo->FloatVar(NAME_FallingScreamMaxSpeed) && player->mo->alternative == nullptr && player->mo->waterlevel == 0) { auto id = S_FindSkinnedSound(player->mo, S_FindSound("*falling")); @@ -1239,6 +1239,17 @@ void P_PlayerThink (player_t *player) I_Error ("No player %td start\n", player - players + 1); } + for (unsigned int i = 0u; i < 3u; ++i) + { + if (fabs(player->angleOffsetTargets[i].Degrees()) >= EQUAL_EPSILON) + { + player->mo->Angles[i] += player->angleOffsetTargets[i]; + player->mo->PrevAngles[i] = player->mo->Angles[i]; + } + + player->angleOffsetTargets[i] = nullAngle; + } + if (player->SubtitleCounter > 0) { player->SubtitleCounter--; diff --git a/src/playsim/shadowinlines.h b/src/playsim/shadowinlines.h index fce131f7b..60caa9506 100644 --- a/src/playsim/shadowinlines.h +++ b/src/playsim/shadowinlines.h @@ -25,14 +25,14 @@ inline FRandom pr_shadowaimz("VerticalShadowAim"); // //========================================================================== -struct ShadowCheckData +struct SightCheckData { AActor* HitShadow; }; -static ETraceStatus CheckForShadowBlockers(FTraceResults& res, void* userdata) +inline ETraceStatus CheckForShadowBlockers(FTraceResults& res, void* userdata) { - ShadowCheckData* output = (ShadowCheckData*)userdata; + SightCheckData* output = (SightCheckData*)userdata; if (res.HitType == TRACE_HitActor && res.Actor && (res.Actor->flags9 & MF9_SHADOWBLOCK)) { output->HitShadow = res.Actor; @@ -48,10 +48,10 @@ static ETraceStatus CheckForShadowBlockers(FTraceResults& res, void* userdata) } // [inkoalawetrust] Check if an MF9_SHADOWBLOCK actor is standing between t1 and t2. -inline bool P_CheckForShadowBlock(AActor* t1, AActor* t2, DVector3 pos, double& penaltyFactor) +inline AActor* P_CheckForShadowBlock(AActor* t1, AActor* t2, DVector3 pos, double& penaltyFactor) { FTraceResults result; - ShadowCheckData ShadowCheck; + SightCheckData ShadowCheck; ShadowCheck.HitShadow = nullptr; DVector3 dir; double dist; @@ -78,21 +78,21 @@ inline bool P_CheckForShadowBlock(AActor* t1, AActor* t2, DVector3 pos, double& return ShadowCheck.HitShadow; } -inline bool AffectedByShadows(AActor* self, AActor* other) +inline bool AffectedByShadows(AActor* self) { return (!(self->flags6 & MF6_SEEINVISIBLE) || self->flags9 & MF9_SHADOWAIM); } -inline bool CheckForShadows(AActor* self, AActor* other, DVector3 pos, double& penaltyFactor) +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)); + return ((other && (other->flags & MF_SHADOW)) || (self->flags9 & MF9_DOSHADOWBLOCK)) ? P_CheckForShadowBlock(self, other, pos, penaltyFactor) : nullptr; } -inline bool PerformShadowChecks(AActor* self, AActor* other, DVector3 pos, double& penaltyFactor) +inline AActor* PerformShadowChecks(AActor* self, AActor* other, DVector3 pos, double& penaltyFactor) { - if (other != nullptr) penaltyFactor = other->ShadowPenaltyFactor; //Use target penalty factor by default. - else penaltyFactor = 1.0; - return (AffectedByShadows(self, other) && CheckForShadows(self, other, pos, penaltyFactor)); + if (other != nullptr) penaltyFactor = other->ShadowPenaltyFactor; //Use target penalty factor by default. + else penaltyFactor = 1.0; + return AffectedByShadows(self) ? CheckForShadows(self, other, pos, penaltyFactor) : nullptr; } //========================================================================== diff --git a/src/rendering/hwrenderer/scene/hw_sprites.cpp b/src/rendering/hwrenderer/scene/hw_sprites.cpp index 578001c91..12fbf1ce1 100644 --- a/src/rendering/hwrenderer/scene/hw_sprites.cpp +++ b/src/rendering/hwrenderer/scene/hw_sprites.cpp @@ -391,19 +391,32 @@ void HWSprite::DrawSprite(HWDrawInfo *di, FRenderState &state, bool translucent) // //========================================================================== -bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp) +void HandleSpriteOffsets(Matrix3x4 *mat, const FRotator *HW, FVector2 *offset, bool XYBillboard) { - const auto &HWAngles = di->Viewpoint.HWAngles; + FAngle zero = FAngle::fromDeg(0); + FAngle pitch = (XYBillboard) ? HW->Pitch : zero; + FAngle yaw = FAngle::fromDeg(270.) - HW->Yaw; + + FQuaternion quat = FQuaternion::FromAngles(yaw, pitch, zero); + FVector3 sideVec = quat * FVector3(0, 1, 0); + FVector3 upVec = quat * FVector3(0, 0, 1); + FVector3 res = sideVec * offset->X + upVec * offset->Y; + mat->Translate(res.X, res.Z, res.Y); +} + +bool HWSprite::CalculateVertices(HWDrawInfo* di, FVector3* v, DVector3* vp) +{ + FVector3 center = FVector3((x1 + x2) * 0.5, (y1 + y2) * 0.5, (z1 + z2) * 0.5); + const auto& HWAngles = di->Viewpoint.HWAngles; + Matrix3x4 mat; if (actor != nullptr && (actor->renderflags & RF_SPRITETYPEMASK) == RF_FLATSPRITE) { - Matrix3x4 mat; - mat.MakeIdentity(); - // [MC] Rotate around the center or offsets given to the sprites. // Counteract any existing rotations, then rotate the angle. // Tilt the actor up or down based on pitch (increase 'somersaults' forward). // Then counteract the roll and DO A BARREL ROLL. + mat.MakeIdentity(); FAngle pitch = FAngle::fromDeg(-Angles.Pitch.Degrees()); pitch.Normalized180(); @@ -413,12 +426,9 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp) if (actor->renderflags & RF_ROLLCENTER) { - float cx = (x1 + x2) * 0.5; - float cy = (y1 + y2) * 0.5; - - mat.Translate(cx - x, 0, cy - y); + mat.Translate(center.X - x, 0, center.Y - y); mat.Rotate(0, 1, 0, - Angles.Roll.Degrees()); - mat.Translate(-cx, -z, -cy); + mat.Translate(-center.X, -z, -center.Y); } else { @@ -444,51 +454,45 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp) // [Nash] has +ROLLSPRITE const bool drawRollSpriteActor = (actor != nullptr && actor->renderflags & RF_ROLLSPRITE); - const bool drawRollParticle = (particle != nullptr && particle->flags & SPF_ROLL); - + const bool doRoll = (drawRollSpriteActor || drawRollParticle); // [fgsfds] check sprite type mask uint32_t spritetype = (uint32_t)-1; if (actor != nullptr) spritetype = actor->renderflags & RF_SPRITETYPEMASK; // [Nash] is a flat sprite - const bool isFlatSprite = (actor != nullptr) && (spritetype == RF_WALLSPRITE); + const bool isWallSprite = (actor != nullptr) && (spritetype == RF_WALLSPRITE); const bool useOffsets = (actor != nullptr) && !(actor->renderflags & RF_ROLLCENTER); + FVector2 offset = FVector2( offx, offy ); + + // Account for +ROLLCENTER flag. Takes the embedded image offsets and adds them in with SpriteOffsets. + if (drawRollSpriteActor && useOffsets) + { + offset.X += center.X - x; + offset.Y += center.Z - z; + } + // [Nash] check for special sprite drawing modes - if (drawWithXYBillboard || drawBillboardFacingCamera || drawRollSpriteActor || drawRollParticle || isFlatSprite) + if (drawWithXYBillboard || isWallSprite) { // Compute center of sprite - float xcenter = (x1 + x2)*0.5; - float ycenter = (y1 + y2)*0.5; - float zcenter = (z1 + z2)*0.5; - float xx = -xcenter + x; - float zz = -zcenter + z; - float yy = -ycenter + y; - Matrix3x4 mat; mat.MakeIdentity(); - mat.Translate(xcenter, zcenter, ycenter); // move to sprite center + mat.Translate(center.X, center.Z, center.Y); // move to sprite center - // [MC] Sprite offsets. These must be calculated separately in their own matrix, - // otherwise "face sprites" would cause some issues whenever enabled. We don't - // want those calculations here. Credit to PhantomBeta for this. - if (offx || offy) - { - FQuaternion quat = FQuaternion::FromAngles(FAngle::fromDeg(270) - di->Viewpoint.HWAngles.Yaw, di->Viewpoint.HWAngles.Pitch, FAngle::fromDeg(0)); - FVector3 sideVec = quat * FVector3(0, 1, 0); - FVector3 upVec = quat * FVector3(0, 0, 1); - FVector3 res = sideVec * -offx + upVec * -offy; - mat.Translate(res.X, res.Z, res.Y); - } + + // [MC] Sprite offsets. + if (!offset.isZero()) + HandleSpriteOffsets(&mat, &HWAngles, &offset, true); // Order of rotations matters. Perform yaw rotation (Y, face camera) before pitch (X, tilt up/down). - if (drawBillboardFacingCamera && !isFlatSprite) + if (drawBillboardFacingCamera && !isWallSprite) { // [CMB] Rotate relative to camera XY position, not just camera direction, // which is nicer in VR - float xrel = xcenter - vp->X; - float yrel = ycenter - vp->Y; + float xrel = center.X - vp->X; + float yrel = center.Y - vp->Y; float absAngleDeg = atan2(-yrel, xrel) * (180 / M_PI); float counterRotationDeg = 270. - HWAngles.Yaw.Degrees(); // counteracts existing sprite rotation float relAngleDeg = counterRotationDeg + absAngleDeg; @@ -497,32 +501,27 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp) } // [fgsfds] calculate yaw vectors - float rollDegrees = 0; + float rollDegrees = doRoll ? Angles.Roll.Degrees() : 0; float angleRad = (FAngle::fromDeg(270.) - HWAngles.Yaw).Radians(); - if (actor || drawRollParticle) rollDegrees = Angles.Roll.Degrees(); // [fgsfds] Rotate the sprite about the sight vector (roll) - if (spritetype == RF_WALLSPRITE) + if (isWallSprite) { float yawvecX = Angles.Yaw.Cos(); float yawvecY = Angles.Yaw.Sin(); mat.Rotate(0, 1, 0, 0); if (drawRollSpriteActor) { - if (useOffsets) mat.Translate(xx, zz, yy); mat.Rotate(yawvecX, 0, yawvecY, rollDegrees); - if (useOffsets) mat.Translate(-xx, -zz, -yy); } } - else if (drawRollSpriteActor || drawRollParticle) + else if (doRoll) { - if (useOffsets) mat.Translate(xx, zz, yy); if (drawWithXYBillboard) { mat.Rotate(-sin(angleRad), 0, cos(angleRad), -HWAngles.Pitch.Degrees()); } mat.Rotate(cos(angleRad), 0, sin(angleRad), rollDegrees); - if (useOffsets) mat.Translate(-xx, -zz, -yy); } else if (drawWithXYBillboard) { @@ -532,8 +531,8 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp) mat.Rotate(-sin(angleRad), 0, cos(angleRad), -HWAngles.Pitch.Degrees()); } - mat.Translate(-xcenter, -zcenter, -ycenter); // retreat from sprite center - + mat.Translate(-center.X, -center.Z, -center.Y); // retreat from sprite center + v[0] = mat * FVector3(x1, z1, y1); v[1] = mat * FVector3(x2, z1, y2); v[2] = mat * FVector3(x1, z2, y1); @@ -541,10 +540,38 @@ bool HWSprite::CalculateVertices(HWDrawInfo *di, FVector3 *v, DVector3 *vp) } else // traditional "Y" billboard mode { - v[0] = FVector3(x1, z1, y1); - v[1] = FVector3(x2, z1, y2); - v[2] = FVector3(x1, z2, y1); - v[3] = FVector3(x2, z2, y2); + if (doRoll || !offset.isZero()) + { + mat.MakeIdentity(); + + if (!offset.isZero()) + HandleSpriteOffsets(&mat, &HWAngles, &offset, false); + + if (doRoll) + { + // Compute center of sprite + float angleRad = (FAngle::fromDeg(270.) - HWAngles.Yaw).Radians(); + float rollDegrees = Angles.Roll.Degrees(); + + mat.Translate(center.X, center.Z, center.Y); + mat.Rotate(cos(angleRad), 0, sin(angleRad), rollDegrees); + mat.Translate(-center.X, -center.Z, -center.Y); + } + + v[0] = mat * FVector3(x1, z1, y1); + v[1] = mat * FVector3(x2, z1, y2); + v[2] = mat * FVector3(x1, z2, y1); + v[3] = mat * FVector3(x2, z2, y2); + + } + else + { + v[0] = FVector3(x1, z1, y1); + v[1] = FVector3(x2, z1, y2); + v[2] = FVector3(x1, z2, y1); + v[3] = FVector3(x2, z2, y2); + } + } return false; } @@ -831,7 +858,7 @@ void HWSprite::Process(HWDrawInfo *di, FRenderState& state, AActor* thing, secto // Some added checks if the camera actor is not supposed to be seen. It can happen that some portal setup has this actor in view in which case it may not be skipped here if (viewmaster == camera && !vp.showviewer) { - if (vp.noviewer || (viewmaster->player && viewmaster->player->crossingPortal)) return; + if (vp.bForceNoViewer || (viewmaster->player && viewmaster->player->crossingPortal)) return; DVector3 vieworigin = viewmaster->Pos(); if (thruportal == 1) vieworigin += di->Level->Displacements.getOffset(viewmaster->Sector->PortalGroup, sector->PortalGroup); if (fabs(vieworigin.X - vp.ActorPos.X) < 2 && fabs(vieworigin.Y - vp.ActorPos.Y) < 2) return; @@ -992,8 +1019,8 @@ void HWSprite::Process(HWDrawInfo *di, FRenderState& state, AActor* thing, secto if (!tex || !tex->isValid()) return; auto& spi = tex->GetSpritePositioning(type == RF_FACESPRITE); - offx = (float)thing->SpriteOffset.X; - offy = (float)thing->SpriteOffset.Y; + offx = (float)thing->GetSpriteOffset(false); + offy = (float)thing->GetSpriteOffset(true); vt = spi.GetSpriteVT(); vb = spi.GetSpriteVB(); @@ -1299,7 +1326,6 @@ void HWSprite::Process(HWDrawInfo *di, FRenderState& state, AActor* thing, secto { lightlist = nullptr; } - PutSprite(di, state, hw_styleflags != STYLEHW_Solid); rendered_sprites++; } @@ -1512,8 +1538,8 @@ void HWSprite::AdjustVisualThinker(HWDrawInfo* di, DVisualThinker* spr, sector_t y = interp.Y; z = interp.Z; - offx = (float)spr->Offset.X; - offy = (float)spr->Offset.Y; + offx = (float)spr->GetOffset(false); + offy = (float)spr->GetOffset(true); if (spr->PT.flags & SPF_ROLL) Angles.Roll = TAngle::fromDeg(spr->InterpolatedRoll(timefrac)); diff --git a/src/rendering/r_utility.cpp b/src/rendering/r_utility.cpp index 4daf7af7c..09cab0583 100644 --- a/src/rendering/r_utility.cpp +++ b/src/rendering/r_utility.cpp @@ -83,8 +83,10 @@ struct InterpolationViewer DRotator ViewAngles; }; - AActor *ViewActor; - int otic; + AActor* ViewActor; + DVector3 ViewOffset, RelativeViewOffset; // This has to be a separate field since it needs the real-time mouse angles. + DRotator AngleOffsets; + int prevTic; instance Old, New; }; @@ -443,18 +445,18 @@ EXTERN_CVAR (Bool, cl_noprediction) bool P_NoInterpolation(player_t const *player, AActor const *actor) { - return player != NULL && - !(player->cheats & CF_INTERPVIEW) && - player - players == consoleplayer && - actor == player->mo && - !demoplayback && - !(player->cheats & (CF_TOTALLYFROZEN | CF_FROZEN)) && - player->playerstate == PST_LIVE && - player->mo->reactiontime == 0 && - !NoInterpolateView && - !paused && - (!netgame || !cl_noprediction) && - !LocalKeyboardTurner; + return player != nullptr + && !(player->cheats & CF_INTERPVIEW) + && player - players == consoleplayer + && actor == player->mo + && !demoplayback + && !(player->cheats & (CF_TOTALLYFROZEN | CF_FROZEN)) + && player->playerstate == PST_LIVE + && player->mo->reactiontime == 0 + && !NoInterpolateView + && !paused + && (!netgame || !cl_noprediction) + && !LocalKeyboardTurner; } //========================================================================== @@ -463,144 +465,178 @@ bool P_NoInterpolation(player_t const *player, AActor const *actor) // //========================================================================== -void R_InterpolateView (FRenderViewpoint &viewpoint, player_t *player, double Frac, InterpolationViewer *iview) +void R_InterpolateView(FRenderViewpoint& viewPoint, const player_t* const player, const double ticFrac, InterpolationViewer* const iView) { if (NoInterpolateView) { InterpolationPath.Clear(); NoInterpolateView = false; - iview->Old = iview->New; + iView->Old = iView->New; } - auto Level = viewpoint.ViewLevel; - int oldgroup = Level->PointInRenderSubsector(iview->Old.Pos)->sector->PortalGroup; - int newgroup = Level->PointInRenderSubsector(iview->New.Pos)->sector->PortalGroup; - DAngle oviewangle = iview->Old.Angles.Yaw; - DAngle nviewangle = iview->New.Angles.Yaw; + const double inverseTicFrac = 1.0 - ticFrac; + const auto viewLvl = viewPoint.ViewLevel; + + DAngle prevYaw = iView->Old.Angles.Yaw; + DAngle curYaw = iView->New.Angles.Yaw; if (!cl_capfps) { - if ((iview->Old.Pos.X != iview->New.Pos.X || iview->Old.Pos.Y != iview->New.Pos.Y) && InterpolationPath.Size() > 0) + if ((iView->Old.Pos.X != iView->New.Pos.X || iView->Old.Pos.Y != iView->New.Pos.Y) && InterpolationPath.Size() > 0) { - DVector3 view = iview->New.Pos; - // Interpolating through line portals is a messy affair. // What needs be done is to store the portal transitions of the camera actor as waypoints // and then find out on which part of the path the current view lies. - // Needless to say, this doesn't work for chasecam mode or viewpos. - if (!viewpoint.showviewer && !viewpoint.NoPortalPath) + double totalPathLength = 0.0; + double totalZDiff = 0.0; + DAngle totalYawDiff = nullAngle; + DVector3a oldPos = { { iView->Old.Pos.X, iView->Old.Pos.Y, 0.0 }, nullAngle }; + DVector3a newPos = { { iView->New.Pos.X, iView->New.Pos.Y, 0.0 }, nullAngle }; + InterpolationPath.Push(newPos); // Add this to the array to simplify the loops below. + + for (size_t i = 0u; i < InterpolationPath.Size(); i += 2u) { - double pathlen = 0; - double zdiff = 0; - double totalzdiff = 0; - DAngle adiff = nullAngle; - DAngle totaladiff = nullAngle; - double oviewz = iview->Old.Pos.Z; - double nviewz = iview->New.Pos.Z; - DVector3a oldpos = { { iview->Old.Pos.X, iview->Old.Pos.Y, 0 }, nullAngle }; - DVector3a newpos = { { iview->New.Pos.X, iview->New.Pos.Y, 0 }, nullAngle }; - InterpolationPath.Push(newpos); // add this to the array to simplify the loops below - - for (unsigned i = 0; i < InterpolationPath.Size(); i += 2) - { - DVector3a &start = i == 0 ? oldpos : InterpolationPath[i - 1]; - DVector3a &end = InterpolationPath[i]; - pathlen += (end.pos - start.pos).Length(); - totalzdiff += start.pos.Z; - totaladiff += start.angle; - } - double interpolatedlen = Frac * pathlen; - - for (unsigned i = 0; i < InterpolationPath.Size(); i += 2) - { - DVector3a &start = i == 0 ? oldpos : InterpolationPath[i - 1]; - DVector3a &end = InterpolationPath[i]; - double fraglen = (end.pos - start.pos).Length(); - zdiff += start.pos.Z; - adiff += start.angle; - if (fraglen <= interpolatedlen) - { - interpolatedlen -= fraglen; - } - else - { - double fragfrac = interpolatedlen / fraglen; - oviewz += zdiff; - nviewz -= totalzdiff - zdiff; - oviewangle += adiff; - nviewangle -= totaladiff - adiff; - DVector2 viewpos = start.pos.XY() + (fragfrac * (end.pos - start.pos).XY()); - viewpoint.Pos = { viewpos, oviewz + Frac * (nviewz - oviewz) }; - break; - } - } - InterpolationPath.Pop(); - viewpoint.Path[0] = iview->Old.Pos; - viewpoint.Path[1] = viewpoint.Path[0] + (InterpolationPath[0].pos - viewpoint.Path[0]).XY().MakeResize(pathlen); + const DVector3a& start = !i ? oldPos : InterpolationPath[i - 1u]; + const DVector3a& end = InterpolationPath[i]; + totalPathLength += (end.pos - start.pos).Length(); + totalZDiff += start.pos.Z; + totalYawDiff += start.angle; } + + double interpolatedLength = totalPathLength * ticFrac; + double zDiff = 0.0; + DAngle yawDiff = nullAngle; + double prevViewZ = iView->Old.Pos.Z; + double curViewZ = iView->New.Pos.Z; + for (size_t i = 0u; i < InterpolationPath.Size(); i += 2u) + { + const DVector3a& start = !i ? oldPos : InterpolationPath[i - 1u]; + const DVector3a& end = InterpolationPath[i]; + const double fragmentLength = (end.pos - start.pos).Length(); + zDiff += start.pos.Z; + yawDiff += start.angle; + + if (fragmentLength <= interpolatedLength) + { + interpolatedLength -= fragmentLength; + } + else + { + prevViewZ += zDiff; + curViewZ -= totalZDiff - zDiff; + prevYaw += yawDiff; + curYaw -= totalYawDiff - yawDiff; + + const DVector2 viewPos = start.pos.XY() + ((interpolatedLength / fragmentLength) * (end.pos - start.pos).XY()); + viewPoint.Pos = { viewPos, prevViewZ * inverseTicFrac + curViewZ * ticFrac }; + break; + } + } + + InterpolationPath.Pop(); + viewPoint.Path[0] = iView->Old.Pos; + viewPoint.Path[1] = viewPoint.Path[0] + (InterpolationPath[0].pos - viewPoint.Path[0]).XY().MakeResize(totalPathLength); } else { - DVector2 disp = viewpoint.ViewLevel->Displacements.getOffset(oldgroup, newgroup); - viewpoint.Pos = iview->Old.Pos + (iview->New.Pos - iview->Old.Pos - disp) * Frac; - viewpoint.Path[0] = viewpoint.Path[1] = iview->New.Pos; + const int prevPortalGroup = viewLvl->PointInRenderSubsector(iView->Old.Pos)->sector->PortalGroup; + const int curPortalGroup = viewLvl->PointInRenderSubsector(iView->New.Pos)->sector->PortalGroup; + + const DVector2 portalOffset = viewLvl->Displacements.getOffset(prevPortalGroup, curPortalGroup); + viewPoint.Pos = iView->Old.Pos * inverseTicFrac + (iView->New.Pos - portalOffset) * ticFrac; + viewPoint.Path[0] = viewPoint.Path[1] = iView->New.Pos; } } else { - viewpoint.Pos = iview->New.Pos; - viewpoint.Path[0] = viewpoint.Path[1] = iview->New.Pos; - } - if (P_NoInterpolation(player, viewpoint.camera) && - iview->New.Pos.X == viewpoint.camera->X() && - iview->New.Pos.Y == viewpoint.camera->Y()) - { - viewpoint.Angles.Yaw = (nviewangle + DAngle::fromBam(LocalViewAngle)).Normalized180(); - DAngle delta = player->centering ? nullAngle : DAngle::fromBam(LocalViewPitch); - viewpoint.Angles.Pitch = clamp((iview->New.Angles.Pitch - delta).Normalized180(), player->MinPitch, player->MaxPitch); - viewpoint.Angles.Roll = iview->New.Angles.Roll.Normalized180(); - } - else - { - viewpoint.Angles.Pitch = (iview->Old.Angles.Pitch + deltaangle(iview->Old.Angles.Pitch, iview->New.Angles.Pitch) * Frac).Normalized180(); - viewpoint.Angles.Yaw = (oviewangle + deltaangle(oviewangle, nviewangle) * Frac).Normalized180(); - viewpoint.Angles.Roll = (iview->Old.Angles.Roll + deltaangle(iview->Old.Angles.Roll, iview->New.Angles.Roll) * Frac).Normalized180(); - } - - // [MR] Apply the view angles as an offset if ABSVIEWANGLES isn't specified. - if (!(viewpoint.camera->flags8 & MF8_ABSVIEWANGLES)) - { - viewpoint.Angles += (!player || (player->cheats & CF_INTERPVIEWANGLES)) ? interpolatedvalue(iview->Old.ViewAngles, iview->New.ViewAngles, Frac) : iview->New.ViewAngles; + viewPoint.Pos = iView->New.Pos; + viewPoint.Path[0] = viewPoint.Path[1] = iView->New.Pos; } // Due to interpolation this is not necessarily the same as the sector the camera is in. - viewpoint.sector = Level->PointInRenderSubsector(viewpoint.Pos)->sector; + viewPoint.sector = viewLvl->PointInRenderSubsector(viewPoint.Pos)->sector; bool moved = false; - while (!viewpoint.sector->PortalBlocksMovement(sector_t::ceiling)) + while (!viewPoint.sector->PortalBlocksMovement(sector_t::ceiling)) { - if (viewpoint.Pos.Z > viewpoint.sector->GetPortalPlaneZ(sector_t::ceiling)) + if (viewPoint.Pos.Z > viewPoint.sector->GetPortalPlaneZ(sector_t::ceiling)) { - viewpoint.Pos += viewpoint.sector->GetPortalDisplacement(sector_t::ceiling); - viewpoint.ActorPos += viewpoint.sector->GetPortalDisplacement(sector_t::ceiling); - viewpoint.sector = Level->PointInRenderSubsector(viewpoint.Pos)->sector; + const DVector2 offset = viewPoint.sector->GetPortalDisplacement(sector_t::ceiling); + viewPoint.Pos += offset; + viewPoint.ActorPos += offset; + viewPoint.sector = viewPoint.sector->GetPortal(sector_t::ceiling)->mDestination; moved = true; } - else break; - } - if (!moved) - { - while (!viewpoint.sector->PortalBlocksMovement(sector_t::floor)) + else { - if (viewpoint.Pos.Z < viewpoint.sector->GetPortalPlaneZ(sector_t::floor)) - { - viewpoint.Pos += viewpoint.sector->GetPortalDisplacement(sector_t::floor); - viewpoint.ActorPos += viewpoint.sector->GetPortalDisplacement(sector_t::floor); - viewpoint.sector = Level->PointInRenderSubsector(viewpoint.Pos)->sector; - moved = true; - } - else break; + break; } } - if (moved) viewpoint.noviewer = true; + + if (!moved) + { + while (!viewPoint.sector->PortalBlocksMovement(sector_t::floor)) + { + if (viewPoint.Pos.Z < viewPoint.sector->GetPortalPlaneZ(sector_t::floor)) + { + const DVector2 offset = viewPoint.sector->GetPortalDisplacement(sector_t::floor); + viewPoint.Pos += offset; + viewPoint.ActorPos += offset; + viewPoint.sector = viewPoint.sector->GetPortal(sector_t::floor)->mDestination; + } + else + { + break; + } + } + } + + if (P_NoInterpolation(player, viewPoint.camera)) + { + viewPoint.Angles.Yaw = curYaw + DAngle::fromBam(LocalViewAngle); + const DAngle delta = player->centering ? nullAngle : DAngle::fromBam(LocalViewPitch); + viewPoint.Angles.Pitch = clamp((iView->New.Angles.Pitch - delta).Normalized180(), player->MinPitch, player->MaxPitch); + viewPoint.Angles.Roll = iView->New.Angles.Roll; + } + else + { + viewPoint.Angles.Pitch = iView->Old.Angles.Pitch * inverseTicFrac + iView->New.Angles.Pitch * ticFrac; + viewPoint.Angles.Yaw = prevYaw * inverseTicFrac + curYaw * ticFrac; + viewPoint.Angles.Roll = iView->Old.Angles.Roll * inverseTicFrac + iView->New.Angles.Roll * ticFrac; + } + + // Now that the base position and angles are set, add offsets. + + DVector3 posOfs = iView->ViewOffset; + if (!iView->RelativeViewOffset.isZero()) + posOfs += DQuaternion::FromAngles(viewPoint.Angles.Yaw, viewPoint.Angles.Pitch, viewPoint.Angles.Roll) * iView->RelativeViewOffset; + + // Now that we have the current interpolated position, offset from that directly (for view offset + chase cam). + if (!posOfs.isZero()) + { + const double distance = posOfs.Length(); + posOfs /= distance; + R_OffsetView(viewPoint, posOfs, distance); + } + + viewPoint.Angles += iView->AngleOffsets; + + // [MR] Apply the view angles as an offset if ABSVIEWANGLES isn't specified. + if (!(viewPoint.camera->flags8 & MF8_ABSVIEWANGLES)) + { + if (player == nullptr || (player->cheats & CF_INTERPVIEWANGLES)) + { + viewPoint.Angles.Yaw += iView->Old.ViewAngles.Yaw * inverseTicFrac + iView->New.ViewAngles.Yaw * ticFrac; + viewPoint.Angles.Pitch += iView->Old.ViewAngles.Pitch * inverseTicFrac + iView->New.ViewAngles.Pitch * ticFrac; + viewPoint.Angles.Roll += iView->Old.ViewAngles.Roll * inverseTicFrac + iView->New.ViewAngles.Roll * ticFrac; + } + else + { + viewPoint.Angles += iView->New.ViewAngles; + } + } + + viewPoint.Angles.Yaw = viewPoint.Angles.Yaw.Normalized180(); + viewPoint.Angles.Pitch = viewPoint.Angles.Pitch.Normalized180(); + viewPoint.Angles.Roll = viewPoint.Angles.Roll.Normalized180(); } //========================================================================== @@ -617,24 +653,23 @@ void R_ResetViewInterpolation () //========================================================================== // -// R_SetViewAngle -// sets all values derived from the view angle. +// R_SetViewAngle +// sets all values derived from the view yaw. // //========================================================================== -void FRenderViewpoint::SetViewAngle (const FViewWindow &viewwindow) +void FRenderViewpoint::SetViewAngle(const FViewWindow& viewWindow) { Sin = Angles.Yaw.Sin(); Cos = Angles.Yaw.Cos(); - TanSin = viewwindow.FocalTangent * Sin; - TanCos = viewwindow.FocalTangent * Cos; + TanSin = viewWindow.FocalTangent * Sin; + TanCos = viewWindow.FocalTangent * Cos; - DVector2 v = Angles.Yaw.ToVector(); + const DVector2 v = Angles.Yaw.ToVector(); ViewVector.X = v.X; ViewVector.Y = v.Y; HWAngles.Yaw = FAngle::fromDeg(270.0 - Angles.Yaw.Degrees()); - } //========================================================================== @@ -657,7 +692,7 @@ static InterpolationViewer *FindPastViewer (AActor *actor) InterpolationViewer iview; memset(&iview, 0, sizeof(iview)); iview.ViewActor = actor; - iview.otic = -1; + iview.prevTic = -1; InterpolationPath.Clear(); return &PastViewers[PastViewers.Push (iview)]; } @@ -783,26 +818,12 @@ static double QuakePower(double factor, double intensity, double offset) // //========================================================================== -static void R_DoActorTickerAngleChanges(player_t* const player, AActor* const actor, const double scale) +static void R_DoActorTickerAngleChanges(player_t* const player, DRotator& angles, const double scale) { for (unsigned i = 0; i < 3; i++) { - if (player->angleTargets[i].Sgn()) - { - // Calculate scaled amount of target and add to the accumlation buffer. - DAngle addition = player->angleTargets[i] * scale; - player->angleAppliedAmounts[i] += addition; - - // Test whether we're now reached/exceeded our target. - if (abs(player->angleAppliedAmounts[i]) >= abs(player->angleTargets[i])) - { - addition -= player->angleAppliedAmounts[i] - player->angleTargets[i]; - player->angleTargets[i] = player->angleAppliedAmounts[i] = nullAngle; - } - - // Apply the scaled addition to the angle. - actor->Angles[i] += addition; - } + if (fabs(player->angleOffsetTargets[i].Degrees()) >= EQUAL_EPSILON) + angles[i] += player->angleOffsetTargets[i] * scale; } } @@ -812,346 +833,263 @@ static void R_DoActorTickerAngleChanges(player_t* const player, AActor* const ac // //========================================================================== -void R_SetupFrame (FRenderViewpoint &viewpoint, FViewWindow &viewwindow, AActor *actor) +EXTERN_CVAR(Float, chase_dist) +EXTERN_CVAR(Float, chase_height) + +void R_SetupFrame(FRenderViewpoint& viewPoint, const FViewWindow& viewWindow, AActor* const actor) { - if (actor == NULL) + viewPoint.TicFrac = I_GetTimeFrac(); + if (cl_capfps || r_NoInterpolate) + viewPoint.TicFrac = 1.0; + + const int curTic = I_GetTime(); + + if (actor == nullptr) + I_Error("Tried to render from a null actor."); + + viewPoint.ViewLevel = actor->Level; + + player_t* player = actor->player; + if (player != nullptr && player->mo == actor) { - I_Error ("Tried to render from a NULL actor."); - } - viewpoint.ViewLevel = actor->Level; + if (player->camera == nullptr) + player->camera = player->mo; - player_t *player = actor->player; - unsigned int newblend; - InterpolationViewer *iview; - bool unlinked = false; - - if (player != NULL && player->mo == actor) - { // [RH] Use camera instead of viewplayer - viewpoint.camera = player->camera; - if (viewpoint.camera == NULL) - { - viewpoint.camera = player->camera = player->mo; - } + viewPoint.camera = player->camera; // [RH] Use camera instead of view player. } else { - viewpoint.camera = actor; + viewPoint.camera = actor; } - if (viewpoint.camera == NULL) + if (viewPoint.camera == nullptr) + I_Error("You lost your body. Bad dehacked work is likely to blame."); + + InterpolationViewer* const iView = FindPastViewer(viewPoint.camera); + // Always reset these back to zero. + iView->ViewOffset.Zero(); + iView->RelativeViewOffset.Zero(); + iView->AngleOffsets.Zero(); + if (iView->prevTic != -1 && curTic > iView->prevTic) { - I_Error ("You lost your body. Bad dehacked work is likely to blame."); + iView->prevTic = curTic; + iView->Old = iView->New; } - // [MR] Get the input fraction, even if we don't need it this frame. Must run every frame. - const auto scaleAdjust = I_GetInputFrac(); - - // [MR] Process player angle changes if permitted to do so. - if (player && (player->cheats & CF_SCALEDNOLERP) && P_NoInterpolation(player, viewpoint.camera)) - { - R_DoActorTickerAngleChanges(player, viewpoint.camera, scaleAdjust); - } - - iview = FindPastViewer (viewpoint.camera); - - int nowtic = I_GetTime (); - if (iview->otic != -1 && nowtic > iview->otic) - { - iview->otic = nowtic; - iview->Old = iview->New; - } + const auto& mainView = r_viewpoint; + AActor* const client = players[consoleplayer].mo; + const bool matchPlayer = gamestate != GS_TITLELEVEL && viewPoint.camera->player == nullptr && (viewPoint.camera->renderflags2 & RF2_CAMFOLLOWSPLAYER); + const bool usePawn = matchPlayer ? mainView.camera != client : false; //============================================================================================== - // Handles offsetting the camera with ChaseCam and/or viewpos. + // Sets up the view position offset. { - AActor *mo = viewpoint.camera; - DViewPosition *VP = mo->ViewPos; - const DVector3 orig = { mo->Pos().XY(), mo->player ? mo->player->viewz : mo->Z() + mo->GetCameraHeight() }; - viewpoint.ActorPos = orig; - - bool DefaultDraw = true; - - sector_t *oldsector = viewpoint.ViewLevel->PointInRenderSubsector(iview->Old.Pos)->sector; - if (gamestate != GS_TITLELEVEL && - ((player && (player->cheats & CF_CHASECAM)) || (r_deathcamera && viewpoint.camera->health <= 0))) + AActor* const mo = viewPoint.camera; + const DViewPosition* const viewOffset = mo->ViewPos; + DVector3 camPos; + if (matchPlayer) { - // [RH] Use chasecam view - DefaultDraw = false; - DVector3 campos; - DAngle camangle; - P_AimCamera(viewpoint.camera, campos, camangle, viewpoint.sector, unlinked); // fixme: This needs to translate the angle, too. - iview->New.Pos = campos; - iview->New.Angles.Yaw = camangle; - - viewpoint.showviewer = true; - // Interpolating this is a very complicated thing because nothing keeps track of the aim camera's movement, so whenever we detect a portal transition - // it's probably best to just reset the interpolation for this move. - // Note that this can still cause problems with unusually linked portals - if (viewpoint.sector->PortalGroup != oldsector->PortalGroup || (unlinked && ((iview->New.Pos.XY() - iview->Old.Pos.XY()).LengthSquared()) > 256 * 256)) + if (usePawn) { - iview->otic = nowtic; - iview->Old = iview->New; - r_NoInterpolate = true; - } - viewpoint.ActorPos = campos; - } - else if (VP) // No chase/death cam and player is alive, wants viewpos. - { - viewpoint.sector = viewpoint.ViewLevel->PointInRenderSubsector(iview->New.Pos.XY())->sector; - viewpoint.showviewer = false; - - // [MC] Ignores all portal portal transitions since it's meant to be absolute. - // Modders must handle performing offsetting with the appropriate functions to get it to work. - // Hint: Check P_AdjustViewPos. - if (VP->Flags & VPSF_ABSOLUTEPOS) - { - iview->New.Pos = VP->Offset; + camPos = DVector3(client->Pos().XY(), client->player->viewz); + const DViewPosition* const pawnVP = client->ViewPos; + if (pawnVP != nullptr) + { + // Add these directly to the view position offset (not 100% accurate but close enough). + if (pawnVP->Flags & VPSF_ABSOLUTEPOS) + camPos = pawnVP->Offset; + else if (pawnVP->Flags & VPSF_ABSOLUTEOFFSET) + iView->ViewOffset = pawnVP->Offset; + else + iView->ViewOffset = DQuaternion::FromAngles(client->Angles.Yaw, client->Angles.Pitch, client->Angles.Roll) * pawnVP->Offset; + } } else { - DVector3 next = orig; - - if (VP->isZero()) - { - // Since viewpos isn't being used, it's safe to enable path interpolation - viewpoint.NoPortalPath = false; - } - else if (VP->Flags & VPSF_ABSOLUTEOFFSET) - { - // No relativity added from angles. - next += VP->Offset; - } - else - { - // [MC] Do NOT handle portals here! Trace must have the unportaled (absolute) position to - // get the correct angle and distance. Trace automatically handles portals by itself. - // Note: viewpos does not include view angles, and ViewZ/CameraHeight are applied before this. - - DAngle yaw = mo->Angles.Yaw; - DAngle pitch = mo->Angles.Pitch; - DAngle roll = mo->Angles.Roll; - DVector3 relx, rely, relz, Off = VP->Offset; - DMatrix3x3 rot = - DMatrix3x3(DVector3(0., 0., 1.), yaw.Cos(), yaw.Sin()) * - DMatrix3x3(DVector3(0., 1., 0.), pitch.Cos(), pitch.Sin()) * - DMatrix3x3(DVector3(1., 0., 0.), roll.Cos(), roll.Sin()); - relx = DVector3(1., 0., 0.)*rot; - rely = DVector3(0., 1., 0.)*rot; - relz = DVector3(0., 0., 1.)*rot; - next += relx * Off.X + rely * Off.Y + relz * Off.Z; - } - - if (next != orig) - { - // [MC] Disable interpolation if the camera view is crossing through a portal. Sometimes - // the player is made visible when crossing a portal and it's extremely jarring. - // Also, disable the portal interpolation pathing entirely when using the viewpos feature. - // Interpolation still happens with everything else though and seems to work fine. - DefaultDraw = false; - viewpoint.NoPortalPath = true; - P_AdjustViewPos(mo, orig, next, viewpoint.sector, unlinked, VP, &viewpoint); - - if (viewpoint.sector->PortalGroup != oldsector->PortalGroup || (unlinked && ((iview->New.Pos.XY() - iview->Old.Pos.XY()).LengthSquared()) > 256 * 256)) - { - iview->otic = nowtic; - iview->Old = iview->New; - r_NoInterpolate = true; - } - iview->New.Pos = next; - } + camPos = mainView.Pos; } } - - if (DefaultDraw) + else { - iview->New.Pos = orig; - viewpoint.sector = viewpoint.camera->Sector; - viewpoint.showviewer = viewpoint.NoPortalPath = false; + camPos = { mo->Pos().XY(), mo->player != nullptr ? mo->player->viewz : mo->Z() + mo->GetCameraHeight() }; } - } - // [MR] Apply view angles as the viewpoint angles if asked to do so. - iview->New.Angles = !(viewpoint.camera->flags8 & MF8_ABSVIEWANGLES) ? viewpoint.camera->Angles : viewpoint.camera->ViewAngles; - iview->New.ViewAngles = viewpoint.camera->ViewAngles; + viewPoint.showviewer = false; + viewPoint.bForceNoViewer = matchPlayer; - if (viewpoint.camera->player != 0) - { - player = viewpoint.camera->player; - } - - if (iview->otic == -1 || r_NoInterpolate || (viewpoint.camera->renderflags & RF_NOINTERPOLATEVIEW)) - { - viewpoint.camera->renderflags &= ~RF_NOINTERPOLATEVIEW; - R_ResetViewInterpolation (); - iview->otic = nowtic; - } - - viewpoint.TicFrac = I_GetTimeFrac (); - if (cl_capfps || r_NoInterpolate) - { - viewpoint.TicFrac = 1.; - } - R_InterpolateView (viewpoint, player, viewpoint.TicFrac, iview); - - viewpoint.SetViewAngle (viewwindow); - - // Keep the view within the sector's floor and ceiling - if (viewpoint.sector->PortalBlocksMovement(sector_t::ceiling)) - { - double theZ = viewpoint.sector->ceilingplane.ZatPoint(viewpoint.Pos) - 4; - if (viewpoint.Pos.Z > theZ) + if (gamestate != GS_TITLELEVEL && + ((player != nullptr && (player->cheats & CF_CHASECAM)) || (r_deathcamera && (viewPoint.camera->flags6 & MF6_KILLED)))) { - viewpoint.Pos.Z = theZ; + // The cam Actor should probably be visible in third person. + viewPoint.showviewer = true; + camPos.Z = mo->Top() - mo->Floorclip; + iView->ViewOffset.Z = clamp(chase_height, -1000.0, 1000.0); + iView->RelativeViewOffset.X = -clamp(chase_dist, 0.0, 30000.0); } - } - - if (viewpoint.sector->PortalBlocksMovement(sector_t::floor)) - { - double theZ = viewpoint.sector->floorplane.ZatPoint(viewpoint.Pos) + 4; - if (viewpoint.Pos.Z < theZ) + else if (viewOffset != nullptr) { - viewpoint.Pos.Z = theZ; + // No chase/death cam, so use the view offset. + if (!viewPoint.bForceNoViewer) + viewPoint.bForceNoViewer = (viewOffset->Flags & VPSF_ABSOLUTEPOS) || !viewOffset->Offset.isZero(); + + if (viewOffset->Flags & VPSF_ABSOLUTEPOS) + { + if (!matchPlayer) + camPos = viewOffset->Offset; + } + else if (viewOffset->Flags & VPSF_ABSOLUTEOFFSET) + { + iView->ViewOffset += viewOffset->Offset; + } + else + { + iView->RelativeViewOffset = viewOffset->Offset; + } } + + viewPoint.ActorPos = iView->New.Pos = camPos; + viewPoint.sector = viewPoint.ViewLevel->PointInRenderSubsector(camPos)->sector; } if (!paused) { FQuakeJiggers jiggers; - - memset(&jiggers, 0, sizeof(jiggers)); - if (DEarthquake::StaticGetQuakeIntensities(viewpoint.TicFrac, viewpoint.camera, jiggers) > 0) + if (DEarthquake::StaticGetQuakeIntensities(viewPoint.TicFrac, viewPoint.camera, jiggers) > 0) { - double quakefactor = r_quakeintensity; - DVector3 pos; pos.Zero(); - if (jiggers.RollIntensity != 0 || jiggers.RollWave != 0) - { - viewpoint.Angles.Roll += DAngle::fromDeg(QuakePower(quakefactor, jiggers.RollIntensity, jiggers.RollWave)); - } - if (jiggers.RelIntensity.X != 0 || jiggers.RelOffset.X != 0) - { - pos.X += QuakePower(quakefactor, jiggers.RelIntensity.X, jiggers.RelOffset.X); - } - if (jiggers.RelIntensity.Y != 0 || jiggers.RelOffset.Y != 0) - { - pos.Y += QuakePower(quakefactor, jiggers.RelIntensity.Y, jiggers.RelOffset.Y); - } - if (jiggers.RelIntensity.Z != 0 || jiggers.RelOffset.Z != 0) - { - pos.Z += QuakePower(quakefactor, jiggers.RelIntensity.Z, jiggers.RelOffset.Z); - } - // [MC] Tremendous thanks to Marisa Kirisame for helping me with this. - // Use a rotation matrix to make the view relative. - if (!pos.isZero()) - { - DAngle yaw = viewpoint.camera->Angles.Yaw; - DAngle pitch = viewpoint.camera->Angles.Pitch; - DAngle roll = viewpoint.camera->Angles.Roll; - DVector3 relx, rely, relz; - DMatrix3x3 rot = - DMatrix3x3(DVector3(0., 0., 1.), yaw.Cos(), yaw.Sin()) * - DMatrix3x3(DVector3(0., 1., 0.), pitch.Cos(), pitch.Sin()) * - DMatrix3x3(DVector3(1., 0., 0.), roll.Cos(), roll.Sin()); - relx = DVector3(1., 0., 0.)*rot; - rely = DVector3(0., 1., 0.)*rot; - relz = DVector3(0., 0., 1.)*rot; - viewpoint.Pos += relx * pos.X + rely * pos.Y + relz * pos.Z; - } + const double quakeFactor = r_quakeintensity; + if (jiggers.RollIntensity || jiggers.RollWave) + iView->AngleOffsets.Roll = DAngle::fromDeg(QuakePower(quakeFactor, jiggers.RollIntensity, jiggers.RollWave)); - if (jiggers.Intensity.X != 0 || jiggers.Offset.X != 0) - { - viewpoint.Pos.X += QuakePower(quakefactor, jiggers.Intensity.X, jiggers.Offset.X); - } - if (jiggers.Intensity.Y != 0 || jiggers.Offset.Y != 0) - { - viewpoint.Pos.Y += QuakePower(quakefactor, jiggers.Intensity.Y, jiggers.Offset.Y); - } - if (jiggers.Intensity.Z != 0 || jiggers.Offset.Z != 0) - { - viewpoint.Pos.Z += QuakePower(quakefactor, jiggers.Intensity.Z, jiggers.Offset.Z); - } + if (jiggers.RelIntensity.X || jiggers.RelOffset.X) + iView->RelativeViewOffset.X += QuakePower(quakeFactor, jiggers.RelIntensity.X, jiggers.RelOffset.X); + if (jiggers.RelIntensity.Y || jiggers.RelOffset.Y) + iView->RelativeViewOffset.Y += QuakePower(quakeFactor, jiggers.RelIntensity.Y, jiggers.RelOffset.Y); + if (jiggers.RelIntensity.Z || jiggers.RelOffset.Z) + iView->RelativeViewOffset.Z += QuakePower(quakeFactor, jiggers.RelIntensity.Z, jiggers.RelOffset.Z); + + if (jiggers.Intensity.X || jiggers.Offset.X) + iView->ViewOffset.X += QuakePower(quakeFactor, jiggers.Intensity.X, jiggers.Offset.X); + if (jiggers.Intensity.Y || jiggers.Offset.Y) + iView->ViewOffset.Y += QuakePower(quakeFactor, jiggers.Intensity.Y, jiggers.Offset.Y); + if (jiggers.Intensity.Z || jiggers.Offset.Z) + iView->ViewOffset.Z += QuakePower(quakeFactor, jiggers.Intensity.Z, jiggers.Offset.Z); } } - viewpoint.extralight = viewpoint.camera->player ? viewpoint.camera->player->extralight : 0; + // [MR] Apply view angles as the viewpoint angles if asked to do so. + if (matchPlayer) + iView->New.Angles = usePawn ? (client->flags8 & MF8_ABSVIEWANGLES ? client->ViewAngles : client->Angles + client->ViewAngles) : mainView.Angles; + else + iView->New.Angles = !(viewPoint.camera->flags8 & MF8_ABSVIEWANGLES) ? viewPoint.camera->Angles : viewPoint.camera->ViewAngles; + + iView->New.ViewAngles = viewPoint.camera->ViewAngles; + // [MR] Process player angle changes if permitted to do so. + if (player != nullptr && (player->cheats & CF_SCALEDNOLERP) && P_NoInterpolation(player, viewPoint.camera)) + R_DoActorTickerAngleChanges(player, iView->New.Angles, viewPoint.TicFrac); + + // If currently tracking the player's real view, don't do any sort of interpolating. + if (matchPlayer && !usePawn) + viewPoint.camera->renderflags |= RF_NOINTERPOLATEVIEW; + + if (viewPoint.camera->player != nullptr) + player = viewPoint.camera->player; + + if (iView->prevTic == -1 || r_NoInterpolate || (viewPoint.camera->renderflags & RF_NOINTERPOLATEVIEW)) + { + viewPoint.camera->renderflags &= ~RF_NOINTERPOLATEVIEW; + R_ResetViewInterpolation(); + iView->prevTic = curTic; + } + + R_InterpolateView(viewPoint, player, viewPoint.TicFrac, iView); + + viewPoint.SetViewAngle(viewWindow); + + // Keep the view within the sector's floor and ceiling + if (viewPoint.sector->PortalBlocksMovement(sector_t::ceiling)) + { + const double z = viewPoint.sector->ceilingplane.ZatPoint(viewPoint.Pos) - 4.0; + if (viewPoint.Pos.Z > z) + viewPoint.Pos.Z = z; + } + + if (viewPoint.sector->PortalBlocksMovement(sector_t::floor)) + { + const double z = viewPoint.sector->floorplane.ZatPoint(viewPoint.Pos) + 4.0; + if (viewPoint.Pos.Z < z) + viewPoint.Pos.Z = z; + } + + viewPoint.extralight = viewPoint.camera->player != nullptr ? viewPoint.camera->player->extralight : 0; // killough 3/20/98, 4/4/98: select colormap based on player status // [RH] Can also select a blend - newblend = 0; - - TArray &lightlist = viewpoint.sector->e->XFloor.lightlist; + unsigned int newBlend = 0u; + const TArray& lightlist = viewPoint.sector->e->XFloor.lightlist; if (lightlist.Size() > 0) { - for(unsigned int i = 0; i < lightlist.Size(); i++) + for (size_t i = 0u; i < lightlist.Size(); ++i) { - secplane_t *plane; - int viewside; - plane = (i < lightlist.Size()-1) ? &lightlist[i+1].plane : &viewpoint.sector->floorplane; - viewside = plane->PointOnSide(viewpoint.Pos); + const secplane_t& plane = (i < lightlist.Size() - 1u) ? lightlist[i + 1u].plane : viewPoint.sector->floorplane; + int viewSide = plane.PointOnSide(viewPoint.Pos); + // Reverse the direction of the test if the plane was downward facing. // We want to know if the view is above it, whatever its orientation may be. - if (plane->fC() < 0) - viewside = -viewside; - if (viewside > 0) - { - // 3d floor 'fog' is rendered as a blending value - PalEntry blendv = lightlist[i].blend; + if (plane.fC() < 0.0) + viewSide = -viewSide; - // If no alpha is set, use 50% - if (blendv.a==0 && blendv!=0) blendv.a=128; - newblend = blendv.d; + if (viewSide > 0) + { + // 3d floor 'fog' is rendered as a blending value. + PalEntry blend = lightlist[i].blend; + + // If no alpha is set, use 50%. + if (!blend.a && blend != 0) + blend.a = 128u; + + newBlend = blend.d; break; } } } else { - const sector_t *s = viewpoint.sector->GetHeightSec(); - if (s != NULL) + const sector_t* const sec = viewPoint.sector->GetHeightSec(); + if (sec != nullptr) { - newblend = s->floorplane.PointOnSide(viewpoint.Pos) < 0 - ? s->bottommap - : s->ceilingplane.PointOnSide(viewpoint.Pos) < 0 - ? s->topmap - : s->midmap; - if (APART(newblend) == 0 && newblend >= fakecmaps.Size()) - newblend = 0; + newBlend = sec->floorplane.PointOnSide(viewPoint.Pos) < 0 + ? sec->bottommap + : sec->ceilingplane.PointOnSide(viewPoint.Pos) < 0 + ? sec->topmap + : sec->midmap; + + if (APART(newBlend) == 0u && newBlend >= fakecmaps.Size()) + newBlend = 0u; } } // [RH] Don't override testblend unless entering a sector with a // blend different from the previous sector's. Same goes with // NormalLight's maps pointer. - if (R_OldBlend != newblend) - { - R_OldBlend = newblend; - } + if (R_OldBlend != newBlend) + R_OldBlend = newBlend; - validcount++; + ++validcount; if (r_clearbuffer != 0) { - int color; + int color = 0; int hom = r_clearbuffer; - if (hom == 3) - { hom = ((screen->FrameTime / 128) & 1) + 1; - } if (hom == 1) - { color = GPalette.BlackIndex; - } else if (hom == 2) - { color = GPalette.WhiteIndex; - } else if (hom == 4) - { color = (screen->FrameTime / 32) & 255; - } else - { color = pr_hom(); - } + screen->SetClearColor(color); SWRenderer->SetClearColor(color); } @@ -1165,25 +1103,16 @@ void R_SetupFrame (FRenderViewpoint &viewpoint, FViewWindow &viewwindow, AActor // Scale the pitch to account for the pixel stretching, because the playsim doesn't know about this and treats it as 1:1. // However, to set up a projection matrix this needs to be adjusted. - double radPitch = viewpoint.Angles.Pitch.Normalized180().Radians(); - double angx = cos(radPitch); - double angy = sin(radPitch) * actor->Level->info->pixelstretch; - double alen = sqrt(angx*angx + angy*angy); - viewpoint.HWAngles.Pitch = FAngle::fromRad((float)asin(angy / alen)); - - viewpoint.HWAngles.Roll = FAngle::fromDeg(viewpoint.Angles.Roll.Degrees()); // copied for convenience. - - // ViewActor only gets set, if the camera actor should not be rendered - if (actor->player && actor->player - players == consoleplayer && - ((actor->player->cheats & CF_CHASECAM) || (r_deathcamera && actor->health <= 0)) && actor == actor->player->mo) - { - viewpoint.ViewActor = nullptr; - } - else - { - viewpoint.ViewActor = actor; - } + const double radPitch = viewPoint.Angles.Pitch.Normalized180().Radians(); + const double angx = cos(radPitch); + const double angy = sin(radPitch) * actor->Level->info->pixelstretch; + const double alen = sqrt(angx*angx + angy*angy); + + viewPoint.HWAngles.Pitch = FAngle::fromRad((float)asin(angy / alen)); + viewPoint.HWAngles.Roll = FAngle::fromDeg(viewPoint.Angles.Roll.Degrees()); + // ViewActor only gets set if the camera actor shouldn't be rendered. + viewPoint.ViewActor = viewPoint.showviewer ? nullptr : actor; } diff --git a/src/rendering/r_utility.h b/src/rendering/r_utility.h index 7ba6e738c..3013961eb 100644 --- a/src/rendering/r_utility.h +++ b/src/rendering/r_utility.h @@ -43,9 +43,8 @@ struct FRenderViewpoint int extralight; // extralight to be added to this viewpoint bool showviewer; // show the camera actor? - bool NoPortalPath; // Disable portal interpolation path for actor viewpos. - bool noviewer; // Force camera sprite off for first person. - void SetViewAngle(const FViewWindow &viewwindow); + bool bForceNoViewer; // Never show the camera Actor. + void SetViewAngle(const FViewWindow& viewWindow); }; @@ -118,7 +117,7 @@ void R_ClearInterpolationPath(); void R_AddInterpolationPoint(const DVector3a &vec); void R_SetViewSize (int blocks); void R_SetFOV (FRenderViewpoint &viewpoint, DAngle fov); -void R_SetupFrame (FRenderViewpoint &viewpoint, FViewWindow &viewwindow, AActor * camera); +void R_SetupFrame(FRenderViewpoint& viewPoint, const FViewWindow& viewWindow, AActor* const camera); void R_SetViewAngle (FRenderViewpoint &viewpoint, const FViewWindow &viewwindow); // Called by startup code. diff --git a/src/rendering/swrenderer/scene/r_opaque_pass.cpp b/src/rendering/swrenderer/scene/r_opaque_pass.cpp index 17291bb49..f94244644 100644 --- a/src/rendering/swrenderer/scene/r_opaque_pass.cpp +++ b/src/rendering/swrenderer/scene/r_opaque_pass.cpp @@ -1046,7 +1046,7 @@ namespace swrenderer // The X offsetting (SpriteOffset.X) is performed in r_sprite.cpp, in RenderSprite::Project(). sprite.pos = thing->InterpolatedPosition(Thread->Viewport->viewpoint.TicFrac); sprite.pos += thing->WorldOffset; - sprite.pos.Z += thing->GetBobOffset(Thread->Viewport->viewpoint.TicFrac) - thing->SpriteOffset.Y; + sprite.pos.Z += thing->GetBobOffset(Thread->Viewport->viewpoint.TicFrac) + thing->GetSpriteOffset(true); sprite.spritenum = thing->sprite; sprite.tex = nullptr; sprite.voxel = nullptr; diff --git a/src/rendering/swrenderer/things/r_sprite.cpp b/src/rendering/swrenderer/things/r_sprite.cpp index 39c2e82e3..b05a67448 100644 --- a/src/rendering/swrenderer/things/r_sprite.cpp +++ b/src/rendering/swrenderer/things/r_sprite.cpp @@ -81,7 +81,7 @@ namespace swrenderer const double thingxscalemul = spriteScale.X / tex->GetScale().X; // Calculate billboard line for the sprite - double SpriteOffX = (thing) ? thing->SpriteOffset.X : 0.; + double SpriteOffX = (thing) ? -thing->GetSpriteOffset(false) : 0.; DVector2 dir = { viewport->viewpoint.Sin, -viewport->viewpoint.Cos }; DVector2 trs = pos.XY() - viewport->viewpoint.Pos.XY(); trs = { trs.X + SpriteOffX * dir.X, trs.Y + SpriteOffX * dir.Y }; diff --git a/src/scripting/backend/codegen_doom.cpp b/src/scripting/backend/codegen_doom.cpp index 60e0cc614..0e52790d7 100644 --- a/src/scripting/backend/codegen_doom.cpp +++ b/src/scripting/backend/codegen_doom.cpp @@ -57,6 +57,19 @@ PFunction* FindBuiltinFunction(FName funcname); // //========================================================================== +bool ShouldAllowGameSpecificVirtual(FName name, unsigned index, PType* arg, PType* varg) +{ + return (name == NAME_Morph && index == 3u && arg->isClassPointer() && varg->isClassPointer() + && PType::toClassPointer(varg)->ClassRestriction->TypeName == NAME_Actor + && PType::toClassPointer(arg)->ClassRestriction->TypeName == NAME_MorphedMonster); +} + +//========================================================================== +// +// +// +//========================================================================== + bool isActor(PContainerType *type) { auto cls = PType::toClass(type); diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 49fc9bc5e..9f835aff8 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -353,6 +353,9 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(MF9, SHADOWBLOCK, AActor, flags9), DEFINE_FLAG(MF9, SHADOWAIMVERT, AActor, flags9), DEFINE_FLAG(MF9, DECOUPLEDANIMATIONS, AActor, flags9), + DEFINE_FLAG(MF9, PATHING, AActor, flags9), + DEFINE_FLAG(MF9, KEEPPATH, AActor, flags9), + DEFINE_FLAG(MF9, NOPATHING, AActor, flags9), // Effect flags DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects), @@ -381,6 +384,9 @@ static FFlagDef ActorFlagDefs[]= DEFINE_FLAG(RF2, ONLYVISIBLEINMIRRORS, AActor, renderflags2), DEFINE_FLAG(RF2, BILLBOARDFACECAMERA, AActor, renderflags2), DEFINE_FLAG(RF2, BILLBOARDNOFACECAMERA, AActor, renderflags2), + DEFINE_FLAG(RF2, FLIPSPRITEOFFSETX, AActor, renderflags2), + DEFINE_FLAG(RF2, FLIPSPRITEOFFSETY, AActor, renderflags2), + DEFINE_FLAG(RF2, CAMFOLLOWSPLAYER, AActor, renderflags2), // Bounce flags DEFINE_FLAG2(BOUNCE_Walls, BOUNCEONWALLS, AActor, BounceFlags), diff --git a/src/scripting/thingdef_properties.cpp b/src/scripting/thingdef_properties.cpp index fa7a5b13f..bedd66cd3 100644 --- a/src/scripting/thingdef_properties.cpp +++ b/src/scripting/thingdef_properties.cpp @@ -1814,6 +1814,15 @@ DEFINE_CLASS_PROPERTY(playerclass, S, PowerMorph) defaults->PointerVar(NAME_PlayerClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate); } +//========================================================================== +// +//========================================================================== +DEFINE_CLASS_PROPERTY(monsterclass, S, PowerMorph) +{ + PROP_STRING_PARM(str, 0); + defaults->PointerVar(NAME_MonsterClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate); +} + //========================================================================== // //========================================================================== diff --git a/src/scripting/vmthunks.cpp b/src/scripting/vmthunks.cpp index 94e62782f..5ffa4dba4 100644 --- a/src/scripting/vmthunks.cpp +++ b/src/scripting/vmthunks.cpp @@ -2787,6 +2787,7 @@ DEFINE_FIELD_X(LevelInfo, level_info_t, RedirectMapName) DEFINE_FIELD_X(LevelInfo, level_info_t, teamdamage) DEFINE_GLOBAL_NAMED(currentVMLevel, level) +DEFINE_FIELD(FLevelLocals, PathNodes) DEFINE_FIELD(FLevelLocals, sectors) DEFINE_FIELD(FLevelLocals, lines) DEFINE_FIELD(FLevelLocals, sides) @@ -2854,6 +2855,7 @@ DEFINE_FIELD_BIT(FLevelLocals, flags2, infinite_flight, LEVEL2_INFINITE_FLIGHT) DEFINE_FIELD_BIT(FLevelLocals, flags2, no_dlg_freeze, LEVEL2_CONV_SINGLE_UNFREEZE) DEFINE_FIELD_BIT(FLevelLocals, flags2, keepfullinventory, LEVEL2_KEEPFULLINVENTORY) DEFINE_FIELD_BIT(FLevelLocals, flags3, removeitems, LEVEL3_REMOVEITEMS) +DEFINE_FIELD_BIT(FLevelLocals, flags3, pathing, LEVEL3_PATHING) DEFINE_FIELD_BIT(FLevelLocals, vkdflags, nousersave, VKDLEVELFLAG_NOUSERSAVE) DEFINE_FIELD_BIT(FLevelLocals, vkdflags, noautomap, VKDLEVELFLAG_NOAUTOMAP) diff --git a/src/scripting/vmthunks_actors.cpp b/src/scripting/vmthunks_actors.cpp index e3438d6f6..219d63e0d 100644 --- a/src/scripting/vmthunks_actors.cpp +++ b/src/scripting/vmthunks_actors.cpp @@ -55,6 +55,7 @@ #include "actorinlines.h" #include "p_enemy.h" #include "gi.h" +#include "shadowinlines.h" DVector2 AM_GetPosition(); int Net_GetLatency(int *ld, int *ad); @@ -1227,6 +1228,23 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, LineTrace, LineTrace) ACTION_RETURN_BOOL(P_LineTrace(self,DAngle::fromDeg(angle),distance,DAngle::fromDeg(pitch),flags,offsetz,offsetforward,offsetside,data)); } +DEFINE_ACTION_FUNCTION(AActor, PerformShadowChecks) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OBJECT(other, AActor); //If this pointer is null, the trace uses the facing direction instead. + PARAM_FLOAT(x); + PARAM_FLOAT(y); + PARAM_FLOAT(z); + + double penaltyFactor = 0.0; + AActor* shadow = PerformShadowChecks(self, other, DVector3(x, y, z), penaltyFactor); + if (numret > 2) ret[2].SetFloat(penaltyFactor); + if (numret > 1) ret[1].SetObject(shadow); + if (numret > 0) ret[0].SetInt(bool(shadow)); + return numret; +} + + static void TraceBleedAngle(AActor *self, int damage, double angle, double pitch) { P_TraceBleed(damage, self, DAngle::fromDeg(angle), DAngle::fromDeg(pitch)); @@ -1675,28 +1693,11 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_BossDeath, A_BossDeath) return 0; } -DEFINE_ACTION_FUNCTION_NATIVE(AActor, Substitute, StaticPointerSubstitution) +DEFINE_ACTION_FUNCTION_NATIVE(AActor, MorphInto, MorphPointerSubstitution) { PARAM_SELF_PROLOGUE(AActor); - PARAM_OBJECT(replace, AActor); - StaticPointerSubstitution(self, replace); - return 0; -} - -DEFINE_ACTION_FUNCTION_NATIVE(_PlayerPawn, Substitute, StaticPointerSubstitution) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_OBJECT(replace, AActor); - StaticPointerSubstitution(self, replace); - return 0; -} - -DEFINE_ACTION_FUNCTION_NATIVE(_MorphedMonster, Substitute, StaticPointerSubstitution) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_OBJECT(replace, AActor); - StaticPointerSubstitution(self, replace); - return 0; + PARAM_OBJECT(to, AActor); + ACTION_RETURN_INT(MorphPointerSubstitution(self, to)); } DEFINE_ACTION_FUNCTION_NATIVE(AActor, GetSpawnableType, P_GetSpawnableType) @@ -2123,6 +2124,8 @@ DEFINE_FIELD_NAMED(AActor, ViewAngles.Roll, viewroll) DEFINE_FIELD(AActor, LightLevel) DEFINE_FIELD(AActor, ShadowAimFactor) DEFINE_FIELD(AActor, ShadowPenaltyFactor) +DEFINE_FIELD(AActor, AutomapOffsets) +DEFINE_FIELD(AActor, Path) DEFINE_FIELD_X(FCheckPosition, FCheckPosition, thing); DEFINE_FIELD_X(FCheckPosition, FCheckPosition, pos); diff --git a/vcpkg.json b/vcpkg.json index 723cbb0e2..3dfe2094c 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -10,7 +10,7 @@ { "name": "libvpx", "default-features": false, - "platform": "windows & static & staticcrt" + "platform": "(!windows & static) | (windows & static & staticcrt)" } ] }, @@ -21,7 +21,7 @@ { "name": "openal-soft", "default-features": false, - "platform": "!windows | (windows & static & staticcrt)" + "platform": "(!windows & static) | (windows & static & staticcrt)" } ] } @@ -29,19 +29,23 @@ "dependencies": [ { "name": "bzip2", - "platform": "!windows | (windows & static & staticcrt)" - }, - { - "name": "sdl2", - "platform": "!windows & !osx" + "platform": "(!windows & static) | (windows & static & staticcrt)" }, { "name": "libvpx", - "platform": "!windows" + "platform": "!windows & static" }, { "name": "libwebp", - "platform": "!windows | (windows & static & staticcrt)" - } + "platform": "(!windows & static) | (windows & static & staticcrt)" + }, + { + "name": "gtk3", + "platform": "!windows & !osx & static" + }, + { + "name": "glib", + "platform": "!windows & !osx & static" + } ] -} \ No newline at end of file +} diff --git a/wadsrc/static/engine/commonbinds.txt b/wadsrc/static/engine/commonbinds.txt index 6de42786a..0cb5756cc 100644 --- a/wadsrc/static/engine/commonbinds.txt +++ b/wadsrc/static/engine/commonbinds.txt @@ -61,10 +61,24 @@ dpadleft invprev dpadright invnext dpaddown invuse dpadup togglemap -pad_start pause -pad_back menu_main +pad_start menu_main +pad_back pause lthumb crouch +// Generic gamepad bindings +joy1 +use +joy4 +jump +axis6plus +attack +axis3plus +altattack +joy5 weapprev +joy6 weapnext +pov1left invprev +pov1right invnext +pov1down invuse +pov1up togglemap +joy8 menu_main +joy7 pause +joy10 crouch /* Default automap bindings */ mapbind f am_togglefollow @@ -83,3 +97,24 @@ mapbind kp- +am_zoomout mapbind kp+ +am_zoomin mapbind mwheelup "am_zoom 1.2" mapbind mwheeldown "am_zoom -1.2" + +/* Automap bindings for controllers (bare minimum functionality) */ +mapbind pad_x am_togglefollow +mapbind pad_a am_setmark +mapbind pad_b am_clearmarks +mapbind dpadright +am_panright +mapbind dpadleft +am_panleft +mapbind dpadup +am_panup +mapbind dpaddown +am_pandown +mapbind lshoulder +am_zoomout +mapbind rshoulder +am_zoomin + +mapbind joy3 am_togglefollow +mapbind joy1 am_setmark +mapbind joy2 am_clearmarks +mapbind pov1right +am_panright +mapbind pov1left +am_panleft +mapbind pov1up +am_panup +mapbind pov2down +am_pandown +mapbind joy5 +am_zoomout +mapbind joy6 +am_zoomin diff --git a/wadsrc/static/engine/leftbinds.txt b/wadsrc/static/engine/leftbinds.txt index 63cf50e81..30604f10d 100644 --- a/wadsrc/static/engine/leftbinds.txt +++ b/wadsrc/static/engine/leftbinds.txt @@ -1,4 +1,4 @@ -/* Default keybindings for all games */ +/* Default keybindings (keypad) for all games */ KP8 +forward KP5 +back diff --git a/wadsrc/static/filter/doom.id.doom1/after_iwad/sndinfo.txt b/wadsrc/static/filter/doom.id.doom1/after_iwad/sndinfo.txt new file mode 100644 index 000000000..0c30de958 --- /dev/null +++ b/wadsrc/static/filter/doom.id.doom1/after_iwad/sndinfo.txt @@ -0,0 +1,9 @@ +$musicalias D_E4M1 D_E3M4 +$musicalias D_E4M2 D_E3M2 +$musicalias D_E4M3 D_E3M3 +$musicalias D_E4M4 D_E1M5 +$musicalias D_E4M5 D_E2M7 +$musicalias D_E4M6 D_E2M4 +$musicalias D_E4M7 D_E2M6 +$musicalias D_E4M8 D_E2M5 +$musicalias D_E4M9 D_E1M9 diff --git a/wadsrc/static/language.def b/wadsrc/static/language.def index 79363d01a..337477ba6 100644 --- a/wadsrc/static/language.def +++ b/wadsrc/static/language.def @@ -34,6 +34,15 @@ MUSIC_E3M6 = "e3m6"; MUSIC_E3M7 = "e3m7"; MUSIC_E3M8 = "e3m8"; MUSIC_E3M9 = "e3m9"; +MUSIC_E4M1 = "e4m1"; +MUSIC_E4M2 = "e4m2"; +MUSIC_E4M3 = "e4m3"; +MUSIC_E4M4 = "e4m4"; +MUSIC_E4M5 = "e4m5"; +MUSIC_E4M6 = "e4m6"; +MUSIC_E4M7 = "e4m7"; +MUSIC_E4M8 = "e4m8"; +MUSIC_E4M9 = "e4m9"; MUSIC_INTER = "inter"; MUSIC_INTRO = "intro"; MUSIC_BUNNY = "bunny"; diff --git a/wadsrc/static/mapinfo/common.txt b/wadsrc/static/mapinfo/common.txt index e80a50105..7046f7308 100644 --- a/wadsrc/static/mapinfo/common.txt +++ b/wadsrc/static/mapinfo/common.txt @@ -44,6 +44,7 @@ DoomEdNums 5065 = InvisibleBridge8 9001 = MapSpot 9013 = MapSpotGravity + 9022 = PathNode 9024 = PatrolPoint 9025 = SecurityCamera 9026 = Spark diff --git a/wadsrc/static/mapinfo/doom1.txt b/wadsrc/static/mapinfo/doom1.txt index 8d8be2295..851171530 100644 --- a/wadsrc/static/mapinfo/doom1.txt +++ b/wadsrc/static/mapinfo/doom1.txt @@ -408,7 +408,7 @@ map E4M1 lookup "HUSTR_E4M1" sky1 = "SKY4" cluster = 4 par = 165 - music = "$MUSIC_E3M4" + music = "$MUSIC_E4M1" } map E4M2 lookup "HUSTR_E4M2" @@ -420,7 +420,7 @@ map E4M2 lookup "HUSTR_E4M2" sky1 = "SKY4" cluster = 4 par = 255 - music = "$MUSIC_E3M2" + music = "$MUSIC_E4M2" } map E4M3 lookup "HUSTR_E4M3" @@ -432,7 +432,7 @@ map E4M3 lookup "HUSTR_E4M3" sky1 = "SKY4" cluster = 4 par = 135 - music = "$MUSIC_E3M3" + music = "$MUSIC_E4M3" } map E4M4 lookup "HUSTR_E4M4" @@ -444,7 +444,7 @@ map E4M4 lookup "HUSTR_E4M4" sky1 = "SKY4" cluster = 4 par = 150 - music = "$MUSIC_E1M5" + music = "$MUSIC_E4M4" } map E4M5 lookup "HUSTR_E4M5" @@ -456,7 +456,7 @@ map E4M5 lookup "HUSTR_E4M5" sky1 = "SKY4" cluster = 4 par = 180 - music = "$MUSIC_E2M7" + music = "$MUSIC_E4M5" } map E4M6 lookup "HUSTR_E4M6" @@ -470,7 +470,7 @@ map E4M6 lookup "HUSTR_E4M6" par = 390 e4m6special specialaction_opendoor - music = "$MUSIC_E2M4" + music = "$MUSIC_E4M6" } map E4M7 lookup "HUSTR_E4M7" @@ -482,7 +482,7 @@ map E4M7 lookup "HUSTR_E4M7" sky1 = "SKY4" cluster = 4 par = 135 - music = "$MUSIC_E2M6" + music = "$MUSIC_E4M7" } map E4M8 lookup "HUSTR_E4M8" @@ -498,7 +498,7 @@ map E4M8 lookup "HUSTR_E4M8" nosoundclipping e4m8special specialaction_lowerfloor - music = "$MUSIC_E2M5" + music = "$MUSIC_E4M8" needclustertext } @@ -511,7 +511,7 @@ map E4M9 lookup "HUSTR_E4M9" sky1 = "SKY4" cluster = 4 par = 180 - music = "$MUSIC_E1M9" + music = "$MUSIC_E4M9" } // Clusters (correspond with same-numbered episode) diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 4a6cbb48d..7638b0b2b 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -739,6 +739,7 @@ OptionMenu "OtherControlsMenu" protected Control "$CNTRLMNU_ADJUST_GAMMA" , "bumpgamma" StaticText "" + Control "$CNTRLMNU_OPEN_MAIN" , "menu_main" Control "$CNTRLMNU_OPEN_HELP" , "menu_help" Control "$CNTRLMNU_OPEN_SAVE" , "menu_save" Control "$CNTRLMNU_OPEN_LOAD" , "menu_load" @@ -1299,6 +1300,7 @@ OptionMenu "AltHUDOptions" protected Option "$ALTHUDMNU_AMMOORDER", "hud_ammo_order", "AltHUDAmmoOrder" Slider "$ALTHUDMNU_AMMORED", "hud_ammo_red", 0, 100, 1, 0 Slider "$ALTHUDMNU_AMMOYELLOW", "hud_ammo_yellow", 0, 100, 1, 0 + Option "$ALTHUDMNU_SWAPHEALTHARMOR", "hud_swaphealtharmor", "OnOff" Slider "$ALTHUDMNU_HEALTHRED", "hud_health_red", 0, 100, 1, 0 Slider "$ALTHUDMNU_HEALTHYELLOW", "hud_health_yellow", 0, 100, 1, 0 Slider "$ALTHUDMNU_HEALTHGREEN", "hud_health_green", 0, 100, 1, 0 @@ -1815,6 +1817,8 @@ OptionMenu CoopOptions protected Title "$GMPLYMNU_COOPERATIVE" Option "$GMPLYMNU_MULTIPLAYERTHINGS", "sv_nothingspawn", "NoYes" + Option "$GMPLYMNU_COOPTHINGS", "sv_nocoopthings", "NoYes" + Option "$GMPLYMNU_COOPITEMS", "sv_nocoopitems", "NoYes" Option "$GMPLYMNU_MULTIPLAYERWEAPONS", "sv_noweaponspawn", "NoYes" Option "$GMPLYMNU_LOSEINVENTORY", "sv_cooploseinventory", "YesNo" Option "$GMPLYMNU_KEEPKEYS", "sv_cooplosekeys", "NoYes" @@ -1826,6 +1830,8 @@ OptionMenu CoopOptions protected Option "$GMPLYMNU_SPAWNWHEREDIED", "sv_samespawnspot", "YesNo" Option "$GMPLYMNU_NOPLAYERCLIP", "sv_noplayerclip", "YesNo" Option "$GMPLYMNU_SHAREKEYS", "sv_coopsharekeys", "YesNo" + Option "$GMPLYMNU_LOCALITEMS", "sv_localitems", "YesNo" + Option "$GMPLYMNU_NOLOCALDROP", "sv_nolocaldrops", "YesNo" Class "GameplayMenu" } diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index d3395e656..9c694e38f 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -260,6 +260,8 @@ class Actor : Thinker native native readonly int SpawnTime; private native int InventoryID; // internal counter. native uint freezetics; + native Vector2 AutomapOffsets; + native Array Path; meta String Obituary; // Player was killed by this actor meta String HitObituary; // Player was killed by this actor in melee @@ -364,6 +366,7 @@ class Actor : Thinker native property LightLevel: LightLevel; property ShadowAimFactor: ShadowAimFactor; property ShadowPenaltyFactor: ShadowPenaltyFactor; + property AutomapOffsets : AutomapOffsets; // need some definition work first //FRenderStyle RenderStyle; @@ -443,6 +446,7 @@ class Actor : Thinker native SelfDamageFactor 1; ShadowAimFactor 1; ShadowPenaltyFactor 1; + AutomapOffsets (0,0); StealthAlpha 0; WoundHealth 6; GibHealth int.min; @@ -498,8 +502,10 @@ class Actor : Thinker native virtual native bool Slam(Actor victim); virtual void Touch(Actor toucher) {} virtual native void FallAndSink(double grav, double oldfloorz); - private native void Substitute(Actor replacement); + native bool MorphInto(Actor morph); native ui void DisplayNameTag(); + native clearscope void DisableLocalRendering(uint playerNum, bool disable); + native ui bool ShouldRenderLocally(); // Only clients get to check this, never the playsim. // Called by inventory items to see if this actor is capable of touching them. // If true, the item will attempt to be picked up. Useful for things like @@ -560,7 +566,7 @@ class Actor : Thinker native } // This is called when a missile bounces off something. - virtual int SpecialBounceHit(Actor bounceMobj, Line bounceLine, SecPlane bouncePlane) + virtual int SpecialBounceHit(Actor bounceMobj, Line bounceLine, readonly bouncePlane) { return MHIT_DEFAULT; } @@ -658,7 +664,7 @@ class Actor : Thinker native // called before and after triggering a teleporter // return false in PreTeleport() to cancel the action early virtual bool PreTeleport( Vector3 destpos, double destangle, int flags ) { return true; } - virtual void PostTeleport( Vector3 destpos, double destangle, int flags ) {} + virtual void PostTeleport( Vector3 destpos, double destangle, int flags ) { } native virtual bool OkayToSwitchTarget(Actor other); native clearscope static class GetReplacement(class cls); @@ -692,7 +698,7 @@ class Actor : Thinker native native void SoundAlert(Actor target, bool splash = false, double maxdist = 0); native void ClearBounce(); native TerrainDef GetFloorTerrain(); - native bool CheckLocalView(int consoleplayer = -1 /* parameter is not used anymore but needed for backward compatibilityö. */); + native bool CheckLocalView(int consoleplayer = -1 /* parameter is not used anymore but needed for backward compatibility. */); native bool CheckNoDelay(); native bool UpdateWaterLevel (bool splash = true); native bool IsZeroDamage(); @@ -769,6 +775,7 @@ class Actor : Thinker native native bool LineTrace(double angle, double distance, double pitch, int flags = 0, double offsetz = 0., double offsetforward = 0., double offsetside = 0., out FLineTraceData data = null); native bool CheckSight(Actor target, int flags = 0); native bool IsVisible(Actor other, bool allaround, LookExParams params = null); + native bool, Actor, double PerformShadowChecks (Actor other, Vector3 pos); native bool HitFriend(); native bool MonsterMove(); @@ -790,6 +797,72 @@ class Actor : Thinker native movecount = random[TryWalk](0, 15); return true; } + + native void ClearPath(); + native clearscope bool CanPathfind() const; + virtual void ReachedNode(Actor mo) + { + if (!mo) + { + if (!goal) + return; + mo = goal; + } + + let node = PathNode(mo); + if (!node || !target || (!bKEEPPATH && CheckSight(target))) + { + ClearPath(); + return; + } + + int i = Path.Find(node) + 1; + int end = Path.Size(); + + for (i; i < end; i++) + { + PathNode next = Path[i]; + + if (!next || next == node) + continue; + + // Monsters will never 'reach' AMBUSH flagged nodes. Instead, the engine + // indicates they're reached the moment they tele/portal. + + if (node.bAMBUSH && next.bAMBUSH) + continue; + + goal = next; + break; + } + + if (i >= end) + ClearPath(); + + } + + // Return true to mark the node as ineligible for constructing a path along. + virtual bool ExcludeNode(PathNode node) + { + if (!node) return true; + + // Scale is the size requirements. + // STANDSTILL flag is used to require the actor to be bigger instead of smaller. + double r = node.Scale.X; + double h = node.Scale.Y; + + if (r <= 0.0 && h <= 0.0) + return false; + + // Perfect fit. + if (radius == r && height == h) + return false; + + if ((r < radius) || (h < height)) + return !node.bSTANDSTILL; + + return false; + } native bool TryMove(vector2 newpos, int dropoff, bool missilecheck = false, FCheckPosition tm = null); native bool CheckMove(vector2 newpos, int flags = 0, FCheckPosition tm = null); @@ -798,6 +871,7 @@ class Actor : Thinker native native bool CheckMissileRange(); native bool SetState(state st, bool nofunction = false); clearscope native state FindState(statelabel st, bool exact = false) const; + clearscope native state FindStateByString(string st, bool exact = false) const; bool SetStateLabel(statelabel st, bool nofunction = false) { return SetState(FindState(st), nofunction); } native action state ResolveState(statelabel st); // this one, unlike FindState, is context aware. native void LinkToWorld(LinkContext ctx = null); @@ -840,7 +914,7 @@ class Actor : Thinker native native void PlayPushSound(); native bool BounceActor(Actor blocking, bool onTop); native bool BounceWall(Line l = null); - native bool BouncePlane(SecPlane plane); + native bool BouncePlane(readonly plane); native void PlayBounceSound(bool onFloor); native bool ReflectOffActor(Actor blocking); @@ -1399,7 +1473,7 @@ class Actor : Thinker native bool grunted; // [RH] only make noise if alive - if (self.health > 0 && self.player.morphTics == 0) + if (self.health > 0 && !Alternative) { grunted = false; // Why should this number vary by gravity? diff --git a/wadsrc/static/zscript/actors/heretic/chicken.zs b/wadsrc/static/zscript/actors/heretic/chicken.zs index 4bc2e20de..a49418ca3 100644 --- a/wadsrc/static/zscript/actors/heretic/chicken.zs +++ b/wadsrc/static/zscript/actors/heretic/chicken.zs @@ -221,7 +221,7 @@ class ChickenPlayer : PlayerPawn pspr.y = WEAPONTOP + player.chickenPeck / 2; } } - if (player.morphTics & 15) + if ((player.MorphTics ? player.MorphTics : Random[ChickenPlayerThink]()) & 15) { return; } diff --git a/wadsrc/static/zscript/actors/heretic/hereticartifacts.zs b/wadsrc/static/zscript/actors/heretic/hereticartifacts.zs index d12bf2b19..b620b04b4 100644 --- a/wadsrc/static/zscript/actors/heretic/hereticartifacts.zs +++ b/wadsrc/static/zscript/actors/heretic/hereticartifacts.zs @@ -66,28 +66,26 @@ Class ArtiTomeOfPower : PowerupGiver Loop; } - override bool Use (bool pickup) + override bool Use(bool pickup) { - Playerinfo p = Owner.player; - if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYTOMEOFPOWER)) - { // Attempt to undo chicken - if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYTOMEOFPOWER)) - { // Failed - if (!(p.MorphStyle & MRF_FAILNOTELEFRAG)) - { - Owner.DamageMobj (null, null, TELEFRAG_DAMAGE, 'Telefrag'); - } + EMorphFlags mStyle = Owner.GetMorphStyle(); + if (Owner.Alternative && (mStyle & MRF_UNDOBYTOMEOFPOWER)) + { + // Attempt to undo chicken. + if (!Owner.Unmorph(Owner, MRF_UNDOBYTOMEOFPOWER)) + { + if (!(mStyle & MRF_FAILNOTELEFRAG)) + Owner.DamageMobj(null, null, TELEFRAG_DAMAGE, 'Telefrag'); } - else - { // Succeeded - Owner.A_StartSound ("*evillaugh", CHAN_VOICE); + else if (Owner.player) + { + Owner.A_StartSound("*evillaugh", CHAN_VOICE); } + return true; } - else - { - return Super.Use (pickup); - } + + return Super.Use(pickup); } } diff --git a/wadsrc/static/zscript/actors/hexen/pig.zs b/wadsrc/static/zscript/actors/hexen/pig.zs index 45c601c4e..95e90722e 100644 --- a/wadsrc/static/zscript/actors/hexen/pig.zs +++ b/wadsrc/static/zscript/actors/hexen/pig.zs @@ -145,7 +145,7 @@ class PigPlayer : PlayerPawn override void MorphPlayerThink () { - if (player.morphTics & 15) + if ((player.MorphTics ? player.MorphTics : Random[PigPlayerThink]()) & 15) { return; } diff --git a/wadsrc/static/zscript/actors/inventory/inv_misc.zs b/wadsrc/static/zscript/actors/inventory/inv_misc.zs index 5fc4e3fe2..318d29491 100644 --- a/wadsrc/static/zscript/actors/inventory/inv_misc.zs +++ b/wadsrc/static/zscript/actors/inventory/inv_misc.zs @@ -33,6 +33,7 @@ class Key : Inventory Default { +DONTGIB; // Don't disappear due to a crusher + +INVENTORY.ISKEYITEM; Inventory.InterHubAmount 0; Inventory.PickupSound "misc/k_pkup"; } @@ -59,28 +60,6 @@ class Key : Inventory return false; } - override void AttachToOwner(Actor other) - { - Super.AttachToOwner(other); - - if (multiplayer && !deathmatch && sv_coopsharekeys) - { - for (int i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i]) - { - let pmo = players[i].mo; - - if (pmo == other) - continue; - - if (!pmo.FindInventory(GetClass())) - pmo.GiveInventoryType(GetClass()); - } - } - } - } - override bool ShouldStay () { return !!multiplayer; @@ -127,6 +106,7 @@ class PuzzleItem : Inventory { +NOGRAVITY +INVENTORY.INVBAR + +INVENTORY.ISKEYITEM Inventory.DefMaxAmount; Inventory.UseSound "PuzzleSuccess"; Inventory.PickupSound "misc/i_pkup"; diff --git a/wadsrc/static/zscript/actors/inventory/inventory.zs b/wadsrc/static/zscript/actors/inventory/inventory.zs index ddcbb68be..4108650a4 100644 --- a/wadsrc/static/zscript/actors/inventory/inventory.zs +++ b/wadsrc/static/zscript/actors/inventory/inventory.zs @@ -10,6 +10,9 @@ class Inventory : Actor const BLINKTHRESHOLD = (4*32); const BONUSADD = 6; + private bool bSharingItem; // Currently being shared (avoid infinite recursions). + private bool pickedUp[MAXPLAYERS]; // If items are set to local, track who already picked it up. + deprecated("3.7") private int ItemFlags; Actor Owner; // Who owns this item? NULL if it's still a pickup. int Amount; // Amount of item this instance has @@ -65,6 +68,8 @@ class Inventory : Actor flagdef IsHealth: ItemFlags, 22; flagdef AlwaysPickup: ItemFlags, 23; flagdef Unclearable: ItemFlags, 24; + flagdef NeverLocal: ItemFlags, 25; + flagdef IsKeyItem: ItemFlags, 26; flagdef ForceRespawnInSurvival: none, 0; flagdef PickupFlash: none, 6; @@ -255,6 +260,43 @@ class Inventory : Actor } } + 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) + continue; + + item.bSharingItem = true; + if (!item.CallTryPickup(players[i].mo)) + { + item.Destroy(); + continue; + } + item.bSharingItem = false; + + if (!bQuiet) + { + PlayPickupSound(players[i].mo); + if (!bNoScreenFlash && players[i].PlayerState != PST_DEAD) + players[i].BonusCount = BONUSADD; + } + } + + if (!bQuiet && consoleplayer != skip) + PrintPickupMessage(true, PickupMessage()); + } + //=========================================================================== // // Inventory :: DoRespawn @@ -647,6 +689,10 @@ class Inventory : Actor } // [AA] Let the toucher do something with the item they've just received: toucher.HasReceived(self); + + // If the item can be shared, make sure every player gets a copy. + if (multiplayer && !deathmatch && sv_coopsharekeys && bIsKeyItem) + ShareItemWithPlayers(toucher); } return res, toucher; } @@ -768,12 +814,17 @@ class Inventory : Actor override void Touch (Actor toucher) { + bool localPickUp; let player = toucher.player; - - // If a voodoo doll touches something, pretend the real player touched it instead. - if (player != NULL) + if (player) { + // If a voodoo doll touches something, pretend the real player touched it instead. toucher = player.mo; + // Client already picked this up, so ignore them. + if (HasPickedUpLocally(toucher)) + return; + + localPickUp = CanPickUpLocally(toucher) && !ShouldStay() && !ShouldRespawn(); } bool localview = toucher.CheckLocalView(); @@ -781,9 +832,23 @@ class Inventory : Actor if (!toucher.CanTouchItem(self)) return; + Inventory give = self; + if (localPickUp) + { + give = Inventory(Spawn(GetClass())); + if (!give) + return; + } + bool res; - [res, toucher] = CallTryPickup(toucher); - if (!res) return; + [res, toucher] = give.CallTryPickup(toucher); + if (!res) + { + if (give != self) + give.Destroy(); + + return; + } // This is the only situation when a pickup flash should ever play. if (PickupFlash != NULL && !ShouldStay()) @@ -829,6 +894,9 @@ class Inventory : Actor ac.GiveSecret(true, true); } + if (localPickUp) + PickUpLocally(toucher); + //Added by MC: Check if item taken was the roam destination of any bot for (int i = 0; i < MAXPLAYERS; i++) { @@ -1014,6 +1082,37 @@ class Inventory : Actor SetStateLabel("HoldAndDestroy"); } } + + // Check if the Actor can recieve a local copy of the item instead of outright taking it. + clearscope bool CanPickUpLocally(Actor other) const + { + return other && other.player + && multiplayer && !deathmatch && sv_localitems + && !bNeverLocal && (!bDropped || !sv_nolocaldrops); + } + + // Check if a client has already picked up this item locally. + clearscope bool HasPickedUpLocally(Actor client) const + { + return pickedUp[client.PlayerNumber()]; + } + + // When items are dropped, clear their local pick ups. + void ClearLocalPickUps() + { + DisableLocalRendering(consoleplayer, false); + for (int i; i < MAXPLAYERS; ++i) + pickedUp[i] = false; + } + + // Client picked up this item. Mark it as invisible to that specific player and + // prevent them from picking it up again. + protected void PickUpLocally(Actor client) + { + int pNum = client.PlayerNumber(); + pickedUp[pNum] = true; + DisableLocalRendering(pNum, true); + } //=========================================================================== // diff --git a/wadsrc/static/zscript/actors/inventory/powerups.zs b/wadsrc/static/zscript/actors/inventory/powerups.zs index 9c4903d01..76b0fea2d 100644 --- a/wadsrc/static/zscript/actors/inventory/powerups.zs +++ b/wadsrc/static/zscript/actors/inventory/powerups.zs @@ -1870,13 +1870,14 @@ class PowerReflection : Powerup //=========================================================================== // // PowerMorph +// Now works with monsters too! // //=========================================================================== class PowerMorph : Powerup { - Class PlayerClass; - Class MorphFlash, UnMorphFlash; + class PlayerClass; + class MonsterClass, MorphFlash, UnmorphFlash; int MorphStyle; PlayerInfo MorphedPlayer; @@ -1895,19 +1896,19 @@ class PowerMorph : Powerup { Super.InitEffect(); - if (Owner != null && Owner.player != null && PlayerClass != null) + if (!Owner) + return; + + if (Owner.Morph(Owner, PlayerClass, MonsterClass, int.max, MorphStyle, MorphFlash, UnmorphFlash)) { - let realplayer = Owner.player; // Remember the identity of the player - if (realplayer.mo.MorphPlayer(realplayer, PlayerClass, 0x7fffffff/*INDEFINITELY*/, MorphStyle, MorphFlash, UnMorphFlash)) - { - Owner = realplayer.mo; // Replace the new owner in our owner; safe because we are not attached to anything yet - bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor) - MorphedPlayer = realplayer; // Store the player identity (morphing clears the unmorphed actor's "player" field) - } - else // morph failed - give the caller an opportunity to fail the pickup completely - { - bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate) - } + // Get the real owner; safe because we are not attached to anything yet. + Owner = Owner.Alternative; + bCreateCopyMoved = true; // Let the caller know the "real" owner has changed (to the morphed actor). + MorphedPlayer = Owner.player; + } + else + { + bInitEffectFailed = true; // Let the caller know that the activation failed (can fail the pickup if appropriate). } } @@ -1921,24 +1922,16 @@ class PowerMorph : Powerup { Super.EndEffect(); - // Abort if owner already destroyed or unmorphed - if (Owner == null || MorphedPlayer == null || Owner.alternative == null) - { + // Abort if owner already destroyed or unmorphed. + if (!Owner || !Owner.Alternative) return; - } // Abort if owner is dead; their Die() method will // take care of any required unmorphing on death. - if (MorphedPlayer.health <= 0) - { + if (Owner.player ? Owner.player.Health <= 0 : Owner.Health <= 0) return; - } - int savedMorphTics = MorphedPlayer.morphTics; - MorphedPlayer.mo.UndoPlayerMorph (MorphedPlayer, 0, !!(MorphedPlayer.MorphStyle & MRF_UNDOALWAYS)); + Owner.Unmorph(Owner, force: Owner.GetMorphStyle() & MRF_UNDOALWAYS); MorphedPlayer = null; } - - } - diff --git a/wadsrc/static/zscript/actors/inventory/weapons.zs b/wadsrc/static/zscript/actors/inventory/weapons.zs index 49e32f3a2..66a7f8964 100644 --- a/wadsrc/static/zscript/actors/inventory/weapons.zs +++ b/wadsrc/static/zscript/actors/inventory/weapons.zs @@ -262,7 +262,7 @@ class Weapon : StateProvider } let psp = player.GetPSprite(PSP_WEAPON); if (!psp) return; - if (player.morphTics || player.cheats & CF_INSTANTWEAPSWITCH) + if (Alternative || player.cheats & CF_INSTANTWEAPSWITCH) { psp.y = WEAPONBOTTOM; } diff --git a/wadsrc/static/zscript/actors/inventory_util.zs b/wadsrc/static/zscript/actors/inventory_util.zs index e1820a86d..1bb9be512 100644 --- a/wadsrc/static/zscript/actors/inventory_util.zs +++ b/wadsrc/static/zscript/actors/inventory_util.zs @@ -291,6 +291,8 @@ extend class Actor { Inventory drop = item.CreateTossable(amt); if (drop == null) return NULL; + drop.ClearLocalPickUps(); + drop.bNeverLocal = true; drop.SetOrigin(Pos + (0, 0, 10.), false); drop.Angle = Angle; drop.VelFromAngle(5.); diff --git a/wadsrc/static/zscript/actors/morph.zs b/wadsrc/static/zscript/actors/morph.zs index 1dcfb31fa..b9fdbb101 100644 --- a/wadsrc/static/zscript/actors/morph.zs +++ b/wadsrc/static/zscript/actors/morph.zs @@ -23,8 +23,78 @@ extend class Actor { + // Blockmap, sector, and no interaction are the only relevant flags but the old ones are kept around + // for legacy reasons. + enum EPremorphProperty + { + MPROP_SOLID = 1 << 1, + MPROP_SHOOTABLE = 1 << 2, + MPROP_NO_BLOCKMAP = 1 << 3, + MPROP_NO_SECTOR = 1 << 4, + MPROP_NO_INTERACTION = 1 << 5, + MPROP_INVIS = 1 << 6, + } + + int UnmorphTime; + EMorphFlags MorphFlags; + class MorphExitFlash; + EPremorphProperty PremorphProperties; + + // Players still track these separately for legacy reasons. + void SetMorphStyle(EMorphFlags flags) + { + if (player) + player.MorphStyle = flags; + else + MorphFlags = flags; + } + + clearscope EMorphFlags GetMorphStyle() const + { + return player ? player.MorphStyle : MorphFlags; + } + + void SetMorphExitFlash(class flash) + { + if (player) + player.MorphExitFlash = flash; + else + MorphExitFlash = flash; + } + + clearscope class GetMorphExitFlash() const + { + return player ? player.MorphExitFlash : MorphExitFlash; + } + + void SetMorphTics(int dur) + { + if (player) + player.MorphTics = dur; + else + UnmorphTime = Level.Time + dur; + } + + clearscope int GetMorphTics() const + { + if (player) + return player.MorphTics; + + if (UnmorphTime <= 0) + return UnmorphTime; + + return UnmorphTime > Level.Time ? UnmorphTime - Level.Time : 0; + } + + // This function doesn't return anything anymore since allowing so would be too volatile + // for morphing management. Instead it's now a function that lets special actions occur + // when a morphed Actor dies. virtual Actor, int, int MorphedDeath() { + EMorphFlags mStyle = GetMorphStyle(); + if (mStyle & MRF_UNDOBYDEATH) + Unmorph(self, force: mStyle & MRF_UNDOBYDEATHFORCED); + return null, 0, 0; } @@ -40,16 +110,12 @@ extend class Actor // //=========================================================================== - virtual bool Morph(Actor activator, class playerclass, class monsterclass, int duration = 0, int style = 0, class morphflash = null, classunmorphflash = null) + virtual bool Morph(Actor activator, class playerClass, class monsterClass, int duration = 0, EMorphFlags style = 0, class morphFlash = "TeleportFog", class unmorphFlash = "TeleportFog") { - if (player != null && player.mo != null && playerclass != null) - { - return player.mo.MorphPlayer(activator? activator.player : null, playerclass, duration, style, morphflash, unmorphflash); - } - else - { - return MorphMonster(monsterclass, duration, style, morphflash, unmorphflash); - } + if (player) + return player.mo.MorphPlayer(activator ? activator.player : null, playerClass, duration, style, morphFlash, unmorphFlash); + + return MorphMonster(monsterClass, duration, style, morphFlash, unmorphFlash); } //=========================================================================== @@ -58,18 +124,9 @@ extend class Actor // //=========================================================================== - bool A_Morph(class type, int duration = 0, int style = 0, class morphflash = null, classunmorphflash = null) + bool A_Morph(class type, int duration = 0, EMorphFlags style = 0, class morphFlash = "TeleportFog", class unmorphFlash = "TeleportFog") { - if (self.player != null) - { - let playerclass = (class)(type); - if (playerclass && self.player.mo != null) return player.mo.MorphPlayer(self.player, playerclass, duration, style, morphflash, unmorphflash); - } - else - { - return MorphMonster(type, duration, style, morphflash, unmorphflash); - } - return false; + return Morph(self, (class)(type), type, duration, style, morphFlash, unmorphFlash); } //=========================================================================== @@ -78,21 +135,18 @@ extend class Actor // //=========================================================================== - virtual bool UnMorph(Actor activator, int flags, bool force) + virtual bool Unmorph(Actor activator, EMorphFlags flags = 0, bool force = false) { if (player) - { - return player.mo.UndoPlayerMorph(activator? activator.player : null, flags, force); - } - else - { - let morphed = MorphedMonster(self); - if (morphed) - return morphed.UndoMonsterMorph(force); - } - return false; + return player.mo.UndoPlayerMorph(activator ? activator.player : null, flags, force); + + return UndoMonsterMorph(force); } + virtual bool CheckUnmorph() + { + return UnmorphTime && UnmorphTime <= Level.Time && Unmorph(self, MRF_UNDOBYTIMEOUT); + } //--------------------------------------------------------------------------- // @@ -102,57 +156,265 @@ extend class Actor // //--------------------------------------------------------------------------- - virtual bool MorphMonster (Class spawntype, int duration, int style, Class enter_flash, Class exit_flash) + virtual bool MorphMonster(class spawnType, int duration, EMorphFlags style, class enterFlash = "TeleportFog", class exitFlash = "TeleportFog") { - if (player || spawntype == NULL || bDontMorph || !bIsMonster || !(spawntype is 'MorphedMonster')) + if (player || !bIsMonster || !spawnType || bDontMorph || Health <= 0 || spawnType == GetClass()) + return false; + + Actor morphed = Spawn(spawnType, Pos, ALLOW_REPLACE); + if (!MorphInto(morphed)) { + if (morphed) + { + morphed.ClearCounters(); + morphed.Destroy(); + } + return false; } - let morphed = MorphedMonster(Spawn (spawntype, Pos, NO_REPLACE)); - // [MC] Notify that we're just about to start the transfer. PreMorph(morphed, false); // False: No longer the current. morphed.PreMorph(self, true); // True: Becoming this actor. - Substitute (morphed); if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate) - { morphed.Translation = Translation; - } - morphed.ChangeTid(tid); - ChangeTid(0); + morphed.Angle = Angle; - morphed.UnmorphedMe = self; morphed.Alpha = Alpha; morphed.RenderStyle = RenderStyle; - morphed.Score = Score; - - morphed.UnmorphTime = level.time + ((duration) ? duration : DEFMORPHTICS) + random[morphmonst](); - morphed.MorphStyle = style; - morphed.MorphExitFlash = (exit_flash) ? exit_flash : (class)("TeleportFog"); - morphed.FlagsSave = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility - - morphed.special = special; - morphed.args[0] = args[0]; - morphed.args[1] = args[1]; - morphed.args[2] = args[2]; - morphed.args[3] = args[3]; - morphed.args[4] = args[4]; - morphed.CopyFriendliness (self, true); morphed.bShadow |= bShadow; morphed.bGhost |= bGhost; - special = 0; - bSolid = false; - bShootable = false; - bUnmorphed = true; + morphed.Score = Score; + morphed.Special = Special; + for (int i; i < 5; ++i) + morphed.Args[i] = Args[i]; + + if (TID && (style & MRF_NEWTIDBEHAVIOUR)) + { + morphed.ChangeTID(TID); + ChangeTID(0); + } + + morphed.Tracer = Tracer; + morphed.Master = Master; + morphed.CopyFriendliness(self, true); + + // Remove all armor. + if (!(style & MRF_KEEPARMOR)) + { + for (Inventory item = morphed.Inv; item;) + { + Inventory next = item.Inv; + if (item is "Armor") + item.DepleteOrDestroy(); + + item = next; + } + } + + morphed.SetMorphTics((duration ? duration : DEFMORPHTICS) + Random[morphmonst]()); + morphed.SetMorphStyle(style); + morphed.SetMorphExitFlash(exitFlash); + morphed.PremorphProperties = (bSolid * MPROP_SOLID) | (bShootable * MPROP_SHOOTABLE) + | (bNoBlockmap * MPROP_NO_BLOCKMAP) | (bNoSector * MPROP_NO_SECTOR) + | (bNoInteraction * MPROP_NO_INTERACTION) | (bInvisible * MPROP_INVIS); + + // This is just here for backwards compatibility as MorphedMonster used to be required. + let morphMon = MorphedMonster(morphed); + if (morphMon) + { + morphMon.UnmorphedMe = morphMon.Alternative; + morphMon.MorphStyle = morphMon.GetMorphStyle(); + morphMon.FlagsSave = morphMon.PremorphProperties; + } + + Special = 0; + bNoInteraction = true; + A_ChangeLinkFlags(true, true); + + // Legacy bInvisible = true; - let eflash = Spawn(enter_flash ? enter_flash : (class)("TeleportFog"), Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE); - if (eflash) - eflash.target = morphed; + bSolid = bShootable = false; + PostMorph(morphed, false); morphed.PostMorph(self, true); + + if (enterFlash) + { + Actor fog = Spawn(enterFlash, morphed.Pos.PlusZ(GameInfo.TELEFOGHEIGHT), ALLOW_REPLACE); + if (fog) + fog.Target = morphed; + } + + return true; + } + + //---------------------------------------------------------------------------- + // + // FUNC P_UndoMonsterMorph + // + // Returns true if the monster unmorphs. + // + //---------------------------------------------------------------------------- + + virtual bool UndoMonsterMorph(bool force = false) + { + if (!Alternative || bStayMorphed || Alternative.bStayMorphed) + return false; + + Actor alt = Alternative; + alt.SetOrigin(Pos, false); + if (!force && (PremorphProperties & MPROP_SOLID)) + { + bool altSolid = alt.bSolid; + bool isSolid = bSolid; + bool isTouchy = bTouchy; + + alt.bSolid = true; + bSolid = bTouchy = false; + + bool res = alt.TestMobjLocation(); + + alt.bSolid = altSolid; + bSolid = isSolid; + bTouchy = isTouchy; + + // Didn't fit. + if (!res) + { + SetMorphTics(5 * TICRATE); + return false; + } + } + + if (!MorphInto(alt)) + return false; + + PreUnmorph(alt, false); + alt.PreUnmorph(self, true); + + // Check to see if it had a powerup that caused it to morph. + for (Inventory item = alt.Inv; item;) + { + Inventory next = item.Inv; + if (item is "PowerMorph") + item.Destroy(); + + item = next; + } + + alt.Angle = Angle; + alt.bShadow = bShadow; + alt.bGhost = bGhost; + alt.bSolid = (PremorphProperties & MPROP_SOLID); + alt.bShootable = (PremorphProperties & MPROP_SHOOTABLE); + alt.bInvisible = (PremorphProperties & MPROP_INVIS); + alt.Vel = Vel; + alt.Score = Score; + + alt.bNoInteraction = (PremorphProperties & MPROP_NO_INTERACTION); + alt.A_ChangeLinkFlags((PremorphProperties & MPROP_NO_BLOCKMAP), (PremorphProperties & MPROP_NO_SECTOR)); + + EMorphFlags style = GetMorphStyle(); + if (TID && (style & MRF_NEWTIDBEHAVIOUR)) + { + alt.ChangeTID(TID); + ChangeTID(0); + } + + alt.Special = Special; + for (int i; i < 5; ++i) + alt.Args[i] = Args[i]; + + alt.Tracer = Tracer; + alt.Master = Master; + alt.CopyFriendliness(self, true, false); + if (Health > 0 || (style & MRF_UNDOBYDEATHSAVES)) + alt.Health = alt.SpawnHealth(); + else + alt.Health = Health; + + Special = 0; + + PostUnmorph(alt, false); // From is false here: Leaving the caller's body. + alt.PostUnmorph(self, true); // True here: Entering this body from here. + + if (MorphExitFlash) + { + Actor fog = Spawn(MorphExitFlash, alt.Pos.PlusZ(GameInfo.TELEFOGHEIGHT), ALLOW_REPLACE); + if (fog) + fog.Target = alt; + } + + ClearCounters(); + Destroy(); return true; } } +//=========================================================================== +// +// +// +//=========================================================================== + +class MorphProjectile : Actor +{ + class PlayerClass; + class MonsterClass, MorphFlash, UnmorphFlash; + int Duration, MorphStyle; + + Default + { + Damage 1; + Projectile; + MorphProjectile.MorphFlash "TeleportFog"; + MorphProjectile.UnmorphFlash "TeleportFog"; + + -ACTIVATEIMPACT + -ACTIVATEPCROSS + } + + override int DoSpecialDamage(Actor victim, int dmg, Name dmgType) + { + victim.Morph(Target, PlayerClass, MonsterClass, Duration, MorphStyle, MorphFlash, UnmorphFlash); + return -1; + } +} + +//=========================================================================== +// +// This class is redundant as it's no longer necessary for monsters to +// morph but is kept here for compatibility. Its previous fields either exist +// in the base Actor now or are just a shell for the actual fields +// which either already existed and weren't used for some reason or needed a +// better name. +// +//=========================================================================== + +class MorphedMonster : Actor +{ + Actor UnmorphedMe; + EMorphFlags MorphStyle; + EPremorphProperty FlagsSave; + + Default + { + Monster; + + +FLOORCLIP + -COUNTKILL + } + + // Make sure changes to these are propogated correctly when unmorphing. This is only + // set for monsters since originally players and this class were the only ones + // that were considered valid for morphing. + override bool UndoMonsterMorph(bool force) + { + Alternative = UnmorphedMe; + SetMorphStyle(MorphStyle); + PremorphProperties = FlagsSave; + return super.UndoMonsterMorph(force); + } +} diff --git a/wadsrc/static/zscript/actors/player/player.zs b/wadsrc/static/zscript/actors/player/player.zs index 235b97c8d..d87fc1706 100644 --- a/wadsrc/static/zscript/actors/player/player.zs +++ b/wadsrc/static/zscript/actors/player/player.zs @@ -40,7 +40,7 @@ class PlayerPawn : Actor double SideMove1, SideMove2; TextureID ScoreIcon; int SpawnMask; - Name MorphWeapon; + Name MorphWeapon; // This should really be a class but it's too late to change now. double AttackZOffset; // attack height, relative to player center double UseRange; // [NS] Distance at which player can +use double AirCapacity; // Multiplier for air supply underwater. @@ -477,7 +477,7 @@ class PlayerPawn : Actor let player = self.player; if (!player) return; if ((player.WeaponState & WF_DISABLESWITCH) || // Weapon changing has been disabled. - player.morphTics != 0) // Morphed classes cannot change weapons. + Alternative) // Morphed classes cannot change weapons. { // ...so throw away any pending weapon requests. player.PendingWeapon = WP_NOCHANGE; } @@ -651,7 +651,7 @@ class PlayerPawn : Actor } } - if (player.morphTics) + if (Alternative) { bob = 0; } @@ -1072,7 +1072,7 @@ class PlayerPawn : Actor virtual bool CanCrouch() const { - return player.morphTics == 0 || bCrouchableMorph; + return !Alternative || bCrouchableMorph; } //---------------------------------------------------------------------------- @@ -1238,7 +1238,7 @@ class PlayerPawn : Actor side *= SideMove2; } - if (!player.morphTics) + if (!Alternative) { double factor = 1.; for(let it = Inv; it != null; it = it.Inv) @@ -1528,15 +1528,15 @@ class PlayerPawn : Actor { let player = self.player; // Morph counter - if (player.morphTics) + if (Alternative) { if (player.chickenPeck) { // Chicken attack counter player.chickenPeck -= 3; } - if (!--player.morphTics) + if (player.MorphTics && !--player.MorphTics) { // Attempt to undo the chicken/pig - player.mo.UndoPlayerMorph(player, MRF_UNDOBYTIMEOUT); + Unmorph(self, MRF_UNDOBYTIMEOUT); } } } @@ -1661,7 +1661,7 @@ class PlayerPawn : Actor player.jumpTics = 0; } } - if (player.morphTics && !(player.cheats & CF_PREDICTING)) + if (Alternative && !(player.cheats & CF_PREDICTING)) { MorphPlayerThink (); } @@ -2042,9 +2042,9 @@ class PlayerPawn : Actor Inventory item, next; let p = player; - if (p.morphTics != 0) + if (Alternative) { // Undo morph - Unmorph(self, 0, true); + Unmorph(self, force: true); } // 'self' will be no longer valid from here on in case of an unmorph let me = p.mo; @@ -2851,23 +2851,15 @@ struct PlayerInfo native play // self is what internally is known as player_t native clearscope bool HasWeaponsInSlot(int slot) const; // The actual implementation is on PlayerPawn where it can be overridden. Use that directly in the future. - deprecated("3.7", "MorphPlayer() should be used on a PlayerPawn object") bool MorphPlayer(playerinfo p, Class spawntype, int duration, int style, Class enter_flash = null, Class exit_flash = null) + deprecated("3.7", "MorphPlayer() should be used on a PlayerPawn object") bool MorphPlayer(PlayerInfo activator, class spawnType, int duration, EMorphFlags style, class enterFlash = "TeleportFog", class exitFlash = "TeleportFog") { - if (mo != null) - { - return mo.MorphPlayer(p, spawntype, duration, style, enter_flash, exit_flash); - } - return false; + return mo ? mo.MorphPlayer(activator, spawnType, duration, style, enterFlash, exitFlash) : false; } // This somehow got its arguments mixed up. 'self' should have been the player to be unmorphed, not the activator - deprecated("3.7", "UndoPlayerMorph() should be used on a PlayerPawn object") bool UndoPlayerMorph(playerinfo player, int unmorphflag = 0, bool force = false) + deprecated("3.7", "UndoPlayerMorph() should be used on a PlayerPawn object") bool UndoPlayerMorph(PlayerInfo player, EMorphFlags unmorphFlags = 0, bool force = false) { - if (player.mo != null) - { - return player.mo.UndoPlayerMorph(self, unmorphflag, force); - } - return false; + return player.mo ? player.mo.UndoPlayerMorph(self, unmorphFlags, force) : false; } deprecated("3.7", "DropWeapon() should be used on a PlayerPawn object") void DropWeapon() diff --git a/wadsrc/static/zscript/actors/player/player_cheat.zs b/wadsrc/static/zscript/actors/player/player_cheat.zs index adacdfb52..c89832dca 100644 --- a/wadsrc/static/zscript/actors/player/player_cheat.zs +++ b/wadsrc/static/zscript/actors/player/player_cheat.zs @@ -426,35 +426,40 @@ extend class PlayerPawn } - virtual String CheatMorph(class morphClass, bool quickundo) + virtual String CheatMorph(class morphClass, bool undo) { - let oldclass = GetClass(); - // Set the standard morph style for the current game - int style = MRF_UNDOBYTOMEOFPOWER; - if (gameinfo.gametype == GAME_Hexen) style |= MRF_UNDOBYCHAOSDEVICE; + EMorphFlags style = MRF_UNDOBYTOMEOFPOWER; + if (GameInfo.GameType == GAME_Hexen) + style |= MRF_UNDOBYCHAOSDEVICE; - if (player.morphTics) + if (Alternative) { - if (UndoPlayerMorph (player)) + class cls = GetClass(); + Actor mo = Alternative; + if (!undo || Unmorph(self)) { - if (!quickundo && oldclass != morphclass && MorphPlayer (player, morphclass, 0, style)) + if ((!undo && Morph(self, morphClass, null, 0, style)) + || (undo && morphClass != cls && mo.Morph(mo, morphClass, null, 0, style))) { return StringTable.Localize("$TXT_STRANGER"); } - return StringTable.Localize("$TXT_NOTSTRANGE"); + + if (undo) + return StringTable.Localize("$TXT_NOTSTRANGE"); } } - else if (MorphPlayer (player, morphclass, 0, style)) + else if (Morph(self, morphClass, null, 0, style)) { return StringTable.Localize("$TXT_STRANGE"); } + return ""; } virtual void CheatTakeWeaps() { - if (player.morphTics || health <= 0) + if (Alternative || health <= 0) { return; } diff --git a/wadsrc/static/zscript/actors/player/player_morph.zs b/wadsrc/static/zscript/actors/player/player_morph.zs index 77df1ef26..ff84246a1 100644 --- a/wadsrc/static/zscript/actors/player/player_morph.zs +++ b/wadsrc/static/zscript/actors/player/player_morph.zs @@ -1,26 +1,25 @@ extend class PlayerPawn { - private native void Substitute(PlayerPawn replacement); - //=========================================================================== // - // EndAllPowerupEffects + // InitAllPowerupEffects // - // Calls EndEffect() on every Powerup in the inventory list. + // Calls InitEffect() on every Powerup in the inventory list. Since these + // functions can be overridden it's safest to store what's next in the item + // list before calling it. // //=========================================================================== void InitAllPowerupEffects() { - let item = Inv; - while (item != null) + for (Inventory item = Inv; item;) { + Inventory next = item.Inv; let power = Powerup(item); - if (power != null) - { + if (power) power.InitEffect(); - } - item = item.Inv; + + item = next; } } @@ -34,15 +33,14 @@ extend class PlayerPawn void EndAllPowerupEffects() { - let item = Inv; - while (item != null) + for (Inventory item = Inv; item;) { + Inventory next = item.Inv; let power = Powerup(item); - if (power != null) - { + if (power) power.EndEffect(); - } - item = item.Inv; + + item = next; } } @@ -52,46 +50,36 @@ extend class PlayerPawn // //=========================================================================== - virtual void ActivateMorphWeapon () + virtual void ActivateMorphWeapon() { - class morphweaponcls = MorphWeapon; - player.PendingWeapon = WP_NOCHANGE; - - if (player.ReadyWeapon != null) + if (player.ReadyWeapon) { let psp = player.GetPSprite(PSP_WEAPON); - if (psp) - { - psp.y = WEAPONTOP; - player.ReadyWeapon.ResetPSprite(psp); - } + psp.y = WEAPONTOP; + player.ReadyWeapon.ResetPSprite(psp); } - - if (morphweaponcls == null || !(morphweaponcls is 'Weapon')) - { // No weapon at all while morphed! + + class morphWeapCls = MorphWeapon; + if (!morphWeapCls) + { player.ReadyWeapon = null; } else { - player.ReadyWeapon = Weapon(FindInventory (morphweaponcls)); - if (player.ReadyWeapon == null) + player.ReadyWeapon = Weapon(FindInventory(morphWeapCls)); + if (!player.ReadyWeapon) { - player.ReadyWeapon = Weapon(GiveInventoryType (morphweaponcls)); - if (player.ReadyWeapon != null) - { - player.ReadyWeapon.GivenAsMorphWeapon = true; // flag is used only by new beastweap semantics in UndoPlayerMorph - } - } - if (player.ReadyWeapon != null) - { - player.SetPsprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState()); + player.ReadyWeapon = Weapon(GiveInventoryType(morphWeapCls)); + if (player.ReadyWeapon) + player.ReadyWeapon.GivenAsMorphWeapon = true; // Flag is used only by new morphWeap semantics in UndoPlayerMorph } + + if (player.ReadyWeapon) + player.SetPSprite(PSP_WEAPON, player.ReadyWeapon.GetReadyState()); } - if (player.ReadyWeapon != null) - { - player.SetPsprite(PSP_FLASH, null); - } + if (player.ReadyWeapon) + player.SetPSprite(PSP_FLASH, null); player.PendingWeapon = WP_NOCHANGE; } @@ -107,125 +95,122 @@ extend class PlayerPawn // //--------------------------------------------------------------------------- - virtual bool MorphPlayer(playerinfo activator, Class spawntype, int duration, int style, Class enter_flash = null, Class exit_flash = null) + virtual bool MorphPlayer(PlayerInfo activator, class spawnType, int duration, EMorphFlags style, class enterFlash = "TeleportFog", class exitFlash = "TeleportFog") { - if (bDontMorph) + if (!player || !spawnType || bDontMorph || player.Health <= 0 + || (!(style & MRF_IGNOREINVULN) && bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE)))) { return false; } - if (bInvulnerable && (player != activator || !(style & MRF_WHENINVULNERABLE))) - { // Immune when invulnerable unless this is a power we activated - return false; - } - if (player.morphTics) - { // Player is already a beast - if ((GetClass() == spawntype) && bCanSuperMorph - && (player.morphTics < (((duration) ? duration : DEFMORPHTICS) - TICRATE)) - && FindInventory('PowerWeaponLevel2', true) == null) - { // Make a super chicken - GiveInventoryType ('PowerWeaponLevel2'); + + if (!duration) + duration = DEFMORPHTICS; + + if (spawnType == GetClass()) + { + // Player is already a beast. + if (Alternative && bCanSuperMorph + && GetMorphTics() < duration - TICRATE + && !FindInventory("PowerWeaponLevel2", true)) + { + // Make a super chicken. + GiveInventoryType("PowerWeaponLevel2"); } - return false; - } - if (health <= 0) - { // Dead players cannot morph - return false; - } - if (spawntype == null) - { - return false; - } - if (!(spawntype is 'PlayerPawn')) - { - return false; - } - if (spawntype == GetClass()) - { + return false; } - let morphed = PlayerPawn(Spawn (spawntype, Pos, NO_REPLACE)); + let morphed = PlayerPawn(Spawn(spawnType, Pos, NO_REPLACE)); + if (!MorphInto(morphed)) + { + if (morphed) + morphed.Destroy(); + + return false; + } - // Use GetClass in the event someone actually allows replacements. PreMorph(morphed, false); morphed.PreMorph(self, true); - EndAllPowerupEffects(); - Substitute(morphed); + morphed.EndAllPowerupEffects(); + if ((style & MRF_TRANSFERTRANSLATION) && !morphed.bDontTranslate) - { morphed.Translation = Translation; - } - if (tid != 0 && (style & MRF_NEWTIDBEHAVIOUR)) - { - morphed.ChangeTid(tid); - ChangeTid(0); - } + morphed.Angle = Angle; - morphed.target = target; - morphed.tracer = tracer; - morphed.alternative = self; + morphed.Pitch = Pitch; // Allow pitch here since mouse look in GZDoom is far more common than Heretic/Hexen. + morphed.Target = Target; + morphed.Tracer = Tracer; + morphed.Master = Master; morphed.FriendPlayer = FriendPlayer; morphed.DesignatedTeam = DesignatedTeam; morphed.Score = Score; - player.PremorphWeapon = player.ReadyWeapon; - - morphed.special2 = bSolid * 2 + bShootable * 4 + bInvisible * 0x40; // The factors are for savegame compatibility - morphed.player = player; - - if (morphed.ViewHeight > player.viewheight && player.deltaviewheight == 0) - { // If the new view height is higher than the old one, start moving toward it. - player.deltaviewheight = player.GetDeltaViewHeight(); + morphed.ScoreIcon = ScoreIcon; + morphed.Health = morphed.SpawnHealth(); + if (TID && (style & MRF_NEWTIDBEHAVIOUR)) + { + morphed.ChangeTid(TID); + ChangeTid(0); } + + // special2 is no longer used here since Actors now have a proper field for it. + morphed.PremorphProperties = (bSolid * MPROP_SOLID) | (bShootable * MPROP_SHOOTABLE) + | (bNoBlockmap * MPROP_NO_BLOCKMAP) | (bNoSector * MPROP_NO_SECTOR) + | (bNoInteraction * MPROP_NO_INTERACTION) | (bInvisible * MPROP_INVIS); + morphed.bShadow |= bShadow; morphed.bNoGravity |= bNoGravity; morphed.bFly |= bFly; morphed.bGhost |= bGhost; - if (enter_flash == null) enter_flash = 'TeleportFog'; - let eflash = Spawn(enter_flash, Pos + (0, 0, gameinfo.telefogheight), ALLOW_REPLACE); - let p = player; - player = null; - alternative = morphed; - bSolid = false; - bShootable = false; - bUnmorphed = true; - bInvisible = true; - - p.morphTics = (duration) ? duration : DEFMORPHTICS; - - // [MH] Used by SBARINFO to speed up face drawing - p.MorphedPlayerClass = spawntype; - - p.MorphStyle = style; - if (exit_flash == null) exit_flash = 'TeleportFog'; - p.MorphExitFlash = exit_flash; - p.health = morphed.health; - p.mo = morphed; - p.vel = (0, 0); - morphed.ObtainInventory (self); - // Remove all armor - for (Inventory item = morphed.Inv; item != null; ) + // Remove all armor. + if (!(style & MRF_KEEPARMOR)) { - let next = item.Inv; - if (item is 'Armor') + for (Inventory item = morphed.Inv; item;) { - item.DepleteOrDestroy(); + Inventory next = item.Inv; + if (item is "Armor") + item.DepleteOrDestroy(); + + item = next; } - item = next; - } - morphed.InitAllPowerupEffects(); - morphed.ActivateMorphWeapon (); - if (p.camera == self) // can this happen? - { - p.camera = morphed; } + + // Players store their morph behavior into their PlayerInfo unlike regular Actors which use the + // morph properties. This is needed for backwards compatibility and to give the HUD info. + let p = morphed.player; + morphed.SetMorphTics(duration); + morphed.SetMorphStyle(style); + morphed.SetMorphExitFlash(exitFlash); + p.MorphedPlayerClass = spawnType; + p.PremorphWeapon = p.ReadyWeapon; + p.Health = morphed.Health; + p.Vel = (0.0, 0.0); + // If the new view height is higher than the old one, start moving toward it. + if (morphed.ViewHeight > p.ViewHeight && !p.DeltaViewHeight) + p.DeltaViewHeight = p.GetDeltaViewHeight(); + + bNoInteraction = true; + A_ChangeLinkFlags(true, true); + + // Legacy + bSolid = bShootable = false; + bInvisible = true; + morphed.ClearFOVInterpolation(); - morphed.ScoreIcon = ScoreIcon; // [GRB] - if (eflash) - eflash.target = morphed; + morphed.InitAllPowerupEffects(); + morphed.ActivateMorphWeapon(); + PostMorph(morphed, false); // No longer the current body morphed.PostMorph(self, true); // This is the current body + + if (enterFlash) + { + Actor fog = Spawn(enterFlash, morphed.Pos.PlusZ(GameInfo.TelefogHeight), ALLOW_REPLACE); + if (fog) + fog.Target = morphed; + } + return true; } @@ -235,400 +220,156 @@ extend class PlayerPawn // //---------------------------------------------------------------------------- - virtual bool UndoPlayerMorph(playerinfo activator, int unmorphflag = 0, bool force = false) + virtual bool UndoPlayerMorph(PlayerInfo activator, EMorphFlags unmorphFlags = 0, bool force = false) { - if (alternative == null) + if (!Alternative || bStayMorphed || Alternative.bStayMorphed) + return false; + + if (!(unmorphFlags & MRF_IGNOREINVULN) && bInvulnerable + && (player != activator || (!(player.MorphStyle & MRF_WHENINVULNERABLE) && !(unmorphFlags & MRF_STANDARDUNDOING)))) { return false; } - let player = self.player; - bool DeliberateUnmorphIsOkay = !!(MRF_STANDARDUNDOING & unmorphflag); + let alt = PlayerPawn(Alternative); + alt.SetOrigin(Pos, false); + // Test if there's room to unmorph. + if (!force && (PremorphProperties & MPROP_SOLID)) + { + bool altSolid = alt.bSolid; + bool isSolid = bSolid; + bool isTouchy = bTouchy; - if ((bInvulnerable) // If the player is invulnerable - && ((player != activator) // and either did not decide to unmorph, - || (!((player.MorphStyle & MRF_WHENINVULNERABLE) // or the morph style does not allow it - || (DeliberateUnmorphIsOkay))))) // (but standard morph styles always allow it), - { // Then the player is immune to the unmorph. - return false; + alt.bSolid = true; + bSolid = bTouchy = false; + + bool res = alt.TestMobjLocation(); + + alt.bSolid = altSolid; + bSolid = isSolid; + bTouchy = isTouchy; + + if (!res) + { + SetMorphTics(2 * TICRATE); + return false; + } } - let altmo = PlayerPawn(alternative); - altmo.SetOrigin (Pos, false); - altmo.bSolid = true; - bSolid = false; - if (!force && !altmo.TestMobjLocation()) - { // Didn't fit - altmo.bSolid = false; - bSolid = true; - player.morphTics = 2*TICRATE; + if (!MorphInto(alt)) return false; - } - PreUnmorph(altmo, false); // This body's about to be left. - altmo.PreUnmorph(self, true); // This one's about to become current. + PreUnmorph(alt, false); // This body's about to be left. + alt.PreUnmorph(self, true); // This one's about to become current. - // No longer using tracer as morph storage. That is what 'alternative' is for. - // If the tracer has changed on the morph, change the original too. - altmo.target = target; - altmo.tracer = tracer; - self.player = null; - altmo.alternative = alternative = null; + alt.EndAllPowerupEffects(); // Remove the morph power if the morph is being undone prematurely. - for (Inventory item = Inv; item != null;) + for (Inventory item = alt.Inv; item;) { - let next = item.Inv; + Inventory next = item.Inv; if (item is "PowerMorph") - { item.Destroy(); - } + item = next; } - EndAllPowerupEffects(); - altmo.ObtainInventory (self); - Substitute(altmo); - if ((tid != 0) && (player.MorphStyle & MRF_NEWTIDBEHAVIOUR)) - { - altmo.ChangeTid(tid); - } - altmo.Angle = Angle; - altmo.player = player; - altmo.reactiontime = 18; - altmo.bSolid = !!(special2 & 2); - altmo.bShootable = !!(special2 & 4); - altmo.bInvisible = !!(special2 & 0x40); - altmo.Vel = (0, 0, Vel.Z); - player.Vel = (0, 0); - altmo.floorz = floorz; - altmo.bShadow = bShadow; - altmo.bNoGravity = bNoGravity; - altmo.bGhost = bGhost; - altmo.bUnmorphed = false; - altmo.Score = Score; - altmo.InitAllPowerupEffects(); - let exit_flash = player.MorphExitFlash; - bool correctweapon = !!(player.MorphStyle & MRF_LOSEACTUALWEAPON); - bool undobydeathsaves = !!(player.MorphStyle & MRF_UNDOBYDEATHSAVES); + alt.Angle = Angle; + alt.Pitch = Pitch; + alt.Target = Target; + alt.Tracer = Tracer; + alt.Master = Master; + alt.FriendPlayer = FriendPlayer; + alt.DesignatedTeam = DesignatedTeam; + alt.Score = Score; + alt.ScoreIcon = ScoreIcon; + alt.ReactionTime = 18; + alt.bSolid = (PremorphProperties & MPROP_SOLID); + alt.bShootable = (PremorphProperties & MPROP_SHOOTABLE); + alt.bInvisible = (PremorphProperties & MPROP_INVIS); + alt.bShadow = bShadow; + alt.bNoGravity = bNoGravity; + alt.bGhost = bGhost; + alt.bFly = bFly; + alt.Vel = (0.0, 0.0, Vel.Z); - player.morphTics = 0; - player.MorphedPlayerClass = null; - player.MorphStyle = 0; - player.MorphExitFlash = null; - player.viewheight = altmo.ViewHeight; - Inventory level2 = altmo.FindInventory("PowerWeaponLevel2", true); - if (level2 != null) + alt.bNoInteraction = (PremorphProperties & MPROP_NO_INTERACTION); + alt.A_ChangeLinkFlags((PremorphProperties & MPROP_NO_BLOCKMAP), (PremorphProperties & MPROP_NO_SECTOR)); + + let p = alt.player; + class exitFlash = alt.GetMorphExitFlash(); + EMorphFlags style = alt.GetMorphStyle(); + Weapon premorphWeap = p.PremorphWeapon; + + if (TID && (style & MRF_NEWTIDBEHAVIOUR)) { - level2.Destroy (); + alt.ChangeTid(TID); + ChangeTID(0); } - if ((player.health > 0) || undobydeathsaves) - { - player.health = altmo.health = altmo.SpawnHealth(); - } - else // killed when morphed so stay dead - { - altmo.health = player.health; - } + alt.SetMorphTics(0); + alt.SetMorphStyle(0); + alt.SetMorphExitFlash(null); + p.MorphedPlayerClass = null; + p.PremorphWeapon = null; + p.ViewHeight = alt.ViewHeight; + p.Vel = (0.0, 0.0); + if (p.Health > 0 || (style & MRF_UNDOBYDEATHSAVES)) + p.Health = alt.Health = alt.SpawnHealth(); + else + alt.Health = p.Health; - player.mo = altmo; - if (player.camera == self) - { - player.camera = altmo; - } - altmo.ClearFOVInterpolation(); + Inventory level2 = alt.FindInventory("PowerWeaponLevel2", true); + if (level2) + level2.Destroy(); - // [MH] - // If the player that was morphed is the one - // taking events, reset up the face, if any; - // this is only needed for old-skool skins - // and for the original DOOM status bar. - if (player == players[consoleplayer]) + let morphWeap = p.ReadyWeapon; + if (premorphWeap) { - if (face != 'None') - { - // Assume root-level base skin to begin with - let skinindex = 0; - let skin = player.GetSkin(); - // If a custom skin was in use, then reload it - // or else the base skin for the player class. - if (skin >= PlayerClasses.Size () && skin < PlayerSkins.Size()) - { - skinindex = skin; - } - else if (PlayerClasses.Size () > 1) - { - let whatami = altmo.GetClass(); - for (int i = 0; i < PlayerClasses.Size (); ++i) - { - if (PlayerClasses[i].Type == whatami) - { - skinindex = i; - break; - } - } - } - } - } - - Actor eflash = null; - if (exit_flash != null) - { - eflash = Spawn(exit_flash, Vec3Angle(20., altmo.Angle, gameinfo.telefogheight), ALLOW_REPLACE); - if (eflash) eflash.target = altmo; - } - WeaponSlots.SetupWeaponSlots(altmo); // Use original class's weapon slots. - let beastweap = player.ReadyWeapon; - if (player.PremorphWeapon != null) - { - player.PremorphWeapon.PostMorphWeapon (); + premorphWeap.PostMorphWeapon(); } else { - player.ReadyWeapon = player.PendingWeapon = null; + p.ReadyWeapon = null; + p.PendingWeapon = WP_NOCHANGE; + p.Refire = 0; } - if (correctweapon) - { // Better "lose morphed weapon" semantics - class morphweaponcls = MorphWeapon; - if (morphweaponcls != null && morphweaponcls is 'Weapon') - { - let OriginalMorphWeapon = Weapon(altmo.FindInventory (morphweapon)); - if ((OriginalMorphWeapon != null) && (OriginalMorphWeapon.GivenAsMorphWeapon)) - { // You don't get to keep your morphed weapon. - if (OriginalMorphWeapon.SisterWeapon != null) - { - OriginalMorphWeapon.SisterWeapon.Destroy (); - } - OriginalMorphWeapon.Destroy (); - } - } - } - else // old behaviour (not really useful now) - { // Assumptions made here are no longer valid - if (beastweap != null) - { // You don't get to keep your morphed weapon. - if (beastweap.SisterWeapon != null) - { - beastweap.SisterWeapon.Destroy (); - } - beastweap.Destroy (); - } - } - PostUnmorph(altmo, false); // This body is no longer current. - altmo.PostUnmorph(self, true); // altmo body is current. - Destroy (); - // Restore playerclass armor to its normal amount. - let hxarmor = HexenArmor(altmo.FindInventory('HexenArmor')); - if (hxarmor != null) + + if (style & MRF_LOSEACTUALWEAPON) { - hxarmor.Slots[4] = altmo.HexenArmor[0]; + // Improved "lose morph weapon" semantics. + class morphWeapCls = MorphWeapon; + if (morphWeapCls) + { + let originalMorphWeapon = Weapon(alt.FindInventory(morphWeapCls)); + if (originalMorphWeapon && originalMorphWeapon.GivenAsMorphWeapon) + originalMorphWeapon.Destroy(); + } } + else if (morphWeap) // Old behaviour (not really useful now). + { + morphWeap.Destroy(); + } + + // Reset the base AC of the player's Hexen armor back to its default. + let hexArmor = HexenArmor(alt.FindInventory("HexenArmor")); + if (hexArmor) + hexArmor.Slots[4] = alt.HexenArmor[0]; + + alt.ClearFOVInterpolation(); + alt.InitAllPowerupEffects(); + + PostUnmorph(alt, false); // This body is no longer current. + alt.PostUnmorph(self, true); // altmo body is current. + + if (exitFlash) + { + Actor fog = Spawn(exitFlash, alt.Vec3Angle(20.0, alt.Angle, GameInfo.TelefogHeight), ALLOW_REPLACE); + if (fog) + fog.Target = alt; + } + + Destroy(); return true; } - - //=========================================================================== - // - // - // - //=========================================================================== - - override Actor, int, int MorphedDeath() - { - // Voodoo dolls should not unmorph the real player here. - if (player && (player.mo == self) && - (player.morphTics) && - (player.MorphStyle & MRF_UNDOBYDEATH) && - (alternative)) - { - Actor realme = alternative; - int realstyle = player.MorphStyle; - int realhealth = health; - if (UndoPlayerMorph(player, 0, !!(player.MorphStyle & MRF_UNDOBYDEATHFORCED))) - { - return realme, realstyle, realhealth; - } - } - return null, 0, 0; - } } - -//=========================================================================== -// -// -// -//=========================================================================== - -class MorphProjectile : Actor -{ - - Class PlayerClass; - Class MonsterClass, MorphFlash, UnMorphFlash; - int Duration, MorphStyle; - - Default - { - Damage 1; - Projectile; - -ACTIVATEIMPACT - -ACTIVATEPCROSS - } - - override int DoSpecialDamage (Actor target, int damage, Name damagetype) - { - if (target.player) - { - // Voodoo dolls forward this to the real player - target.player.mo.MorphPlayer (NULL, PlayerClass, Duration, MorphStyle, MorphFlash, UnMorphFlash); - } - else - { - target.MorphMonster (MonsterClass, Duration, MorphStyle, MorphFlash, UnMorphFlash); - } - return -1; - } - - -} - -//=========================================================================== -// -// -// -//=========================================================================== - -class MorphedMonster : Actor -{ - Actor UnmorphedMe; - int UnmorphTime, MorphStyle; - Class MorphExitFlash; - int FlagsSave; - - Default - { - Monster; - -COUNTKILL - +FLOORCLIP - } - - private native void Substitute(Actor replacement); - - override void OnDestroy () - { - if (UnmorphedMe != NULL) - { - UnmorphedMe.Destroy (); - } - Super.OnDestroy(); - } - - override void Die (Actor source, Actor inflictor, int dmgflags, Name MeansOfDeath) - { - Super.Die (source, inflictor, dmgflags, MeansOfDeath); - if (UnmorphedMe != NULL && UnmorphedMe.bUnmorphed) - { - UnmorphedMe.health = health; - UnmorphedMe.Die (source, inflictor, dmgflags, MeansOfDeath); - } - } - - override void Tick () - { - if (UnmorphTime > level.time || !UndoMonsterMorph()) - { - Super.Tick(); - } - } - - //---------------------------------------------------------------------------- - // - // FUNC P_UndoMonsterMorph - // - // Returns true if the monster unmorphs. - // - //---------------------------------------------------------------------------- - - virtual bool UndoMonsterMorph(bool force = false) - { - if (UnmorphTime == 0 || UnmorphedMe == NULL || bStayMorphed || UnmorphedMe.bStayMorphed) - { - return false; - } - let unmorphed = UnmorphedMe; - unmorphed.SetOrigin (Pos, false); - unmorphed.bSolid = true; - bSolid = false; - bool save = bTouchy; - bTouchy = false; - if (!force && !unmorphed.TestMobjLocation ()) - { // Didn't fit - unmorphed.bSolid = false; - bSolid = true; - bTouchy = save; - UnmorphTime = level.time + 5*TICRATE; // Next try in 5 seconds - return false; - } - PreUnmorph(unmorphed, false); - unmorphed.PreUnmorph(self, true); - unmorphed.Angle = Angle; - unmorphed.target = target; - unmorphed.bShadow = bShadow; - unmorphed.bGhost = bGhost; - unmorphed.bSolid = !!(flagssave & 2); - unmorphed.bShootable = !!(flagssave & 4); - unmorphed.bInvisible = !!(flagssave & 0x40); - unmorphed.health = unmorphed.SpawnHealth(); - unmorphed.Vel = Vel; - unmorphed.ChangeTid(tid); - unmorphed.special = special; - unmorphed.Score = Score; - unmorphed.args[0] = args[0]; - unmorphed.args[1] = args[1]; - unmorphed.args[2] = args[2]; - unmorphed.args[3] = args[3]; - unmorphed.args[4] = args[4]; - unmorphed.CopyFriendliness (self, true); - unmorphed.bUnmorphed = false; - PostUnmorph(unmorphed, false); // From is false here: Leaving the caller's body. - unmorphed.PostUnmorph(self, true); // True here: Entering this body from here. - UnmorphedMe = NULL; - Substitute(unmorphed); - Destroy (); - let eflash = Spawn(MorphExitFlash, Pos + (0, 0, gameinfo.TELEFOGHEIGHT), ALLOW_REPLACE); - if (eflash) - eflash.target = unmorphed; - return true; - } - - //=========================================================================== - // - // - // - //=========================================================================== - - override Actor, int, int MorphedDeath() - { - let realme = UnmorphedMe; - if (realme != NULL) - { - if ((UnmorphTime) && - (MorphStyle & MRF_UNDOBYDEATH)) - { - int realstyle = MorphStyle; - int realhealth = health; - if (UndoMonsterMorph(!!(MorphStyle & MRF_UNDOBYDEATHFORCED))) - { - return realme, realstyle, realhealth; - } - } - if (realme.bBossDeath) - { - realme.health = 0; // make sure that A_BossDeath considers it dead. - realme.A_BossDeath(); - } - } - return null, 0, 0; - } - -} - diff --git a/wadsrc/static/zscript/actors/raven/artitele.zs b/wadsrc/static/zscript/actors/raven/artitele.zs index 042ae67df..272ae8602 100644 --- a/wadsrc/static/zscript/actors/raven/artitele.zs +++ b/wadsrc/static/zscript/actors/raven/artitele.zs @@ -23,35 +23,32 @@ class ArtiTeleport : Inventory Loop; } - override bool Use (bool pickup) + override bool Use(bool pickup) { Vector3 dest; - int destAngle; - + double destAngle; if (deathmatch) - { - [dest, destAngle] = level.PickDeathmatchStart(); - } + [dest, destAngle] = Level.PickDeathmatchStart(); else - { - [dest, destAngle] = level.PickPlayerStart(Owner.PlayerNumber()); - } - if (!level.useplayerstartz) + [dest, destAngle] = Level.PickPlayerStart(Owner.PlayerNumber()); + + if (!Level.UsePlayerStartZ) dest.Z = ONFLOORZ; - Owner.Teleport (dest, destAngle, TELF_SOURCEFOG | TELF_DESTFOG); - bool canlaugh = true; - Playerinfo p = Owner.player; - if (p && p.morphTics && (p.MorphStyle & MRF_UNDOBYCHAOSDEVICE)) - { // Teleporting away will undo any morph effects (pig) - if (!p.mo.UndoPlayerMorph (p, MRF_UNDOBYCHAOSDEVICE) && (p.MorphStyle & MRF_FAILNOLAUGH)) - { - canlaugh = false; - } - } - if (canlaugh) - { // Full volume laugh - Owner.A_StartSound ("*evillaugh", CHAN_VOICE, CHANF_DEFAULT, 1., ATTN_NONE); + + Owner.Teleport(dest, destAngle, TELF_SOURCEFOG | TELF_DESTFOG); + + bool canLaugh = Owner.player != null; + EMorphFlags mStyle = Owner.GetMorphStyle(); + if (Owner.Alternative && (mStyle & MRF_UNDOBYCHAOSDEVICE)) + { + // Teleporting away will undo any morph effects (pig). + if (!Owner.Unmorph(Owner, MRF_UNDOBYCHAOSDEVICE) && (mStyle & MRF_FAILNOLAUGH)) + canLaugh = false; } + + if (canLaugh) + Owner.A_StartSound("*evillaugh", CHAN_VOICE, attenuation: ATTN_NONE); + return true; } diff --git a/wadsrc/static/zscript/actors/shared/sharedmisc.zs b/wadsrc/static/zscript/actors/shared/sharedmisc.zs index 5e146f6c9..aa78a99bc 100644 --- a/wadsrc/static/zscript/actors/shared/sharedmisc.zs +++ b/wadsrc/static/zscript/actors/shared/sharedmisc.zs @@ -245,3 +245,123 @@ class SpeakerIcon : Unknown Scale 0.125; } } + +/* +================================================================= +Path Nodes +================================================================= +Special flags are as follows: + +AMBUSH + Node is blind. Things cannot "touch" these nodes with A_Chase. + Useful for tele/portals since the engine makes them "touch" + upon transitioning. These nodes are fast forwarded over in Actor's + ReachedNode() function. + +STANDSTILL + Traveller must be bigger instead of smaller. +*/ +class PathNode : Actor +{ + Array neighbors; + + Default + { + +NOINTERACTION + +NOBLOCKMAP + +INVISIBLE + +DONTSPLASH + +NOTONAUTOMAP + +NOGRAVITY + Radius 16; + Height 56; + RenderStyle "None"; + + // The following properties can be set directly in Ultimate Doom Builder's Custom tab. + + FriendlySeeBlocks 0; // Sight checks limited to this. <= 0 is infinite. + XScale 0; // filter height - actors must be this small for this node. Only effective if > 0. + YScale 0; // filter radius - ^ but for radius + } + + // Args are TIDs. Can be one way to force single directions. + override void PostBeginPlay() + { + Super.PostBeginPlay(); + for (int i = 0; i < Args.Size(); i++) + { + if (!Args[i]) continue; + + let it = level.CreateActorIterator(Args[i], "PathNode"); + PathNode node; + do + { + if (node && node != self) + neighbors.Push(node); + } while (node = PathNode(it.Next())) + + } + level.HandlePathNode(self, true); + } + + override void OnDestroy() + { + level.HandlePathNode(self, false); + Super.OnDestroy(); + } + + // For ACS access with ScriptCall. + // 'con' values are: + // 0: No connections + // 1: Connect tid1 to tid2 one-way + // 2: ^ but two-way. + static void SetConnectionGlobal(int tid1, int tid2, int con) + { + if (tid1 == 0 || tid2 == 0) + return; + + PathNode node; + Array nodes2; // Cache for actors with tid2 + { + let it = Level.CreateActorIterator(tid2, "PathNode"); + + do + { + if (node) + nodes2.Push(node); + } while (node = PathNode(it.Next())) + } + // Nothing to set to. + if (nodes2.Size() < 1) + return; + + let it = Level.CreateActorIterator(tid1, "PathNode"); + node = null; + do + { + if (node) + { + foreach (n2 : nodes2) + { + if (n2) + { + node.SetConnection(n2, con); + n2.SetConnection(node, (con > 1)); + } + } + } + } while (node = PathNode(it.Next())) + } + + void SetConnection(PathNode other, bool on) + { + if (!other) return; + + if (on) + { + if (neighbors.Find(other) >= neighbors.Size()) + neighbors.Push(other); + } + else neighbors.Delete(neighbors.Find(other)); + } +} \ No newline at end of file diff --git a/wadsrc/static/zscript/constants.zs b/wadsrc/static/zscript/constants.zs index 637daff97..b991f7a79 100644 --- a/wadsrc/static/zscript/constants.zs +++ b/wadsrc/static/zscript/constants.zs @@ -232,6 +232,8 @@ enum EMorphFlags MRF_UNDOBYTIMEOUT = 0x00001000, MRF_UNDOALWAYS = 0x00002000, MRF_TRANSFERTRANSLATION = 0x00004000, + MRF_KEEPARMOR = 0x00008000, + MRF_IGNOREINVULN = 0x00010000, MRF_STANDARDUNDOING = MRF_UNDOBYTOMEOFPOWER | MRF_UNDOBYCHAOSDEVICE | MRF_UNDOBYTIMEOUT, }; diff --git a/wadsrc/static/zscript/doombase.zs b/wadsrc/static/zscript/doombase.zs index 4aed3ca97..c67b1c3f1 100644 --- a/wadsrc/static/zscript/doombase.zs +++ b/wadsrc/static/zscript/doombase.zs @@ -137,6 +137,9 @@ extend class Object native static void MarkSound(Sound snd); native static uint BAM(double angle); native static void SetMusicVolume(float vol); + native clearscope static Object GetNetworkEntity(uint id); + native play void EnableNetworking(bool enable); + native clearscope uint GetNetworkID() const; } class Thinker : Object native play @@ -406,13 +409,13 @@ struct LevelLocals native const CLUSTER_HUB = 0x00000001; // Cluster uses hub behavior - + native readonly Array PathNodes; native Array<@Sector> Sectors; native Array<@Line> Lines; native Array<@Side> Sides; native readonly Array<@Vertex> Vertexes; native readonly Array<@LinePortal> LinePortals; - native internal Array<@SectorPortal> SectorPortals; + native internal readonly Array<@SectorPortal> SectorPortals; native readonly int time; native readonly int maptime; @@ -474,6 +477,7 @@ struct LevelLocals native native readonly int compatflags; native readonly int compatflags2; native readonly LevelInfo info; + native readonly bool pathing; native bool nousersave; native bool noautomap; @@ -554,6 +558,9 @@ struct LevelLocals native native void SpawnParticle(FSpawnParticleParams p); native VisualThinker SpawnVisualThinker(Class type); + + native bool FindPath(Actor chaser, Actor target, PathNode startnode = null, PathNode goalnode = null); + native void HandlePathNode(PathNode node, bool add); // This is only here because there's no other way to register the node privately. } // a few values of this need to be readable by the play code. @@ -587,10 +594,10 @@ struct State native native readonly bool bCanRaise; native readonly bool bDehacked; - native int DistanceTo(state other); - native bool ValidateSpriteFrame(); - native TextureID, bool, Vector2 GetSpriteTexture(int rotation, int skin = 0, Vector2 scale = (0,0), int spritenum = -1, int framenum = -1); - native bool InStateSequence(State base); + native int DistanceTo(state other) const; + native bool ValidateSpriteFrame() const; + native TextureID, bool, Vector2 GetSpriteTexture(int rotation, int skin = 0, Vector2 scale = (0,0), int spritenum = -1, int framenum = -1) const; + native bool InStateSequence(State base) const; } struct TerrainDef native diff --git a/wadsrc/static/zscript/engine/ui/menu/listmenu.zs b/wadsrc/static/zscript/engine/ui/menu/listmenu.zs index 672c0ebc7..ed3c2ef16 100644 --- a/wadsrc/static/zscript/engine/ui/menu/listmenu.zs +++ b/wadsrc/static/zscript/engine/ui/menu/listmenu.zs @@ -55,6 +55,7 @@ class ListMenuDescriptor : MenuDescriptor native native int mFontColor; native int mFontColor2; native bool mCenter; + native bool mCenterText; native bool mAnimatedTransition; native bool mAnimated; native bool mDontBlur; diff --git a/wadsrc/static/zscript/engine/ui/menu/listmenuitems.zs b/wadsrc/static/zscript/engine/ui/menu/listmenuitems.zs index 9e2257820..66a4d9338 100644 --- a/wadsrc/static/zscript/engine/ui/menu/listmenuitems.zs +++ b/wadsrc/static/zscript/engine/ui/menu/listmenuitems.zs @@ -161,7 +161,7 @@ class ListMenuItemStaticText : ListMenuItem mText = text; mFont = desc.mFont; mColor = color >= 0? color : desc.mFontColor; - mCentered = false; + mCentered = desc.mCenterText; } void InitDirect(double x, double y, String text, Font font, int color = Font.CR_UNTRANSLATED, bool centered = false) @@ -294,7 +294,12 @@ class ListMenuItemTextItem : ListMenuItemSelectable override void Draw(bool selected, ListMenuDescriptor desc) { let font = menuDelegate.PickFont(mFont); - DrawText(desc, font, selected ? mColorSelected : mColor, mXpos, mYpos, mText); + double x = mXpos; + if (desc.mCenterText) + { + x -= font.StringWidth(mText) / 2; + } + DrawText(desc, font, selected ? mColorSelected : mColor, x, mYpos, mText); } override int GetWidth() diff --git a/wadsrc/static/zscript/engine/ui/menu/optionmenuitems.zs b/wadsrc/static/zscript/engine/ui/menu/optionmenuitems.zs index d044d6aa3..8276dda3e 100644 --- a/wadsrc/static/zscript/engine/ui/menu/optionmenuitems.zs +++ b/wadsrc/static/zscript/engine/ui/menu/optionmenuitems.zs @@ -467,7 +467,7 @@ class EnterKey : Menu menuactive = Menu.On; SetMenuMessage(0); Close(); - mParentMenu.MenuEvent((ev.KeyScan == InputEvent.KEY_ESCAPE)? Menu.MKEY_Abort : Menu.MKEY_Input, 0); + mParentMenu.MenuEvent((ev.KeyScan == InputEvent.KEY_ESCAPE) || (ev.KeyScan == InputEvent.KEY_JOY2) ? Menu.MKEY_Abort : Menu.MKEY_Input, 0); return true; } return false; diff --git a/wadsrc/static/zscript/events.zs b/wadsrc/static/zscript/events.zs index 738af124e..52cf3a7eb 100644 --- a/wadsrc/static/zscript/events.zs +++ b/wadsrc/static/zscript/events.zs @@ -19,11 +19,12 @@ struct NetworkCommand native play version("4.12") native double ReadFloat(); native double ReadDouble(); native string ReadString(); + native bool EndOfStream(); // Wrappers native Name ReadName(); - native double ReadMapUnit(); // 16.16 long -> double - native double ReadAngle(); // BAM long -> double + native double ReadMapUnit(); // 16.16 int -> double + native double ReadAngle(); // BAM int -> double native Vector2 ReadVector2(); native Vector3 ReadVector3(); native Vector4 ReadVector4(); @@ -44,8 +45,8 @@ class NetworkBuffer native version("4.12") // Wrappers native void AddName(Name value); - native void AddMapUnit(double value); // double -> 16.16 long - native void AddAngle(double value); // double -> BAM long + native void AddMapUnit(double value); // double -> 16.16 int + native void AddAngle(double value); // double -> BAM int native void AddVector2(Vector2 value); native void AddVector3(Vector3 value); native void AddVector4(Vector4 value); diff --git a/wadsrc/static/zscript/mapdata.zs b/wadsrc/static/zscript/mapdata.zs index 8abfe8f1b..dd1d406e8 100644 --- a/wadsrc/static/zscript/mapdata.zs +++ b/wadsrc/static/zscript/mapdata.zs @@ -17,15 +17,15 @@ struct SectorPortal native play FLAG_INSKYBOX = 2, // to avoid recursion }; - native int mType; - native int mFlags; - native uint mPartner; - native int mPlane; - native Sector mOrigin; - native Sector mDestination; - native Vector2 mDisplacement; - native double mPlaneZ; - native Actor mSkybox; + native readonly int mType; + native internal readonly int mFlags; + native readonly uint mPartner; + native readonly int mPlane; + native readonly Sector mOrigin; + native internal readonly Sector mDestination; + native readonly Vector2 mDisplacement; + native readonly double mPlaneZ; + native internal readonly Actor mSkybox; }; struct LinePortal native play @@ -58,16 +58,16 @@ struct LinePortal native play PORG_CEILING, }; - native Line mOrigin; - native Line mDestination; - native Vector2 mDisplacement; - native uint8 mType; - native uint8 mFlags; - native uint8 mDefFlags; - native uint8 mAlign; - native double mAngleDiff; - native double mSinRot; - native double mCosRot; + native readonly Line mOrigin; + native readonly Line mDestination; + native readonly Vector2 mDisplacement; + native readonly uint8 mType; + native readonly uint8 mFlags; + native readonly uint8 mDefFlags; + native readonly uint8 mAlign; + native readonly double mAngleDiff; + native readonly double mSinRot; + native readonly double mCosRot; } struct Vertex native play @@ -262,16 +262,16 @@ struct Line native play struct SecPlane native play { - native Vector3 Normal; - native double D; - native double negiC; + native readonly Vector3 Normal; + native readonly double D; + native readonly double negiC; native clearscope bool isSlope() const; native clearscope int PointOnSide(Vector3 pos) const; native clearscope double ZatPoint (Vector2 v) const; native clearscope double ZatPointDist(Vector2 v, double dist) const; - native clearscope bool isEqual(Secplane other) const; - native void ChangeHeight(double hdiff); + native clearscope bool isEqual(readonly other) const; + //native void ChangeHeight(double hdiff); native clearscope double GetChangedHeight(double hdiff) const; native clearscope double HeightDiff(double oldd, double newd = 1e37) const; native clearscope double PointToDist(Vector2 xy, double z) const; @@ -340,11 +340,11 @@ struct SecSpecial play struct FColormap { - Color LightColor; - Color FadeColor; - uint8 Desaturation; - uint8 BlendFactor; - uint16 FogDensity; + readonly Color LightColor; + readonly Color FadeColor; + readonly uint8 Desaturation; + readonly uint8 BlendFactor; + readonly uint16 FogDensity; } struct Sector native play @@ -435,6 +435,7 @@ struct Sector native play SECMF_UNDERWATERMASK = 32+64, SECMF_DRAWN = 128, // sector has been drawn at least once SECMF_HIDDEN = 256, // Do not draw on textured automap + SECMF_NOPATHING = 4096, // monsters cannot path find in these areas, saves on time and resources } native uint16 MoreFlags; @@ -472,8 +473,8 @@ struct Sector native play native SectorAction SecActTarget; - native internal uint Portals[2]; - native readonly int PortalGroup; + native internal readonly uint Portals[2]; + native readonly int PortalGroup; native readonly int sectornum; diff --git a/wadsrc/static/zscript/ui/statusbar/alt_hud.zs b/wadsrc/static/zscript/ui/statusbar/alt_hud.zs index 69939da27..203d55ec4 100644 --- a/wadsrc/static/zscript/ui/statusbar/alt_hud.zs +++ b/wadsrc/static/zscript/ui/statusbar/alt_hud.zs @@ -926,8 +926,12 @@ class AltHud ui DrawStatus(CPlayer, 5, hudheight-75); DrawFrags(CPlayer, 5, hudheight-70); } - DrawHealth(CPlayer, 5, hudheight-45); - DrawArmor(BasicArmor(CPlayer.mo.FindInventory('BasicArmor')), HexenArmor(CPlayer.mo.FindInventory('HexenArmor')), 5, hudheight-20); + + int armory = hud_swaphealtharmor ? hudheight-45 : hudheight-20; + int healthy = hud_swaphealtharmor ? hudheight-20 : hudheight-45; + DrawHealth(CPlayer, 5, healthy); + DrawArmor(BasicArmor(CPlayer.mo.FindInventory('BasicArmor')), HexenArmor(CPlayer.mo.FindInventory('HexenArmor')), 5, armory); + int y = DrawKeys(CPlayer, hudwidth-4, hudheight-10); y = DrawAmmo(CPlayer, hudwidth-5, y); if (hud_showweapons) DrawWeapons(CPlayer, hudwidth - 5, y); diff --git a/wadsrc/static/zscript/visualthinker.zs b/wadsrc/static/zscript/visualthinker.zs index e18fe1823..12a8c41d6 100644 --- a/wadsrc/static/zscript/visualthinker.zs +++ b/wadsrc/static/zscript/visualthinker.zs @@ -12,7 +12,9 @@ Class VisualThinker : Thinker native native TranslationID Translation; native uint16 Flags; native int16 LightLevel; - native bool bXFlip, + native bool bFlipOffsetX, + bFlipOffsetY, + bXFlip, bYFlip, bDontInterpolate, bAddLightLevel;