Files
2025-07-19 23:30:20 +02:00

846 lines
20 KiB
C++

/*
===========================================================================
Copyright (C) 2015 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
===========================================================================
*/
// viewthing.cpp: Actor code for the Viewthing.
//
#include "animate.h"
#include "viewthing.h"
#include "game.h"
#include "level.h"
#include "scriptexception.h"
Event EV_ViewThing_Think
(
"viewthing_think",
EV_DEFAULT,
NULL,
NULL,
"Called every frame to process the view thing.",
EV_NORMAL
);
Event EV_ViewThing_ToggleAnimate
(
"viewanimate",
EV_CHEAT,
NULL,
NULL,
"Cycle through the animations modes of the current viewthing\n"
"No Animation\n"
"Animation with no motion\n"
"Animation with looping motion\n"
"Animation with motion\n",
EV_NORMAL
);
Event EV_ViewThing_SetModel
(
"viewmodel",
EV_CHEAT,
"s",
"viewthingModel",
"Set the model of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_NextFrame
(
"viewnext",
EV_CHEAT,
NULL,
NULL,
"Advance to the next frame of animation of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_PrevFrame
(
"viewprev",
EV_CHEAT,
NULL,
NULL,
"Advance to the previous frame of animation of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_NextAnim
(
"viewnextanim",
EV_CHEAT,
NULL,
NULL,
"Advance to the next animation of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_PrevAnim
(
"viewprevanim",
EV_CHEAT,
NULL,
NULL,
"Advance to the previous animation of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_ScaleUp
(
"viewscaleup",
EV_CHEAT,
NULL,
NULL,
"Increase the scale of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_ScaleDown
(
"viewscaledown",
EV_CHEAT,
NULL,
NULL,
"Decrease the scale of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_SetScale
(
"viewscale",
EV_CHEAT,
"f",
"scale",
"Set the scale of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_SetYaw
(
"viewyaw",
EV_CHEAT,
"f",
"yaw",
"Set the yaw of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_SetPitch
(
"viewpitch",
EV_CHEAT,
"f",
"pitch",
"Set the pitch of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_SetRoll
(
"viewroll",
EV_CHEAT,
"f",
"roll",
"Set the roll of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_SetAngles
(
"viewangles",
EV_CHEAT,
"f[0,360]f[0,360]f[0,360]",
"pitch yaw roll",
"Set the angles of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_Spawn
(
"viewspawn",
EV_CHEAT,
"s",
"model",
"Create a viewthing with the specified model",
EV_NORMAL
);
Event EV_ViewThing_Next
(
"viewthingnext",
EV_CHEAT,
NULL,
NULL,
"Change the active viewthing to the next viewthing",
EV_NORMAL
);
Event EV_ViewThing_Prev
(
"viewthingprev",
EV_CHEAT,
NULL,
NULL,
"Change the active viewthing to the previous viewthing",
EV_NORMAL
);
Event EV_ViewThing_Attach
(
"viewattach",
EV_CHEAT,
"ss",
"tagname model",
"Attach a model the the specified tagname",
EV_NORMAL
);
Event EV_ViewThing_Detach
(
"viewdetach",
EV_CHEAT,
NULL,
NULL,
"Detach the current viewthing from its parent",
EV_NORMAL
);
Event EV_ViewThing_DetachAll
(
"viewdetachall",
EV_CHEAT,
NULL,
NULL,
"Detach all the models attached to the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_Delete
(
"viewdelete",
EV_CHEAT,
NULL,
NULL,
"Delete the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_SetOrigin
(
"vieworigin",
EV_CHEAT,
"fff",
"x y z",
"Set the origin of the current viewthing",
EV_NORMAL
);
Event EV_ViewThing_DeleteAll
(
"viewdeleteall",
EV_CHEAT,
NULL,
NULL,
"Delete all viewthings",
EV_NORMAL
);
Event EV_ViewThing_LastFrame
(
"viewlastframe",
EV_DEFAULT,
NULL,
NULL,
"Called when the view things last animation frame is displayed.",
EV_NORMAL
);
Event EV_ViewThing_SaveOffSurfaces
(
"viewsavesurfaces",
EV_DEFAULT,
NULL,
NULL,
"Called after the model is spawned to save off the models original surfaces.",
EV_NORMAL
);
Event EV_ViewThing_PrintTime
(
"_viewthing_printtime",
EV_DEFAULT,
NULL,
NULL,
"Prints out the current level.time.",
EV_NORMAL
);
Event EV_ViewThing_SetAnim
(
"viewsetanim",
EV_CHEAT,
"f",
"animNum",
"Set the animation absolutely based off a floating point value",
EV_NORMAL
);
CLASS_DECLARATION(Animate, ViewMaster, NULL) {
{&EV_ViewThing_Spawn, &ViewMaster::Spawn },
{&EV_ViewThing_Next, &ViewMaster::Next },
{&EV_ViewThing_Prev, &ViewMaster::Prev },
{&EV_ViewThing_SetModel, &ViewMaster::SetModelEvent},
{&EV_ViewThing_DeleteAll, &ViewMaster::DeleteAll },
{&EV_ViewThing_ToggleAnimate, &ViewMaster::PassEvent },
{&EV_ViewThing_NextFrame, &ViewMaster::PassEvent },
{&EV_ViewThing_PrevFrame, &ViewMaster::PassEvent },
{&EV_ViewThing_NextAnim, &ViewMaster::PassEvent },
{&EV_ViewThing_PrevAnim, &ViewMaster::PassEvent },
{&EV_ViewThing_ScaleUp, &ViewMaster::PassEvent },
{&EV_ViewThing_ScaleDown, &ViewMaster::PassEvent },
{&EV_ViewThing_SetScale, &ViewMaster::PassEvent },
{&EV_ViewThing_SetYaw, &ViewMaster::PassEvent },
{&EV_ViewThing_SetPitch, &ViewMaster::PassEvent },
{&EV_ViewThing_SetRoll, &ViewMaster::PassEvent },
{&EV_ViewThing_SetAngles, &ViewMaster::PassEvent },
{&EV_ViewThing_Attach, &ViewMaster::PassEvent },
{&EV_ViewThing_Detach, &ViewMaster::PassEvent },
{&EV_ViewThing_DetachAll, &ViewMaster::PassEvent },
{&EV_ViewThing_Delete, &ViewMaster::PassEvent },
{&EV_ViewThing_SetOrigin, &ViewMaster::PassEvent },
{&EV_ViewThing_SetAnim, &ViewMaster::PassEvent },
{NULL, NULL }
};
ViewMaster Viewmodel;
ViewMaster::ViewMaster()
{
current_viewthing = NULL;
}
void ViewMaster::Init(void)
{
gi.AddCommand("viewanimate", NULL);
gi.AddCommand("viewmodel", NULL);
gi.AddCommand("viewnext", NULL);
gi.AddCommand("viewprev", NULL);
gi.AddCommand("viewnextanim", NULL);
gi.AddCommand("viewprevanim", NULL);
gi.AddCommand("viewscaleup", NULL);
gi.AddCommand("viewscaledown", NULL);
gi.AddCommand("viewscale", NULL);
gi.AddCommand("viewyaw", NULL);
gi.AddCommand("viewpitch", NULL);
gi.AddCommand("viewroll", NULL);
gi.AddCommand("viewangles", NULL);
gi.AddCommand("viewspawn", NULL);
gi.AddCommand("viewthingnext", NULL);
gi.AddCommand("viewthingprev", NULL);
gi.AddCommand("viewattach", NULL);
gi.AddCommand("viewdetach", NULL);
gi.AddCommand("viewdetachall", NULL);
gi.AddCommand("viewdelete", NULL);
gi.AddCommand("vieworigin", NULL);
gi.AddCommand("viewdeleteall", NULL);
gi.AddCommand("viewsetanim", NULL);
}
void ViewMaster::Next(Event *ev)
{
Viewthing *viewthing;
Entity *ent;
ent = (Entity *)G_FindClass(current_viewthing, "viewthing");
if (ent) {
current_viewthing = ent;
viewthing = (Viewthing *)((Entity *)current_viewthing);
gi.Printf("current viewthing model %s.\n", viewthing->model.c_str());
viewthing->UpdateCvars();
} else {
gi.Printf("no more viewthings on map.\n");
}
}
void ViewMaster::Prev(Event *ev)
{
Viewthing *viewthing;
Entity *prev;
Entity *next;
next = NULL;
do {
prev = next;
next = (Entity *)G_FindClass(prev, "viewthing");
} while (next != current_viewthing);
if (prev) {
current_viewthing = prev;
viewthing = (Viewthing *)((Entity *)current_viewthing);
gi.Printf("current viewthing model %s.\n", viewthing->model.c_str());
viewthing->UpdateCvars();
} else {
gi.Printf("no more viewthings on map.\n");
}
}
void ViewMaster::DeleteAll(Event *ev)
{
Entity *next;
for (next = (Entity *)G_FindClass(NULL, "viewthing"); next != NULL;
next = (Entity *)G_FindClass(next, "viewthing")) {
next->PostEvent(EV_Remove, 0);
}
current_viewthing = NULL;
}
void ViewMaster::Spawn(Event *ev)
{
Viewthing *viewthing;
str mdl;
Vector forward;
Vector up;
Vector delta;
Event *event;
Entity *ent;
mdl = ev->GetString(1);
if (!mdl || !mdl[0]) {
ScriptError("Must specify a model name");
return;
}
// Check if we have a client
ent = g_entities[0].entity;
assert(ent);
if (!ent) {
return;
}
// create a new viewthing
viewthing = new Viewthing;
// set the current_viewthing
current_viewthing = viewthing;
//FIXME FIXME
ent->angles.AngleVectors(&forward, NULL, &up);
viewthing->baseorigin = ent->origin;
viewthing->baseorigin += forward * 48;
viewthing->baseorigin += up * 48;
viewthing->setOrigin(viewthing->baseorigin);
viewthing->droptofloor(256);
viewthing->baseorigin = viewthing->origin;
delta = ent->origin - viewthing->origin;
viewthing->setAngles(delta.toAngles());
event = new Event(EV_ViewThing_SetModel);
event->AddString(mdl);
viewthing->ProcessEvent(event);
if (!gi.modeltiki(viewthing->model)) {
ScriptError("model %s not found, viewmodel not spawned.", mdl.c_str());
delete viewthing;
current_viewthing = NULL;
return;
}
}
void ViewMaster::SetModelEvent(Event *ev)
{
str mdl;
char str[128];
Event *event;
Viewthing *viewthing;
mdl = ev->GetString(1);
if (!mdl || !mdl[0]) {
ScriptError("Must specify a model name");
return;
}
if (!current_viewthing) {
// try to find one on the map
current_viewthing = (Entity *)G_FindClass(NULL, "viewthing");
if (!current_viewthing) {
ScriptError("No viewmodel");
return;
}
}
viewthing = (Viewthing *)((Entity *)current_viewthing);
// Prepend 'models/' to make things easier
str[0] = 0;
if ((mdl[1] != ':') && Q_stricmpn(mdl, "models", 6)) {
strcpy(str, "models/");
}
strcat(str, mdl);
event = new Event(EV_ViewThing_SetModel);
event->AddString(str);
viewthing->ProcessEvent(event);
viewthing->UpdateCvars();
}
void ViewMaster::PassEvent(Event *ev)
{
Viewthing *viewthing;
Event *event;
if (!current_viewthing) {
ScriptError("No viewmodel");
return;
}
viewthing = (Viewthing *)((Entity *)current_viewthing);
if (viewthing) {
viewthing->ProcessEvent(*ev);
}
}
CLASS_DECLARATION(Animate, Viewthing, "viewthing") {
{&EV_ViewThing_Think, &Viewthing::ThinkEvent },
{&EV_ViewThing_LastFrame, &Viewthing::LastFrameEvent },
{&EV_ViewThing_ToggleAnimate, &Viewthing::ToggleAnimateEvent},
{&EV_ViewThing_SetModel, &Viewthing::SetModelEvent },
{&EV_ViewThing_NextFrame, &Viewthing::NextFrameEvent },
{&EV_ViewThing_PrevFrame, &Viewthing::PrevFrameEvent },
{&EV_ViewThing_NextAnim, &Viewthing::NextAnimEvent },
{&EV_ViewThing_PrevAnim, &Viewthing::PrevAnimEvent },
{&EV_ViewThing_ScaleUp, &Viewthing::ScaleUpEvent },
{&EV_ViewThing_ScaleDown, &Viewthing::ScaleDownEvent },
{&EV_ViewThing_SetScale, &Viewthing::SetScaleEvent },
{&EV_ViewThing_SetYaw, &Viewthing::SetYawEvent },
{&EV_ViewThing_SetPitch, &Viewthing::SetPitchEvent },
{&EV_ViewThing_SetRoll, &Viewthing::SetRollEvent },
{&EV_ViewThing_SetAngles, &Viewthing::SetAnglesEvent },
{&EV_ViewThing_Attach, &Viewthing::AttachModel },
{&EV_ViewThing_Detach, &Viewthing::Delete },
{&EV_ViewThing_DetachAll, &Viewthing::DetachAll },
{&EV_ViewThing_Delete, &Viewthing::Delete },
{&EV_ViewThing_SetOrigin, &Viewthing::ChangeOrigin },
{&EV_ViewThing_SaveOffSurfaces, &Viewthing::SaveSurfaces },
{&EV_ViewThing_PrintTime, &Viewthing::PrintTime },
{&EV_ViewThing_SetAnim, &Viewthing::SetAnim },
{NULL, NULL }
};
Viewthing::Viewthing(void)
{
frame = 0;
animstate = 0;
setSolidType(SOLID_NOT);
baseorigin = origin;
Viewmodel.current_viewthing = this;
edict->s.eType = ET_MODELANIM;
edict->s.renderfx |= RF_SHADOW;
edict->s.renderfx |= RF_SHADOW_PRECISE;
edict->s.renderfx |= RF_EXTRALIGHT;
// save off surfaces once the model is spawned
PostEvent(EV_ViewThing_SaveOffSurfaces, FRAMETIME);
PostEvent(EV_ViewThing_Think, FRAMETIME);
}
void Viewthing::PrintTime(Event *ev)
{
gi.Printf("ev current frame %d leveltime %.2f\n", ev->GetInteger(1), level.time);
}
void Viewthing::UpdateCvars(qboolean quiet)
{
gi.cvar_set("viewmodelentity", va("%d", edict->s.number));
}
void Viewthing::ThinkEvent(Event *ev)
{
int f;
if (animstate >= 2) {
Vector forward;
Vector left;
Vector up;
Vector realmove;
angles.AngleVectors(&forward, &left, &up);
realmove = left * accel[1] + up * accel[2] + forward * accel[0];
setOrigin(baseorigin + realmove);
gi.cvar_set(
"viewthingorigin", va("%0.2f,%0.2f,%0.2f", edict->s.origin[0], edict->s.origin[1], edict->s.origin[2])
);
}
PostEvent(EV_ViewThing_Think, FRAMETIME);
if ((animstate > 0) && (Viewmodel.current_viewthing == this)) {
f = GetTime() / gi.Anim_Frametime(edict->tiki, CurrentAnim());
if (f != lastframe) {
float time;
lastframe = f;
time = f * AnimTime() / NumFrames();
gi.Printf("current frame %d time %.2f\n", f, time);
gi.cvar_set("viewmodeltime", va("%.2f", time));
gi.cvar_set("viewmodelframe", va("%d", f));
gi.cvar_set("viewmodelanim", AnimName());
}
}
}
void Viewthing::LastFrameEvent(Event *ev) {}
void Viewthing::ToggleAnimateEvent(Event *ev)
{
animstate = (animstate + 1) % 4;
setOrigin(baseorigin);
// reset to a known state
switch (animstate) {
case 0:
SetFrame();
gi.Printf("Animation stopped.\n");
gi.cvar_set("viewmodelanimmode", "Stopped");
break;
case 1:
NewAnim(CurrentAnim());
gi.Printf("Animation no motion.\n");
gi.cvar_set("viewmodelanimmode", "No Motion");
break;
case 2:
NewAnim(CurrentAnim(), EV_ViewThing_LastFrame);
gi.Printf("Animation with motion and looping.\n");
gi.cvar_set("viewmodelanimmode", "Motion and Looping");
break;
case 3:
NewAnim(CurrentAnim(), EV_ViewThing_LastFrame);
gi.Printf("Animation with motion no looping.\n");
gi.cvar_set("viewmodelanimmode", "Motion and No Looping");
break;
}
UpdateCvars(qtrue);
}
void Viewthing::SetModelEvent(Event *ev)
{
str modelname;
modelname = ev->GetString(1);
setModel(modelname);
if (gi.modeltiki(model)) {
NewAnim(0);
SetFrame();
}
UpdateCvars();
}
void Viewthing::NextFrameEvent(Event *ev)
{
int numframes;
numframes = NumFrames();
if (numframes) {
frame = (frame + 1) % numframes;
SetFrame();
animstate = 0;
UpdateCvars();
}
}
void Viewthing::PrevFrameEvent(Event *ev)
{
int numframes;
numframes = NumFrames();
if (numframes) {
frame = (frame - 1) % numframes;
SetFrame();
animstate = 0;
UpdateCvars();
}
}
void Viewthing::SetAnim(Event *ev)
{
int numanims, anim;
numanims = NumAnims();
if (numanims) {
// restore original surfaces
memcpy(edict->s.surfaces, origSurfaces, sizeof(origSurfaces));
anim = ev->GetFloat(1) * numanims;
if (anim >= numanims) {
anim = numanims - 1;
}
NewAnim(anim % numanims);
frame = 0;
SetFrame();
animstate = 0;
UpdateCvars();
}
}
void Viewthing::NextAnimEvent(Event *ev)
{
int numanims;
numanims = NumAnims();
if (numanims) {
// restore original surfaces
memcpy(edict->s.surfaces, origSurfaces, sizeof(origSurfaces));
NewAnim((CurrentAnim() + 1) % numanims);
frame = 0;
SetFrame();
animstate = 0;
UpdateCvars();
}
}
void Viewthing::PrevAnimEvent(Event *ev)
{
int anim;
int numanims;
numanims = NumAnims();
if (numanims) {
// restore original surfaces
memcpy(edict->s.surfaces, origSurfaces, sizeof(origSurfaces));
anim = CurrentAnim() - 1;
while (anim < 0) {
anim += numanims;
}
NewAnim(anim);
frame = 0;
SetFrame();
animstate = 0;
UpdateCvars();
}
}
void Viewthing::ScaleUpEvent(Event *ev)
{
edict->s.scale += 0.01f;
UpdateCvars();
}
void Viewthing::ScaleDownEvent(Event *ev)
{
edict->s.scale -= 0.01f;
UpdateCvars();
}
void Viewthing::SetScaleEvent(Event *ev)
{
float s;
if (ev->NumArgs()) {
s = ev->GetFloat(1);
edict->s.scale = s;
UpdateCvars();
} else {
gi.Printf("viewscale = %f\n", edict->s.scale);
}
}
void Viewthing::SetYawEvent(Event *ev)
{
if (ev->NumArgs() > 0) {
angles.setYaw(ev->GetFloat(1));
setAngles(angles);
}
gi.Printf("yaw = %f\n", angles.yaw());
}
void Viewthing::SetPitchEvent(Event *ev)
{
if (ev->NumArgs() > 0) {
angles.setPitch(ev->GetFloat(1));
setAngles(angles);
}
gi.Printf("pitch = %f\n", angles.pitch());
}
void Viewthing::SetRollEvent(Event *ev)
{
if (ev->NumArgs() > 0) {
angles.setRoll(ev->GetFloat(1));
setAngles(angles);
}
gi.Printf("roll = %f\n", angles.roll());
}
void Viewthing::SetAnglesEvent(Event *ev)
{
if (ev->NumArgs() > 2) {
angles.x = ev->GetFloat(1);
angles.y = ev->GetFloat(2);
angles.z = ev->GetFloat(3);
setAngles(angles);
}
gi.Printf("angles = %f, %f, %f\n", angles.x, angles.y, angles.z);
}
void Viewthing::AttachModel(Event *ev)
{
Event *event;
Viewthing *child;
child = new Viewthing;
child->setModel(ev->GetString(2));
//
// attach the child
//
event = new Event(EV_Attach);
event->AddEntity(this);
event->AddString(ev->GetString(1));
child->ProcessEvent(event);
}
void Viewthing::Delete(Event *ev)
{
Viewmodel.current_viewthing = NULL;
PostEvent(EV_Remove, 0);
Viewmodel.PostEvent(EV_ViewThing_Next, 0);
}
void Viewthing::DetachAll(Event *ev)
{
int i;
int num;
num = numchildren;
for (i = 0; i < MAX_MODEL_CHILDREN; i++) {
Entity *ent;
if (!children[i]) {
continue;
}
ent = (Entity *)G_GetEntity(children[i]);
ent->PostEvent(EV_Remove, 0);
num--;
if (!num) {
break;
}
}
}
void Viewthing::ChangeOrigin(Event *ev)
{
if (ev->NumArgs()) {
origin.x = ev->GetFloat(1);
origin.y = ev->GetFloat(2);
origin.z = ev->GetFloat(3);
setOrigin(origin);
baseorigin = origin;
UpdateCvars();
}
gi.Printf("vieworigin = x%f y%f z%f\n", origin.x, origin.y, origin.z);
}
void Viewthing::SaveSurfaces(Event *ev)
{
memcpy(origSurfaces, edict->s.surfaces, sizeof(origSurfaces));
}