- Backend update from GZDoom

* IQM enhancements
* config storage in Users
* moving of savegame filename generation to the backend
This commit is contained in:
Christoph Oelckers 2022-11-06 11:46:26 +01:00
parent b4a49ea228
commit 41fc5660e5
25 changed files with 454 additions and 315 deletions

View file

@ -138,7 +138,7 @@ IMPLEMENT_CLASS(DShape2D, false, false)
static void Shape2D_SetTransform(DShape2D* self, DShape2DTransform *transform)
{
self->transform = transform->transform;
self->transform = PARAM_NULLCHECK(transform, transform)->transform;
}
DEFINE_ACTION_FUNCTION_NATIVE(DShape2D, SetTransform, Shape2D_SetTransform)

View file

@ -177,7 +177,7 @@ static void setmsgcolor (int index, int color);
FILE *Logfile = NULL;
CVARD_NAMED(Int, msglevel, "msg", 0, CVAR_ARCHIVE, "Filters HUD message by importance");
CVARD_NAMED(Int, msglevel, msg, 0, CVAR_ARCHIVE, "Filters HUD message by importance");
CUSTOM_CVAR (Int, msg0color, CR_UNTRANSLATED, CVAR_ARCHIVE)
{

View file

@ -16,6 +16,7 @@ FString M_GetDemoPath();
FString M_GetNormalizedPath(const char* path);
#ifdef __APPLE__
FString M_GetMacAppSupportPath(const bool create = true);
void M_GetMacSearchDirectories(FString& user_docs, FString& user_app_support, FString& local_app_support);

View file

@ -75,8 +75,8 @@ enum
};
void I_Error(const char *fmt, ...) ATTRIBUTE((format(printf,1,2)));
void I_FatalError(const char* fmt, ...) ATTRIBUTE((format(printf, 1, 2)));
[[noreturn]] void I_Error(const char *fmt, ...) ATTRIBUTE((format(printf,1,2)));
[[noreturn]] void I_FatalError(const char* fmt, ...) ATTRIBUTE((format(printf, 1, 2)));
// This really could need some cleanup - the main problem is that it'd create
// lots of potential for merge conflicts.

View file

@ -46,8 +46,11 @@
#include "findfile.h"
#include "v_draw.h"
#include "savegamemanager.h"
#include "m_argv.h"
#include "i_specialpaths.h"
CVAR(String, save_dir, "", CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
FString SavegameFolder;
//=============================================================================
//
@ -538,3 +541,56 @@ DEFINE_FIELD_X(SavegameManager, FSavegameManagerBase, WindowSize);
DEFINE_FIELD_X(SavegameManager, FSavegameManagerBase, quickSaveSlot);
DEFINE_FIELD_X(SavegameManager, FSavegameManagerBase, SaveCommentString);
//=============================================================================
//
// todo: cache this - it never changes once set up.
//
//=============================================================================
FString G_GetSavegamesFolder()
{
FString name;
bool usefilter;
if (const char* const dir = Args->CheckValue("-savedir"))
{
name = dir;
usefilter = false; //-savedir specifies an absolute save directory path.
}
else
{
name = **save_dir ? save_dir : M_GetSavegamesPath();
usefilter = true;
}
const size_t len = name.Len();
if (len > 0)
{
FixPathSeperator(name);
if (name[len - 1] != '/')
name << '/';
}
if (usefilter && SavegameFolder.IsNotEmpty())
name << SavegameFolder << '/';
name = NicePath(name);
CreatePath(name);
return name;
}
//=============================================================================
//
//
//
//=============================================================================
FString G_BuildSaveName(const char* prefix)
{
FString name = G_GetSavegamesFolder() + prefix;
DefaultExtension(name, "." SAVEGAME_EXT); // only add an extension if the prefix doesn't have one already.
name = NicePath(name);
name.Substitute("\\", "/");
return name;
}

View file

@ -59,3 +59,6 @@ public:
};
extern FString SavegameFolder; // specifies a subdirectory for the current IWAD.
FString G_GetSavegamesFolder();
FString G_BuildSaveName(const char* prefix);

View file

@ -0,0 +1,16 @@
#pragma once
#include "dobject.h"
#include "tarray.h"
#include "TRS.h"
#include "matrix.h"
class DBoneComponents : public DObject
{
DECLARE_CLASS(DBoneComponents, DObject);
public:
TArray<TArray<TRS>> trscomponents;
TArray<TArray<VSMatrix>> trsmatrix;
DBoneComponents() = default;
};

View file

@ -4,7 +4,9 @@
#include "textureid.h"
#include "i_modelvertexbuffer.h"
#include "matrix.h"
#include "TRS.h"
class DBoneComponents;
class FModelRenderer;
class FGameTexture;
class IModelVertexBuffer;
@ -74,8 +76,8 @@ public:
virtual void BuildVertexBuffer(FModelRenderer *renderer) = 0;
virtual void AddSkins(uint8_t *hitlist, const FTextureID* surfaceskinids) = 0;
virtual float getAspectFactor(float vscale) { return 1.f; }
virtual const TArray<VSMatrix>* AttachAnimationData() { return nullptr; };
virtual const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<VSMatrix>& animationData) { return {}; };
virtual const TArray<TRS>* AttachAnimationData() { return nullptr; };
virtual const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>& animationData, DBoneComponents* bones, int index) { return {}; };
void SetVertexBuffer(int type, IModelVertexBuffer *buffer) { mVBuf[type] = buffer; }
IModelVertexBuffer *GetVertexBuffer(int type) const { return mVBuf[type]; }

View file

@ -5,6 +5,9 @@
#include "vectors.h"
#include "matrix.h"
#include "common/rendering/i_modelvertexbuffer.h"
#include "m_swap.h"
class DBoneComponents;
struct IQMMesh
{
@ -112,8 +115,8 @@ public:
void RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame, int frame2, double inter, int translation, const FTextureID* surfaceskinids, const TArray<VSMatrix>& boneData, int boneStartPosition) override;
void BuildVertexBuffer(FModelRenderer* renderer) override;
void AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids) override;
const TArray<VSMatrix>* AttachAnimationData() override;
const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<VSMatrix>& animationData) override;
const TArray<TRS>* AttachAnimationData() override;
const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>& animationData, DBoneComponents* bones, int index) override;
private:
void LoadGeometry();
@ -132,7 +135,6 @@ private:
TArray<IQMJoint> Joints;
TArray<IQMPose> Poses;
TArray<IQMAnim> Anims;
TArray<VSMatrix> FrameTransforms;
TArray<IQMBounds> Bounds;
TArray<IQMVertexArray> VertexArrays;
uint32_t NumVertices = 0;
@ -141,6 +143,7 @@ private:
TArray<VSMatrix> baseframe;
TArray<VSMatrix> inversebaseframe;
TArray<TRS> TRSData;
};
struct IQMReadErrorException { };

View file

@ -5,6 +5,11 @@
#include "texturemanager.h"
#include "modelrenderer.h"
#include "engineerrors.h"
#include "dobject.h"
#include "bonecomponents.h"
IMPLEMENT_CLASS(DBoneComponents, false, false);
IQMModel::IQMModel()
{
@ -58,8 +63,11 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
uint32_t num_extensions = reader.ReadUInt32();
uint32_t ofs_extensions = reader.ReadUInt32();
if (num_meshes <= 0)
I_FatalError("Invalid model: \"%s%s\", no mesh data is unsupported", path, fileSystem.GetLongName(mLumpNum).GetChars());
/*if (num_joints <= 0)
{
Printf("Invalid model: \"%s%s\", no joint data is present\n", path, fileSystem.GetLongName(mLumpNum).GetChars());
return false;
}*/
if (num_text == 0)
return false;
@ -168,18 +176,10 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
{
baseframe[i] = m;
inversebaseframe[i] = invm;
}
}
}
// Swap YZ axis as we did that with the vertices down in LoadGeometry.
// This is an unfortunate side effect of the coordinate system in the gzdoom model rendering system
float swapYZ[16] = { 0.0f };
swapYZ[0 + 0 * 4] = 1.0f;
swapYZ[1 + 2 * 4] = 1.0f;
swapYZ[2 + 1 * 4] = 1.0f;
swapYZ[3 + 3 * 4] = 1.0f;
FrameTransforms.Resize(num_frames * num_poses);
TRSData.Resize(num_frames * num_poses);
reader.SeekTo(ofs_frames);
for (uint32_t i = 0; i < num_frames; i++)
{
@ -204,39 +204,9 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
scale.Y = p.ChannelOffset[8]; if (p.ChannelMask & 0x100) scale.Y += reader.ReadUInt16() * p.ChannelScale[8];
scale.Z = p.ChannelOffset[9]; if (p.ChannelMask & 0x200) scale.Z += reader.ReadUInt16() * p.ChannelScale[9];
VSMatrix m;
m.loadIdentity();
m.translate(translate.X, translate.Y, translate.Z);
m.multQuaternion(quaternion);
m.scale(scale.X, scale.Y, scale.Z);
// Concatenate each pose with the inverse base pose to avoid doing this at animation time.
// If the joint has a parent, then it needs to be pre-concatenated with its parent's base pose.
// Thus it all negates at animation time like so:
// (parentPose * parentInverseBasePose) * (parentBasePose * childPose * childInverseBasePose) =>
// parentPose * (parentInverseBasePose * parentBasePose) * childPose * childInverseBasePose =>
// parentPose * childPose * childInverseBasePose
VSMatrix& result = FrameTransforms[i * num_poses + j];
if (p.Parent >= 0)
{
result = baseframe[p.Parent];
result.multMatrix(m);
result.multMatrix(inversebaseframe[j]);
}
else
{
result = m;
result.multMatrix(inversebaseframe[j]);
}
}
for (uint32_t j = 0; j < num_poses; j++)
{
VSMatrix m;
m.loadMatrix(swapYZ);
m.multMatrix(FrameTransforms[i * num_poses + j]);
m.multMatrix(swapYZ);
FrameTransforms[i * num_poses + j] = m;
TRSData[i * num_poses + j].translation = translate;
TRSData[i * num_poses + j].rotation = quaternion;
TRSData[i * num_poses + j].scaling = scale;
}
}
@ -244,7 +214,7 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
if (num_frames <= 0)
{
num_frames = 1;
FrameTransforms.Resize(num_joints);
TRSData.Resize(num_joints);
for (uint32_t j = 0; j < num_joints; j++)
{
@ -265,33 +235,9 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
scale.Y = Joints[j].Scale.Y;
scale.Z = Joints[j].Scale.Z;
VSMatrix m;
m.loadIdentity();
m.translate(translate.X, translate.Y, translate.Z);
m.multQuaternion(quaternion);
m.scale(scale.X, scale.Y, scale.Z);
VSMatrix& result = FrameTransforms[j];
if (Joints[j].Parent >= 0)
{
result = baseframe[Joints[j].Parent];
result.multMatrix(m);
result.multMatrix(inversebaseframe[j]);
}
else
{
result = m;
result.multMatrix(inversebaseframe[j]);
}
}
for (uint32_t j = 0; j < num_joints; j++)
{
VSMatrix m;
m.loadMatrix(swapYZ);
m.multMatrix(FrameTransforms[j]);
m.multMatrix(swapYZ);
FrameTransforms[j] = m;
TRSData[j].translation = translate;
TRSData[j].rotation = quaternion;
TRSData[j].scaling = scale;
}
}
@ -511,24 +457,25 @@ void IQMModel::RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int fra
{
meshSkin = TexMan.GetGameTexture(surfaceskinids[i], true);
}
else if (!Meshes[i].Skin.isValid())
else if (Meshes[i].Skin.isValid())
{
meshSkin = TexMan.GetGameTexture(Meshes[i].Skin, true);
}
else
{
continue;
}
else
{
meshSkin = TexMan.GetGameTexture(Meshes[i].Skin, true);
}
if (!meshSkin) continue;
}
if (meshSkin != lastSkin)
if (meshSkin->isValid())
{
renderer->SetMaterial(meshSkin, false, translation);
lastSkin = meshSkin;
if (meshSkin != lastSkin)
{
renderer->SetMaterial(meshSkin, false, translation);
lastSkin = meshSkin;
}
renderer->DrawElements(Meshes[i].NumTriangles * 3, Meshes[i].FirstTriangle * 3 * sizeof(unsigned int));
}
renderer->DrawElements(Meshes[i].NumTriangles * 3, Meshes[i].FirstTriangle * 3 * sizeof(unsigned int));
}
}
@ -562,49 +509,99 @@ void IQMModel::AddSkins(uint8_t* hitlist, const FTextureID* surfaceskinids)
}
}
const TArray<VSMatrix>* IQMModel::AttachAnimationData()
const TArray<TRS>* IQMModel::AttachAnimationData()
{
return &FrameTransforms;
return &TRSData;
}
const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double inter, const TArray<VSMatrix>& animationData)
const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>& animationData, DBoneComponents* boneComponentData, int index)
{
const TArray<VSMatrix>& animationFrames = &animationData ? animationData : FrameTransforms;
int numbones = Joints.Size();
frame1 = clamp(frame1, 0, ((int)animationFrames.Size() - 1) / numbones);
frame2 = clamp(frame2, 0, ((int)animationFrames.Size() - 1) / numbones);
int offset1 = frame1 * numbones;
int offset2 = frame2 * numbones;
float t = (float)inter;
float invt = 1.0f - t;
TArray<VSMatrix> bones(numbones, true);
for (int i = 0; i < numbones; i++)
const TArray<TRS>& animationFrames = &animationData ? animationData : TRSData;
if (Joints.Size() > 0)
{
const float* from = animationFrames[offset1 + i].get();
const float* to = animationFrames[offset2 + i].get();
int numbones = Joints.Size();
// Interpolate bone between the two frames
float bone[16];
for (int j = 0; j < 16; j++)
if (boneComponentData->trscomponents[index].Size() != numbones)
boneComponentData->trscomponents[index].Resize(numbones);
if (boneComponentData->trsmatrix[index].Size() != numbones)
boneComponentData->trsmatrix[index].Resize(numbones);
frame1 = clamp(frame1, 0, ((int)animationFrames.Size() - 1) / numbones);
frame2 = clamp(frame2, 0, ((int)animationFrames.Size() - 1) / numbones);
int offset1 = frame1 * numbones;
int offset2 = frame2 * numbones;
float t = (float)inter;
float invt = 1.0f - t;
float swapYZ[16] = { 0.0f };
swapYZ[0 + 0 * 4] = 1.0f;
swapYZ[1 + 2 * 4] = 1.0f;
swapYZ[2 + 1 * 4] = 1.0f;
swapYZ[3 + 3 * 4] = 1.0f;
TArray<VSMatrix> bones(numbones, true);
TArray<bool> modifiedBone(numbones, true);
for (int i = 0; i < numbones; i++)
{
bone[j] = from[j] * invt + to[j] * t;
TRS bone;
TRS from = animationFrames[offset1 + i];
TRS to = animationFrames[offset2 + i];
bone.translation = from.translation * invt + to.translation * t;
bone.rotation = from.rotation * invt;
if ((bone.rotation | to.rotation * t) < 0)
{
bone.rotation.X *= -1; bone.rotation.Y *= -1; bone.rotation.Z *= -1; bone.rotation.W *= -1;
}
bone.rotation += to.rotation * t;
bone.rotation.MakeUnit();
bone.scaling = from.scaling * invt + to.scaling * t;
if (Joints[i].Parent >= 0 && modifiedBone[Joints[i].Parent])
{
boneComponentData->trscomponents[index][i] = bone;
modifiedBone[i] = true;
}
else if (boneComponentData->trscomponents[index][i].Equals(bone))
{
bones[i] = boneComponentData->trsmatrix[index][i];
modifiedBone[i] = false;
continue;
}
else
{
boneComponentData->trscomponents[index][i] = bone;
modifiedBone[i] = true;
}
VSMatrix m;
m.loadIdentity();
m.translate(bone.translation.X, bone.translation.Y, bone.translation.Z);
m.multQuaternion(bone.rotation);
m.scale(bone.scaling.X, bone.scaling.Y, bone.scaling.Z);
VSMatrix& result = bones[i];
if (Joints[i].Parent >= 0)
{
result = bones[Joints[i].Parent];
result.multMatrix(swapYZ);
result.multMatrix(baseframe[Joints[i].Parent]);
result.multMatrix(m);
result.multMatrix(inversebaseframe[i]);
}
else
{
result.loadMatrix(swapYZ);
result.multMatrix(m);
result.multMatrix(inversebaseframe[i]);
}
result.multMatrix(swapYZ);
}
// Apply parent bone
if (Joints[i].Parent >= 0)
{
bones[i] = bones[Joints[i].Parent];
bones[i].multMatrix(bone);
}
else
{
bones[i].loadMatrix(bone);
}
boneComponentData->trsmatrix[index] = bones;
return bones;
}
return bones;
return {};
}

View file

@ -515,5 +515,4 @@ void *DObject::ScriptVar(FName field, PType *type)
}
// This is only for internal use so I_Error is fine.
I_Error("Variable %s not found in %s\n", field.GetChars(), cls->TypeName.GetChars());
return nullptr;
}

View file

@ -37,21 +37,19 @@
#include <lmcons.h>
#include <shlobj.h>
#include <Shlwapi.h>
#include <VersionHelpers.h>
#include "i_specialpaths.h"
#include "printf.h"
#include "cmdlib.h"
#include "findfile.h"
#include "version.h" // for GAMENAME
#include "gstrings.h"
#include "i_mainwindow.h"
#include "engineerrors.h"
// Vanilla MinGW does not have folder ids
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
static const GUID FOLDERID_LocalAppData = { 0xf1b32785, 0x6fba, 0x4fcf, 0x9d, 0x55, 0x7b, 0x8e, 0x7f, 0x15, 0x70, 0x91 };
static const GUID FOLDERID_RoamingAppData = { 0x3eb685db, 0x65f9, 0x4cf6, 0xa0, 0x3a, 0xe3, 0xef, 0x65, 0x72, 0x9f, 0x3d };
static const GUID FOLDERID_SavedGames = { 0x4c5c32ff, 0xbb9d, 0x43b0, 0xb5, 0xb4, 0x2d, 0x72, 0xe5, 0x4e, 0xaa, 0xa4 };
static const GUID FOLDERID_Documents = { 0xfdd39ad0, 0x238f, 0x46af, 0xad, 0xb4, 0x6c, 0x85, 0x48, 0x03, 0x69, 0xc7 };
static const GUID FOLDERID_Pictures = { 0x33e28130, 0x4e1e, 0x4676, 0x83, 0x5a, 0x98, 0x39, 0x5c, 0x3b, 0xc3, 0xbb };
#endif
static int isportable = -1;
//===========================================================================
//
@ -62,18 +60,18 @@ static const GUID FOLDERID_Pictures = { 0x33e28130, 0x4e1e, 0x4676, 0x83, 0x5a,
//
//===========================================================================
bool UseKnownFolders()
bool IsPortable()
{
// Cache this value so the semantics don't change during a single run
// of the program. (e.g. Somebody could add write access while the
// program is running.)
static int iswritable = -1;
HANDLE file;
if (iswritable >= 0)
if (isportable >= 0)
{
return !iswritable;
return !!isportable;
}
// Consider 'Program Files' read only without actually checking.
bool found = false;
for (auto p : { L"ProgramFiles", L"ProgramFiles(x86)" })
@ -85,50 +83,51 @@ bool UseKnownFolders()
FixPathSeperator(envpath);
if (progdir.MakeLower().IndexOf(envpath.MakeLower()) == 0)
{
found = true;
break;
isportable = false;
return false;
}
}
}
if (!found)
// A portable INI means that this storage location should also be portable if the file can be written to.
FStringf path("%s" GAMENAME "_portable.ini", progdir.GetChars());
if (FileExists(path))
{
std::wstring testpath = progdir.WideString() + L"writest";
file = CreateFile(testpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
file = CreateFile(path.WideString().c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file != INVALID_HANDLE_VALUE)
{
CloseHandle(file);
if (!batchrun) Printf("Using program directory for storage\n");
iswritable = true;
return false;
if (!batchrun) Printf("Using portable configuration\n");
isportable = true;
return true;
}
}
if (!batchrun) Printf("Using known folders for storage\n");
iswritable = false;
return true;
isportable = false;
return false;
}
//===========================================================================
//
// GetKnownFolder
//
// Returns the known_folder if SHGetKnownFolderPath is available, otherwise
// returns the shell_folder using SHGetFolderPath.
// Returns the known_folder from SHGetKnownFolderPath
//
//===========================================================================
bool GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool create, FString &path)
FString GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool create)
{
PWSTR wpath;
if (FAILED(SHGetKnownFolderPath(known_folder, create ? KF_FLAG_CREATE : 0, NULL, &wpath)))
{
return false;
// This should never be triggered unless the OS was compromised
I_FatalError("Unable to retrieve known folder.");
}
path = wpath;
FString path = FString(wpath);
FixPathSeperator(path);
CoTaskMemFree(wpath);
return true;
return path;
}
//===========================================================================
@ -141,14 +140,9 @@ bool GetKnownFolder(int shell_folder, REFKNOWNFOLDERID known_folder, bool create
FString M_GetAppDataPath(bool create)
{
FString path;
FString path = GetKnownFolder(CSIDL_LOCAL_APPDATA, FOLDERID_LocalAppData, create);
if (!GetKnownFolder(CSIDL_LOCAL_APPDATA, FOLDERID_LocalAppData, create, path))
{ // Failed (e.g. On Win9x): use program directory
path = progdir;
}
path += "/" GAMENAMELOWERCASE;
path.Substitute("//", "/"); // needed because progdir ends with a slash.
if (create)
{
CreatePath(path);
@ -166,16 +160,11 @@ FString M_GetAppDataPath(bool create)
FString M_GetCachePath(bool create)
{
FString path;
FString path = GetKnownFolder(CSIDL_LOCAL_APPDATA, FOLDERID_LocalAppData, create);
if (!GetKnownFolder(CSIDL_LOCAL_APPDATA, FOLDERID_LocalAppData, create, path))
{ // Failed (e.g. On Win9x): use program directory
path = progdir;
}
// Don't use GAME_DIR and such so that ZDoom and its child ports can
// share the node cache.
path += "/zdoom/cache";
path.Substitute("//", "/"); // needed because progdir ends with a slash.
if (create)
{
CreatePath(path);
@ -196,6 +185,84 @@ FString M_GetAutoexecPath()
return "$PROGDIR/autoexec.cfg";
}
//===========================================================================
//
// M_GetOldConfigPath
//
// Check if we have a config in a place that's no longer used.
//
//===========================================================================
FString M_GetOldConfigPath(int& type)
{
FString path;
HRESULT hr;
// construct "$PROGDIR/-$USER.ini"
WCHAR uname[UNLEN + 1];
DWORD unamelen = UNLEN;
path = progdir;
hr = GetUserNameW(uname, &unamelen);
if (SUCCEEDED(hr) && uname[0] != 0)
{
// Is it valid for a user name to have slashes?
// Check for them and substitute just in case.
auto probe = uname;
while (*probe != 0)
{
if (*probe == '\\' || *probe == '/')
*probe = '_';
++probe;
}
path << GAMENAMELOWERCASE "-" << FString(uname) << ".ini";
type = 0;
if (FileExists(path))
return path;
}
// Check in app data where this was previously stored.
// We actually prefer to store the config in a more visible place so this is no longer used.
path = GetKnownFolder(CSIDL_APPDATA, FOLDERID_RoamingAppData, true);
path += "/" GAME_DIR "/" GAMENAMELOWERCASE ".ini";
type = 1;
if (FileExists(path))
return path;
return "";
}
//===========================================================================
//
// M_MigrateOldConfig
//
// Ask the user what to do with their old config.
//
//===========================================================================
int M_MigrateOldConfig()
{
int selection = IDCANCEL;
auto globalstr = L"Move to Users/ folder";
auto portablestr = L"Convert to portable installation";
auto cancelstr = L"Cancel";
auto titlestr = L"Migrate existing configuration";
auto infostr = L"" GAMENAME " found a user specific config in the game folder";
const TASKDIALOG_BUTTON buttons[] = { {IDYES, globalstr}, {IDNO, portablestr}, {IDCANCEL, cancelstr} };
TASKDIALOGCONFIG taskDialogConfig = {};
taskDialogConfig.cbSize = sizeof(TASKDIALOGCONFIG);
taskDialogConfig.pszMainIcon = TD_WARNING_ICON;
taskDialogConfig.pButtons = buttons;
taskDialogConfig.cButtons = countof(buttons);
taskDialogConfig.pszWindowTitle = titlestr;
taskDialogConfig.pszContent = infostr;
taskDialogConfig.hwndParent = mainwindow.GetHandle();
taskDialogConfig.dwFlags = TDF_USE_COMMAND_LINKS;
TaskDialogIndirect(&taskDialogConfig, &selection, NULL, NULL);
if (selection == IDYES || selection == IDNO) return selection;
throw CExitEvent(3);
}
//===========================================================================
//
// M_GetConfigPath Windows
@ -208,51 +275,43 @@ FString M_GetAutoexecPath()
FString M_GetConfigPath(bool for_reading)
{
FString path;
HRESULT hr;
path.Format("%s" GAMENAMELOWERCASE "_portable.ini", progdir.GetChars());
if (FileExists(path))
if (IsPortable())
{
return path;
return FStringf("%s" GAMENAMELOWERCASE "_portable.ini", progdir.GetChars());
}
path = "";
// Construct a user-specific config name
if (UseKnownFolders() && GetKnownFolder(CSIDL_APPDATA, FOLDERID_RoamingAppData, true, path))
FString path = GetKnownFolder(CSIDL_APPDATA, FOLDERID_Documents, true);
path += "/My Games/" GAME_DIR;
CreatePath(path);
path += "/" GAMENAMELOWERCASE ".ini";
if (!for_reading || FileExists(path))
return path;
// No config was found in the accepted locations.
// Look in previously valid places to see if we have something we can migrate
int type = 0;
FString oldpath = M_GetOldConfigPath(type);
if (!oldpath.IsEmpty())
{
path += "/" GAME_DIR;
CreatePath(path);
path += "/" GAMENAMELOWERCASE ".ini";
}
else
{ // construct "$PROGDIR/-$USER.ini"
WCHAR uname[UNLEN+1];
DWORD unamelen = UNLEN;
path = progdir;
hr = GetUserNameW(uname, &unamelen);
if (SUCCEEDED(hr) && uname[0] != 0)
if (type == 0)
{
// Is it valid for a user name to have slashes?
// Check for them and substitute just in case.
auto probe = uname;
while (*probe != 0)
// If we find a local per-user config, ask the user what to do with it.
int action = M_MigrateOldConfig();
if (action == IDNO)
{
if (*probe == '\\' || *probe == '/')
*probe = '_';
++probe;
path.Format("%s" GAMENAMELOWERCASE "_portable.ini", progdir.GetChars());
isportable = true;
}
path << GAMENAMELOWERCASE "-" << FString(uname) << ".ini";
}
else
{ // Couldn't get user name, so just use base version.
path += GAMENAMELOWERCASE ".ini";
}
bool res = MoveFileExW(WideString(oldpath).c_str(), WideString(path).c_str(), MOVEFILE_COPY_ALLOWED);
if (res) return path;
else return oldpath; // if we cannot move, just use the config where it was. It won't be written back, though and never be used again if a new one gets saved.
}
// If we are reading the config file, check if it exists. If not, fallback
// to base version.
// Fall back to the global template if nothing was found.
// If we are reading the config file, check if it exists. If not, fallback to base version.
if (for_reading)
{
if (!FileExists(path))
@ -273,30 +332,25 @@ FString M_GetConfigPath(bool for_reading)
//
//===========================================================================
// I'm not sure when FOLDERID_Screenshots was added, but it was probably
// for Windows 8, since it's not in the v7.0 Windows SDK.
static const GUID MyFOLDERID_Screenshots = { 0xb7bede81, 0xdf94, 0x4682, 0xa7, 0xd8, 0x57, 0xa5, 0x26, 0x20, 0xb8, 0x6f };
FString M_GetScreenshotsPath()
{
FString path;
if (!UseKnownFolders())
if (IsPortable())
{
path << progdir << "Screenshots/";
}
else if (GetKnownFolder(-1, MyFOLDERID_Screenshots, true, path))
else if (IsWindows8OrGreater())
{
path = GetKnownFolder(-1, FOLDERID_Screenshots, true);
path << "/" GAMENAME "/";
}
else if (GetKnownFolder(CSIDL_MYPICTURES, FOLDERID_Pictures, true, path))
else
{
path = GetKnownFolder(CSIDL_MYPICTURES, FOLDERID_Pictures, true);
path << "/Screenshots/" GAMENAME "/";
}
else
{
path << progdir << "/Screenshots/";
}
CreatePath(path);
return path;
}
@ -313,27 +367,16 @@ FString M_GetSavegamesPath()
{
FString path;
if (!UseKnownFolders())
if (IsPortable())
{
path << progdir << "Save/";
}
// Try standard Saved Games folder
else if (GetKnownFolder(-1, FOLDERID_SavedGames, true, path))
{
path << "/" GAMENAME "/";
}
// Try defacto My Documents/My Games folder
else if (GetKnownFolder(CSIDL_PERSONAL, FOLDERID_Documents, true, path))
{
// I assume since this isn't a standard folder, it doesn't have
// a localized name either.
path << "/My Games/" GAMENAME "/";
}
else
{
path << progdir << "Save/";
path = GetKnownFolder(-1, FOLDERID_SavedGames, true);
path << "/" GAMENAME "/";
}
return path;
}
@ -349,29 +392,18 @@ FString M_GetDocumentsPath()
{
FString path;
// A portable INI means that this storage location should also be portable.
path.Format("%s" GAMENAME "_portable.ini", progdir.GetChars());
if (FileExists(path))
{
return progdir;
}
if (!UseKnownFolders())
if (IsPortable())
{
return progdir;
}
// Try defacto My Documents/My Games folder
else if (GetKnownFolder(CSIDL_PERSONAL, FOLDERID_Documents, true, path))
else
{
// I assume since this isn't a standard folder, it doesn't have
// a localized name either.
// I assume since this isn't a standard folder, it doesn't have a localized name either.
path = GetKnownFolder(CSIDL_PERSONAL, FOLDERID_Documents, true);
path << "/My Games/" GAMENAME "/";
CreatePath(path);
}
else
{
path = progdir;
}
return path;
}
@ -388,23 +420,17 @@ FString M_GetDemoPath()
FString path;
// A portable INI means that this storage location should also be portable.
FStringf inipath("%s" GAMENAME "_portable.ini", progdir.GetChars());
if (FileExists(inipath) || !UseKnownFolders())
if (IsPortable())
{
path << progdir << "Demos/";
}
else
// Try defacto My Documents/My Games folder
if (GetKnownFolder(CSIDL_PERSONAL, FOLDERID_Documents, true, path))
{
// I assume since this isn't a standard folder, it doesn't have
// a localized name either.
// I assume since this isn't a standard folder, it doesn't have a localized name either.
path = GetKnownFolder(CSIDL_PERSONAL, FOLDERID_Documents, true);
path << "/My Games/" GAMENAME "/";
}
else
{
path << progdir << "Demos/";
}
return path;
}

View file

@ -170,6 +170,8 @@ void DFrameBuffer::SetViewportRects(IntRect *bounds)
mSceneViewport = *bounds;
mScreenViewport = *bounds;
mOutputLetterbox = *bounds;
mGameScreenWidth = mScreenViewport.width;
mGameScreenHeight = mScreenViewport.height;
return;
}
@ -216,6 +218,9 @@ void DFrameBuffer::SetViewportRects(IntRect *bounds)
mSceneViewport.width = (int)round(mSceneViewport.width * scaleX);
mSceneViewport.height = (int)round(mSceneViewport.height * scaleY);
}
mGameScreenWidth = GetWidth();
mGameScreenHeight = GetHeight();
}
//===========================================================================
@ -226,12 +231,12 @@ void DFrameBuffer::SetViewportRects(IntRect *bounds)
int DFrameBuffer::ScreenToWindowX(int x)
{
return mScreenViewport.left + (int)round(x * mScreenViewport.width / (float)GetWidth());
return mScreenViewport.left + (int)round(x * mScreenViewport.width / (float)mGameScreenWidth);
}
int DFrameBuffer::ScreenToWindowY(int y)
{
return mScreenViewport.top + mScreenViewport.height - (int)round(y * mScreenViewport.height / (float)GetHeight());
return mScreenViewport.top + mScreenViewport.height - (int)round(y * mScreenViewport.height / (float)mGameScreenHeight);
}
void DFrameBuffer::ScaleCoordsFromWindow(int16_t &x, int16_t &y)

View file

@ -147,6 +147,8 @@ public:
BoneBuffer* mBones = nullptr; // Model bones
IShadowMap mShadowMap;
int mGameScreenWidth = 0;
int mGameScreenHeight = 0;
IntRect mScreenViewport;
IntRect mSceneViewport;
IntRect mOutputLetterbox;

View file

@ -206,7 +206,7 @@ void VulkanFrameBuffer::RenderTextureView(FCanvasTexture* tex, std::function<voi
mRenderState->EndRenderPass();
VkImageTransition()
.AddImage(image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true)
.AddImage(image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, false)
.Execute(mCommands->GetDrawCommands());
mRenderState->SetRenderTarget(image, depthStencil->View.get(), image->Image->width, image->Image->height, VK_FORMAT_R8G8B8A8_UNORM, VK_SAMPLE_COUNT_1_BIT);

View file

@ -110,8 +110,8 @@ public:
};
// This must be a separate function because the VC compiler would otherwise allocate memory on the stack for every separate instance of the exception object that may get thrown.
void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...);
void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...);
[[noreturn]] void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...);
[[noreturn]] void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...);
void ClearGlobalVMStack();
@ -496,6 +496,7 @@ inline int VMCallAction(VMFunction *func, VMValue *params, int numparams, VMRetu
// Use these to collect the parameters in a native function.
// variable name <x> at position <p>
[[noreturn]]
void NullParam(const char *varname);
#ifndef NDEBUG

View file

@ -688,14 +688,14 @@ void CVMAbortException::MaybePrintMessage()
}
void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...)
[[noreturn]] void ThrowAbortException(EVMAbortException reason, const char *moreinfo, ...)
{
va_list ap;
va_start(ap, moreinfo);
throw CVMAbortException(reason, moreinfo, ap);
}
void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...)
[[noreturn]] void ThrowAbortException(VMScriptFunction *sfunc, VMOP *line, EVMAbortException reason, const char *moreinfo, ...)
{
va_list ap;
va_start(ap, moreinfo);
@ -711,10 +711,9 @@ DEFINE_ACTION_FUNCTION(DObject, ThrowAbortException)
PARAM_PROLOGUE;
FString s = FStringFormat(VM_ARGS_NAMES);
ThrowAbortException(X_OTHER, s.GetChars());
return 0;
}
void NullParam(const char *varname)
[[noreturn]] void NullParam(const char *varname)
{
ThrowAbortException(X_READ_NIL, "In function parameter %s", varname);
}

View file

@ -0,0 +1,56 @@
/*
** TRS
**
**---------------------------------------------------------------------------
** Copyright 2022 Andrew Clarke
** 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.
**---------------------------------------------------------------------------
**
*/
#pragma once
#include "vectors.h"
class TRS
{
public:
FVector3 translation;
FVector4 rotation;
FVector3 scaling;
TRS()
{
translation = FVector3(0,0,0);
rotation = FVector4(0,0,0,1);
scaling = FVector3(0,0,0);
}
bool Equals(TRS& compare)
{
return compare.translation == this->translation && compare.rotation == this->rotation && compare.scaling == this->scaling;
}
};

View file

@ -63,7 +63,7 @@ void I_DebugPrint(const char *cp)
//
//==========================================================================
void I_Error(const char *error, ...)
[[noreturn]] void I_Error(const char *error, ...)
{
va_list argptr;
char errortext[MAX_ERRORTEXT];
@ -85,7 +85,7 @@ void I_Error(const char *error, ...)
//==========================================================================
extern FILE *Logfile;
void I_FatalError(const char *error, ...)
[[noreturn]] void I_FatalError(const char *error, ...)
{
static bool alreadyThrown = false;
gameisdead = true;

View file

@ -110,7 +110,7 @@ public:
};
void I_ShowFatalError(const char *message);
void I_Error (const char *error, ...) GCCPRINTF(1,2);
void I_FatalError (const char *error, ...) GCCPRINTF(1,2);
[[noreturn]] void I_Error (const char *error, ...) GCCPRINTF(1,2);
[[noreturn]] void I_FatalError (const char *error, ...) GCCPRINTF(1,2);
#endif //__ERRORS_H__

View file

@ -846,6 +846,22 @@ struct TVector4
return TVector4(v.X * scalar, v.Y * scalar, v.Z * scalar, v.W * scalar);
}
// Multiply as Quaternion
TVector4& operator*= (const TVector4& v)
{
*this = *this * v;
return *this;
}
friend TVector4 operator* (const TVector4& v1, const TVector4& v2)
{
return TVector4(v2.W * v1.X + v2.X * v1.W + v2.Y * v1.Z - v1.Z * v1.Y,
v2.W * v1.Y + v2.Y * v1.W + v2.Z * v1.X - v2.X * v1.Z,
v2.W * v1.Z + v2.Z * v1.W + v2.X * v1.Y - v2.Y * v1.X,
v2.W * v1.W - v2.X * v1.X - v2.Y * v1.Y - v2.Z * v1.Z
);
}
// Scalar division
TVector4 &operator/= (vec_t scalar)
{

View file

@ -842,7 +842,7 @@ static TArray<GrpEntry> SetupGame()
LumpFilter = usedgroups.Last().FileInfo.name;
LumpFilter.StripChars(".:/\\<>?\"*| \t\r\n");
}
SavegameFolder = LumpFilter;
currentGame = LumpFilter;
currentGame.Truncate(currentGame.IndexOf("."));
PClass::StaticInit();

