Merge branch 'master' of https://github.com/ZDoom/gzdoom
This commit is contained in:
commit
74a27be3fd
114 changed files with 3366 additions and 1677 deletions
104
auto-setup-linux.sh
Executable file
104
auto-setup-linux.sh
Executable file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
**
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include "palutil.h"
|
||||
#include "sc_man.h"
|
||||
#include "m_crc32.h"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
#include <time.h>
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
#include "w_zip.h"
|
||||
#include "ancientzip.h"
|
||||
#include "resourcefile.h"
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ public:
|
|||
EColorRange mFontColor;
|
||||
EColorRange mFontColor2;
|
||||
bool mCenter;
|
||||
bool mCenterText;
|
||||
bool mFromEngine;
|
||||
bool mAnimated;
|
||||
bool mAnimatedTransition;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
** compile with something other than Visual C++ or GCC.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include "autosegs.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#define AUTOSEGS_H
|
||||
|
||||
#include <type_traits>
|
||||
#include <cstdint>
|
||||
|
||||
#if defined(__clang__)
|
||||
#if defined(__has_feature) && __has_feature(address_sanitizer)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 T> class TObjPtr;
|
||||
|
|
@ -214,6 +215,9 @@ class TObjPtr
|
|||
mutable DObject *o;
|
||||
};
|
||||
public:
|
||||
TObjPtr() = default;
|
||||
|
||||
TObjPtr(T t) : pp(t) {}
|
||||
|
||||
constexpr TObjPtr<T>& operator=(T q) noexcept
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public:
|
|||
void InitializeSpecials(void* addr, void* defaults, TArray<FTypeAndOffset> 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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class ColorBlendAttachmentBuilder;
|
|||
class VkPPShader;
|
||||
class VkPPRenderPassKey;
|
||||
class VkPPRenderPassSetup;
|
||||
class ColorBlendAttachmentBuilder;
|
||||
|
||||
class VkPipelineKey
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<PObjectPointer*>(source)->PointedClass();
|
||||
auto tocls = static_cast<PObjectPointer*>(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<PClassPointer*>(source)->ClassRestriction;
|
||||
auto tocls = static_cast<PClassPointer*>(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<PFunctionPointer*>(source);
|
||||
auto to = static_cast<PFunctionPointer*>(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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<PClassType *>(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<PFunction>(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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#ifndef __BITMAP_H__
|
||||
#define __BITMAP_H__
|
||||
|
||||
#include <cstring>
|
||||
#include "palentry.h"
|
||||
|
||||
struct FCopyInfo;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
#pragma once
|
||||
#define TEXTUREID_H
|
||||
|
||||
#include <cstddef>
|
||||
#include "tarray.h"
|
||||
|
|
|
|||
2
src/common/thirdparty/md5.h
vendored
2
src/common/thirdparty/md5.h
vendored
|
|
@ -18,6 +18,8 @@
|
|||
#ifndef MD5_H
|
||||
#define MD5_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct MD5Context
|
||||
{
|
||||
MD5Context() { Init(); }
|
||||
|
|
|
|||
|
|
@ -35,8 +35,9 @@
|
|||
|
||||
bool gameisdead;
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <cstdarg>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include "zstring.h"
|
||||
void I_DebugPrint(const char *cp)
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ bool D_AddFile(std::vector<std::string>& 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@
|
|||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
#include "palutil.h"
|
||||
#include "palentry.h"
|
||||
#include "sc_man.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<typename Func>
|
||||
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<typename Func>
|
||||
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<typename Func>
|
||||
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<typename Func>
|
||||
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<typename Func>
|
||||
void SortedInsert (const T &item, Func lt)
|
||||
{
|
||||
Insert (SortedFind (item, lt, false), item);
|
||||
}
|
||||
|
||||
void ShrinkToFit ()
|
||||
{
|
||||
if (Most > Count)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
#include "files.h"
|
||||
#include "m_swap.h"
|
||||
#include "w_zip.h"
|
||||
#include "fs_decompress.h"
|
||||
|
||||
using FileSys::FCompressedBuffer;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "netstartwindow.h"
|
||||
#include "version.h"
|
||||
#include "engineerrors.h"
|
||||
#include "gstrings.h"
|
||||
#include <zwidget/core/timer.h>
|
||||
#include <zwidget/widgets/textlabel/textlabel.h>
|
||||
#include <zwidget/widgets/pushbutton/pushbutton.h>
|
||||
|
|
|
|||
|
|
@ -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<std::string>& allwads, std::vector<std::string>& pwads)
|
||||
{
|
||||
NetworkEntityManager::InitializeNetworkEntities();
|
||||
|
||||
if (!restart)
|
||||
{
|
||||
V_InitScreenSize();
|
||||
|
|
@ -3257,6 +3263,9 @@ static int D_InitGame(const FIWADInfo* iwad_info, std::vector<std::string>& allw
|
|||
exec = NULL;
|
||||
}
|
||||
|
||||
if (!restart)
|
||||
V_Init2();
|
||||
|
||||
// [RH] Initialize localizable strings.
|
||||
GStrings.LoadStrings(fileSystem, language);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
23
src/d_net.h
23
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<DObject*> s_netEntities = {};
|
||||
inline static TArray<uint32_t> 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:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
352
src/events.cpp
352
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<int>);
|
||||
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<int>* 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<int>);
|
||||
PARAM_INT(type);
|
||||
|
||||
NativeReadIntArray(self, values, type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadDoubleArray)
|
||||
static void NativeReadDoubleArray(FNetworkCommand* const self, TArray<double>* const values, const bool doublePrecision)
|
||||
{
|
||||
PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand);
|
||||
PARAM_OUTPOINTER(values, TArray<double>);
|
||||
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<double>);
|
||||
PARAM_BOOL(doublePrecision);
|
||||
|
||||
NativeReadDoubleArray(self, values, doublePrecision);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(FNetworkCommand, ReadStringArray)
|
||||
static void NativeReadStringArray(FNetworkCommand* const self, TArray<FString>* const values, const bool skipEmpty)
|
||||
{
|
||||
PARAM_SELF_STRUCT_PROLOGUE(FNetworkCommand);
|
||||
PARAM_OUTPOINTER(values, TArray<FString>);
|
||||
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<FString>);
|
||||
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<int>(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<int>(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<int>* const values, const int type)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(DNetworkBuffer);
|
||||
PARAM_POINTER(values, TArray<int>);
|
||||
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<int>);
|
||||
PARAM_INT(type);
|
||||
|
||||
AddIntArray(self, values, type);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddDoubleArray)
|
||||
static void AddDoubleArray(DNetworkBuffer* const self, TArray<double>* const values, const bool doublePrecision)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(DNetworkBuffer);
|
||||
PARAM_POINTER(values, TArray<double>);
|
||||
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<double>);
|
||||
PARAM_BOOL(doublePrecision);
|
||||
|
||||
AddDoubleArray(self, values, doublePrecision);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION(DNetworkBuffer, AddStringArray)
|
||||
static void AddStringArray(DNetworkBuffer* const self, TArray<FString>* 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<FString>);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
50
src/events.h
50
src/events.h
|
|
@ -36,11 +36,6 @@ private:
|
|||
size_t _index = 0;
|
||||
TArray<uint8_t> _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<const char*>(&_stream[_index]);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
188
src/g_level.cpp
188
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<TObjPtr<AActor*>>& 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<PField>(nodeCls->FindSymbol("neighbors", true));
|
||||
|
||||
assert(var);
|
||||
assert(var->Type->isDynArray());
|
||||
assert(static_cast<PDynArray*>(var->Type)->ElementType == nodeCls->VMType);
|
||||
|
||||
return *reinterpret_cast<TArray<TObjPtr<AActor*>>*>(reinterpret_cast<uintptr_t>(self) + var->Offset);
|
||||
}
|
||||
|
||||
static void ReconstructPath(TMap<AActor*, AActor*> &cameFrom, AActor* current, TArray<TObjPtr<AActor*>> &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<typename K, typename V>
|
||||
static V GetOr(TMap<K, V> 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<TObjPtr<AActor*>> &path)
|
||||
{
|
||||
TArray<AActor*> openSet;
|
||||
TMap<AActor*, AActor*> cameFrom;
|
||||
TMap<AActor*, double> gScore;
|
||||
TMap<AActor*, double> 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<AActor*>(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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -444,6 +444,9 @@ public:
|
|||
|
||||
void SetMusic();
|
||||
|
||||
bool FindPath(AActor *chaser, AActor *target, AActor *startnode = nullptr, AActor *goalnode = nullptr);
|
||||
|
||||
TArray<AActor *> PathNodes;
|
||||
TArray<vertex_t> vertexes;
|
||||
TArray<sector_t> sectors;
|
||||
TArray<extsector_t> extsectors; // container for non-trivial sector information. sector_t must be trivially copyable for *_fakeflat to work as intended.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <zwidget/core/widget.h>
|
||||
#include "gstrings.h"
|
||||
|
||||
// #define RENDER_BACKENDS
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ void FLevelLocals::ClearLevelData(bool fullgc)
|
|||
}
|
||||
ClearPortals();
|
||||
|
||||
PathNodes.Clear();
|
||||
tagManager.Clear();
|
||||
ClearTIDHashes();
|
||||
if (SpotState) SpotState->Destroy();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<DViewPosition*> 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<DBoneComponents*> boneComponentData;
|
||||
|
||||
// interaction info
|
||||
TArray<TObjPtr<AActor*> > 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<double>(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<double>(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<double>(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;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
|
|
|||
|
|
@ -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<AActor *>(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<AActor>(NAME_Owner) != nullptr)
|
||||
|| (to->IsKindOf(NAME_Inventory) && to->PointerVar<AActor>(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<AActor>();
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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--;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
|
|
|||
|
|
@ -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<double>::fromDeg(spr->InterpolatedRoll(timefrac));
|
||||
|
|
|
|||
|
|
@ -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<DAngle>((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<DAngle>((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<double>(chase_height, -1000.0, 1000.0);
|
||||
iView->RelativeViewOffset.X = -clamp<double>(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_t> &lightlist = viewpoint.sector->e->XFloor.lightlist;
|
||||
unsigned int newBlend = 0u;
|
||||
const TArray<lightlist_t>& 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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -1814,6 +1814,15 @@ DEFINE_CLASS_PROPERTY(playerclass, S, PowerMorph)
|
|||
defaults->PointerVar<PClassActor>(NAME_PlayerClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//==========================================================================
|
||||
DEFINE_CLASS_PROPERTY(monsterclass, S, PowerMorph)
|
||||
{
|
||||
PROP_STRING_PARM(str, 0);
|
||||
defaults->PointerVar<PClassActor>(NAME_MonsterClass) = FindClassTentative(str, RUNTIME_CLASS(AActor), bag.fromDecorate);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//==========================================================================
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
26
vcpkg.json
26
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Default keybindings for all games */
|
||||
/* Default keybindings (keypad) for all games */
|
||||
|
||||
KP8 +forward
|
||||
KP5 +back
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ DoomEdNums
|
|||
5065 = InvisibleBridge8
|
||||
9001 = MapSpot
|
||||
9013 = MapSpotGravity
|
||||
9022 = PathNode
|
||||
9024 = PatrolPoint
|
||||
9025 = SecurityCamera
|
||||
9026 = Spark
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<PathNode> 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<SecPlane> 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<Actor> GetReplacement(class<Actor> 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<SecPlane> 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?
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class PigPlayer : PlayerPawn
|
|||
|
||||
override void MorphPlayerThink ()
|
||||
{
|
||||
if (player.morphTics & 15)
|
||||
if ((player.MorphTics ? player.MorphTics : Random[PigPlayerThink]()) & 15)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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<Inventory> 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);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1870,13 +1870,14 @@ class PowerReflection : Powerup
|
|||
//===========================================================================
|
||||
//
|
||||
// PowerMorph
|
||||
// Now works with monsters too!
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
class PowerMorph : Powerup
|
||||
{
|
||||
Class<PlayerPawn> PlayerClass;
|
||||
Class<Actor> MorphFlash, UnMorphFlash;
|
||||
class<PlayerPawn> PlayerClass;
|
||||
class<Actor> 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.);
|
||||
|
|
|
|||
|
|
@ -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<Actor> 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<Actor> flash)
|
||||
{
|
||||
if (player)
|
||||
player.MorphExitFlash = flash;
|
||||
else
|
||||
MorphExitFlash = flash;
|
||||
}
|
||||
|
||||
clearscope class<Actor> 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<PlayerPawn> playerclass, class<MorphedMonster> monsterclass, int duration = 0, int style = 0, class<Actor> morphflash = null, class<Actor>unmorphflash = null)
|
||||
virtual bool Morph(Actor activator, class<PlayerPawn> playerClass, class<Actor> monsterClass, int duration = 0, EMorphFlags style = 0, class<Actor> morphFlash = "TeleportFog", class<Actor> 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<Actor> type, int duration = 0, int style = 0, class<Actor> morphflash = null, class<Actor>unmorphflash = null)
|
||||
bool A_Morph(class<Actor> type, int duration = 0, EMorphFlags style = 0, class<Actor> morphFlash = "TeleportFog", class<Actor> unmorphFlash = "TeleportFog")
|
||||
{
|
||||
if (self.player != null)
|
||||
{
|
||||
let playerclass = (class<PlayerPawn>)(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<PlayerPawn>)(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<Actor> spawntype, int duration, int style, Class<Actor> enter_flash, Class<Actor> exit_flash)
|
||||
virtual bool MorphMonster(class<Actor> spawnType, int duration, EMorphFlags style, class<Actor> enterFlash = "TeleportFog", class<Actor> 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<Actor>)("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<Actor>)("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<PlayerPawn> PlayerClass;
|
||||
class<Actor> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue