mirror of
https://github.com/ZDoom/gzdoom.git
synced 2025-03-20 09:52:06 +00:00
Decoupled IQM Model Animations
This commit is contained in:
parent
1a8dfd2dfa
commit
5d5e7ca042
14 changed files with 515 additions and 75 deletions
|
@ -240,7 +240,8 @@ FSerializer &Serialize(FSerializer &arc, const char *key, FName &value, FName *d
|
|||
FSerializer &Serialize(FSerializer &arc, const char *key, FSoundID &sid, FSoundID *def);
|
||||
FSerializer &Serialize(FSerializer &arc, const char *key, FString &sid, FString *def);
|
||||
FSerializer &Serialize(FSerializer &arc, const char *key, NumericValue &sid, NumericValue *def);
|
||||
FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &sid, struct ModelOverride *def);
|
||||
FSerializer &Serialize(FSerializer &arc, const char *key, struct ModelOverride &mo, struct ModelOverride *def);
|
||||
FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimOverride &ao, struct AnimOverride *def);
|
||||
FSerializer& Serialize(FSerializer& arc, const char* key, FTranslationID& value, FTranslationID* defval);
|
||||
|
||||
void SerializeFunctionPointer(FSerializer &arc, const char *key, FunctionPointerValue *&p);
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
TArray<FString> savedModelFiles;
|
||||
TDeletingArray<FModel*> Models;
|
||||
TArray<FSpriteModelFrame> SpriteModelFrames;
|
||||
TMap<void*, FSpriteModelFrame> BaseSpriteModelFrames;
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include "matrix.h"
|
||||
#include "palettecontainer.h"
|
||||
#include "TRS.h"
|
||||
#include "tarray.h"
|
||||
#include "name.h"
|
||||
|
||||
class DBoneComponents;
|
||||
class FModelRenderer;
|
||||
|
@ -19,6 +21,7 @@ void FlushModels();
|
|||
extern TArray<FString> savedModelFiles;
|
||||
extern TDeletingArray<FModel*> Models;
|
||||
extern TArray<FSpriteModelFrame> SpriteModelFrames;
|
||||
extern TMap<void*, FSpriteModelFrame> BaseSpriteModelFrames;
|
||||
|
||||
#define MD3_MAX_SURFACES 32
|
||||
#define MIN_MODELS 4
|
||||
|
@ -72,13 +75,20 @@ public:
|
|||
virtual ~FModel();
|
||||
|
||||
virtual bool Load(const char * fn, int lumpnum, const char * buffer, int length) = 0;
|
||||
|
||||
virtual int FindFrame(const char * name, bool nodefault = false) = 0;
|
||||
|
||||
// [RL0] these are used for decoupled iqm animations
|
||||
virtual int FindFirstFrame(FName name) { return FErr_NotFound; }
|
||||
virtual int FindLastFrame(FName name) { return FErr_NotFound; }
|
||||
virtual double FindFramerate(FName name) { return FErr_NotFound; }
|
||||
|
||||
virtual void RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frame, int frame2, double inter, FTranslationID translation, const FTextureID* surfaceskinids, const TArray<VSMatrix>& boneData, int boneStartPosition) = 0;
|
||||
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<TRS>* AttachAnimationData() { return nullptr; };
|
||||
virtual const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index) { return {}; };
|
||||
virtual const TArray<VSMatrix> CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, 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]; }
|
||||
|
@ -86,7 +96,8 @@ public:
|
|||
|
||||
bool hasSurfaces = false;
|
||||
FString mFileName;
|
||||
|
||||
|
||||
FSpriteModelFrame *baseFrame;
|
||||
private:
|
||||
IModelVertexBuffer *mVBuf[NumModelRendererTypes];
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "matrix.h"
|
||||
#include "common/rendering/i_modelvertexbuffer.h"
|
||||
#include "m_swap.h"
|
||||
#include "name.h"
|
||||
|
||||
class DBoneComponents;
|
||||
|
||||
|
@ -112,11 +113,14 @@ public:
|
|||
|
||||
bool Load(const char* fn, int lumpnum, const char* buffer, int length) override;
|
||||
int FindFrame(const char* name, bool nodefault) override;
|
||||
int FindFirstFrame(FName name) override;
|
||||
int FindLastFrame(FName name) override;
|
||||
double FindFramerate(FName name) override;
|
||||
void RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame, int frame2, double inter, FTranslationID 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<TRS>* AttachAnimationData() override;
|
||||
const TArray<VSMatrix> CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>* animationData, DBoneComponents* bones, int index) override;
|
||||
const TArray<VSMatrix> CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray<TRS>* animationData, DBoneComponents* bones, int index) override;
|
||||
|
||||
private:
|
||||
void LoadGeometry();
|
||||
|
@ -129,6 +133,9 @@ private:
|
|||
void LoadBlendWeights(IQMFileReader& reader, const IQMVertexArray& vertexArray);
|
||||
|
||||
int mLumpNum = -1;
|
||||
|
||||
TMap<FName, int> NamedAnimations;
|
||||
|
||||
TArray<IQMMesh> Meshes;
|
||||
TArray<IQMTriangle> Triangles;
|
||||
TArray<IQMAdjacency> Adjacency;
|
||||
|
|
|
@ -137,9 +137,12 @@ bool IQMModel::Load(const char* path, int lumpnum, const char* buffer, int lengt
|
|||
}
|
||||
|
||||
reader.SeekTo(ofs_anims);
|
||||
for (IQMAnim& anim : Anims)
|
||||
for(int i = 0; i < Anims.Size(); i++)
|
||||
{
|
||||
IQMAnim& anim = Anims[i];
|
||||
anim.Name = reader.ReadName(text);
|
||||
NamedAnimations.Insert(anim.Name, i);
|
||||
|
||||
anim.FirstFrame = reader.ReadUInt32();
|
||||
anim.NumFrames = reader.ReadUInt32();
|
||||
anim.Framerate = reader.ReadFloat();
|
||||
|
@ -445,6 +448,27 @@ int IQMModel::FindFrame(const char* name, bool nodefault)
|
|||
return FErr_NotFound;
|
||||
}
|
||||
|
||||
int IQMModel::FindFirstFrame(FName name)
|
||||
{
|
||||
int * nam = NamedAnimations.CheckKey(name);
|
||||
if(nam) return Anims[*nam].FirstFrame;
|
||||
return FErr_NotFound;
|
||||
}
|
||||
|
||||
int IQMModel::FindLastFrame(FName name)
|
||||
{
|
||||
int * nam = NamedAnimations.CheckKey(name);
|
||||
if(nam) return Anims[*nam].FirstFrame + Anims[*nam].NumFrames;
|
||||
return FErr_NotFound;
|
||||
}
|
||||
|
||||
double IQMModel::FindFramerate(FName name)
|
||||
{
|
||||
int * nam = NamedAnimations.CheckKey(name);
|
||||
if(nam) return Anims[*nam].Framerate;
|
||||
return FErr_NotFound;
|
||||
}
|
||||
|
||||
void IQMModel::RenderFrame(FModelRenderer* renderer, FGameTexture* skin, int frame1, int frame2, double inter, FTranslationID translation, const FTextureID* surfaceskinids, const TArray<VSMatrix>& boneData, int boneStartPosition)
|
||||
{
|
||||
renderer->SetupFrame(this, 0, 0, NumVertices, boneData, boneStartPosition);
|
||||
|
@ -517,7 +541,26 @@ const TArray<TRS>* IQMModel::AttachAnimationData()
|
|||
return &TRSData;
|
||||
}
|
||||
|
||||
const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double inter, const TArray<TRS>* animationData, DBoneComponents* boneComponentData, int index)
|
||||
static TRS InterpolateBone(const TRS &from, const TRS &to, float t, float invt)
|
||||
{
|
||||
TRS bone;
|
||||
|
||||
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;
|
||||
|
||||
return bone;
|
||||
}
|
||||
|
||||
const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, float inter, int frame1_prev, float inter1_prev, int frame2_prev, float inter2_prev, const TArray<TRS>* animationData, DBoneComponents* boneComponentData, int index)
|
||||
{
|
||||
const TArray<TRS>& animationFrames = animationData ? *animationData : TRSData;
|
||||
if (Joints.Size() > 0)
|
||||
|
@ -534,8 +577,13 @@ const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double i
|
|||
|
||||
int offset1 = frame1 * numbones;
|
||||
int offset2 = frame2 * numbones;
|
||||
float t = (float)inter;
|
||||
float invt = 1.0f - t;
|
||||
|
||||
int offset1_1 = frame1_prev * numbones;
|
||||
int offset2_1 = frame2_prev * numbones;
|
||||
|
||||
float invt = 1.0f - inter;
|
||||
float invt1 = 1.0f - inter1_prev;
|
||||
float invt2 = 1.0f - inter2_prev;
|
||||
|
||||
float swapYZ[16] = { 0.0f };
|
||||
swapYZ[0 + 0 * 4] = 1.0f;
|
||||
|
@ -547,19 +595,26 @@ const TArray<VSMatrix> IQMModel::CalculateBones(int frame1, int frame2, double i
|
|||
TArray<bool> modifiedBone(numbones, true);
|
||||
for (int i = 0; i < numbones; i++)
|
||||
{
|
||||
TRS bone;
|
||||
TRS from = animationFrames[offset1 + i];
|
||||
TRS to = animationFrames[offset2 + i];
|
||||
TRS prev;
|
||||
|
||||
bone.translation = from.translation * invt + to.translation * t;
|
||||
bone.rotation = from.rotation * invt;
|
||||
if ((bone.rotation | to.rotation * t) < 0)
|
||||
if(frame1 >= 0 && (frame1_prev >= 0 || inter1_prev < 0))
|
||||
{
|
||||
bone.rotation.X *= -1; bone.rotation.Y *= -1; bone.rotation.Z *= -1; bone.rotation.W *= -1;
|
||||
prev = inter1_prev < 0 ? animationFrames[offset1 + i] : InterpolateBone(animationFrames[offset1_1 + i], animationFrames[offset1 + i], inter1_prev, invt1);
|
||||
}
|
||||
|
||||
TRS next;
|
||||
|
||||
if(frame2 >= 0 && (frame2_prev >= 0 || inter2_prev < 0))
|
||||
{
|
||||
next = inter2_prev < 0 ? animationFrames[offset2 + i] : InterpolateBone(animationFrames[offset2_1 + i], animationFrames[offset2 + i], inter2_prev, invt2);
|
||||
}
|
||||
|
||||
TRS bone;
|
||||
|
||||
if(frame1 >= 0 || inter < 0)
|
||||
{
|
||||
bone = inter < 0 ? animationFrames[offset1 + i] : InterpolateBone(prev, next , inter, invt);
|
||||
}
|
||||
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])
|
||||
{
|
||||
|
|
|
@ -28,6 +28,7 @@ class FTextureID
|
|||
|
||||
public:
|
||||
FTextureID() = default;
|
||||
FTextureID(nullptr_t) : texnum(0) {}
|
||||
bool isNull() const { return texnum == 0; }
|
||||
bool isValid() const { return texnum > 0; }
|
||||
bool Exists() const { return texnum >= 0; }
|
||||
|
|
|
@ -438,10 +438,11 @@ enum ActorFlag8
|
|||
// --- mobj.flags9 ---
|
||||
enum ActorFlag9
|
||||
{
|
||||
MF9_SHADOWAIM = 0x00000001, // [inkoalawetrust] Monster still gets aim penalty from aiming at shadow actors even with MF6_SEEINVISIBLE on.
|
||||
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_SHADOWAIM = 0x00000001, // [inkoalawetrust] Monster still gets aim penalty from aiming at shadow actors even with MF6_SEEINVISIBLE on.
|
||||
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
|
||||
};
|
||||
|
||||
// --- mobj.renderflags ---
|
||||
|
@ -689,13 +690,31 @@ enum EViewPosFlags // [MC] Flags for SetViewPos.
|
|||
VPSF_ABSOLUTEPOS = 1 << 2, // Use absolute position.
|
||||
};
|
||||
|
||||
enum EAnimOverrideFlags
|
||||
{
|
||||
ANIMOVERRIDE_NONE = 1 << 0, // no animation
|
||||
ANIMOVERRIDE_LOOP = 1 << 1, // animation loops, otherwise it stays on the last frame once it ends
|
||||
};
|
||||
|
||||
struct AnimOverride
|
||||
{
|
||||
int firstFrame;
|
||||
int lastFrame;
|
||||
int loopFrame;
|
||||
double startFrame;
|
||||
int flags = ANIMOVERRIDE_NONE;
|
||||
float framerate;
|
||||
double startTic; // when the animation starts if interpolating from previous animation
|
||||
double switchTic; // when the animation was changed -- where to interpolate the switch from
|
||||
};
|
||||
|
||||
struct ModelOverride
|
||||
{
|
||||
int modelID;
|
||||
TArray<FTextureID> surfaceSkinIDs;
|
||||
};
|
||||
|
||||
enum ModelDataFlags
|
||||
enum EModelDataFlags
|
||||
{
|
||||
MODELDATA_HADMODEL = 1 << 0,
|
||||
};
|
||||
|
@ -705,11 +724,14 @@ class DActorModelData : public DObject
|
|||
DECLARE_CLASS(DActorModelData, DObject);
|
||||
public:
|
||||
FName modelDef;
|
||||
int flags;
|
||||
TArray<ModelOverride> models;
|
||||
TArray<FTextureID> skinIDs;
|
||||
TArray<int> animationIDs;
|
||||
TArray<int> modelFrameGenerators;
|
||||
int flags;
|
||||
|
||||
AnimOverride curAnim;
|
||||
AnimOverride prevAnim; // used for interpolation when switching anims
|
||||
|
||||
DActorModelData() = default;
|
||||
virtual void Serialize(FSerializer& arc) override;
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
#include "types.h"
|
||||
#include "model.h"
|
||||
#include "shadowinlines.h"
|
||||
#include "i_time.h"
|
||||
|
||||
static FRandom pr_camissile ("CustomActorfire");
|
||||
static FRandom pr_cabullet ("CustomBullet");
|
||||
|
@ -5105,7 +5106,8 @@ static void EnsureModelData(AActor * mobj)
|
|||
|
||||
static void CleanupModelData(AActor * mobj)
|
||||
{
|
||||
if ( mobj->modelData->models.Size() == 0
|
||||
if ( !(mobj->flags9 & MF9_DECOUPLEDANIMATIONS)
|
||||
&& mobj->modelData->models.Size() == 0
|
||||
&& mobj->modelData->modelFrameGenerators.Size() == 0
|
||||
&& mobj->modelData->skinIDs.Size() == 0
|
||||
&& mobj->modelData->animationIDs.Size() == 0
|
||||
|
@ -5117,11 +5119,157 @@ static void CleanupModelData(AActor * mobj)
|
|||
}
|
||||
}
|
||||
|
||||
enum ESetAnimationFlags
|
||||
{
|
||||
SAF_INSTANT = 1 << 0,
|
||||
SAF_LOOP = 1 << 1,
|
||||
};
|
||||
|
||||
void SetAnimationInternal(AActor * self, FName animName, double framerate, int startFrame, int loopFrame, int interpolateTics, int flags, double ticFrac)
|
||||
{
|
||||
if(!self) ThrowAbortException(X_READ_NIL, "In function parameter self");
|
||||
|
||||
if(!(self->flags9 & MF9_DECOUPLEDANIMATIONS))
|
||||
{
|
||||
ThrowAbortException(X_OTHER, "Cannot set animation for non-decoupled actors");
|
||||
}
|
||||
|
||||
if(interpolateTics <= 0) interpolateTics = 1;
|
||||
|
||||
EnsureModelData(self);
|
||||
|
||||
if(animName == NAME_None)
|
||||
{
|
||||
self->modelData->curAnim.flags = ANIMOVERRIDE_NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!(flags & SAF_INSTANT))
|
||||
{
|
||||
self->modelData->prevAnim = self->modelData->curAnim;
|
||||
}
|
||||
|
||||
double tic = self->Level->totaltime;
|
||||
if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !self->Level->isFrozen())
|
||||
{
|
||||
tic += ticFrac;
|
||||
}
|
||||
|
||||
FModel * mdl = Models[(self->modelData->models.Size() > 0 && self->modelData->models[0].modelID >= 0) ? self->modelData->models[0].modelID : BaseSpriteModelFrames[self->GetClass()].modelIDs[0]];
|
||||
|
||||
int animStart = mdl->FindFirstFrame(animName);
|
||||
if(animStart == FErr_NotFound)
|
||||
{
|
||||
self->modelData->curAnim.flags = ANIMOVERRIDE_NONE;
|
||||
Printf("Could not find animation %s", animName.GetChars());
|
||||
return;
|
||||
}
|
||||
int animEnd = mdl->FindLastFrame(animName);
|
||||
|
||||
if(framerate < 0)
|
||||
{
|
||||
framerate = mdl->FindFramerate(animName);
|
||||
}
|
||||
|
||||
int len = animEnd - animStart;
|
||||
|
||||
if(startFrame >= len)
|
||||
{
|
||||
self->modelData->curAnim.flags = ANIMOVERRIDE_NONE;
|
||||
Printf("frame %d is past the end of animation %s", startFrame, animName.GetChars());
|
||||
return;
|
||||
}
|
||||
else if(loopFrame >= len)
|
||||
{
|
||||
self->modelData->curAnim.flags = ANIMOVERRIDE_NONE;
|
||||
Printf("frame %d is past the end of animation %s", startFrame, animName.GetChars());
|
||||
return;
|
||||
}
|
||||
|
||||
self->modelData->curAnim.firstFrame = animStart;
|
||||
self->modelData->curAnim.lastFrame = animEnd - 1;
|
||||
self->modelData->curAnim.startFrame = startFrame < 0 ? animStart : animStart + startFrame;
|
||||
self->modelData->curAnim.loopFrame = loopFrame < 0 ? animStart : animStart + loopFrame;
|
||||
self->modelData->curAnim.flags = (flags&SAF_LOOP) ? ANIMOVERRIDE_LOOP : 0;
|
||||
self->modelData->curAnim.switchTic = tic;
|
||||
self->modelData->curAnim.framerate = (float)framerate;
|
||||
|
||||
if(!(flags & SAF_INSTANT))
|
||||
{
|
||||
self->modelData->curAnim.startTic = floor(tic) + interpolateTics;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->modelData->curAnim.startTic = tic;
|
||||
}
|
||||
}
|
||||
|
||||
void SetAnimationNative(AActor * self, int i_animName, double framerate, int startFrame, int loopFrame, int interpolateTics, int flags)
|
||||
{
|
||||
SetAnimationInternal(self, FName(ENamedName(i_animName)), framerate, startFrame, loopFrame, interpolateTics, flags, 1);
|
||||
}
|
||||
|
||||
void SetAnimationUINative(AActor * self, int i_animName, double framerate, int startFrame, int loopFrame, int interpolateTics, int flags)
|
||||
{
|
||||
SetAnimationInternal(self, FName(ENamedName(i_animName)), framerate, startFrame, loopFrame, interpolateTics, flags, I_GetTimeFrac());
|
||||
}
|
||||
|
||||
extern double getCurrentFrame(const AnimOverride &anim, double tic);
|
||||
|
||||
void SetAnimationFrameRateInternal(AActor * self, double framerate, double ticFrac)
|
||||
{
|
||||
if(!self) ThrowAbortException(X_READ_NIL, "In function parameter self");
|
||||
|
||||
if(!(self->flags9 & MF9_DECOUPLEDANIMATIONS))
|
||||
{
|
||||
ThrowAbortException(X_OTHER, "Cannot set animation for non-decoupled actors");
|
||||
}
|
||||
|
||||
EnsureModelData(self);
|
||||
|
||||
if(self->modelData->curAnim.flags & ANIMOVERRIDE_NONE) return;
|
||||
|
||||
if(framerate < 0)
|
||||
{
|
||||
ThrowAbortException(X_OTHER, "Cannot set negative framerate");
|
||||
}
|
||||
|
||||
|
||||
if(self->modelData->curAnim.startTic < ticFrac)
|
||||
{
|
||||
self->modelData->curAnim.framerate = (float)framerate;
|
||||
return;
|
||||
}
|
||||
|
||||
double tic = self->Level->totaltime;
|
||||
if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !self->Level->isFrozen())
|
||||
{
|
||||
tic += ticFrac;
|
||||
}
|
||||
|
||||
double frame = getCurrentFrame(self->modelData->curAnim, tic);
|
||||
|
||||
self->modelData->curAnim.startFrame = frame;
|
||||
self->modelData->curAnim.startTic = tic;
|
||||
self->modelData->curAnim.switchTic = tic;
|
||||
self->modelData->curAnim.framerate = (float)framerate;
|
||||
}
|
||||
|
||||
void SetAnimationFrameRateNative(AActor * self, double framerate)
|
||||
{
|
||||
SetAnimationFrameRateInternal(self, framerate, 1);
|
||||
}
|
||||
|
||||
void SetAnimationFrameRateUINative(AActor * self, double framerate)
|
||||
{
|
||||
SetAnimationFrameRateInternal(self, framerate, I_GetTimeFrac());
|
||||
}
|
||||
|
||||
enum ChangeModelFlags
|
||||
{
|
||||
CMDL_WEAPONTOPLAYER = 1,
|
||||
CMDL_HIDEMODEL = 1 << 1,
|
||||
CMDL_USESURFACESKIN = 1 << 2,
|
||||
CMDL_WEAPONTOPLAYER = 1 << 0,
|
||||
CMDL_HIDEMODEL = 1 << 1,
|
||||
CMDL_USESURFACESKIN = 1 << 2,
|
||||
};
|
||||
|
||||
void ChangeModelNative(
|
||||
|
@ -5335,6 +5483,56 @@ DEFINE_ACTION_FUNCTION_NATIVE(AActor, A_ChangeModel, ChangeModelNative)
|
|||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimation, SetAnimationNative)
|
||||
{
|
||||
PARAM_ACTION_PROLOGUE(AActor);
|
||||
PARAM_NAME(animName);
|
||||
PARAM_FLOAT(framerate);
|
||||
PARAM_INT(startFrame);
|
||||
PARAM_INT(loopFrame);
|
||||
PARAM_INT(interpolateTics);
|
||||
PARAM_INT(flags);
|
||||
|
||||
SetAnimationInternal(self, animName, framerate, startFrame, loopFrame, interpolateTics, flags, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationUI, SetAnimationUINative)
|
||||
{
|
||||
PARAM_ACTION_PROLOGUE(AActor);
|
||||
PARAM_NAME(animName);
|
||||
PARAM_FLOAT(framerate);
|
||||
PARAM_INT(startFrame);
|
||||
PARAM_INT(loopFrame);
|
||||
PARAM_INT(interpolateTics);
|
||||
PARAM_INT(flags);
|
||||
|
||||
SetAnimationInternal(self, animName, framerate, startFrame, loopFrame, interpolateTics, flags, I_GetTimeFrac());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationFrameRate, SetAnimationFrameRateNative)
|
||||
{
|
||||
PARAM_ACTION_PROLOGUE(AActor);
|
||||
PARAM_FLOAT(framerate);
|
||||
|
||||
SetAnimationFrameRateInternal(self, framerate, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, SetAnimationFrameRateUI, SetAnimationFrameRateUINative)
|
||||
{
|
||||
PARAM_ACTION_PROLOGUE(AActor);
|
||||
PARAM_FLOAT(framerate);
|
||||
|
||||
SetAnimationFrameRateInternal(self, framerate, I_GetTimeFrac());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This needs to account for the fact that internally renderstyles are stored as a series of operations,
|
||||
// but the script side only cares about symbolic constants.
|
||||
DEFINE_ACTION_FUNCTION(AActor, GetRenderStyle)
|
||||
|
|
|
@ -1327,11 +1327,27 @@ bool AActor::Massacre ()
|
|||
//
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
FSerializer &Serialize(FSerializer &arc, const char *key, ModelOverride &sid, ModelOverride *def)
|
||||
FSerializer &Serialize(FSerializer &arc, const char *key, ModelOverride &mo, ModelOverride *def)
|
||||
{
|
||||
arc.BeginObject(key);
|
||||
arc("modelID", sid.modelID);
|
||||
arc("surfaceSkinIDs", sid.surfaceSkinIDs);
|
||||
arc("modelID", mo.modelID);
|
||||
arc("surfaceSkinIDs", mo.surfaceSkinIDs);
|
||||
arc.EndObject();
|
||||
return arc;
|
||||
}
|
||||
|
||||
FSerializer &Serialize(FSerializer &arc, const char *key, struct AnimOverride &ao, struct AnimOverride *def)
|
||||
{
|
||||
//TODO
|
||||
arc.BeginObject(key);
|
||||
arc("firstFrame", ao.firstFrame);
|
||||
arc("lastFrame", ao.lastFrame);
|
||||
arc("loopFrame", ao.loopFrame);
|
||||
arc("startFrame", ao.startFrame);
|
||||
arc("flags", ao.flags);
|
||||
arc("framerate", ao.framerate);
|
||||
arc("startTic", ao.startTic);
|
||||
arc("switchTic", ao.switchTic);
|
||||
arc.EndObject();
|
||||
return arc;
|
||||
}
|
||||
|
@ -1342,10 +1358,11 @@ void DActorModelData::Serialize(FSerializer& arc)
|
|||
arc("modelDef", modelDef)
|
||||
("models", models)
|
||||
("skinIDs", skinIDs)
|
||||
//("surfaceSkinIDs", surfaceSkinIDs)
|
||||
("animationIDs", animationIDs)
|
||||
("modelFrameGenerators", modelFrameGenerators)
|
||||
("flags", flags);
|
||||
("flags", flags)
|
||||
("curAnim", curAnim)
|
||||
("prevAnim", prevAnim);
|
||||
}
|
||||
|
||||
void DActorModelData::OnDestroy()
|
||||
|
|
|
@ -253,13 +253,88 @@ void RenderHUDModel(FModelRenderer *renderer, DPSprite *psp, FVector3 translatio
|
|||
renderer->EndDrawHUDModel(playermo->RenderStyle, smf);
|
||||
}
|
||||
|
||||
double getCurrentFrame(const AnimOverride &anim, double tic)
|
||||
{
|
||||
if(anim.framerate <= 0) return anim.startFrame;
|
||||
|
||||
double duration = double(anim.lastFrame - anim.firstFrame) / double(anim.framerate); // duration in seconds
|
||||
double startPos = double(anim.startFrame - anim.firstFrame) / double(anim.framerate);
|
||||
|
||||
double pos = startPos + ((tic - anim.startTic) / GameTicRate); // position in seconds
|
||||
|
||||
return (((anim.flags & ANIMOVERRIDE_LOOP) ? fmod(pos, duration) : min(pos, duration)) * anim.framerate) + anim.firstFrame;
|
||||
}
|
||||
|
||||
static void calcFrame(const AnimOverride &anim, double tic, double &inter, int &prev, int &next)
|
||||
{
|
||||
double frame = getCurrentFrame(anim, tic);
|
||||
|
||||
prev = int(floor(frame));
|
||||
|
||||
inter = frame - prev;
|
||||
|
||||
if(frame > anim.lastFrame)
|
||||
{
|
||||
if(anim.flags & ANIMOVERRIDE_LOOP)
|
||||
{
|
||||
next = anim.loopFrame + (prev - anim.lastFrame);
|
||||
}
|
||||
else
|
||||
{
|
||||
inter = 0;
|
||||
prev = next = anim.lastFrame;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
next = int(ceil(frame));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpriteModelFrame *smf, const FState *curState, const int curTics, FTranslationID translation, AActor* actor)
|
||||
{
|
||||
// [BB] Frame interpolation: Find the FSpriteModelFrame smfNext which follows after smf in the animation
|
||||
// and the scalar value inter ( element of [0,1) ), both necessary to determine the interpolated frame.
|
||||
FSpriteModelFrame * smfNext = nullptr;
|
||||
const FSpriteModelFrame * smfNext = nullptr;
|
||||
double inter = 0.;
|
||||
if (gl_interpolate_model_frames && !(smf->flags & MDL_NOINTERPOLATION))
|
||||
double inter_main = -1.f;
|
||||
double inter_next = -1.f;
|
||||
|
||||
bool is_decoupled = (actor->flags9 & MF9_DECOUPLEDANIMATIONS);
|
||||
|
||||
int decoupled_main_prev_frame = -1;
|
||||
int decoupled_next_prev_frame = -1;
|
||||
|
||||
int decoupled_main_frame = -1;
|
||||
int decoupled_next_frame = -1;
|
||||
|
||||
// if prev_frame == -1: interpolate(main_frame, next_frame, inter), else: interpolate(interpolate(main_prev_frame, main_frame, inter_main), interpolate(next_prev_frame, next_frame, inter_next), inter)
|
||||
// 4-way interpolation is needed to interpolate animation switches between animations that aren't 35hz
|
||||
|
||||
if(is_decoupled)
|
||||
{
|
||||
smfNext = smf = &BaseSpriteModelFrames[actor->GetClass()];
|
||||
if(actor->modelData && !(actor->modelData->curAnim.flags & ANIMOVERRIDE_NONE))
|
||||
{
|
||||
double tic = actor->Level->totaltime;
|
||||
if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !Level->isFrozen())
|
||||
{
|
||||
tic += I_GetTimeFrac();
|
||||
}
|
||||
if(actor->modelData->curAnim.startTic > tic)
|
||||
{
|
||||
inter = (tic - actor->modelData->curAnim.switchTic) / (actor->modelData->curAnim.startTic - actor->modelData->curAnim.switchTic);
|
||||
|
||||
calcFrame(actor->modelData->curAnim, actor->modelData->curAnim.startTic, inter_next, decoupled_next_prev_frame, decoupled_next_frame);
|
||||
calcFrame(actor->modelData->prevAnim, actor->modelData->curAnim.switchTic, inter_main, decoupled_main_prev_frame, decoupled_main_frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
calcFrame(actor->modelData->curAnim, tic, inter, decoupled_main_frame, decoupled_next_frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (gl_interpolate_model_frames && !(smf->flags & MDL_NOINTERPOLATION))
|
||||
{
|
||||
FState *nextState = curState->GetNextState();
|
||||
if (curState != nextState && nextState)
|
||||
|
@ -267,7 +342,7 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
|
|||
// [BB] To interpolate at more than 35 fps we take tic fractions into account.
|
||||
float ticFraction = 0.;
|
||||
// [BB] In case the tic counter is frozen we have to leave ticFraction at zero.
|
||||
if (ConsoleState == c_up && menuactive != MENU_On && !Level->isFrozen())
|
||||
if ((ConsoleState == c_up || ConsoleState == c_rising) && (menuactive == MENU_Off || menuactive == MENU_OnNoPause) && !Level->isFrozen())
|
||||
{
|
||||
ticFraction = I_GetTimeFrac();
|
||||
}
|
||||
|
@ -311,7 +386,7 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
|
|||
|
||||
TArray<FTextureID> surfaceskinids;
|
||||
|
||||
TArray<VSMatrix> boneData = TArray<VSMatrix>();
|
||||
TArray<VSMatrix> boneData;
|
||||
int boneStartingPosition = 0;
|
||||
bool evaluatedSingle = false;
|
||||
|
||||
|
@ -321,7 +396,7 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
|
|||
int animationid = -1;
|
||||
int modelframe = -1;
|
||||
int modelframenext = -1;
|
||||
FTextureID skinid; skinid.SetNull();
|
||||
FTextureID skinid(nullptr);
|
||||
|
||||
surfaceskinids.Clear();
|
||||
|
||||
|
@ -350,30 +425,32 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
|
|||
{
|
||||
animationid = smf->animationIDs[i];
|
||||
}
|
||||
if(!is_decoupled)
|
||||
{
|
||||
//modelFrame
|
||||
if (actor->modelData->modelFrameGenerators.Size() > i
|
||||
&& (unsigned)actor->modelData->modelFrameGenerators[i] < modelsamount
|
||||
&& smf->modelframes[actor->modelData->modelFrameGenerators[i]] >= 0
|
||||
) {
|
||||
modelframe = smf->modelframes[actor->modelData->modelFrameGenerators[i]];
|
||||
|
||||
//modelFrame
|
||||
if (actor->modelData->modelFrameGenerators.Size() > i
|
||||
&& (unsigned)actor->modelData->modelFrameGenerators[i] < modelsamount
|
||||
&& smf->modelframes[actor->modelData->modelFrameGenerators[i]] >= 0
|
||||
) {
|
||||
modelframe = smf->modelframes[actor->modelData->modelFrameGenerators[i]];
|
||||
|
||||
if (smfNext)
|
||||
{
|
||||
if(smfNext->modelframes[actor->modelData->modelFrameGenerators[i]] >= 0)
|
||||
if (smfNext)
|
||||
{
|
||||
modelframenext = smfNext->modelframes[actor->modelData->modelFrameGenerators[i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
modelframenext = smfNext->modelframes[i];
|
||||
if(smfNext->modelframes[actor->modelData->modelFrameGenerators[i]] >= 0)
|
||||
{
|
||||
modelframenext = smfNext->modelframes[actor->modelData->modelFrameGenerators[i]];
|
||||
}
|
||||
else
|
||||
{
|
||||
modelframenext = smfNext->modelframes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(smf->modelsAmount > i)
|
||||
{
|
||||
modelframe = smf->modelframes[i];
|
||||
if (smfNext) modelframenext = smfNext->modelframes[i];
|
||||
else if(smf->modelsAmount > i)
|
||||
{
|
||||
modelframe = smf->modelframes[i];
|
||||
if (smfNext) modelframenext = smfNext->modelframes[i];
|
||||
}
|
||||
}
|
||||
|
||||
//skinID
|
||||
|
@ -444,29 +521,47 @@ void RenderFrameModels(FModelRenderer *renderer, FLevelLocals *Level, const FSpr
|
|||
GC::WriteBarrier(actor, ptr);
|
||||
}
|
||||
|
||||
if (animationid >= 0)
|
||||
// [RL0] while per-model animations aren't done, DECOUPLEDANIMATIONS does the same as MODELSAREATTACHMENTS
|
||||
if ((!(smf->flags & MDL_MODELSAREATTACHMENTS) && !is_decoupled) || !evaluatedSingle)
|
||||
{
|
||||
FModel* animation = Models[animationid];
|
||||
const TArray<TRS>* animationData = animation->AttachAnimationData();
|
||||
|
||||
if (!(smf->flags & MDL_MODELSAREATTACHMENTS) || evaluatedSingle == false)
|
||||
if (animationid >= 0)
|
||||
{
|
||||
boneData = animation->CalculateBones(modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, animationData, actor->boneComponentData, i);
|
||||
FModel* animation = Models[animationid];
|
||||
const TArray<TRS>* animationData = animation->AttachAnimationData();
|
||||
|
||||
if(is_decoupled)
|
||||
{
|
||||
if(decoupled_main_frame != -1)
|
||||
{
|
||||
boneData = animation->CalculateBones(decoupled_main_frame, decoupled_next_frame, inter, decoupled_main_prev_frame, inter_main, decoupled_next_prev_frame, inter_next, animationData, actor->boneComponentData, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
boneData = animation->CalculateBones(modelframe, modelframenext, nextFrame ? inter : -1.f, 0, -1.f, 0, -1.f, animationData, actor->boneComponentData, i);
|
||||
}
|
||||
boneStartingPosition = renderer->SetupFrame(animation, 0, 0, 0, boneData, -1);
|
||||
evaluatedSingle = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(smf->flags & MDL_MODELSAREATTACHMENTS) || evaluatedSingle == false)
|
||||
else
|
||||
{
|
||||
boneData = mdl->CalculateBones(modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, nullptr, actor->boneComponentData, i);
|
||||
if(is_decoupled)
|
||||
{
|
||||
if(decoupled_main_frame != -1)
|
||||
{
|
||||
boneData = mdl->CalculateBones(decoupled_main_frame, decoupled_next_frame, inter, decoupled_main_prev_frame, inter_main, decoupled_next_prev_frame, inter_next, nullptr, actor->boneComponentData, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
boneData = mdl->CalculateBones(modelframe, modelframenext, nextFrame ? inter : -1.f, 0, -1.f, 0, -1.f, nullptr, actor->boneComponentData, i);
|
||||
}
|
||||
boneStartingPosition = renderer->SetupFrame(mdl, 0, 0, 0, boneData, -1);
|
||||
evaluatedSingle = true;
|
||||
}
|
||||
}
|
||||
|
||||
mdl->RenderFrame(renderer, tex, modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : 0.f, translation, ssidp, boneData, boneStartingPosition);
|
||||
mdl->RenderFrame(renderer, tex, modelframe, nextFrame ? modelframenext : modelframe, nextFrame ? inter : -1.f, translation, ssidp, boneData, boneStartingPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -849,6 +944,16 @@ static void ParseModelDefLump(int Lump)
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (sc.Compare("baseframe"))
|
||||
{
|
||||
FSpriteModelFrame *smfp = &BaseSpriteModelFrames.Insert(type, smf);
|
||||
for(int modelID : smf.modelIDs)
|
||||
{
|
||||
if(modelID >= 0)
|
||||
Models[modelID]->baseFrame = smfp;
|
||||
}
|
||||
GetDefaultByType(type)->hasmodel = true;
|
||||
}
|
||||
else if (sc.Compare("frameindex") || sc.Compare("frame"))
|
||||
{
|
||||
bool isframe=!!sc.Compare("frame");
|
||||
|
@ -1008,7 +1113,16 @@ FSpriteModelFrame * FindModelFrameRaw(const PClass * ti, int sprite, int frame,
|
|||
|
||||
FSpriteModelFrame * FindModelFrame(const AActor * thing, int sprite, int frame, bool dropped)
|
||||
{
|
||||
return FindModelFrameRaw((thing->modelData != nullptr && thing->modelData->modelDef != NAME_None) ? PClass::FindActor(thing->modelData->modelDef) : thing->GetClass(), sprite, frame, dropped);
|
||||
if(!thing) return nullptr;
|
||||
|
||||
if(thing->flags9 & MF9_DECOUPLEDANIMATIONS)
|
||||
{
|
||||
return &BaseSpriteModelFrames[thing->GetClass()];
|
||||
}
|
||||
else
|
||||
{
|
||||
return FindModelFrameRaw((thing->modelData != nullptr && thing->modelData->modelDef != NAME_None) ? PClass::FindActor(thing->modelData->modelDef) : thing->GetClass(), sprite, frame, dropped);
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
@ -1025,8 +1139,7 @@ bool IsHUDModelForPlayerAvailable (player_t * player)
|
|||
// [MK] check that at least one psprite uses models
|
||||
for (DPSprite *psp = player->psprites; psp != nullptr && psp->GetID() < PSP_TARGETCENTER; psp = psp->GetNext())
|
||||
{
|
||||
FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false) : nullptr;
|
||||
if ( smf != nullptr ) return true;
|
||||
if ( FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false) != nullptr ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -707,7 +707,7 @@ void HWDrawInfo::PreparePlayerSprites2D(sector_t * viewsector, area_t in_area)
|
|||
{
|
||||
if (!psp->GetState()) continue;
|
||||
|
||||
FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false) : nullptr;
|
||||
FSpriteModelFrame *smf = FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false);
|
||||
|
||||
// This is an 'either-or' proposition. This maybe needs some work to allow overlays with weapon models but as originally implemented this just won't work.
|
||||
if (smf) continue;
|
||||
|
@ -792,7 +792,7 @@ void HWDrawInfo::PreparePlayerSprites3D(sector_t * viewsector, area_t in_area)
|
|||
for (DPSprite *psp = player->psprites; psp != nullptr && psp->GetID() < PSP_TARGETCENTER; psp = psp->GetNext())
|
||||
{
|
||||
if (!psp->GetState()) continue;
|
||||
FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false) : nullptr;
|
||||
FSpriteModelFrame *smf = FindModelFrame(psp->Caller, psp->GetSprite(), psp->GetFrame(), false);
|
||||
|
||||
// This is an 'either-or' proposition. This maybe needs some work to allow overlays with weapon models but as originally implemented this just won't work.
|
||||
if (!smf) continue;
|
||||
|
|
|
@ -351,6 +351,7 @@ static FFlagDef ActorFlagDefs[]=
|
|||
DEFINE_FLAG(MF9, DOSHADOWBLOCK, AActor, flags9),
|
||||
DEFINE_FLAG(MF9, SHADOWBLOCK, AActor, flags9),
|
||||
DEFINE_FLAG(MF9, SHADOWAIMVERT, AActor, flags9),
|
||||
DEFINE_FLAG(MF9, DECOUPLEDANIMATIONS, AActor, flags9),
|
||||
|
||||
// Effect flags
|
||||
DEFINE_FLAG(FX, VISIBILITYPULSE, AActor, effects),
|
||||
|
|
|
@ -1278,6 +1278,12 @@ class Actor : Thinker native
|
|||
native bool A_AttachLight(Name lightid, int type, Color lightcolor, int radius1, int radius2, int flags = 0, Vector3 ofs = (0,0,0), double param = 0, double spoti = 10, double spoto = 25, double spotp = 0);
|
||||
native bool A_RemoveLight(Name lightid);
|
||||
|
||||
native version("4.12") void SetAnimation(Name animName, double framerate = -1, int startFrame = -1, int loopFrame= -1, int interpolateTics = -1, int flags = 0);
|
||||
native version("4.12") ui void SetAnimationUI(Name animName, double framerate = -1, int startFrame = -1, int loopFrame = -1, int interpolateTics = -1, int flags = 0);
|
||||
|
||||
native version("4.12") void SetAnimationFrameRate(double framerate);
|
||||
native version("4.12") ui void SetAnimationFrameRateUI(double framerate);
|
||||
|
||||
int ACS_NamedExecute(name script, int mapnum=0, int arg1=0, int arg2=0, int arg3=0)
|
||||
{
|
||||
return ACS_Execute(-int(script), mapnum, arg1, arg2, arg3);
|
||||
|
|
|
@ -368,6 +368,13 @@ enum ERadiusGiveFlags
|
|||
RGF_EITHER = 1 << 17,
|
||||
};
|
||||
|
||||
// SetAnimation flags
|
||||
enum ESetAnimationFlags
|
||||
{
|
||||
SAF_INSTANT = 1 << 0,
|
||||
SAF_LOOP = 1 << 1,
|
||||
};
|
||||
|
||||
// Change model flags
|
||||
enum ChangeModelFlags
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue