openmohaa/code/fgame/animate.cpp

1061 lines
27 KiB
C++

/*
===========================================================================
Copyright (C) 2024 the OpenMoHAA team
This file is part of OpenMoHAA source code.
OpenMoHAA source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
OpenMoHAA source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenMoHAA source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// animate.cpp : Animate Class
#include "animate.h"
#include "player.h"
#include "qfiles.h"
#include "scriptexception.h"
#include "g_spawn.h"
#include "animationevent.h"
#include <tiki.h>
extern Event EV_Entity_Start;
// Leg Animation events
Event EV_SetAnim
(
"anim",
EV_ZERO,
"s",
"animName",
"Exec anim commands on server or client.",
EV_NORMAL
);
Event EV_SetSyncTime
(
"setsynctime",
EV_ZERO,
"f",
"synctime",
"Set sync time for entity.",
EV_NORMAL
);
Event EV_Animate_IsLoopingAnim
(
"isloopinganim",
EV_DEFAULT,
"s",
"anim_name",
"returns 1 if the anim is a looping anim, or 0 otherwise",
EV_RETURN
);
Event EV_Animate_SetYawfromBone
(
"setyawfrombone",
EV_DEFAULT,
"s",
"bone_name",
"Set the yaw of the model based on the current animation time",
EV_NORMAL
);
Event EV_Animate_PlayerSpawn
(
"playerspawn",
EV_DEFAULT,
"sFVFF",
"model_name range vector_offset inFOV speed",
"spawn something near the player, either within the player's view or behind him\n"
"model - name of model to spawn\n"
"range - how close does this need to be to the player to actually get spawned, default 480 (30 foot radius).\n"
"vector_offset - oriented vector offset of where to spawn the item, default (0 0 0)\n"
"inFOV - \n"
"\t\t1 - Only spawn when this position is within the FOV of the player\n"
"\t -1 - Only spawn when this position is outside the FOV of the player\n"
" 0 - (default) don't care, always spawn\n"
"speed - how fast the effect should travel, in other words, how long before the effect gets spawned.\n"
"\t delay is calculated based on the distance between object and player divided by the speed\n"
"\t 0 - no delay\n"
"\t 960 - (default) 60 feet per second. If the object is 60 feet from the player, the player effect will spawn "
"one second later.",
EV_NORMAL
);
Event EV_Animate_PlayerSpawn_Utility
(
"testmojo",
EV_DEFAULT,
"sv",
"model_name vector_offset",
"INTERNAL EVENT",
EV_NORMAL
);
Event EV_Animate_PauseAnim
(
"pauseanims",
EV_DEFAULT,
"i",
"pause",
"Pause (or unpause) animations",
EV_NORMAL
);
Event EV_Animate_Client_Sound
(
"_client_sound",
EV_DEFAULT,
NULL,
NULL,
NULL,
EV_NORMAL
);
CLASS_DECLARATION(Entity, Animate, "animate") {
{&EV_SetControllerAngles, &Animate::SetControllerAngles },
{&EV_SetAnim, &Animate::ForwardExec },
{&EV_SetSyncTime, &Animate::EventSetSyncTime },
{&EV_Animate_IsLoopingAnim, &Animate::EventIsLoopingAnim },
{&EV_Animate_SetYawfromBone, &Animate::EventSetYawFromBone },
{&EV_Animate_PlayerSpawn, &Animate::EventPlayerSpawn },
{&EV_Animate_PlayerSpawn_Utility, &Animate::EventPlayerSpawnUtility},
{&EV_Animate_PauseAnim, &Animate::EventPauseAnim },
{&EV_Animate_Client_Sound, &Animate::ClientSound },
{NULL, NULL }
};
static size_t GetLongestClientCmdLength(const tiki_cmd_t& cmds)
{
size_t ii;
size_t longest = 0;
for (ii = 0; ii < cmds.num_cmds; ii++) {
const tiki_singlecmd_t& single_cmd = cmds.cmds[ii];
size_t length = strlen(single_cmd.args[0]);
if (length > longest) {
longest = length;
}
}
return longest;
}
static size_t GetLongestClientCmdAllFramesLength(dtiki_t *tiki, int animnum, int numframes)
{
tiki_cmd_t cmds;
size_t longest = 0;
int i;
for (i = 0; i < numframes; i++) {
// we want normal frame commands to occur right on the frame
if (gi.Frame_Commands_Client(tiki, animnum, i, &cmds)) {
size_t length = GetLongestClientCmdLength(cmds);
if (length > longest) {
longest = length;
}
}
}
return longest;
}
Animate::Animate()
{
entflags |= ECF_ANIMATE;
syncTime = 0.0f;
syncRate = 1.0f;
pauseSyncTime = 0.0f;
is_paused = false;
frame_delta = vec_zero;
angular_delta = 0;
if (!LoadingSavegame) {
edict->s.actionWeight = 1.0f;
for (int i = 0; i < MAX_FRAMEINFOS; i++) {
edict->s.frameInfo[i].index = 0;
edict->s.frameInfo[i].time = 0.0f;
edict->s.frameInfo[i].weight = 0.0f;
animtimes[i] = 0;
frametimes[i] = 0;
doneEvents[i] = NULL;
animFlags[i] = ANIM_LOOP | ANIM_NODELTA | ANIM_NOEXIT | ANIM_PAUSED;
ClearAnimSlot(i);
}
flags |= FL_ANIMATE;
}
}
Animate::~Animate()
{
entflags &= ~ECF_ANIMATE;
}
void Animate::ForwardExec(Event *ev)
{
if (!edict->tiki) {
ScriptError(
"trying to play animation on( entnum: %d, targetname : '%s', classname : '%s' ) which does not have a "
"model",
entnum,
targetname.c_str(),
getClassname()
);
}
NewAnim(ev->GetString(1), 0);
RestartAnimSlot(0);
}
void Animate::SetSyncTime(float s)
{
if (s < 0.0f || s > 1.0f) {
Com_Printf("\nERROR SetSyncTime: synctime must be 0 to 1 - attempt to set to %f\n", s);
return;
}
syncTime = s;
for (int i = 0; i < MAX_FRAMEINFOS; i++) {
if (!(animFlags[i] & ANIM_SYNC)) {
continue;
}
SlotChanged(i);
}
}
void Animate::EventSetSyncTime(Event *ev)
{
SetSyncTime(ev->GetFloat(1));
}
void Animate::NewAnim(int animnum, int slot, float weight)
{
qboolean newanim = edict->s.frameInfo[slot].index != animnum;
tiki_cmd_t cmds;
float time;
int numframes;
AnimationEvent *ev;
int i;
if (newanim) {
DoExitCommands(slot);
}
if (doneEvents[slot]) {
delete doneEvents[slot];
doneEvents[slot] = NULL;
}
CancelFlaggedEvents(1 << slot);
edict->s.frameInfo[slot].index = animnum;
animFlags[slot] = ANIM_LOOP | ANIM_NODELTA | ANIM_NOEXIT;
if (!(gi.Anim_FlagsSkel(edict->tiki, animnum) & ANIM_LOOP)) {
SetOnceType(slot);
}
SetWeight(slot, weight);
animtimes[slot] = gi.Anim_Time(edict->tiki, animnum);
frametimes[slot] = gi.Anim_Frametime(edict->tiki, animnum);
if (edict->s.eType == ET_GENERAL) {
edict->s.eType = ET_MODELANIM;
}
qboolean hascommands =
gi.Anim_HasCommands(edict->tiki, animnum) || gi.Anim_HasCommands_Client(edict->tiki, animnum);
// enter this animation
if (newanim) {
if (!hascommands) {
return;
}
if (gi.Frame_Commands(edict->tiki, animnum, TIKI_FRAME_ENTRY, &cmds)) {
int ii, j;
for (ii = 0; ii < cmds.num_cmds; ii++) {
const tiki_singlecmd_t& single_cmd = cmds.cmds[ii];
AnimationEvent ev(single_cmd.args[0], single_cmd.num_args);
ev.SetAnimationNumber(animnum);
ev.SetAnimationFrame(0);
for (j = 1; j < single_cmd.num_args; j++) {
ev.AddToken(single_cmd.args[j]);
}
ProcessEvent(ev);
}
}
if (gi.Frame_Commands_Client(edict->tiki, animnum, TIKI_FRAME_ENTRY, &cmds)) {
size_t longest = GetLongestClientCmdLength(cmds);
int ii, j;
if (longest) {
char *cmdName = (char *)alloca(longest + 8 + 1);
for (ii = 0; ii < cmds.num_cmds; ii++) {
const tiki_singlecmd_t& single_cmd = cmds.cmds[ii];
int eventNum;
Q_strncpyz(cmdName, "_client_", longest + 8 + 1);
Q_strncpyz(cmdName + 8, single_cmd.args[0], longest + 1);
eventNum = Event::FindEventNum(cmdName);
AnimationEvent ev(eventNum, single_cmd.num_args);
if (!ev.eventnum) {
continue;
}
ev.SetAnimationNumber(animnum);
ev.SetAnimationFrame(0);
for (j = 1; j < single_cmd.num_args; j++) {
ev.AddToken(single_cmd.args[j]);
}
ProcessEvent(ev);
}
}
}
}
if (!hascommands) {
return;
}
if (!edict->tiki) {
return;
}
time = 0.0f;
numframes = gi.Anim_NumFrames(edict->tiki, animnum);
for (i = 0; i < numframes; i++, time += frametimes[slot]) {
// we want normal frame commands to occur right on the frame
if (gi.Frame_Commands(edict->tiki, animnum, i, &cmds)) {
int ii, j;
for (ii = 0; ii < cmds.num_cmds; ii++) {
const tiki_singlecmd_t& single_cmd = cmds.cmds[ii];
ev = new AnimationEvent(single_cmd.args[0], single_cmd.num_args);
ev->SetAnimationNumber(animnum);
ev->SetAnimationFrame(i);
for (j = 1; j < single_cmd.num_args; j++) {
ev->AddToken(single_cmd.args[j]);
}
PostEvent(ev, time, 1 << slot);
}
}
}
time = 0.0f;
const size_t longestAllFrames = GetLongestClientCmdAllFramesLength(edict->tiki, animnum, numframes);
if (longestAllFrames) {
char *cmdName = (char *)alloca(longestAllFrames + 8 + 1);
for (i = 0; i < numframes; i++, time += frametimes[slot]) {
// we want normal frame commands to occur right on the frame
if (gi.Frame_Commands_Client(edict->tiki, animnum, i, &cmds)) {
int ii, j;
for (ii = 0; ii < cmds.num_cmds; ii++) {
const tiki_singlecmd_t& single_cmd = cmds.cmds[ii];
int eventNum;
Q_strncpyz(cmdName, "_client_", longestAllFrames + 8 + 1);
Q_strncpyz(cmdName + 8, single_cmd.args[0], longestAllFrames + 1);
eventNum = Event::FindEventNum(cmdName);
AnimationEvent ev(eventNum, single_cmd.num_args);
if (!ev.eventnum) {
continue;
}
ev.SetAnimationNumber(animnum);
ev.SetAnimationFrame(i);
for (j = 1; j < single_cmd.num_args; j++) {
ev.AddToken(single_cmd.args[j]);
}
PostEvent(ev, time, 1 << slot);
}
}
}
}
}
void Animate::NewAnim(int animnum, Event& newevent, int slot, float weight)
{
if (animnum != -1) {
NewAnim(animnum, slot);
SetAnimDoneEvent(newevent, slot);
} else {
PostEvent(newevent, level.frametime);
}
}
void Animate::NewAnim(int animnum, Event *newevent, int slot, float weight)
{
if (animnum == -1 && newevent) {
PostEvent(newevent, level.frametime);
}
NewAnim(animnum, slot);
SetAnimDoneEvent(newevent, slot);
}
void Animate::NewAnim(const char *animname, int slot, float weight)
{
int animnum = gi.Anim_NumForName(edict->tiki, animname);
if (animnum != -1) {
NewAnim(animnum, slot);
}
}
void Animate::NewAnim(const char *animname, Event *endevent, int slot, float weight)
{
int animnum = gi.Anim_NumForName(edict->tiki, animname);
if (animnum != -1) {
NewAnim(animnum, slot, weight);
SetAnimDoneEvent(endevent, slot);
} else if (endevent) {
PostEvent(endevent, level.frametime);
}
}
void Animate::NewAnim(const char *animname, Event& endevent, int slot, float weight)
{
int animnum = gi.Anim_NumForName(edict->tiki, animname);
if (animnum != -1) {
NewAnim(animnum, slot, weight);
SetAnimDoneEvent(endevent, slot);
} else {
PostEvent(endevent, level.frametime);
}
}
void Animate::SetAnimDoneEvent(Event *event, int slot)
{
if (doneEvents[slot]) {
delete doneEvents[slot];
}
doneEvents[slot] = event;
}
void Animate::SetAnimDoneEvent(Event& event, int slot)
{
SetAnimDoneEvent(new Event(event), slot);
}
void Animate::SetFrame(void)
{
edict->s.frameInfo[0].time = 0;
animFlags[0] = (animFlags[0] | ANIM_NODELTA) & ~ANIM_FINISHED;
}
qboolean Animate::HasAnim(const char *animname)
{
int num;
num = gi.Anim_Random(edict->tiki, animname);
return (num >= 0);
}
void Animate::StopAnimating(int slot)
{
DoExitCommands(slot);
if (doneEvents[slot]) {
delete doneEvents[slot];
doneEvents[slot] = NULL;
}
CancelFlaggedEvents(1 << slot);
if (edict->s.frameInfo[slot].index || gi.TIKI_NumAnims(edict->tiki) <= 1) {
edict->s.frameInfo[slot].index = 0;
} else {
edict->s.frameInfo[slot].index = 1;
}
animFlags[slot] = ANIM_LOOP | ANIM_NODELTA | ANIM_NOEXIT | ANIM_PAUSED;
SetWeight(slot, 0);
animtimes[slot] = 0.0f;
SlotChanged(slot);
}
void Animate::DoExitCommands(int slot)
{
tiki_cmd_t cmds;
AnimationEvent *ev;
if (animFlags[slot] & ANIM_NOEXIT) {
return;
}
// exit the previous animation
if (gi.Frame_Commands(edict->tiki, edict->s.frameInfo[slot].index, TIKI_FRAME_EXIT, &cmds)) {
int ii, j;
for (ii = 0; ii < cmds.num_cmds; ii++) {
const tiki_singlecmd_t& single_cmd = cmds.cmds[ii];
ev = new AnimationEvent(single_cmd.args[0], single_cmd.num_args);
ev->SetAnimationNumber(edict->s.frameInfo[slot].index);
ev->SetAnimationFrame(0);
for (j = 1; j < single_cmd.num_args; j++) {
ev->AddToken(single_cmd.args[j]);
}
PostEvent(ev, 0);
}
}
if (gi.Frame_Commands_Client(edict->tiki, edict->s.frameInfo[slot].index, TIKI_FRAME_EXIT, &cmds)) {
size_t longest = GetLongestClientCmdLength(cmds);
if (longest) {
char *cmdName = (char *)alloca(longest + 8 + 1);
int ii, j;
for (ii = 0; ii < cmds.num_cmds; ii++) {
const tiki_singlecmd_t& single_cmd = cmds.cmds[ii];
int eventNum;
Q_strncpyz(cmdName, "_client_", longest + 8 + 1);
Q_strncpyz(cmdName + 8, single_cmd.args[0], longest + 1);
eventNum = Event::FindEventNum(cmdName);
AnimationEvent ev(eventNum, single_cmd.num_args);
if (!ev.eventnum) {
continue;
}
ev.SetAnimationNumber(edict->s.frameInfo[slot].index);
ev.SetAnimationFrame(0);
for (j = 1; j < single_cmd.num_args; j++) {
ev.AddToken(single_cmd.args[j]);
}
PostEvent(ev, 0);
}
}
}
animFlags[slot] |= ANIM_NOEXIT;
}
int Animate::CurrentAnim(int slot) const
{
return edict->s.frameInfo[slot].index;
}
float Animate::AnimTime(int slot)
{
return animtimes[slot];
}
int Animate::NumAnims(void)
{
return gi.TIKI_NumAnims(edict->tiki);
}
const char *Animate::AnimName(int slot)
{
return gi.Anim_NameForNum(edict->tiki, CurrentAnim(slot));
}
void Animate::AnimFinished(int slot)
{
animFlags[slot] &= ~ANIM_FINISHED;
if (doneEvents[slot]) {
Event *ev = doneEvents[slot];
doneEvents[slot] = NULL;
ProcessEvent(ev);
}
}
void Animate::PreAnimate(void)
{
int i;
for (i = 0; i < MAX_FRAMEINFOS; i++) {
if (animFlags[i] & ANIM_FINISHED) {
AnimFinished(i);
}
}
}
void Animate::PostAnimate(void)
{
float startTime;
float deltaSyncTime;
float total_weight;
float total_angular_delta;
float beforeFrameTime;
Vector vFrameDelta;
bool hasAction = false;
if (!edict->tiki) {
return;
}
deltaSyncTime = syncTime;
beforeFrameTime = level.frametime / 10.f;
if (!pauseSyncTime) {
syncTime = 1.0f / syncRate * level.frametime + deltaSyncTime;
}
total_weight = 0;
total_angular_delta = 0;
for (int i = 0; i < MAX_FRAMEINFOS; i++) {
if (edict->s.frameInfo[i].weight > 0.0f && !(animFlags[i] & ANIM_NOACTION)) {
hasAction = true;
}
if (animFlags[i] & ANIM_PAUSED) {
continue;
}
if (animFlags[i] & ANIM_SYNC) {
startTime = deltaSyncTime * animtimes[i];
edict->s.frameInfo[i].time = animtimes[i] * syncTime;
} else {
startTime = edict->s.frameInfo[i].time;
edict->s.frameInfo[i].time += level.frametime;
}
if (animtimes[i] == 0.0f) {
animFlags[i] &= ~ANIM_NODELTA;
animFlags[i] |= ANIM_FINISHED;
RestartAnimSlot(i);
} else {
if (!(animFlags[i] & ANIM_NODELTA) && gi.Anim_HasDelta(edict->tiki, edict->s.frameInfo[i].index)) {
vec3_t vDelta;
float angleDelta;
//
// Get the animation's delta position from start time
//
gi.Anim_DeltaOverTime(
edict->tiki, edict->s.frameInfo[i].index, startTime, edict->s.frameInfo[i].time, vDelta
);
VectorScale(vDelta, edict->s.frameInfo[i].weight, vDelta);
vFrameDelta += vDelta;
total_weight += edict->s.frameInfo[i].weight;
//
// Get the animation's delta angle from start time
//
gi.Anim_AngularDeltaOverTime(
edict->tiki, edict->s.frameInfo[i].index, startTime, edict->s.frameInfo[i].time, &angleDelta
);
angleDelta *= edict->s.frameInfo[i].weight;
total_angular_delta += angleDelta;
}
animFlags[i] &= ~ANIM_NODELTA;
if (animFlags[i] & ANIM_SYNC) {
if (edict->s.frameInfo[i].time < animtimes[i]) {
continue;
}
} else {
// Fixed in OPM
// Previously, beforeFrameTime was a constant of 0.01.
// It caused issues when the number of server frames per second
// was higher. Some animation events were not executing properly.
if (edict->s.frameInfo[i].time < (animtimes[i] - beforeFrameTime)) {
continue;
}
}
if (IsRepeatType(i)) {
animFlags[i] |= ANIM_FINISHED;
do {
edict->s.frameInfo[i].time -= animtimes[i];
} while (edict->s.frameInfo[i].time >= animtimes[i]);
if (edict->s.frameInfo[i].time < 0) {
edict->s.frameInfo[i].time = 0;
}
} else {
if (startTime != animtimes[i]) {
animFlags[i] |= ANIM_FINISHED;
}
edict->s.frameInfo[i].time = animtimes[i];
}
}
}
if (total_weight) {
vFrameDelta *= 1.f / total_weight;
total_angular_delta *= 1.f / total_weight;
}
MatrixTransformVector(vFrameDelta, orientation, frame_delta);
angular_delta = total_angular_delta;
while (syncTime >= 1.0f) {
syncTime -= 1.0f;
}
total_weight = level.frametime * 4.0f;
if (hasAction) {
edict->s.actionWeight += total_weight;
if (edict->s.actionWeight > 1.0f) {
edict->s.actionWeight = 1.0f;
}
} else {
edict->s.actionWeight -= total_weight;
if (edict->s.actionWeight < 0.0f) {
edict->s.actionWeight = 0.0f;
}
}
}
void Animate::SetTime(int slot, float time)
{
if (time < 0.0) {
Com_Printf("ERROR: SetTime %f lesser than anim length %f\n", time, animtimes[slot]);
return;
}
if (time > animtimes[slot]) {
Com_Printf("ERROR: SetTime %f greater than anim length %f\n", time, animtimes[slot]);
return;
}
edict->s.frameInfo[slot].time = time;
}
void Animate::SetNormalTime(int slot, float normal)
{
if (normal < 0.0f || normal > 1.0f) {
Com_Printf("ERROR: Animate::SetNormalTime: Normal must be between 0 and 1\n");
} else {
edict->s.frameInfo[slot].time = animtimes[slot] * normal;
}
}
float Animate::GetTime(int slot)
{
return edict->s.frameInfo[slot].time;
}
float Animate::GetNormalTime(int slot)
{
return edict->s.frameInfo[slot].time / animtimes[slot];
}
void Animate::Pause(int slot, int pause)
{
if (pause) {
animFlags[slot] |= ANIM_PAUSED;
} else {
if (animFlags[slot] & ANIM_PAUSED) {
if (animFlags[slot] & ANIM_SYNC) {
SlotChanged(animFlags[slot]);
}
animFlags[slot] &= ~ANIM_PAUSED;
}
}
}
void Animate::UseSyncTime(int slot, int sync)
{
if (sync) {
if (!(animFlags[slot] & ANIM_SYNC)) {
animFlags[slot] |= ANIM_SYNC;
SlotChanged(slot);
}
} else {
if (animFlags[slot] & ANIM_SYNC) {
animFlags[slot] &= ~ANIM_SYNC;
SlotChanged(slot);
}
}
}
void Animate::SetSyncRate(float rate)
{
if (rate < 0.001f) {
Com_Printf("ERROR SetSyncRate: canot set syncrate below 0.001.\n");
syncRate = 0.001f;
} else {
syncRate = rate;
}
}
float Animate::GetCrossTime(int slot)
{
return gi.Anim_CrossTime(edict->tiki, edict->s.frameInfo[slot].index);
}
void Animate::SetRepeatType(int slot)
{
animFlags[slot] |= ANIM_LOOP;
}
void Animate::SetOnceType(int slot)
{
animFlags[slot] &= ~ANIM_LOOP;
}
void Animate::EventIsLoopingAnim(Event *ev)
{
str anim_name = ev->GetString(1);
int animnum;
if (!edict->tiki) {
ScriptError("^~^~^ no tiki set");
}
animnum = gi.Anim_NumForName(edict->tiki, anim_name.c_str());
if (animnum < 0) {
ScriptError("anim '%s' not found, so can't tell if it is looping", anim_name.c_str());
}
if (gi.Anim_FlagsSkel(edict->tiki, animnum) & ANIM_LOOP) {
ev->AddInteger(1);
} else {
ev->AddInteger(0);
}
}
void Animate::EventSetYawFromBone(Event *ev)
{
str bonename;
int tagnum;
orientation_t ori;
vec3_t vAngles;
bonename = ev->GetString(1);
tagnum = gi.Tag_NumForName(edict->tiki, bonename.c_str());
if (tagnum < 0) {
ScriptError("Could not find tag '%s' in '%s'", bonename.c_str(), edict->tiki->name);
}
GetTagPositionAndOrientation(tagnum, &ori);
MatrixToEulerAngles(ori.axis, vAngles);
angles.y += vAngles[1];
setAngles(angles);
NoLerpThisFrame();
}
void Animate::EventPlayerSpawn(Event *ev)
{
Player *player;
float range;
Vector vector_offset;
float inFOV;
float speed;
Vector delta;
float dist;
float time;
float dot;
Event *event;
player = static_cast<Player *>(G_FindTarget(this, "player"));
if (!player) {
ScriptError("Could not find player!");
}
if (ev->NumArgs() > 1) {
range = ev->GetFloat(2);
} else {
range = 480;
}
if (ev->NumArgs() > 2) {
vector_offset = ev->GetVector(3);
} else {
vector_offset = vec_zero;
}
if (ev->NumArgs() > 3) {
inFOV = ev->GetFloat(4);
} else {
inFOV = 0;
}
if (ev->NumArgs() > 4) {
speed = ev->GetFloat(5);
} else {
speed = 960;
}
delta = origin - player->origin;
dist = delta.length();
if (dist > range) {
// above the range, won't spawn
return;
}
time = dist / speed;
dot = DotProduct(delta, player->orientation[0]);
if (inFOV < 0) {
if (dot > cos(DEG2RAD(45))) {
return;
}
} else if (inFOV > 0) {
if (dot < cos(DEG2RAD(45))) {
return;
}
}
event = new Event(EV_Animate_PlayerSpawn_Utility);
event->AddString(ev->GetString(1));
event->AddVector(vector_offset);
player->PostEvent(event, time);
}
void Animate::EventPlayerSpawnUtility(Event *ev)
{
Player *player;
str modelname;
Vector vector_offset;
Vector transformed;
SpawnArgs args;
ClassDef *cls;
Entity *newEnt;
Event *event;
player = static_cast<Player *>(G_FindTarget(this, "player"));
if (!player) {
ScriptError("Could not find player!");
}
modelname = ev->GetString(1);
vector_offset = ev->GetVector(2);
MatrixTransformVector(vector_offset, player->orientation, transformed);
transformed += player->origin;
args.setArg("classname", modelname.c_str());
args.setArg("model", modelname.c_str());
cls = args.getClassDef();
if (!cls) {
cls = Entity::classinfostatic();
}
// Spawn the new entity
newEnt = static_cast<Entity *>(cls->newInstance());
event = new Event(EV_Model);
event->AddString(modelname);
newEnt->PostEvent(event, EV_SETUP_ROPEPIECE);
event = new Event(EV_SetOrigin);
event->AddVector(transformed);
newEnt->PostEvent(event, EV_SETUP_ROPEBASE);
event = new Event(EV_SetAngles);
event->AddVector(transformed.toAngles());
newEnt->PostEvent(event, EV_SETUP_ROPEBASE);
newEnt->ProcessPendingEvents();
newEnt->ProcessEvent(EV_Entity_Start);
}
float Animate::GetYawOffset()
{
SkelMat4 *transform;
int boneNum;
boneNum = gi.Tag_NumForName(edict->tiki, "Bip01");
if (boneNum == -1) {
return 0.0;
}
transform = G_TIKI_Transform(edict, boneNum);
if (transform->val[0][0] != 0) {
return RAD2DEG(atan2(transform->val[0][1], transform->val[0][0]));
} else {
return 0;
}
}
void Animate::DumpAnimInfo(void)
{
MPrintf("----------------------------------------\n");
for (int i = 0; i < MAX_FRAMEINFOS; i++) {
if (edict->s.frameInfo[i].weight <= 0.0f) {
continue;
}
str animname = gi.Anim_NameForNum(edict->tiki, CurrentAnim(i));
MPrintf(
"slot: %d anim: %s weight: %f time: %f len: %f\n",
i,
animname.c_str(),
edict->s.frameInfo[i].weight,
edict->s.frameInfo[i].time,
animtimes[i]
);
}
MPrintf("actionWeight: %f\n", edict->s.actionWeight);
}
void Animate::ClientSound(Event *ev)
{
PlayNonPvsSound(ev->GetString(1));
}
void Animate::EventPauseAnim(Event *ev)
{
is_paused = ev->GetInteger(1) ? true : false;
for (int i = 0; i < MAX_FRAMEINFOS; i++) {
Pause(i, is_paused);
}
}