View file

@ -393,47 +393,6 @@ int G_ValidateSavegame(FileReader &fr, FString *savetitle, bool formenu)
return 0;
}
//=============================================================================
//
//
//
//=============================================================================
FString G_BuildSaveName (const char *prefix)
{
FString name;
bool usefilter;
if (const char *const dir = Args->CheckValue("-savedir"))
{
name = dir;
usefilter = false;
}
else
{
name = **cl_savedir ? cl_savedir : M_GetSavegamesPath();
usefilter = true;
}
const size_t len = name.Len();
if (len > 0)
{
name.Substitute("\\", "/");
if (name[len - 1] != '/')
name << '/';
}
if (usefilter)
name << LumpFilter << '/';
CreatePath(name);
name << prefix;
if (!strchr(prefix, '.')) name << SAVEGAME_EXT; // only add an extension if the prefix doesn't have one already.
name = NicePath(name);
name.Substitute("\\", "/");
return name;
}
#include "build.h"

View file

@ -9,7 +9,6 @@
class FileReader;
extern int SaveVersion;
FString G_BuildSaveName (const char *prefix);
int G_ValidateSavegame(FileReader &fr, FString *savetitle, bool formenu);
void G_LoadGame(const char* filename, bool hidecon = false);
@ -19,9 +18,6 @@ void G_DoLoadGame();
void M_Autosave();
#define SAVEGAME_EXT ".dsave"
template<> inline FSerializer& Serialize(FSerializer& arc, const char* keyname, sectortype*& w, sectortype** def)
{
assert(arc.isReading() || w == nullptr || (w >= &sector[0] && w <= &sector.Last()));

View file

@ -69,6 +69,8 @@ const char *GetVersionString();
#define SAVESIG_SW GAMENAME ".ShadowWarrior"
#define SAVESIG_PS GAMENAME ".Exhumed"
#define SAVEGAME_EXT "dsave"
#define MINSAVEVER_DN3D 16
#define MINSAVEVER_BLD 16
#define MINSAVEVER_SW 17