openmohaa/code/cgame/cg_eventSystem.c
2016-03-27 11:49:47 +02:00

613 lines
18 KiB
C

/*
===========================================================================
Copyright (C) 2011 su44
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
===========================================================================
*/
// cg_eventSystem.c
#include "cg_local.h"
#include "../qcommon/tiki_local.h"
typedef struct event_s {
centity_t *ent;
char buffer[1024];
int execTime; // event should be executed and fried if cg.time >= ev->execTime
struct event_s *next,*prev; // linked list
} event_t;
#define MAX_PENDING_EVENTS 512
static event_t events[MAX_PENDING_EVENTS];
static event_t cg_pendingEvents; // double linked list
static event_t *cg_freeEvents; // single linked list
void CG_InitEventSystem() {
int i;
memset( events, 0, sizeof( events ) );
cg_pendingEvents.next = &cg_pendingEvents;
cg_pendingEvents.prev = &cg_pendingEvents;
cg_freeEvents = events;
for ( i = 0 ; i < MAX_PENDING_EVENTS - 1 ; i++ ) {
events[i].next = &events[i+1];
}
}
/*
Generates a random float component for MoHAA events.
If random is specified, the component will range from 0 to specified value.
If crandom is specified, the component will range from -specified to +specified value.
If range is specified, the component needs two values; it will randomly pick a number in the range
from the first number to the first number plus the second number.
This is used by "randvel", "randvelaxis", "offset", "offsetalongaxis", "angles", MoHAA events
*/
float CG_ParseEventFloatParm(char **text) {
char *token;
float value;
token = COM_ParseExt( text, qfalse );
if(!Q_stricmp(token,"random")) {
token = COM_ParseExt( text, qfalse );
value = random()*atof(token);
} else if(!Q_stricmp(token,"crandom")) {
token = COM_ParseExt( text, qfalse );
value = crandom()*atof(token);
} else if(!Q_stricmp(token,"range")) {
float range;
token = COM_ParseExt( text, qfalse );
value = atof(token);
token = COM_ParseExt( text, qfalse );
range = atof(token);
value += random()*range;
} else {
#if 0
CG_Printf("CG_ParseEventFloatParm: unknown first parm, expected 'random', 'crandom' or 'range', found %s\n",token);
value = 0;
#else
value = atof(token);
#endif
}
return value;
}
// this is used by both "tagspawn" and "originspawn" events
void CG_ProcessSpawnEvent(centity_t *ent, vec3_t spawnOrigin, vec3_t spawnAngles, char *text) {
char *token;
qhandle_t h;
tiki_t *tiki;
vec3_t v;
localEntity_t *le;
char *dot;
// skip optional paramter(s) ? see rechamber anim of weapons/kar98.tik
do {
token = COM_ParseExt( &text, qtrue );
if(token[0]==0) {
CG_Printf("'***spawn' event without parameters block\n");
return;
}
} while(token[0] != '(');
if(token[1] == 0) {
token = COM_ParseExt( &text, qtrue );
} else {
token++;
}
le = CG_AllocLocalEntity();
le->endTime = cg.time + 10000;
le->leType = LE_SKIP;
//le->leFlags = LEF_TUMBLE;
le->pos.trTime = cg.time - (rand()&15);
VectorCopy( spawnOrigin, le->pos.trDelta );
VectorCopy(spawnOrigin,le->refEntity.origin);
VectorCopy( spawnAngles, le->angles.trDelta );
AnglesToAxis(spawnAngles,le->refEntity.axis);
tiki = le->tiki;
// parse parameters ( ... ) block
while(token[0] != ')') {
if(token[0]==0) {
CG_Printf("'***spawn' missing ')' in tiki %s\n",tiki->name);
return;
}
Q_strlwr(token);
if(!strcmp(token,"spawnrange")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"spawnrate")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"count")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"model")) {
// both .tik and .spr models are used here
token = COM_ParseExt( &text, qtrue );
h = cgi.R_RegisterModel(token);
le->refEntity.model = h;
le->leType = LE_FRAGMENT;
dot = strchr(token,'.');
if(dot && !Q_stricmp(dot+1,"tik")) {
le->tiki = cgi.TIKI_RegisterModel(token);
}
} else if(!strcmp(token,"color")) {
token = COM_ParseExt( &text, qfalse );
token = COM_ParseExt( &text, qfalse );
token = COM_ParseExt( &text, qfalse );
} else if(!strcmp(token,"varycolor")) {
token = COM_ParseExt( &text, qfalse );
} else if(!strcmp(token,"alpha")) {
token = COM_ParseExt( &text, qfalse );
} else if(!strcmp(token,"offset")) {
token = COM_ParseExt( &text, qfalse ); // x
token = COM_ParseExt( &text, qfalse ); // y
token = COM_ParseExt( &text, qfalse ); // z
} else if(!strcmp(token,"offsetalongaxis")) {
v[0] = CG_ParseEventFloatParm(&text); // x
v[1] = CG_ParseEventFloatParm(&text); // y
v[2] = CG_ParseEventFloatParm(&text); // z
} else if(!strcmp(token,"randvelaxis")) {
v[0] = CG_ParseEventFloatParm(&text); // x
v[1] = CG_ParseEventFloatParm(&text); // y
v[2] = CG_ParseEventFloatParm(&text); // z
} else if(!strcmp(token,"scale")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"scalemin")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"scalemax")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"scalerate")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"velocity")) {
token = COM_ParseExt( &text, qtrue ); // forwardVelocity
} else if(!strcmp(token,"angles")) {
v[0] = CG_ParseEventFloatParm(&text);
v[1] = CG_ParseEventFloatParm(&text);
v[2] = CG_ParseEventFloatParm(&text);
VectorCopy( v, le->angles.trDelta );
AnglesToAxis(v,le->refEntity.axis);
} else if(!strcmp(token,"randvel")) {
v[0] = CG_ParseEventFloatParm(&text);
v[1] = CG_ParseEventFloatParm(&text);
v[2] = CG_ParseEventFloatParm(&text);
VectorCopy(v,le->pos.trDelta);
} else if(!strcmp(token,"emitterangles")) {
token = COM_ParseExt( &text, qfalse ); // pitchofs
token = COM_ParseExt( &text, qfalse ); // yawofs
token = COM_ParseExt( &text, qfalse ); // rollofs
} else if(!strcmp(token,"avelocity")) {
// crandom keyword is used for avelocity in weapons/mp40.tik
v[YAW] = CG_ParseEventFloatParm(&text); // yawVel
v[PITCH] = CG_ParseEventFloatParm(&text); // pitchVel
v[ROLL] = CG_ParseEventFloatParm(&text); // rollVel
//VectorCopy(v,le->apos.trDelta);
} else if(!strcmp(token,"accel")) {
// NOTE: This acceleration is applied using the world axis
token = COM_ParseExt( &text, qfalse ); // accelX
token = COM_ParseExt( &text, qfalse ); // accelY
token = COM_ParseExt( &text, qfalse ); // accelZ
} else if(!strcmp(token,"physicsrate")) {
token = COM_ParseExt( &text, qtrue );
if(!Q_stricmp(token,"every")) {
}
} else if(!strcmp(token,"life")) {
token = COM_ParseExt( &text, qtrue );
le->endTime = cg.time + atof(token)*1000;
} else if(!strcmp(token,"fadedelay")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"alignstretch")) {
token = COM_ParseExt( &text, qfalse ); // scaleMultiplier
} else if(!strcmp(token,"alignonce")) {
} else if(!strcmp(token,"align")) {
} else if(!strcmp(token,"collision")) {
} else if(!strcmp(token,"fade")) {
} else if(!strcmp(token,"randomroll")) {
} else if(!strcmp(token,"spritegridlighting")) {
} else if(!strcmp(token,"volumetric")) {
// set the effect to spawn volumetric sources rather than tempmodels
} else if(!strcmp(token,"wateronly")) {
// makes the temp model remove itself if it leaves water
} else if(!strcmp(token,"wind")) {
// makes the temp model be affected by wind
} else if(!strcmp(token,"bouncefactor")) {
token = COM_ParseExt( &text, qtrue );
le->bounceFactor = atof(token);
} else if(!strcmp(token,"bouncesoundonce")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"friction")) {
token = COM_ParseExt( &text, qtrue );
} else {
//CG_Printf("Uknown '***spawn' parameter %s\n",token);
}
COM_ParseExt( &text, qtrue );
}
}
void CG_ProcessEventText(centity_t *ent, const char *eventText) {
char str[128];
char *text;
char *token;
float f;
int i;
int boneName;
tiki_t *tiki;
vec3_t v,a;
text = (char*)eventText;
again:
// if(cg_printEvents.integer)
// CG_Printf("CG_ProcessEventText: event %s\n",eventText);
// get event name
token = COM_ParseExt( &text, qtrue );
Q_strlwr(token);
if(!strcmp(token,"sound")) {
// syntax: sound( String soundName,
// [ String channelName ], [ Float volume ],
// [ Float min_distance ], [ Float pitch ],
// [ Float randompitch ], [ String randomvolume ] )
char *name;
//float volume, minDist, pitch, randompitch;
soundChannel_t channel;
ubersound_t* sound;
name = COM_ParseExt( &text, qtrue );
sound = CG_GetUbersound(name);
if (!sound)
return;
token = COM_ParseExt( &text, qtrue );
if ( token[0] ) {
if ( !Q_strncmp( token, "auto", MAX_QPATH ) )
channel = CHAN_AUTO;
else if ( !Q_strncmp( token, "body", MAX_QPATH ) )
channel = CHAN_BODY;
else if ( !Q_strncmp( token, "item", MAX_QPATH ) )
channel = CHAN_ITEM;
else if ( !Q_strncmp( token, "weaponidle", MAX_QPATH ) )
channel = CHAN_WEAPONIDLE;
else if ( !Q_strncmp( token, "voice", MAX_QPATH ) )
channel = CHAN_VOICE;
else if ( !Q_strncmp( token, "local", MAX_QPATH ) )
channel = CHAN_LOCAL;
else if ( !Q_strncmp( token, "weapon", MAX_QPATH ) )
channel = CHAN_WEAPON;
else if ( !Q_strncmp( token, "dialog_secondary", MAX_QPATH ) )
channel = CHAN_DIALOG_SECONDARY;
else if ( !Q_strncmp( token, "dialog", MAX_QPATH ) )
channel = CHAN_DIALOG;
else if ( !Q_strncmp( token, "menu", MAX_QPATH ) )
channel = CHAN_MENU;
else {
//CG_Printf( "Event sound unrecognized channel %s for %s\n", token, name );
channel = sound->channel;
}
//token = COM_ParseExt( &text, qtrue );
} else channel = sound->channel;
/* if ( token[0] ) {
volume = atof( token );
token = COM_ParseExt( &text, qtrue );
}
minDist = atof( COM_ParseExt( &text, qtrue ) );
pitch = atof( COM_ParseExt( &text, qtrue ) );
randompitch = atof( COM_ParseExt( &text, qtrue ) );
randomvolume = COM_ParseExt( &text, qtrue );*/
#if 0
cgi.S_StartSound( ent->currentState.origin, -1, channel, sound->sfxHandle );
#else
cgi.S_StartSound( 0, ent->currentState.number, channel, sound->sfxHandle );
#endif
} else if(!strcmp(token,"stopsound")) {
// syntax: stopsound( String channelName )
// TODO
} else if(!strcmp(token,"loopsound")) {
// syntax: loopsound( String soundName, [ Float volume ],
// [ Float min_distance ], Float pitch )
char *name;
//float volume, minDist, pitch;
ubersound_t* snd;
name = COM_ParseExt( &text, qtrue );
snd = CG_GetUbersound(name);
/* volume = atof( COM_ParseExt( &text, qtrue ) );
minDist = atof( COM_ParseExt( &text, qtrue ) );
pitch = atof( COM_ParseExt( &text, qtrue ) );
*/
if (snd)
cgi.S_AddLoopingSound( ent->currentState.number, ent->currentState.origin, vec3_origin, snd->sfxHandle );
} else if(!strcmp(token,"stoploopsound")) {
cgi.S_StopLoopingSound( ent->currentState.number );
} else if(!strcmp(token,"stopaliaschannel")) {
token = COM_ParseExt( &text, qtrue );
} else if(!strcmp(token,"viewkick")) {
// viewkick( Float pitchmin, Float pitchmax,
// Float yawmin, Float yawmax, Float recenterspeed,
// String patters, Float pitchmax, Float yawmax )
// TODO
} else if(!strcmp(token,"cache")) {
token = COM_ParseExt( &text, qtrue );
//CG_Printf("Caching %s...\n",token);
cgi.R_RegisterModel(token);
} else if(!strcmp(token,"commanddelay")) {
token = COM_ParseExt( &text, qtrue );
f = atof(token);
CG_PostEvent(ent,text,f*1000);
} else if(!strcmp(token,"originspawn")) {
CG_ProcessSpawnEvent(ent,ent->lerpOrigin, ent->lerpAngles, text);
} else if(!strcmp(token,"tagspawn")) {
tiki = cgs.gameTIKIs[ent->currentState.modelindex];
if(!tiki) {
CG_Printf("'tagspawn' event cast on entity with null TIKI\n");
return;
}
// parse tagname
token = COM_ParseExt( &text, qtrue );
// find tag in tiki
boneName = cgi.TIKI_GetBoneNameIndex(token);
for(i = 0; i < tiki->numBones; i++) {
if(tiki->boneNames[i] == boneName) {
break;
}
}
if(i == tiki->numBones) {
CG_Printf("Cant find bone %s in tiki %s for 'tagspawn' event\n",token,tiki->name);
return;
}
if(ent->bones == 0) {
CG_Printf("'tagspawn' event cast on entity with null bones ptr\n");
return;
}
CG_CentBoneIndexLocal2World(i,ent,v,a);
CG_ProcessSpawnEvent(ent,v,a,text);
} else if(!strcmp(token,"tagspawnlinked")) {
return; // FIXME!
} else if(!strcmp(token,"tagdlight")) {
float r,g,b,intensity;
// tagdlight( String tagName, Float red, Float green,
// Float blue, Float intensity, Float life,
// [ String intvel ], [ String type1 ] )
/*
Spawn a dynamic light from the specified tag
The red,green,blue parms are the color of the light
The intensity is the radius of the light
type is the type of light to create (lensflare,viewlensflare,additive)
*/
tiki = cgs.gameTIKIs[ent->currentState.modelindex];
if(!tiki) {
CG_Printf("'tagdlight' event cast on entity with null TIKI\n");
return;
}
// parse tagname
token = COM_ParseExt( &text, qtrue );
// find tag in tiki
boneName = cgi.TIKI_GetBoneNameIndex(token);
for(i = 0; i < tiki->numBones; i++) {
if(tiki->boneNames[i] == boneName) {
break;
}
}
if(i == tiki->numBones) {
CG_Printf("Cant find bone %s in tiki %s for 'tagdlight' event\n",token,tiki->name);
return;
}
if(ent->bones == 0) {
CG_Printf("'tagdlight' event cast on entity with null bones ptr\n");
return;
}
token = COM_ParseExt( &text, qtrue );
r = atof(token);
token = COM_ParseExt( &text, qtrue );
g = atof(token);
token = COM_ParseExt( &text, qtrue );
b = atof(token);
token = COM_ParseExt( &text, qtrue );
intensity = atof(token);
// optional parameters
token = COM_ParseExt( &text, qtrue );
if(token[0]) {
// TODO
token = COM_ParseExt( &text, qtrue );
if(token[0]) {
if(!Q_stricmp(token,"addictive")) {
} else if(!Q_stricmp(token,"lensflare")) {
} else if(!Q_stricmp(token,"viewlensflare")) {
}
}
}
CG_CentBoneIndexLocal2World(i,ent,v,0);
cgi.R_AddLightToScene(v,intensity,r,g,b);
} else if(!strcmp(token,"sfx")) {
goto again;
} else if(!strcmp(token,"bodyfall")) {
} else if(!strcmp(token,"footstep")) {
int iRunning;
int iEquipment;
//footstep( String tag, String sRunning, [ Integer iEquipment ] )
// plays a footstep sound that is appropriate to the surface
// we are currently stepping on.
// sRunning should be set to run, walk, or ladder
tiki = cgs.gameTIKIs[ent->currentState.modelindex];
if(!tiki) {
CG_Printf("'footstep' event cast on entity with null TIKI\n");
return;
}
// parse tagname
token = COM_ParseExt( &text, qfalse );
strcpy(str,token);
/*
// find tag in tiki
boneName = cgi.TIKI_GetBoneNameIndex(token);
for(i = 0; i < tiki->numBones; i++) {
if(tiki->boneNames[i] == boneName) {
break;
}
}
if(i == tiki->numBones) {
CG_Printf("Cant find bone %s in tiki %s for 'footstep' event\n",token,tiki->name);
return;
}
if(ent->bones == 0) {
CG_Printf("'footstep' event cast on entity with null bones ptr\n");
return;
}
*/
// sRunning
token = COM_ParseExt( &text, qfalse );
if(!Q_stricmp(token,"run")) {
//iRunning = ??;
} else if(!Q_stricmp(token,"walk")) {
//iRunning = ??;
} else if(!Q_stricmp(token,"ladder")) {
//iRunning = ??;
} else if(!Q_stricmp(token,"R")) {
//iRunning = ??;
} else if(!Q_stricmp(token,"L")) {
//iRunning = ??;
} else {
//iRunning = ??;
CG_Printf("'footstep' event: unknown sRunning type %s\n",token);
}
// Integer iEquipment
token = COM_ParseExt( &text, qfalse );
iEquipment = 0;
if(token[0]) {
iEquipment = atoi(token);
}
iRunning = 0;
CG_Footstep(str,ent,/*0,*/iRunning,iEquipment);
} else if(!strcmp(token,"emitteroff")) {
} else if(!strcmp(token,"emitteron")) {
} else {
CG_Printf("CG_ProcessEventText: unknown event %s\n",token);
}
}
void CG_FreeEvent(event_t *ev) {
if ( !ev->prev ) {
CG_Error( "CG_FreeEvent: not active" );
}
// remove from the doubly linked active list
ev->prev->next = ev->next;
ev->next->prev = ev->prev;
// the free list is only singly linked
ev->next = cg_freeEvents;
cg_freeEvents = ev;
}
event_t *CG_AllocEvent() {
event_t *ev;
if ( !cg_freeEvents ) {
CG_Printf("CG_AllocEvent: no free events\n");
return 0;
}
ev = cg_freeEvents;
cg_freeEvents = cg_freeEvents->next;
memset( ev, 0, sizeof( *ev ) );
// link into the active list
ev->next = cg_pendingEvents.next;
ev->prev = &cg_pendingEvents;
cg_pendingEvents.next->prev = ev;
cg_pendingEvents.next = ev;
return ev;
}
void CG_PostEvent(centity_t *ent, const char *eventText, int eventDelay) {
event_t *ev;
ev = CG_AllocEvent();
if(!ev)
return; // failed to alloc an event
ev->ent = ent;
strcpy(ev->buffer,eventText);
ev->execTime = cg.time + eventDelay;
}
void CG_ProcessEvent(event_t *ev) {
CG_ProcessEventText(ev->ent,ev->buffer);
CG_FreeEvent(ev);
}
void CG_ProcessPendingEvents() {
event_t *ev, *next;
ev = cg_pendingEvents.next;
for ( ; ev != &cg_pendingEvents ; ev = next ) {
// grab next now, so if the event is freed we
// still have it
next = ev->next;
if ( cg.time >= ev->execTime ) {
// execute it
CG_ProcessEventText(ev->ent, ev->buffer);
// and free
CG_FreeEvent(ev);
continue;
}
}
}