summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile102
-rw-r--r--src/cgame/cg_animation.c111
-rw-r--r--src/cgame/cg_animmapobj.c227
-rw-r--r--src/cgame/cg_attachment.c404
-rw-r--r--src/cgame/cg_buildable.c1423
-rw-r--r--src/cgame/cg_consolecmds.c325
-rw-r--r--src/cgame/cg_draw.c3524
-rw-r--r--src/cgame/cg_drawtools.c378
-rw-r--r--src/cgame/cg_ents.c1256
-rw-r--r--src/cgame/cg_event.c1034
-rw-r--r--src/cgame/cg_local.h2072
-rw-r--r--src/cgame/cg_main.c1842
-rw-r--r--src/cgame/cg_marks.c289
-rw-r--r--src/cgame/cg_mem.c202
-rw-r--r--src/cgame/cg_particles.c2567
-rw-r--r--src/cgame/cg_players.c2565
-rw-r--r--src/cgame/cg_playerstate.c317
-rw-r--r--src/cgame/cg_predict.c878
-rw-r--r--src/cgame/cg_ptr.c81
-rw-r--r--src/cgame/cg_scanner.c365
-rw-r--r--src/cgame/cg_servercmds.c1016
-rw-r--r--src/cgame/cg_snapshot.c411
-rw-r--r--src/cgame/cg_syscalls.asm115
-rw-r--r--src/cgame/cg_syscalls.c570
-rw-r--r--src/cgame/cg_trails.c1500
-rw-r--r--src/cgame/cg_tutorial.c663
-rw-r--r--src/cgame/cg_view.c1340
-rw-r--r--src/cgame/cg_weapons.c1845
-rw-r--r--src/client/keycodes.h278
-rw-r--r--src/renderer/tr_types.h239
-rw-r--r--src/ui/menudef.h362
-rw-r--r--src/ui/ui_atoms.c519
-rw-r--r--src/ui/ui_gameinfo.c333
-rw-r--r--src/ui/ui_local.h1207
-rw-r--r--src/ui/ui_main.c6500
-rw-r--r--src/ui/ui_players.c1369
-rw-r--r--src/ui/ui_shared.c6075
-rw-r--r--src/ui/ui_shared.h454
-rw-r--r--src/ui/ui_syscalls.asm98
-rw-r--r--src/ui/ui_syscalls.c387
40 files changed, 45241 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index 5f0ee05..17e66fc 100644
--- a/Makefile
+++ b/Makefile
@@ -698,13 +698,17 @@ TARGETS =
ifneq ($(BUILD_GAME_SO),0)
TARGETS += \
- $(B)/base/game$(ARCH).$(SHLIBEXT)
+ $(B)/base/cgame$(ARCH).$(SHLIBEXT) \
+ $(B)/base/game$(ARCH).$(SHLIBEXT) \
+ $(B)/base/ui$(ARCH).$(SHLIBEXT)
endif
ifneq ($(BUILD_GAME_QVM),0)
ifneq ($(CROSS_COMPILING),1)
TARGETS += \
- $(B)/base/vm/game.qvm
+ $(B)/base/vm/cgame.qvm \
+ $(B)/base/vm/game.qvm \
+ $(B)/base/vm/ui.qvm
endif
endif
@@ -835,6 +839,56 @@ define DO_Q3LCC
endef
#############################################################################
+## TREMULOUS CGAME
+#############################################################################
+
+CGOBJ_ = \
+ $(B)/base/cgame/cg_main.o \
+ $(B)/base/game/bg_misc.o \
+ $(B)/base/game/bg_pmove.o \
+ $(B)/base/game/bg_slidemove.o \
+ $(B)/base/cgame/cg_consolecmds.o \
+ $(B)/base/cgame/cg_buildable.o \
+ $(B)/base/cgame/cg_animation.o \
+ $(B)/base/cgame/cg_animmapobj.o \
+ $(B)/base/cgame/cg_draw.o \
+ $(B)/base/cgame/cg_drawtools.o \
+ $(B)/base/cgame/cg_ents.o \
+ $(B)/base/cgame/cg_event.o \
+ $(B)/base/cgame/cg_marks.o \
+ $(B)/base/cgame/cg_players.o \
+ $(B)/base/cgame/cg_playerstate.o \
+ $(B)/base/cgame/cg_predict.o \
+ $(B)/base/cgame/cg_servercmds.o \
+ $(B)/base/cgame/cg_snapshot.o \
+ $(B)/base/cgame/cg_view.o \
+ $(B)/base/cgame/cg_weapons.o \
+ $(B)/base/cgame/cg_mem.o \
+ $(B)/base/cgame/cg_scanner.o \
+ $(B)/base/cgame/cg_attachment.o \
+ $(B)/base/cgame/cg_trails.o \
+ $(B)/base/cgame/cg_particles.o \
+ $(B)/base/cgame/cg_ptr.o \
+ $(B)/base/cgame/cg_tutorial.o \
+ $(B)/base/ui/ui_shared.o \
+ \
+ $(B)/base/qcommon/q_math.o \
+ $(B)/base/qcommon/q_shared.o
+
+CGOBJ = $(CGOBJ_) $(B)/base/cgame/cg_syscalls.o
+CGVMOBJ = $(CGOBJ_:%.o=%.asm) $(B)/base/game/bg_lib.asm
+
+$(B)/base/cgame$(ARCH).$(SHLIBEXT) : $(CGOBJ)
+ @echo "LD $@"
+ @$(CC) $(SHLIBLDFLAGS) -o $@ $(CGOBJ)
+
+$(B)/base/vm/cgame.qvm: $(CGVMOBJ) $(CGDIR)/cg_syscalls.asm
+ @echo "Q3ASM $@"
+ @$(Q3ASM) -o $@ $(CGVMOBJ) $(CGDIR)/cg_syscalls.asm
+
+
+
+#############################################################################
## TREMULOUS GAME
#############################################################################
@@ -880,16 +934,60 @@ $(B)/base/vm/game.qvm: $(GVMOBJ) $(GDIR)/g_syscalls.asm
@$(Q3ASM) -o $@ $(GVMOBJ) $(GDIR)/g_syscalls.asm
+
+#############################################################################
+## TREMULOUS UI
+#############################################################################
+
+UIOBJ_ = \
+ $(B)/base/ui/ui_main.o \
+ $(B)/base/ui/ui_atoms.o \
+ $(B)/base/ui/ui_players.o \
+ $(B)/base/ui/ui_shared.o \
+ $(B)/base/ui/ui_gameinfo.o \
+ \
+ $(B)/base/game/bg_misc.o \
+ $(B)/base/qcommon/q_math.o \
+ $(B)/base/qcommon/q_shared.o
+
+UIOBJ = $(UIOBJ_) $(B)/base/ui/ui_syscalls.o
+UIVMOBJ = $(UIOBJ_:%.o=%.asm) $(B)/base/game/bg_lib.asm
+
+$(B)/base/ui$(ARCH).$(SHLIBEXT) : $(UIOBJ)
+ @echo "LD $@"
+ @$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(UIOBJ)
+
+$(B)/base/vm/ui.qvm: $(UIVMOBJ) $(UIDIR)/ui_syscalls.asm
+ @echo "Q3ASM $@"
+ @$(Q3ASM) -o $@ $(UIVMOBJ) $(UIDIR)/ui_syscalls.asm
+
+
+
#############################################################################
## GAME MODULE RULES
#############################################################################
+$(B)/base/cgame/%.o: $(CGDIR)/%.c
+ $(DO_SHLIB_CC)
+
+$(B)/base/cgame/%.asm: $(CGDIR)/%.c
+ $(DO_Q3LCC)
+
+
$(B)/base/game/%.o: $(GDIR)/%.c
$(DO_SHLIB_CC)
$(B)/base/game/%.asm: $(GDIR)/%.c
$(DO_Q3LCC)
+
+$(B)/base/ui/%.o: $(UIDIR)/%.c
+ $(DO_SHLIB_CC)
+
+$(B)/base/ui/%.asm: $(UIDIR)/%.c
+ $(DO_Q3LCC)
+
+
$(B)/base/qcommon/%.o: $(CMDIR)/%.c
$(DO_SHLIB_CC)
diff --git a/src/cgame/cg_animation.c b/src/cgame/cg_animation.c
new file mode 100644
index 0000000..c370c53
--- /dev/null
+++ b/src/cgame/cg_animation.c
@@ -0,0 +1,111 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "cg_local.h"
+
+/*
+===============
+CG_RunLerpFrame
+
+Sets cg.snap, cg.oldFrame, and cg.backlerp
+cg.time should be between oldFrameTime and frameTime after exit
+===============
+*/
+void CG_RunLerpFrame( lerpFrame_t *lf )
+{
+ int f, numFrames;
+ animation_t *anim;
+
+ // debugging tool to get no animations
+ if( cg_animSpeed.integer == 0 )
+ {
+ lf->oldFrame = lf->frame = lf->backlerp = 0;
+ return;
+ }
+
+ // if we have passed the current frame, move it to
+ // oldFrame and calculate a new frame
+ if( cg.time >= lf->frameTime )
+ {
+ lf->oldFrame = lf->frame;
+ lf->oldFrameTime = lf->frameTime;
+
+ // get the next frame based on the animation
+ anim = lf->animation;
+ if( !anim->frameLerp )
+ return; // shouldn't happen
+
+ if( cg.time < lf->animationTime )
+ lf->frameTime = lf->animationTime; // initial lerp
+ else
+ lf->frameTime = lf->oldFrameTime + anim->frameLerp;
+
+ f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
+ numFrames = anim->numFrames;
+ if( anim->flipflop )
+ numFrames *= 2;
+
+ if( f >= numFrames )
+ {
+ f -= numFrames;
+ if( anim->loopFrames )
+ {
+ f %= anim->loopFrames;
+ f += anim->numFrames - anim->loopFrames;
+ }
+ else
+ {
+ f = numFrames - 1;
+ // the animation is stuck at the end, so it
+ // can immediately transition to another sequence
+ lf->frameTime = cg.time;
+ }
+ }
+
+ if( anim->reversed )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
+ else if( anim->flipflop && f >= anim->numFrames )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames );
+ else
+ lf->frame = anim->firstFrame + f;
+
+ if( cg.time > lf->frameTime )
+ {
+ lf->frameTime = cg.time;
+ if( cg_debugAnim.integer )
+ CG_Printf( "Clamp lf->frameTime\n" );
+ }
+ }
+
+ if( lf->frameTime > cg.time + 200 )
+ lf->frameTime = cg.time;
+
+ if( lf->oldFrameTime > cg.time )
+ lf->oldFrameTime = cg.time;
+
+ // calculate current lerp value
+ if( lf->frameTime == lf->oldFrameTime )
+ lf->backlerp = 0;
+ else
+ lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
+}
diff --git a/src/cgame/cg_animmapobj.c b/src/cgame/cg_animmapobj.c
new file mode 100644
index 0000000..2e80b7e
--- /dev/null
+++ b/src/cgame/cg_animmapobj.c
@@ -0,0 +1,227 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "cg_local.h"
+
+
+/*
+===============
+CG_DoorAnimation
+===============
+*/
+static void CG_DoorAnimation( centity_t *cent, int *old, int *now, float *backLerp )
+{
+ CG_RunLerpFrame( &cent->lerpFrame );
+
+ *old = cent->lerpFrame.oldFrame;
+ *now = cent->lerpFrame.frame;
+ *backLerp = cent->lerpFrame.backlerp;
+}
+
+
+/*
+===============
+CG_ModelDoor
+===============
+*/
+void CG_ModelDoor( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *es;
+ animation_t anim;
+ lerpFrame_t *lf = &cent->lerpFrame;
+
+ es = &cent->currentState;
+
+ if( !es->modelindex )
+ return;
+
+ //create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( cent->lerpOrigin, ent.oldorigin );
+ AnglesToAxis( cent->lerpAngles, ent.axis );
+
+ ent.renderfx = RF_NOSHADOW;
+
+ //add the door model
+ ent.skinNum = 0;
+ ent.hModel = cgs.gameModels[ es->modelindex ];
+
+ //scale the door
+ VectorScale( ent.axis[ 0 ], es->origin2[ 0 ], ent.axis[ 0 ] );
+ VectorScale( ent.axis[ 1 ], es->origin2[ 1 ], ent.axis[ 1 ] );
+ VectorScale( ent.axis[ 2 ], es->origin2[ 2 ], ent.axis[ 2 ] );
+ ent.nonNormalizedAxes = qtrue;
+
+ //setup animation
+ anim.firstFrame = es->powerups;
+ anim.numFrames = es->weapon;
+ anim.reversed = !es->legsAnim;
+ anim.flipflop = qfalse;
+ anim.loopFrames = 0;
+ anim.frameLerp = 1000 / es->torsoAnim;
+ anim.initialLerp = 1000 / es->torsoAnim;
+
+ //door changed state
+ if( es->legsAnim != cent->doorState )
+ {
+ lf->animationTime = lf->frameTime + anim.initialLerp;
+ cent->doorState = es->legsAnim;
+ }
+
+ lf->animation = &anim;
+
+ //run animation
+ CG_DoorAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp );
+
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+
+/*
+===============
+CG_AMOAnimation
+===============
+*/
+static void CG_AMOAnimation( centity_t *cent, int *old, int *now, float *backLerp )
+{
+ if( !( cent->currentState.eFlags & EF_MOVER_STOP ) || cent->animPlaying )
+ {
+ int delta = cg.time - cent->miscTime;
+
+ //hack to prevent "pausing" mucking up the lerping
+ if( delta > 900 )
+ {
+ cent->lerpFrame.oldFrameTime += delta;
+ cent->lerpFrame.frameTime += delta;
+ }
+
+ CG_RunLerpFrame( &cent->lerpFrame );
+ cent->miscTime = cg.time;
+ }
+
+ *old = cent->lerpFrame.oldFrame;
+ *now = cent->lerpFrame.frame;
+ *backLerp = cent->lerpFrame.backlerp;
+}
+
+
+/*
+==================
+CG_animMapObj
+==================
+*/
+void CG_AnimMapObj( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *es;
+ float scale;
+ animation_t anim;
+
+ es = &cent->currentState;
+
+ // if set to invisible, skip
+ if( !es->modelindex || ( es->eFlags & EF_NODRAW ) )
+ return;
+
+ memset( &ent, 0, sizeof( ent ) );
+
+ VectorCopy( es->angles, cent->lerpAngles );
+ AnglesToAxis( cent->lerpAngles, ent.axis );
+
+ ent.hModel = cgs.gameModels[ es->modelindex ];
+
+ VectorCopy( cent->lerpOrigin, ent.origin);
+ VectorCopy( cent->lerpOrigin, ent.oldorigin);
+
+ ent.nonNormalizedAxes = qfalse;
+
+ //scale the model
+ if( es->angles2[ 0 ] )
+ {
+ scale = es->angles2[ 0 ];
+ VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] );
+ VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] );
+ VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] );
+ ent.nonNormalizedAxes = qtrue;
+ }
+
+ //setup animation
+ anim.firstFrame = es->powerups;
+ anim.numFrames = es->weapon;
+ anim.reversed = qfalse;
+ anim.flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( anim.numFrames < 0 )
+ {
+ anim.numFrames = -anim.numFrames;
+ anim.reversed = qtrue;
+ }
+
+ anim.loopFrames = es->torsoAnim;
+
+ if( !es->legsAnim )
+ {
+ anim.frameLerp = 1000;
+ anim.initialLerp = 1000;
+ }
+ else
+ {
+ anim.frameLerp = 1000 / es->legsAnim;
+ anim.initialLerp = 1000 / es->legsAnim;
+ }
+
+ cent->lerpFrame.animation = &anim;
+
+ if( !anim.loopFrames )
+ {
+ // add one frame to allow the animation to play the last frame
+ // add another to get it to stop playing at the first frame
+ anim.numFrames += 2;
+
+ if( !cent->animInit )
+ {
+ cent->animInit = qtrue;
+ cent->animPlaying = !( cent->currentState.eFlags & EF_MOVER_STOP );
+ }
+ else
+ {
+ if( cent->animLastState !=
+ !( cent->currentState.eFlags & EF_MOVER_STOP ) )
+ {
+ cent->animPlaying = qtrue;
+ cent->lerpFrame.animationTime = cg.time;
+ cent->lerpFrame.frameTime = cg.time;
+ }
+ }
+ cent->animLastState = !( cent->currentState.eFlags & EF_MOVER_STOP );
+ }
+
+ //run animation
+ CG_AMOAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp );
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene(&ent);
+}
diff --git a/src/cgame/cg_attachment.c b/src/cgame/cg_attachment.c
new file mode 100644
index 0000000..0d3c8ea
--- /dev/null
+++ b/src/cgame/cg_attachment.c
@@ -0,0 +1,404 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_attachment.c -- an abstract attachment system
+
+#include "cg_local.h"
+
+/*
+===============
+CG_AttachmentPoint
+
+Return the attachment point
+===============
+*/
+qboolean CG_AttachmentPoint( attachment_t *a, vec3_t v )
+{
+ centity_t *cent;
+
+ if( !a )
+ return qfalse;
+
+ // if it all breaks, then use the last point we know was correct
+ VectorCopy( a->lastValidAttachmentPoint, v );
+
+ switch( a->type )
+ {
+ case AT_STATIC:
+ if( !a->staticValid )
+ return qfalse;
+
+ VectorCopy( a->origin, v );
+ break;
+
+ case AT_TAG:
+ if( !a->tagValid )
+ return qfalse;
+
+ AxisCopy( axisDefault, a->re.axis );
+ CG_PositionRotatedEntityOnTag( &a->re, &a->parent,
+ a->model, a->tagName );
+ VectorCopy( a->re.origin, v );
+ break;
+
+ case AT_CENT:
+ if( !a->centValid )
+ return qfalse;
+
+ if( a->centNum == cg.predictedPlayerState.clientNum )
+ {
+ // this is smoother if it's the local client
+ VectorCopy( cg.predictedPlayerState.origin, v );
+ }
+ else
+ {
+ cent = &cg_entities[ a->centNum ];
+ VectorCopy( cent->lerpOrigin, v );
+ }
+ break;
+
+ case AT_PARTICLE:
+ if( !a->particleValid )
+ return qfalse;
+
+ if( !a->particle->valid )
+ {
+ a->particleValid = qfalse;
+ return qfalse;
+ }
+ else
+ VectorCopy( a->particle->origin, v );
+ break;
+
+ default:
+ CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" );
+ break;
+ }
+
+ if( a->hasOffset )
+ VectorAdd( v, a->offset, v );
+
+ VectorCopy( v, a->lastValidAttachmentPoint );
+
+ return qtrue;
+}
+
+/*
+===============
+CG_AttachmentDir
+
+Return the attachment direction
+===============
+*/
+qboolean CG_AttachmentDir( attachment_t *a, vec3_t v )
+{
+ vec3_t forward;
+ centity_t *cent;
+
+ if( !a )
+ return qfalse;
+
+ switch( a->type )
+ {
+ case AT_STATIC:
+ return qfalse;
+ break;
+
+ case AT_TAG:
+ if( !a->tagValid )
+ return qfalse;
+
+ VectorCopy( a->re.axis[ 0 ], v );
+ break;
+
+ case AT_CENT:
+ if( !a->centValid )
+ return qfalse;
+
+ cent = &cg_entities[ a->centNum ];
+ AngleVectors( cent->lerpAngles, forward, NULL, NULL );
+ VectorCopy( forward, v );
+ break;
+
+ case AT_PARTICLE:
+ if( !a->particleValid )
+ return qfalse;
+
+ if( !a->particle->valid )
+ {
+ a->particleValid = qfalse;
+ return qfalse;
+ }
+ else
+ VectorCopy( a->particle->velocity, v );
+ break;
+
+ default:
+ CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" );
+ break;
+ }
+
+ VectorNormalize( v );
+ return qtrue;
+}
+
+/*
+===============
+CG_AttachmentAxis
+
+Return the attachment axis
+===============
+*/
+qboolean CG_AttachmentAxis( attachment_t *a, vec3_t axis[ 3 ] )
+{
+ centity_t *cent;
+
+ if( !a )
+ return qfalse;
+
+ switch( a->type )
+ {
+ case AT_STATIC:
+ return qfalse;
+ break;
+
+ case AT_TAG:
+ if( !a->tagValid )
+ return qfalse;
+
+ AxisCopy( a->re.axis, axis );
+ break;
+
+ case AT_CENT:
+ if( !a->centValid )
+ return qfalse;
+
+ cent = &cg_entities[ a->centNum ];
+ AnglesToAxis( cent->lerpAngles, axis );
+ break;
+
+ case AT_PARTICLE:
+ return qfalse;
+ break;
+
+ default:
+ CG_Printf( S_COLOR_RED "ERROR: Invalid attachmentType_t in attachment\n" );
+ break;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_AttachmentVelocity
+
+If the attachment can have velocity, return it
+===============
+*/
+qboolean CG_AttachmentVelocity( attachment_t *a, vec3_t v )
+{
+ if( !a )
+ return qfalse;
+
+ if( a->particleValid && a->particle->valid )
+ {
+ VectorCopy( a->particle->velocity, v );
+ return qtrue;
+ }
+ else if( a->centValid )
+ {
+ centity_t *cent = &cg_entities[ a->centNum ];
+
+ VectorCopy( cent->currentState.pos.trDelta, v );
+ return qtrue;
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_AttachmentCentNum
+
+If the attachment has a centNum, return it
+===============
+*/
+int CG_AttachmentCentNum( attachment_t *a )
+{
+ if( !a || !a->centValid )
+ return -1;
+
+ return a->centNum;
+}
+
+/*
+===============
+CG_Attached
+
+If the attachment is valid, return qtrue
+===============
+*/
+qboolean CG_Attached( attachment_t *a )
+{
+ if( !a )
+ return qfalse;
+
+ return a->attached;
+}
+
+/*
+===============
+CG_AttachToPoint
+
+Attach to a point in space
+===============
+*/
+void CG_AttachToPoint( attachment_t *a )
+{
+ if( !a || !a->staticValid )
+ return;
+
+ a->type = AT_STATIC;
+ a->attached = qtrue;
+}
+
+/*
+===============
+CG_AttachToCent
+
+Attach to a centity_t
+===============
+*/
+void CG_AttachToCent( attachment_t *a )
+{
+ if( !a || !a->centValid )
+ return;
+
+ a->type = AT_CENT;
+ a->attached = qtrue;
+}
+
+/*
+===============
+CG_AttachToTag
+
+Attach to a model tag
+===============
+*/
+void CG_AttachToTag( attachment_t *a )
+{
+ if( !a || !a->tagValid )
+ return;
+
+ a->type = AT_TAG;
+ a->attached = qtrue;
+}
+
+/*
+===============
+CG_AttachToParticle
+
+Attach to a particle
+===============
+*/
+void CG_AttachToParticle( attachment_t *a )
+{
+ if( !a || !a->particleValid )
+ return;
+
+ a->type = AT_PARTICLE;
+ a->attached = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentPoint
+===============
+*/
+void CG_SetAttachmentPoint( attachment_t *a, vec3_t v )
+{
+ if( !a )
+ return;
+
+ VectorCopy( v, a->origin );
+ a->staticValid = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentCent
+===============
+*/
+void CG_SetAttachmentCent( attachment_t *a, centity_t *cent )
+{
+ if( !a || !cent )
+ return;
+
+ a->centNum = cent->currentState.number;
+ a->centValid = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentTag
+===============
+*/
+void CG_SetAttachmentTag( attachment_t *a, refEntity_t parent,
+ qhandle_t model, char *tagName )
+{
+ if( !a )
+ return;
+
+ a->parent = parent;
+ a->model = model;
+ strncpy( a->tagName, tagName, MAX_STRING_CHARS );
+ a->tagValid = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentParticle
+===============
+*/
+void CG_SetAttachmentParticle( attachment_t *a, particle_t *p )
+{
+ if( !a )
+ return;
+
+ a->particle = p;
+ a->particleValid = qtrue;
+}
+
+/*
+===============
+CG_SetAttachmentOffset
+===============
+*/
+void CG_SetAttachmentOffset( attachment_t *a, vec3_t v )
+{
+ if( !a )
+ return;
+
+ VectorCopy( v, a->offset );
+ a->hasOffset = qtrue;
+}
diff --git a/src/cgame/cg_buildable.c b/src/cgame/cg_buildable.c
new file mode 100644
index 0000000..e7b4d85
--- /dev/null
+++ b/src/cgame/cg_buildable.c
@@ -0,0 +1,1423 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#include "cg_local.h"
+
+char *cg_buildableSoundNames[ MAX_BUILDABLE_ANIMATIONS ] =
+{
+ "construct1.wav",
+ "construct2.wav",
+ "idle1.wav",
+ "idle2.wav",
+ "idle3.wav",
+ "attack1.wav",
+ "attack2.wav",
+ "spawn1.wav",
+ "spawn2.wav",
+ "pain1.wav",
+ "pain2.wav",
+ "destroy1.wav",
+ "destroy2.wav",
+ "destroyed.wav"
+};
+
+static sfxHandle_t defaultAlienSounds[ MAX_BUILDABLE_ANIMATIONS ];
+static sfxHandle_t defaultHumanSounds[ MAX_BUILDABLE_ANIMATIONS ];
+
+/*
+===================
+CG_AlienBuildableExplosion
+
+Generated a bunch of gibs launching out from a location
+===================
+*/
+void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir )
+{
+ particleSystem_t *ps;
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.alienBuildableExplosion );
+
+ //particle system
+ ps = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDestroyedPS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentPoint( &ps->attachment, origin );
+ CG_SetParticleSystemNormal( ps, dir );
+ CG_AttachToPoint( &ps->attachment );
+ }
+}
+
+/*
+=================
+CG_HumanBuildableExplosion
+
+Called for human buildables as they are destroyed
+=================
+*/
+void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir )
+{
+ particleSystem_t *ps;
+
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.humanBuildableExplosion );
+
+ //particle system
+ ps = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDestroyedPS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentPoint( &ps->attachment, origin );
+ CG_SetParticleSystemNormal( ps, dir );
+ CG_AttachToPoint( &ps->attachment );
+ }
+}
+
+
+#define CREEP_SIZE 64.0f
+
+/*
+==================
+CG_Creep
+==================
+*/
+static void CG_Creep( centity_t *cent )
+{
+ int msec;
+ float size, frac;
+ trace_t tr;
+ vec3_t temp, origin;
+ int scaleUpTime = BG_FindBuildTimeForBuildable( cent->currentState.modelindex );
+ int time;
+
+ time = cent->currentState.time;
+
+ //should the creep be growing or receding?
+ if( time >= 0 )
+ {
+ msec = cg.time - time;
+ if( msec >= 0 && msec < scaleUpTime )
+ frac = (float)msec / scaleUpTime;
+ else
+ frac = 1.0f;
+ }
+ else if( time < 0 )
+ {
+ msec = cg.time + time;
+ if( msec >= 0 && msec < CREEP_SCALEDOWN_TIME )
+ frac = 1.0f - ( (float)msec / CREEP_SCALEDOWN_TIME );
+ else
+ frac = 0.0f;
+ }
+
+ VectorCopy( cent->currentState.origin2, temp );
+ VectorScale( temp, -4096, temp );
+ VectorAdd( temp, cent->lerpOrigin, temp );
+
+ CG_Trace( &tr, cent->lerpOrigin, NULL, NULL, temp, cent->currentState.number, MASK_PLAYERSOLID );
+
+ VectorCopy( tr.endpos, origin );
+
+ size = CREEP_SIZE * frac;
+
+ if( size > 0.0f )
+ CG_ImpactMark( cgs.media.creepShader, origin, cent->currentState.origin2,
+ 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue );
+}
+
+/*
+======================
+CG_ParseBuildableAnimationFile
+
+Read a configuration file containing animation counts and rates
+models/buildables/hivemind/animation.cfg, etc
+======================
+*/
+static qboolean CG_ParseBuildableAnimationFile( const char *filename, buildable_t buildable )
+{
+ char *text_p;
+ int len;
+ int i;
+ char *token;
+ float fps;
+ char text[ 20000 ];
+ fileHandle_t f;
+ animation_t *animations;
+
+ animations = cg_buildables[ buildable ].animations;
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( "File %s too long\n", filename );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read information for each frame
+ for( i = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ )
+ {
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ animations[ i ].firstFrame = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ animations[ i ].numFrames = atoi( token );
+ animations[ i ].reversed = qfalse;
+ animations[ i ].flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( animations[ i ].numFrames < 0 )
+ {
+ animations[ i ].numFrames = -animations[ i ].numFrames;
+ animations[ i ].reversed = qtrue;
+ }
+
+ token = COM_Parse( &text_p );
+ if ( !*token )
+ break;
+
+ animations[i].loopFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ fps = atof( token );
+ if( fps == 0 )
+ fps = 1;
+
+ animations[ i ].frameLerp = 1000 / fps;
+ animations[ i ].initialLerp = 1000 / fps;
+ }
+
+ if( i != MAX_BUILDABLE_ANIMATIONS )
+ {
+ CG_Printf( "Error parsing animation file: %s\n", filename );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+======================
+CG_ParseBuildableSoundFile
+
+Read a configuration file containing sound properties
+sound/buildables/hivemind/sound.cfg, etc
+======================
+*/
+static qboolean CG_ParseBuildableSoundFile( const char *filename, buildable_t buildable )
+{
+ char *text_p;
+ int len;
+ int i;
+ char *token;
+ char text[ 20000 ];
+ fileHandle_t f;
+ sound_t *sounds;
+
+ sounds = cg_buildables[ buildable ].sounds;
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( len <= 0 )
+ return qfalse;
+
+ if ( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( "File %s too long\n", filename );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read information for each frame
+ for( i = BANIM_NONE + 1; i < MAX_BUILDABLE_ANIMATIONS; i++ )
+ {
+
+ token = COM_Parse( &text_p );
+ if ( !*token )
+ break;
+
+ sounds[ i ].enabled = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if ( !*token )
+ break;
+
+ sounds[ i ].looped = atoi( token );
+
+ }
+
+ if( i != MAX_BUILDABLE_ANIMATIONS )
+ {
+ CG_Printf( "Error parsing sound file: %s\n", filename );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+/*
+===============
+CG_InitBuildables
+
+Initialises the animation db
+===============
+*/
+void CG_InitBuildables( void )
+{
+ char filename[ MAX_QPATH ];
+ char soundfile[ MAX_QPATH ];
+ char *buildableName;
+ char *modelFile;
+ int i;
+ int j;
+ fileHandle_t f;
+
+ memset( cg_buildables, 0, sizeof( cg_buildables ) );
+
+ //default sounds
+ for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ )
+ {
+ strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] );
+
+ Com_sprintf( filename, sizeof( filename ), "sound/buildables/alien/%s", soundfile );
+ defaultAlienSounds[ j ] = trap_S_RegisterSound( filename, qfalse );
+
+ Com_sprintf( filename, sizeof( filename ), "sound/buildables/human/%s", soundfile );
+ defaultHumanSounds[ j ] = trap_S_RegisterSound( filename, qfalse );
+ }
+
+ cg.buildablesFraction = 0.0f;
+
+ for( i = BA_NONE + 1; i < BA_NUM_BUILDABLES; i++ )
+ {
+ buildableName = BG_FindNameForBuildable( i );
+
+ //animation.cfg
+ Com_sprintf( filename, sizeof( filename ), "models/buildables/%s/animation.cfg", buildableName );
+ if ( !CG_ParseBuildableAnimationFile( filename, i ) )
+ Com_Printf( S_COLOR_YELLOW "WARNING: failed to load animation file %s\n", filename );
+
+ //sound.cfg
+ Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/sound.cfg", buildableName );
+ if ( !CG_ParseBuildableSoundFile( filename, i ) )
+ Com_Printf( S_COLOR_YELLOW "WARNING: failed to load sound file %s\n", filename );
+
+ //models
+ for( j = 0; j <= 3; j++ )
+ {
+ if( ( modelFile = BG_FindModelsForBuildable( i, j ) ) )
+ cg_buildables[ i ].models[ j ] = trap_R_RegisterModel( modelFile );
+ }
+
+ //sounds
+ for( j = BANIM_NONE + 1; j < MAX_BUILDABLE_ANIMATIONS; j++ )
+ {
+ strcpy( soundfile, cg_buildableSoundNames[ j - 1 ] );
+ Com_sprintf( filename, sizeof( filename ), "sound/buildables/%s/%s", buildableName, soundfile );
+
+ if( cg_buildables[ i ].sounds[ j ].enabled )
+ {
+ if( trap_FS_FOpenFile( filename, &f, FS_READ ) > 0 )
+ {
+ //file exists so close it
+ trap_FS_FCloseFile( f );
+
+ cg_buildables[ i ].sounds[ j ].sound = trap_S_RegisterSound( filename, qfalse );
+ }
+ else
+ {
+ //file doesn't exist - use default
+ if( BG_FindTeamForBuildable( i ) == BIT_ALIENS )
+ cg_buildables[ i ].sounds[ j ].sound = defaultAlienSounds[ j ];
+ else
+ cg_buildables[ i ].sounds[ j ].sound = defaultHumanSounds[ j ];
+ }
+ }
+ }
+
+ cg.buildablesFraction = (float)i / (float)( BA_NUM_BUILDABLES - 1 );
+ trap_UpdateScreen( );
+ }
+
+ cgs.media.teslaZapTS = CG_RegisterTrailSystem( "models/buildables/tesla/zap" );
+}
+
+/*
+===============
+CG_SetBuildableLerpFrameAnimation
+
+may include ANIM_TOGGLEBIT
+===============
+*/
+static void CG_SetBuildableLerpFrameAnimation( buildable_t buildable, lerpFrame_t *lf, int newAnimation )
+{
+ animation_t *anim;
+
+ lf->animationNumber = newAnimation;
+
+ if( newAnimation < 0 || newAnimation >= MAX_BUILDABLE_ANIMATIONS )
+ CG_Error( "Bad animation number: %i", newAnimation );
+
+ anim = &cg_buildables[ buildable ].animations[ newAnimation ];
+
+ //this item has just spawned so lf->frameTime will be zero
+ if( !lf->animation )
+ lf->frameTime = cg.time + 1000; //1 sec delay before starting the spawn anim
+
+ lf->animation = anim;
+ lf->animationTime = lf->frameTime + anim->initialLerp;
+
+ if( cg_debugAnim.integer )
+ CG_Printf( "Anim: %i\n", newAnimation );
+}
+
+/*
+===============
+CG_RunBuildableLerpFrame
+
+Sets cg.snap, cg.oldFrame, and cg.backlerp
+cg.time should be between oldFrameTime and frameTime after exit
+===============
+*/
+static void CG_RunBuildableLerpFrame( centity_t *cent )
+{
+ int f, numFrames;
+ buildable_t buildable = cent->currentState.modelindex;
+ lerpFrame_t *lf = &cent->lerpFrame;
+ animation_t *anim;
+ buildableAnimNumber_t newAnimation = cent->buildableAnim & ~( ANIM_TOGGLEBIT|ANIM_FORCEBIT );
+
+ // debugging tool to get no animations
+ if( cg_animSpeed.integer == 0 )
+ {
+ lf->oldFrame = lf->frame = lf->backlerp = 0;
+ return;
+ }
+
+ // see if the animation sequence is switching
+ if( newAnimation != lf->animationNumber || !lf->animation )
+ {
+ if( cg_debugRandom.integer )
+ CG_Printf( "newAnimation: %d lf->animationNumber: %d lf->animation: %d\n",
+ newAnimation, lf->animationNumber, lf->animation );
+
+ CG_SetBuildableLerpFrameAnimation( buildable, lf, newAnimation );
+
+ if( !cg_buildables[ buildable ].sounds[ newAnimation ].looped &&
+ cg_buildables[ buildable ].sounds[ newAnimation ].enabled )
+ {
+ if( cg_debugRandom.integer )
+ CG_Printf( "Sound for animation %d for a %s\n",
+ newAnimation, BG_FindHumanNameForBuildable( buildable ) );
+
+ trap_S_StartSound( cent->lerpOrigin, cent->currentState.number, CHAN_AUTO,
+ cg_buildables[ buildable ].sounds[ newAnimation ].sound );
+ }
+ }
+
+ if( cg_buildables[ buildable ].sounds[ lf->animationNumber ].looped &&
+ cg_buildables[ buildable ].sounds[ lf->animationNumber ].enabled )
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
+ cg_buildables[ buildable ].sounds[ lf->animationNumber ].sound );
+
+ // if we have passed the current frame, move it to
+ // oldFrame and calculate a new frame
+ if( cg.time >= lf->frameTime )
+ {
+ lf->oldFrame = lf->frame;
+ lf->oldFrameTime = lf->frameTime;
+
+ // get the next frame based on the animation
+ anim = lf->animation;
+ if( !anim->frameLerp )
+ return; // shouldn't happen
+
+ if ( cg.time < lf->animationTime )
+ lf->frameTime = lf->animationTime; // initial lerp
+ else
+ lf->frameTime = lf->oldFrameTime + anim->frameLerp;
+
+ f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
+ numFrames = anim->numFrames;
+ if(anim->flipflop)
+ numFrames *= 2;
+
+ if( f >= numFrames )
+ {
+ f -= numFrames;
+ if( anim->loopFrames )
+ {
+ f %= anim->loopFrames;
+ f += anim->numFrames - anim->loopFrames;
+ }
+ else
+ {
+ f = numFrames - 1;
+ // the animation is stuck at the end, so it
+ // can immediately transition to another sequence
+ lf->frameTime = cg.time;
+ cent->buildableAnim = cent->currentState.torsoAnim;
+ }
+ }
+
+ if( anim->reversed )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
+ else if( anim->flipflop && f >= anim->numFrames )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames );
+ else
+ lf->frame = anim->firstFrame + f;
+
+ if( cg.time > lf->frameTime )
+ {
+ lf->frameTime = cg.time;
+ if( cg_debugAnim.integer )
+ CG_Printf( "Clamp lf->frameTime\n");
+ }
+ }
+
+ if( lf->frameTime > cg.time + 200 )
+ lf->frameTime = cg.time;
+
+ if( lf->oldFrameTime > cg.time )
+ lf->oldFrameTime = cg.time;
+
+ // calculate current lerp value
+ if( lf->frameTime == lf->oldFrameTime )
+ lf->backlerp = 0;
+ else
+ lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
+}
+
+/*
+===============
+CG_BuildableAnimation
+===============
+*/
+static void CG_BuildableAnimation( centity_t *cent, int *old, int *now, float *backLerp )
+{
+ entityState_t *es = &cent->currentState;
+
+ //if no animation is set default to idle anim
+ if( cent->buildableAnim == BANIM_NONE )
+ cent->buildableAnim = es->torsoAnim;
+
+ //display the first frame of the construction anim if not yet spawned
+ if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) )
+ {
+ animation_t *anim = &cg_buildables[ es->modelindex ].animations[ BANIM_CONSTRUCT1 ];
+
+ //so that when animation starts for real it has sensible numbers
+ cent->lerpFrame.oldFrameTime =
+ cent->lerpFrame.frameTime =
+ cent->lerpFrame.animationTime =
+ cg.time;
+
+ *old = cent->lerpFrame.oldFrame = anim->firstFrame;
+ *now = cent->lerpFrame.frame = anim->firstFrame;
+ *backLerp = cent->lerpFrame.backlerp = 0.0f;
+
+ //ensure that an animation is triggered once the buildable has spawned
+ cent->oldBuildableAnim = BANIM_NONE;
+ }
+ else
+ {
+ if( ( cent->oldBuildableAnim ^ es->legsAnim ) & ANIM_TOGGLEBIT )
+ {
+ if( cg_debugAnim.integer )
+ CG_Printf( "%d->%d l:%d t:%d %s(%d)\n",
+ cent->oldBuildableAnim, cent->buildableAnim,
+ es->legsAnim, es->torsoAnim,
+ BG_FindHumanNameForBuildable( es->modelindex ), es->number );
+
+ if( cent->buildableAnim == es->torsoAnim || es->legsAnim & ANIM_FORCEBIT )
+ cent->buildableAnim = cent->oldBuildableAnim = es->legsAnim;
+ else
+ cent->buildableAnim = cent->oldBuildableAnim = es->torsoAnim;
+ }
+
+ CG_RunBuildableLerpFrame( cent );
+
+ *old = cent->lerpFrame.oldFrame;
+ *now = cent->lerpFrame.frame;
+ *backLerp = cent->lerpFrame.backlerp;
+ }
+}
+
+#define TRACE_DEPTH 64.0f
+
+/*
+===============
+CG_PositionAndOrientateBuildable
+===============
+*/
+static void CG_PositionAndOrientateBuildable( const vec3_t angles, const vec3_t inOrigin,
+ const vec3_t normal, const int skipNumber,
+ const vec3_t mins, const vec3_t maxs,
+ vec3_t outAxis[ 3 ], vec3_t outOrigin )
+{
+ vec3_t forward, start, end;
+ trace_t tr;
+
+ AngleVectors( angles, forward, NULL, NULL );
+ VectorCopy( normal, outAxis[ 2 ] );
+ ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] );
+
+ if( !VectorNormalize( outAxis[ 0 ] ) )
+ {
+ AngleVectors( angles, NULL, NULL, forward );
+ ProjectPointOnPlane( outAxis[ 0 ], forward, outAxis[ 2 ] );
+ VectorNormalize( outAxis[ 0 ] );
+ }
+
+ CrossProduct( outAxis[ 0 ], outAxis[ 2 ], outAxis[ 1 ] );
+ outAxis[ 1 ][ 0 ] = -outAxis[ 1 ][ 0 ];
+ outAxis[ 1 ][ 1 ] = -outAxis[ 1 ][ 1 ];
+ outAxis[ 1 ][ 2 ] = -outAxis[ 1 ][ 2 ];
+
+ VectorMA( inOrigin, -TRACE_DEPTH, normal, end );
+ VectorMA( inOrigin, 1.0f, normal, start );
+ CG_CapTrace( &tr, start, mins, maxs, end, skipNumber, MASK_PLAYERSOLID );
+
+ if( tr.fraction == 1.0f )
+ {
+ //erm we missed completely - try again with a box trace
+ CG_Trace( &tr, start, mins, maxs, end, skipNumber, MASK_PLAYERSOLID );
+ }
+
+ VectorMA( inOrigin, tr.fraction * -TRACE_DEPTH, normal, outOrigin );
+}
+
+/*
+==================
+CG_GhostBuildable
+==================
+*/
+void CG_GhostBuildable( buildable_t buildable )
+{
+ refEntity_t ent;
+ playerState_t *ps;
+ vec3_t angles, entity_origin;
+ vec3_t mins, maxs;
+ trace_t tr;
+ float scale;
+
+ ps = &cg.predictedPlayerState;
+
+ memset( &ent, 0, sizeof( ent ) );
+
+ BG_FindBBoxForBuildable( buildable, mins, maxs );
+
+ BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, CG_Trace, entity_origin, angles, &tr );
+
+ CG_PositionAndOrientateBuildable( ps->viewangles, entity_origin, tr.plane.normal, ps->clientNum,
+ mins, maxs, ent.axis, ent.origin );
+
+ //offset on the Z axis if required
+ VectorMA( ent.origin, BG_FindZOffsetForBuildable( buildable ), tr.plane.normal, ent.origin );
+
+ VectorCopy( ent.origin, ent.lightingOrigin );
+ VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all
+
+ ent.hModel = cg_buildables[ buildable ].models[ 0 ];
+
+ if( ps->stats[ STAT_BUILDABLE ] & SB_VALID_TOGGLEBIT )
+ ent.customShader = cgs.media.greenBuildShader;
+ else
+ ent.customShader = cgs.media.redBuildShader;
+
+ //rescale the model
+ scale = BG_FindModelScaleForBuildable( buildable );
+
+ if( scale != 1.0f )
+ {
+ VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] );
+ VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] );
+ VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] );
+
+ ent.nonNormalizedAxes = qtrue;
+ }
+ else
+ ent.nonNormalizedAxes = qfalse;
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+/*
+==================
+CG_BuildableParticleEffects
+==================
+*/
+static void CG_BuildableParticleEffects( centity_t *cent )
+{
+ entityState_t *es = &cent->currentState;
+ buildableTeam_t team = BG_FindTeamForBuildable( es->modelindex );
+ int health = es->generic1 & B_HEALTH_MASK;
+ float healthFrac = (float)health / B_HEALTH_MASK;
+
+ if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) )
+ return;
+
+ if( team == BIT_HUMANS )
+ {
+ if( healthFrac < 0.33f && !CG_IsParticleSystemValid( &cent->buildablePS ) )
+ {
+ cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.humanBuildableDamagedPS );
+
+ if( CG_IsParticleSystemValid( &cent->buildablePS ) )
+ {
+ CG_SetAttachmentCent( &cent->buildablePS->attachment, cent );
+ CG_AttachToCent( &cent->buildablePS->attachment );
+ }
+ }
+ else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( &cent->buildablePS ) )
+ CG_DestroyParticleSystem( &cent->buildablePS );
+ }
+ else if( team == BIT_ALIENS )
+ {
+ if( healthFrac < 0.33f && !CG_IsParticleSystemValid( &cent->buildablePS ) )
+ {
+ cent->buildablePS = CG_SpawnNewParticleSystem( cgs.media.alienBuildableDamagedPS );
+
+ if( CG_IsParticleSystemValid( &cent->buildablePS ) )
+ {
+ CG_SetAttachmentCent( &cent->buildablePS->attachment, cent );
+ CG_SetParticleSystemNormal( cent->buildablePS, es->origin2 );
+ CG_AttachToCent( &cent->buildablePS->attachment );
+ }
+ }
+ else if( healthFrac >= 0.33f && CG_IsParticleSystemValid( &cent->buildablePS ) )
+ CG_DestroyParticleSystem( &cent->buildablePS );
+ }
+}
+
+
+void CG_BuildableStatusParse( const char *filename, buildStat_t *bs )
+{
+ pc_token_t token;
+ int handle;
+ const char *s;
+ int i;
+ float f;
+ vec4_t c;
+
+ handle = trap_Parse_LoadSource( filename );
+ if( !handle )
+ return;
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+ if( !Q_stricmp( token.string, "frameShader" ) )
+ {
+ if( PC_String_Parse( handle, &s ) )
+ bs->frameShader = trap_R_RegisterShader( s );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "overlayShader" ) )
+ {
+ if( PC_String_Parse( handle, &s ) )
+ bs->overlayShader = trap_R_RegisterShader( s );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "noPowerShader" ) )
+ {
+ if( PC_String_Parse( handle, &s ) )
+ bs->noPowerShader = trap_R_RegisterShader( s );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "markedShader" ) )
+ {
+ if( PC_String_Parse( handle, &s ) )
+ bs->markedShader = trap_R_RegisterShader( s );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthSevereColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthSevereColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthHighColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthHighColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthElevatedColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthElevatedColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthGuardedColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthGuardedColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthLowColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->healthLowColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "foreColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->foreColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "backColor" ) )
+ {
+ if( PC_Color_Parse( handle, &c ) )
+ Vector4Copy( c, bs->backColor );
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "frameHeight" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->frameHeight = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "frameWidth" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->frameWidth = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "healthPadding" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->healthPadding = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "overlayHeight" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->overlayHeight = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "overlayWidth" ) )
+ {
+ if( PC_Int_Parse( handle, &i ) )
+ bs->overlayWidth = i;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "verticalMargin" ) )
+ {
+ if( PC_Float_Parse( handle, &f ) )
+ bs->verticalMargin = f;
+ continue;
+ }
+ else if( !Q_stricmp( token.string, "horizontalMargin" ) )
+ {
+ if( PC_Float_Parse( handle, &f ) )
+ bs->horizontalMargin = f;
+ continue;
+ }
+ else
+ {
+ Com_Printf("CG_BuildableStatusParse: unknown token %s in %s\n",
+ token.string, filename );
+ bs->loaded = qfalse;
+ return;
+ }
+ }
+ bs->loaded = qtrue;
+}
+
+#define STATUS_FADE_TIME 200
+#define STATUS_MAX_VIEW_DIST 900.0f
+#define STATUS_PEEK_DIST 20
+/*
+==================
+CG_BuildableStatusDisplay
+==================
+*/
+static void CG_BuildableStatusDisplay( centity_t *cent )
+{
+ entityState_t *es = &cent->currentState;
+ vec3_t origin;
+ float healthScale;
+ int health;
+ float x, y;
+ vec4_t color;
+ qboolean powered, marked;
+ trace_t tr;
+ float d;
+ buildStat_t *bs;
+ int i, j;
+ int entNum;
+ vec3_t trOrigin;
+ vec3_t right;
+ qboolean visible = qfalse;
+ vec3_t mins, maxs;
+ entityState_t *hit;
+
+ if( BG_FindTeamForBuildable( es->modelindex ) == BIT_ALIENS )
+ bs = &cgs.alienBuildStat;
+ else
+ bs = &cgs.humanBuildStat;
+
+ if( !bs->loaded )
+ return;
+
+ d = Distance( cent->lerpOrigin, cg.refdef.vieworg );
+ if( d > STATUS_MAX_VIEW_DIST )
+ return;
+
+ Vector4Copy( bs->foreColor, color );
+
+ // trace for center point
+ BG_FindBBoxForBuildable( es->modelindex, mins, maxs );
+
+ VectorCopy( cent->lerpOrigin, origin );
+
+ // center point
+ origin[ 2 ] += mins[ 2 ];
+ origin[ 2 ] += ( abs( mins[ 2 ] ) + abs( maxs[ 2 ] ) ) / 2;
+
+ entNum = cg.predictedPlayerState.clientNum;
+
+ // if first try fails, step left, step right
+ for( j = 0; j < 3; j++ )
+ {
+ VectorCopy( cg.refdef.vieworg, trOrigin );
+ switch( j )
+ {
+ case 1:
+ // step right
+ AngleVectors( cg.refdefViewAngles, NULL, right, NULL );
+ VectorMA( trOrigin, STATUS_PEEK_DIST, right, trOrigin );
+ break;
+ case 2:
+ // step left
+ AngleVectors( cg.refdefViewAngles, NULL, right, NULL );
+ VectorMA( trOrigin, -STATUS_PEEK_DIST, right, trOrigin );
+ break;
+ default:
+ break;
+ }
+ // look through up to 3 players and/or transparent buildables
+ for( i = 0; i < 3; i++ )
+ {
+ CG_Trace( &tr, trOrigin, NULL, NULL, origin, entNum, MASK_SHOT );
+ if( tr.entityNum == cent->currentState.number )
+ {
+ visible = qtrue;
+ break;
+ }
+
+ if( tr.entityNum == ENTITYNUM_WORLD )
+ break;
+
+ hit = &cg_entities[ tr.entityNum ].currentState;
+
+ if( tr.entityNum < MAX_CLIENTS || ( hit->eType == ET_BUILDABLE &&
+ ( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) ||
+ BG_FindTransparentTestForBuildable( hit->modelindex ) ) ) )
+ {
+ entNum = tr.entityNum;
+ VectorCopy( tr.endpos, trOrigin );
+ }
+ else
+ break;
+ }
+ }
+ // hack to make the kit obscure view
+ if( cg_drawGun.integer && visible &&
+ cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS &&
+ CG_WorldToScreen( origin, &x, &y ) )
+ {
+ if( x > 450 && y > 290 )
+ visible = qfalse;
+ }
+
+ if( !visible && cent->buildableStatus.visible )
+ {
+ cent->buildableStatus.visible = qfalse;
+ cent->buildableStatus.lastTime = cg.time;
+ }
+ else if( visible && !cent->buildableStatus.visible )
+ {
+ cent->buildableStatus.visible = qtrue;
+ cent->buildableStatus.lastTime = cg.time;
+ }
+
+ // Fade up
+ if( cent->buildableStatus.visible )
+ {
+ if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time )
+ color[ 3 ] = (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME;
+ }
+
+ // Fade down
+ if( !cent->buildableStatus.visible )
+ {
+ if( cent->buildableStatus.lastTime + STATUS_FADE_TIME > cg.time )
+ color[ 3 ] = 1.0f - (float)( cg.time - cent->buildableStatus.lastTime ) / STATUS_FADE_TIME;
+ else
+ return;
+ }
+
+ health = es->generic1 & B_HEALTH_MASK;
+ healthScale = (float)health / B_HEALTH_MASK;
+
+ if( health > 0 && healthScale < 0.01f )
+ healthScale = 0.01f;
+ else if( healthScale < 0.0f )
+ healthScale = 0.0f;
+ else if( healthScale > 1.0f )
+ healthScale = 1.0f;
+
+ if( CG_WorldToScreen( origin, &x, &y ) )
+ {
+ float picH = bs->frameHeight;
+ float picW = bs->frameWidth;
+ float picX = x;
+ float picY = y;
+ float scale;
+ float subH, subY;
+ vec4_t frameColor;
+
+ // this is fudged to get the width/height in the cfg to be more realistic
+ scale = ( picH / d ) * 3;
+
+ powered = es->generic1 & B_POWERED_TOGGLEBIT;
+ marked = es->generic1 & B_MARKED_TOGGLEBIT;
+
+ picH *= scale;
+ picW *= scale;
+ picX -= ( picW * 0.5f );
+ picY -= ( picH * 0.5f );
+
+ // sub-elements such as icons and number
+ subH = picH - ( picH * bs->verticalMargin );
+ subY = picY + ( picH * 0.5f ) - ( subH * 0.5f );
+
+ if( bs->frameShader )
+ {
+ Vector4Copy( bs->backColor, frameColor );
+ frameColor[ 3 ] = color[ 3 ];
+ trap_R_SetColor( frameColor );
+ CG_DrawPic( picX, picY, picW, picH, bs->frameShader );
+ trap_R_SetColor( NULL );
+ }
+
+ if( health > 0 )
+ {
+ float hX, hY, hW, hH;
+ vec4_t healthColor;
+
+ hX = picX + ( bs->healthPadding * scale );
+ hY = picY + ( bs->healthPadding * scale );
+ hH = picH - ( bs->healthPadding * 2.0f * scale );
+ hW = picW * healthScale - ( bs->healthPadding * 2.0f * scale );
+
+ if( healthScale == 1.0f )
+ Vector4Copy( bs->healthLowColor, healthColor );
+ else if( healthScale >= 0.75f )
+ Vector4Copy( bs->healthGuardedColor, healthColor );
+ else if( healthScale >= 0.50f )
+ Vector4Copy( bs->healthElevatedColor, healthColor );
+ else if( healthScale >= 0.25f )
+ Vector4Copy( bs->healthHighColor, healthColor );
+ else
+ Vector4Copy( bs->healthSevereColor, healthColor );
+
+ healthColor[ 3 ] = color[ 3 ];
+ trap_R_SetColor( healthColor );
+
+ CG_DrawPic( hX, hY, hW, hH, cgs.media.whiteShader );
+ trap_R_SetColor( NULL );
+ }
+
+ if( bs->overlayShader )
+ {
+ float oW = bs->overlayWidth;
+ float oH = bs->overlayHeight;
+ float oX = x;
+ float oY = y;
+
+ oH *= scale;
+ oW *= scale;
+ oX -= ( oW * 0.5f );
+ oY -= ( oH * 0.5f );
+
+ trap_R_SetColor( frameColor );
+ CG_DrawPic( oX, oY, oW, oH, bs->overlayShader );
+ trap_R_SetColor( NULL );
+ }
+
+ trap_R_SetColor( color );
+ if( !powered )
+ {
+ float pX;
+
+ pX = picX + ( subH * bs->horizontalMargin );
+ CG_DrawPic( pX, subY, subH, subH, bs->noPowerShader );
+ }
+
+ if( marked )
+ {
+ float mX;
+
+ mX = picX + picW - ( subH * bs->horizontalMargin ) - subH;
+ CG_DrawPic( mX, subY, subH, subH, bs->markedShader );
+ }
+
+ {
+ float nX;
+ int healthMax;
+ int healthPoints;
+
+ healthMax = BG_FindHealthForBuildable( es->modelindex );
+ healthPoints = (int)( healthScale * healthMax );
+ if( health > 0 && healthPoints < 1 )
+ healthPoints = 1;
+ nX = picX + ( picW * 0.5f ) - 2.0f - ( ( subH * 4 ) * 0.5f );
+
+ if( healthPoints > 999 )
+ nX -= 0.0f;
+ else if( healthPoints > 99 )
+ nX -= subH * 0.5f;
+ else if( healthPoints > 9 )
+ nX -= subH * 1.0f;
+ else
+ nX -= subH * 1.5f;
+
+ CG_DrawField( nX, subY, 4, subH, subH, healthPoints );
+ }
+ trap_R_SetColor( NULL );
+ }
+}
+
+static int QDECL CG_SortDistance( const void *a, const void *b )
+{
+ centity_t *aent, *bent;
+ float adist, bdist;
+
+ aent = &cg_entities[ *(int *)a ];
+ bent = &cg_entities[ *(int *)b ];
+ adist = Distance( cg.refdef.vieworg, aent->lerpOrigin );
+ bdist = Distance( cg.refdef.vieworg, bent->lerpOrigin );
+ if( adist > bdist )
+ return -1;
+ else if( adist < bdist )
+ return 1;
+ else
+ return 0;
+}
+
+/*
+==================
+CG_DrawBuildableStatus
+==================
+*/
+void CG_DrawBuildableStatus( void )
+{
+ int i;
+ centity_t *cent;
+ entityState_t *es;
+ int buildableList[ MAX_ENTITIES_IN_SNAPSHOT ];
+ int buildables = 0;
+
+ switch( cg.predictedPlayerState.weapon )
+ {
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ i ].number ];
+ es = &cent->currentState;
+
+ if( es->eType == ET_BUILDABLE &&
+ BG_FindTeamForBuildable( es->modelindex ) ==
+ BG_FindTeamForWeapon( cg.predictedPlayerState.weapon ) )
+ buildableList[ buildables++ ] = cg.snap->entities[ i ].number;
+ }
+ qsort( buildableList, buildables, sizeof( int ), CG_SortDistance );
+ for( i = 0; i < buildables; i++ )
+ CG_BuildableStatusDisplay( &cg_entities[ buildableList[ i ] ] );
+ break;
+
+ default:
+ break;
+ }
+}
+
+#define BUILDABLE_SOUND_PERIOD 500
+
+/*
+==================
+CG_Buildable
+==================
+*/
+void CG_Buildable( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *es = &cent->currentState;
+ vec3_t angles;
+ vec3_t surfNormal, xNormal, mins, maxs;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ float rotAngle;
+ buildableTeam_t team = BG_FindTeamForBuildable( es->modelindex );
+ float scale;
+ int health;
+ float healthScale;
+
+ //must be before EF_NODRAW check
+ if( team == BIT_ALIENS )
+ CG_Creep( cent );
+
+ // if set to invisible, skip
+ if( es->eFlags & EF_NODRAW )
+ {
+ if( CG_IsParticleSystemValid( &cent->buildablePS ) )
+ CG_DestroyParticleSystem( &cent->buildablePS );
+
+ return;
+ }
+
+ memset ( &ent, 0, sizeof( ent ) );
+
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( cent->lerpOrigin, ent.oldorigin );
+ VectorCopy( cent->lerpOrigin, ent.lightingOrigin );
+
+ VectorCopy( es->origin2, surfNormal );
+
+ VectorCopy( es->angles, angles );
+ BG_FindBBoxForBuildable( es->modelindex, mins, maxs );
+
+ if( es->pos.trType == TR_STATIONARY )
+ CG_PositionAndOrientateBuildable( angles, ent.origin, surfNormal, es->number,
+ mins, maxs, ent.axis, ent.origin );
+
+ //offset on the Z axis if required
+ VectorMA( ent.origin, BG_FindZOffsetForBuildable( es->modelindex ), surfNormal, ent.origin );
+
+ VectorCopy( ent.origin, ent.oldorigin ); // don't positionally lerp at all
+ VectorCopy( ent.origin, ent.lightingOrigin );
+
+ ent.hModel = cg_buildables[ es->modelindex ].models[ 0 ];
+
+ if( !( es->generic1 & B_SPAWNED_TOGGLEBIT ) )
+ {
+ sfxHandle_t prebuildSound = cgs.media.humanBuildablePrebuild;
+
+ if( team == BIT_HUMANS )
+ {
+ ent.customShader = cgs.media.humanSpawningShader;
+ prebuildSound = cgs.media.humanBuildablePrebuild;
+ }
+ else if( team == BIT_ALIENS )
+ prebuildSound = cgs.media.alienBuildablePrebuild;
+
+ trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, prebuildSound );
+ }
+
+ CG_BuildableAnimation( cent, &ent.oldframe, &ent.frame, &ent.backlerp );
+
+ //rescale the model
+ scale = BG_FindModelScaleForBuildable( es->modelindex );
+
+ if( scale != 1.0f )
+ {
+ VectorScale( ent.axis[ 0 ], scale, ent.axis[ 0 ] );
+ VectorScale( ent.axis[ 1 ], scale, ent.axis[ 1 ] );
+ VectorScale( ent.axis[ 2 ], scale, ent.axis[ 2 ] );
+
+ ent.nonNormalizedAxes = qtrue;
+ }
+ else
+ ent.nonNormalizedAxes = qfalse;
+
+
+ //add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+
+ CrossProduct( surfNormal, refNormal, xNormal );
+ VectorNormalize( xNormal );
+ rotAngle = RAD2DEG( acos( DotProduct( surfNormal, refNormal ) ) );
+
+ //turret barrel bit
+ if( cg_buildables[ es->modelindex ].models[ 1 ] )
+ {
+ refEntity_t turretBarrel;
+ vec3_t flatAxis[ 3 ];
+
+ memset( &turretBarrel, 0, sizeof( turretBarrel ) );
+
+ turretBarrel.hModel = cg_buildables[ es->modelindex ].models[ 1 ];
+
+ CG_PositionEntityOnTag( &turretBarrel, &ent, ent.hModel, "tag_turret" );
+ VectorCopy( cent->lerpOrigin, turretBarrel.lightingOrigin );
+ AnglesToAxis( es->angles2, flatAxis );
+
+ RotatePointAroundVector( turretBarrel.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle );
+ RotatePointAroundVector( turretBarrel.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle );
+ RotatePointAroundVector( turretBarrel.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle );
+
+ turretBarrel.oldframe = ent.oldframe;
+ turretBarrel.frame = ent.frame;
+ turretBarrel.backlerp = ent.backlerp;
+
+ turretBarrel.customShader = ent.customShader;
+
+ if( scale != 1.0f )
+ {
+ VectorScale( turretBarrel.axis[ 0 ], scale, turretBarrel.axis[ 0 ] );
+ VectorScale( turretBarrel.axis[ 1 ], scale, turretBarrel.axis[ 1 ] );
+ VectorScale( turretBarrel.axis[ 2 ], scale, turretBarrel.axis[ 2 ] );
+
+ turretBarrel.nonNormalizedAxes = qtrue;
+ }
+ else
+ turretBarrel.nonNormalizedAxes = qfalse;
+
+ trap_R_AddRefEntityToScene( &turretBarrel );
+ }
+
+ //turret barrel bit
+ if( cg_buildables[ es->modelindex ].models[ 2 ] )
+ {
+ refEntity_t turretTop;
+ vec3_t flatAxis[ 3 ];
+ vec3_t swivelAngles;
+
+ memset( &turretTop, 0, sizeof( turretTop ) );
+
+ VectorCopy( es->angles2, swivelAngles );
+ swivelAngles[ PITCH ] = 0.0f;
+
+ turretTop.hModel = cg_buildables[ es->modelindex ].models[ 2 ];
+
+ CG_PositionRotatedEntityOnTag( &turretTop, &ent, ent.hModel, "tag_turret" );
+ VectorCopy( cent->lerpOrigin, turretTop.lightingOrigin );
+ AnglesToAxis( swivelAngles, flatAxis );
+
+ RotatePointAroundVector( turretTop.axis[ 0 ], xNormal, flatAxis[ 0 ], -rotAngle );
+ RotatePointAroundVector( turretTop.axis[ 1 ], xNormal, flatAxis[ 1 ], -rotAngle );
+ RotatePointAroundVector( turretTop.axis[ 2 ], xNormal, flatAxis[ 2 ], -rotAngle );
+
+ turretTop.oldframe = ent.oldframe;
+ turretTop.frame = ent.frame;
+ turretTop.backlerp = ent.backlerp;
+
+ turretTop.customShader = ent.customShader;
+
+ if( scale != 1.0f )
+ {
+ VectorScale( turretTop.axis[ 0 ], scale, turretTop.axis[ 0 ] );
+ VectorScale( turretTop.axis[ 1 ], scale, turretTop.axis[ 1 ] );
+ VectorScale( turretTop.axis[ 2 ], scale, turretTop.axis[ 2 ] );
+
+ turretTop.nonNormalizedAxes = qtrue;
+ }
+ else
+ turretTop.nonNormalizedAxes = qfalse;
+
+ trap_R_AddRefEntityToScene( &turretTop );
+ }
+
+ //weapon effects for turrets
+ if( es->eFlags & EF_FIRING )
+ {
+ weaponInfo_t *weapon = &cg_weapons[ es->weapon ];
+
+ if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME ||
+ BG_FindProjTypeForBuildable( es->modelindex ) == WP_TESLAGEN )
+ {
+ if( weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ] ||
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ] ||
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] )
+ {
+ trap_R_AddLightToScene( cent->lerpOrigin, 300 + ( rand( ) & 31 ),
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 0 ],
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 1 ],
+ weapon->wim[ WPM_PRIMARY ].flashDlightColor[ 2 ] );
+ }
+ }
+
+ if( weapon->wim[ WPM_PRIMARY ].firingSound )
+ {
+ trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin,
+ weapon->wim[ WPM_PRIMARY ].firingSound );
+ }
+ else if( weapon->readySound )
+ trap_S_AddLoopingSound( es->number, cent->lerpOrigin, vec3_origin, weapon->readySound );
+ }
+
+ health = es->generic1 & B_HEALTH_MASK;
+ healthScale = (float)health / B_HEALTH_MASK;
+
+ if( healthScale < cent->lastBuildableHealthScale && ( es->generic1 & B_SPAWNED_TOGGLEBIT ) )
+ {
+ if( cent->lastBuildableDamageSoundTime + BUILDABLE_SOUND_PERIOD < cg.time )
+ {
+ if( team == BIT_HUMANS )
+ {
+ int i = rand( ) % 4;
+ trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.humanBuildableDamage[ i ] );
+ }
+ else if( team == BIT_ALIENS )
+ trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienBuildableDamage );
+
+ cent->lastBuildableDamageSoundTime = cg.time;
+ }
+ }
+
+ cent->lastBuildableHealthScale = healthScale;
+
+ //smoke etc for damaged buildables
+ CG_BuildableParticleEffects( cent );
+}
diff --git a/src/cgame/cg_consolecmds.c b/src/cgame/cg_consolecmds.c
new file mode 100644
index 0000000..68aab6c
--- /dev/null
+++ b/src/cgame/cg_consolecmds.c
@@ -0,0 +1,325 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_consolecmds.c -- text commands typed in at the local console, or
+// executed by a key binding
+
+
+#include "cg_local.h"
+
+
+
+void CG_TargetCommand_f( void )
+{
+ int targetNum;
+ char test[ 4 ];
+
+ targetNum = CG_CrosshairPlayer( );
+ if( !targetNum )
+ return;
+
+ trap_Argv( 1, test, 4 );
+ trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) );
+}
+
+
+
+/*
+=================
+CG_SizeUp_f
+
+Keybinding command
+=================
+*/
+static void CG_SizeUp_f( void )
+{
+ trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer + 10 ) ) );
+}
+
+
+/*
+=================
+CG_SizeDown_f
+
+Keybinding command
+=================
+*/
+static void CG_SizeDown_f( void )
+{
+ trap_Cvar_Set( "cg_viewsize", va( "%i", (int)( cg_viewsize.integer - 10 ) ) );
+}
+
+
+/*
+=============
+CG_Viewpos_f
+
+Debugging command to print the current position
+=============
+*/
+static void CG_Viewpos_f( void )
+{
+ CG_Printf( "(%i %i %i) : %i\n", (int)cg.refdef.vieworg[ 0 ],
+ (int)cg.refdef.vieworg[ 1 ], (int)cg.refdef.vieworg[ 2 ],
+ (int)cg.refdefViewAngles[ YAW ] );
+}
+
+qboolean CG_RequestScores( void )
+{
+ if( cg.scoresRequestTime + 2000 < cg.time )
+ {
+ // the scores are more than two seconds out of data,
+ // so request new ones
+ cg.scoresRequestTime = cg.time;
+ //TA: added \n SendClientCommand doesn't call flush( )?
+ trap_SendClientCommand( "score\n" );
+
+ return qtrue;
+ }
+ else
+ return qfalse;
+}
+
+extern menuDef_t *menuScoreboard;
+
+static void CG_scrollScoresDown_f( void )
+{
+ if( menuScoreboard && cg.scoreBoardShowing )
+ {
+ Menu_ScrollFeeder( menuScoreboard, FEEDER_ALIENTEAM_LIST, qtrue );
+ Menu_ScrollFeeder( menuScoreboard, FEEDER_HUMANTEAM_LIST, qtrue );
+ }
+}
+
+
+static void CG_scrollScoresUp_f( void )
+{
+ if( menuScoreboard && cg.scoreBoardShowing )
+ {
+ Menu_ScrollFeeder( menuScoreboard, FEEDER_ALIENTEAM_LIST, qfalse );
+ Menu_ScrollFeeder( menuScoreboard, FEEDER_HUMANTEAM_LIST, qfalse );
+ }
+}
+
+static void CG_ScoresDown_f( void )
+{
+ if( !cg.showScores )
+ {
+ Menu_SetFeederSelection( menuScoreboard, FEEDER_ALIENTEAM_LIST, 0, NULL );
+ Menu_SetFeederSelection( menuScoreboard, FEEDER_HUMANTEAM_LIST, 0, NULL );
+ }
+
+ if( CG_RequestScores( ) )
+ {
+ // leave the current scores up if they were already
+ // displayed, but if this is the first hit, clear them out
+ if( !cg.showScores )
+ {
+ if( cg_debugRandom.integer )
+ CG_Printf( "CG_ScoresDown_f: scores out of date\n" );
+
+ cg.showScores = qtrue;
+ cg.numScores = 0;
+ }
+ }
+ else
+ {
+ // show the cached contents even if they just pressed if it
+ // is within two seconds
+ cg.showScores = qtrue;
+ }
+}
+
+static void CG_ScoresUp_f( void )
+{
+ if( cg.showScores )
+ {
+ cg.showScores = qfalse;
+ cg.scoreFadeTime = cg.time;
+ }
+}
+
+static void CG_TellTarget_f( void )
+{
+ int clientNum;
+ char command[ 128 ];
+ char message[ 128 ];
+
+ clientNum = CG_CrosshairPlayer( );
+ if( clientNum == -1 )
+ return;
+
+ trap_Args( message, 128 );
+ Com_sprintf( command, 128, "tell %i %s", clientNum, message );
+ trap_SendClientCommand( command );
+}
+
+static void CG_TellAttacker_f( void )
+{
+ int clientNum;
+ char command[ 128 ];
+ char message[ 128 ];
+
+ clientNum = CG_LastAttacker( );
+ if( clientNum == -1 )
+ return;
+
+ trap_Args( message, 128 );
+ Com_sprintf( command, 128, "tell %i %s", clientNum, message );
+ trap_SendClientCommand( command );
+}
+
+typedef struct
+{
+ char *cmd;
+ void (*function)( void );
+} consoleCommand_t;
+
+static consoleCommand_t commands[ ] =
+{
+ { "testgun", CG_TestGun_f },
+ { "testmodel", CG_TestModel_f },
+ { "nextframe", CG_TestModelNextFrame_f },
+ { "prevframe", CG_TestModelPrevFrame_f },
+ { "nextskin", CG_TestModelNextSkin_f },
+ { "prevskin", CG_TestModelPrevSkin_f },
+ { "viewpos", CG_Viewpos_f },
+ { "+scores", CG_ScoresDown_f },
+ { "-scores", CG_ScoresUp_f },
+ { "scoresUp", CG_scrollScoresUp_f },
+ { "scoresDown", CG_scrollScoresDown_f },
+ { "sizeup", CG_SizeUp_f },
+ { "sizedown", CG_SizeDown_f },
+ { "weapnext", CG_NextWeapon_f },
+ { "weapprev", CG_PrevWeapon_f },
+ { "weapon", CG_Weapon_f },
+ { "tell_target", CG_TellTarget_f },
+ { "tell_attacker", CG_TellAttacker_f },
+ { "tcmd", CG_TargetCommand_f },
+ { "testPS", CG_TestPS_f },
+ { "destroyTestPS", CG_DestroyTestPS_f },
+ { "testTS", CG_TestTS_f },
+ { "destroyTestTS", CG_DestroyTestTS_f },
+};
+
+
+/*
+=================
+CG_ConsoleCommand
+
+The string has been tokenized and can be retrieved with
+Cmd_Argc() / Cmd_Argv()
+=================
+*/
+qboolean CG_ConsoleCommand( void )
+{
+ const char *cmd;
+ const char *arg1;
+ int i;
+
+ cmd = CG_Argv( 0 );
+
+ //TA: ugly hacky special case
+ if( !Q_stricmp( cmd, "ui_menu" ) )
+ {
+ arg1 = CG_Argv( 1 );
+ trap_SendConsoleCommand( va( "menu %s\n", arg1 ) );
+ return qtrue;
+ }
+
+ for( i = 0; i < sizeof( commands ) / sizeof( commands[ 0 ] ); i++ )
+ {
+ if( !Q_stricmp( cmd, commands[ i ].cmd ) )
+ {
+ commands[ i ].function( );
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+
+/*
+=================
+CG_InitConsoleCommands
+
+Let the client system know about all of our commands
+so it can perform tab completion
+=================
+*/
+void CG_InitConsoleCommands( void )
+{
+ int i;
+
+ for( i = 0 ; i < sizeof( commands ) / sizeof( commands[ 0 ] ) ; i++ )
+ trap_AddCommand( commands[ i ].cmd );
+
+ //
+ // the game server will interpret these commands, which will be automatically
+ // forwarded to the server after they are not recognized locally
+ //
+ trap_AddCommand( "kill" );
+ trap_AddCommand( "say" );
+ trap_AddCommand( "say_team" );
+ trap_AddCommand( "tell" );
+ trap_AddCommand( "vsay" );
+ trap_AddCommand( "vsay_team" );
+ trap_AddCommand( "vtell" );
+ trap_AddCommand( "vtaunt" );
+ trap_AddCommand( "vosay" );
+ trap_AddCommand( "vosay_team" );
+ trap_AddCommand( "votell" );
+ trap_AddCommand( "give" );
+ trap_AddCommand( "god" );
+ trap_AddCommand( "notarget" );
+ trap_AddCommand( "noclip" );
+ trap_AddCommand( "team" );
+ trap_AddCommand( "follow" );
+ trap_AddCommand( "levelshot" );
+ trap_AddCommand( "addbot" );
+ trap_AddCommand( "setviewpos" );
+ trap_AddCommand( "callvote" );
+ trap_AddCommand( "vote" );
+ trap_AddCommand( "callteamvote" );
+ trap_AddCommand( "teamvote" );
+ trap_AddCommand( "stats" );
+ trap_AddCommand( "teamtask" );
+ trap_AddCommand( "class" );
+ trap_AddCommand( "build" );
+ trap_AddCommand( "buy" );
+ trap_AddCommand( "sell" );
+ trap_AddCommand( "reload" );
+ trap_AddCommand( "itemact" );
+ trap_AddCommand( "itemdeact" );
+ trap_AddCommand( "itemtoggle" );
+ trap_AddCommand( "destroy" );
+ trap_AddCommand( "deconstruct" );
+ trap_AddCommand( "menu" );
+ trap_AddCommand( "ui_menu" );
+ trap_AddCommand( "mapRotation" );
+ trap_AddCommand( "stopMapRotation" );
+ trap_AddCommand( "advanceMapRotation" );
+ trap_AddCommand( "alienWin" );
+ trap_AddCommand( "humanWin" );
+}
diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c
new file mode 100644
index 0000000..82df413
--- /dev/null
+++ b/src/cgame/cg_draw.c
@@ -0,0 +1,3524 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_draw.c -- draw all of the graphical elements during
+// active (after loading) gameplay
+
+
+#include "cg_local.h"
+#include "../ui/ui_shared.h"
+
+// used for scoreboard
+extern displayContextDef_t cgDC;
+menuDef_t *menuScoreboard = NULL;
+
+int drawTeamOverlayModificationCount = -1;
+
+int sortedTeamPlayers[ TEAM_MAXOVERLAY ];
+int numSortedTeamPlayers;
+
+//TA UI
+int CG_Text_Width( const char *text, float scale, int limit )
+{
+ int count,len;
+ float out;
+ glyphInfo_t *glyph;
+ float useScale;
+// FIXME: see ui_main.c, same problem
+// const unsigned char *s = text;
+ const char *s = text;
+ fontInfo_t *font = &cgDC.Assets.textFont;
+
+ if( scale <= cg_smallFont.value )
+ font = &cgDC.Assets.smallFont;
+ else if( scale > cg_bigFont.value )
+ font = &cgDC.Assets.bigFont;
+
+ useScale = scale * font->glyphScale;
+ out = 0;
+
+ if( text )
+ {
+ len = strlen( text );
+ if( limit > 0 && len > limit )
+ len = limit;
+
+ count = 0;
+ while( s && *s && count < len )
+ {
+ if( Q_IsColorString( s ) )
+ {
+ s += 2;
+ continue;
+ }
+ else
+ {
+ glyph = &font->glyphs[ (int)*s ];
+ //TTimo: FIXME: getting nasty warnings without the cast,
+ //hopefully this doesn't break the VM build
+ out += glyph->xSkip;
+ s++;
+ count++;
+ }
+ }
+ }
+
+ return out * useScale;
+}
+
+int CG_Text_Height( const char *text, float scale, int limit )
+{
+ int len, count;
+ float max;
+ glyphInfo_t *glyph;
+ float useScale;
+// TTimo: FIXME
+// const unsigned char *s = text;
+ const char *s = text;
+ fontInfo_t *font = &cgDC.Assets.textFont;
+
+ if( scale <= cg_smallFont.value )
+ font = &cgDC.Assets.smallFont;
+ else if( scale > cg_bigFont.value )
+ font = &cgDC.Assets.bigFont;
+
+ useScale = scale * font->glyphScale;
+ max = 0;
+
+ if( text )
+ {
+ len = strlen( text );
+ if( limit > 0 && len > limit )
+ len = limit;
+
+ count = 0;
+ while( s && *s && count < len )
+ {
+ if( Q_IsColorString( s ) )
+ {
+ s += 2;
+ continue;
+ }
+ else
+ {
+ glyph = &font->glyphs[ (int)*s ];
+ //TTimo: FIXME: getting nasty warnings without the cast,
+ //hopefully this doesn't break the VM build
+ if( max < glyph->height )
+ max = glyph->height;
+
+ s++;
+ count++;
+ }
+ }
+ }
+
+ return max * useScale;
+}
+
+void CG_Text_PaintChar( float x, float y, float width, float height, float scale,
+ float s, float t, float s2, float t2, qhandle_t hShader )
+{
+ float w, h;
+ w = width * scale;
+ h = height * scale;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader );
+}
+
+void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text,
+ float adjust, int limit, int style )
+{
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph;
+ float useScale;
+ fontInfo_t *font = &cgDC.Assets.textFont;
+
+ if( scale <= cg_smallFont.value )
+ font = &cgDC.Assets.smallFont;
+ else if( scale > cg_bigFont.value )
+ font = &cgDC.Assets.bigFont;
+
+ useScale = scale * font->glyphScale;
+ if( text )
+ {
+// TTimo: FIXME
+// const unsigned char *s = text;
+ const char *s = text;
+
+ trap_R_SetColor( color );
+ memcpy( &newColor[ 0 ], &color[ 0 ], sizeof( vec4_t ) );
+ len = strlen( text );
+
+ if( limit > 0 && len > limit )
+ len = limit;
+
+ count = 0;
+ while( s && *s && count < len )
+ {
+ glyph = &font->glyphs[ (int)*s ];
+ //TTimo: FIXME: getting nasty warnings without the cast,
+ //hopefully this doesn't break the VM build
+
+ if( Q_IsColorString( s ) )
+ {
+ memcpy( newColor, g_color_table[ ColorIndex( *( s + 1 ) ) ], sizeof( newColor ) );
+ newColor[ 3 ] = color[ 3 ];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ }
+ else
+ {
+ float yadj = useScale * glyph->top;
+ if( style == ITEM_TEXTSTYLE_SHADOWED ||
+ style == ITEM_TEXTSTYLE_SHADOWEDMORE )
+ {
+ int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2;
+ colorBlack[ 3 ] = newColor[ 3 ];
+ trap_R_SetColor( colorBlack );
+ CG_Text_PaintChar( x + ofs, y - yadj + ofs,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ colorBlack[ 3 ] = 1.0;
+ trap_R_SetColor( newColor );
+ }
+ else if( style == ITEM_TEXTSTYLE_NEON )
+ {
+ vec4_t glow, outer, inner, white;
+
+ glow[ 0 ] = newColor[ 0 ] * 0.5;
+ glow[ 1 ] = newColor[ 1 ] * 0.5;
+ glow[ 2 ] = newColor[ 2 ] * 0.5;
+ glow[ 3 ] = newColor[ 3 ] * 0.2;
+
+ outer[ 0 ] = newColor[ 0 ];
+ outer[ 1 ] = newColor[ 1 ];
+ outer[ 2 ] = newColor[ 2 ];
+ outer[ 3 ] = newColor[ 3 ];
+
+ inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5;
+ inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5;
+ inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5;
+ inner[ 3 ] = newColor[ 3 ];
+
+ white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f;
+
+ trap_R_SetColor( glow );
+ CG_Text_PaintChar( x - 3, y - yadj - 3,
+ glyph->imageWidth + 6,
+ glyph->imageHeight + 6,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( outer );
+ CG_Text_PaintChar( x - 1, y - yadj - 1,
+ glyph->imageWidth + 2,
+ glyph->imageHeight + 2,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( inner );
+ CG_Text_PaintChar( x - 0.5, y - yadj - 0.5,
+ glyph->imageWidth + 1,
+ glyph->imageHeight + 1,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( white );
+ }
+
+
+ CG_Text_PaintChar( x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ x += ( glyph->xSkip * useScale ) + adjust;
+ s++;
+ count++;
+ }
+ }
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+/*
+==============
+CG_DrawFieldPadded
+
+Draws large numbers for status bar and powerups
+==============
+*/
+static void CG_DrawFieldPadded( int x, int y, int width, int cw, int ch, int value )
+{
+ char num[ 16 ], *ptr;
+ int l, orgL;
+ int frame;
+ int charWidth, charHeight;
+
+ if( !( charWidth = cw ) )
+ charWidth = CHAR_WIDTH;
+
+ if( !( charHeight = ch ) )
+ charWidth = CHAR_HEIGHT;
+
+ if( width < 1 )
+ return;
+
+ // draw number string
+ if( width > 4 )
+ width = 4;
+
+ switch( width )
+ {
+ case 1:
+ value = value > 9 ? 9 : value;
+ value = value < 0 ? 0 : value;
+ break;
+ case 2:
+ value = value > 99 ? 99 : value;
+ value = value < -9 ? -9 : value;
+ break;
+ case 3:
+ value = value > 999 ? 999 : value;
+ value = value < -99 ? -99 : value;
+ break;
+ case 4:
+ value = value > 9999 ? 9999 : value;
+ value = value < -999 ? -999 : value;
+ break;
+ }
+
+ Com_sprintf( num, sizeof( num ), "%d", value );
+ l = strlen( num );
+
+ if( l > width )
+ l = width;
+
+ orgL = l;
+
+ x += 2;
+
+ ptr = num;
+ while( *ptr && l )
+ {
+ if( width > orgL )
+ {
+ CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ 0 ] );
+ width--;
+ x += charWidth;
+ continue;
+ }
+
+ if( *ptr == '-' )
+ frame = STAT_MINUS;
+ else
+ frame = *ptr - '0';
+
+ CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] );
+ x += charWidth;
+ ptr++;
+ l--;
+ }
+}
+
+/*
+==============
+CG_DrawField
+
+Draws large numbers for status bar and powerups
+==============
+*/
+void CG_DrawField( float x, float y, int width, float cw, float ch, int value )
+{
+ char num[ 16 ], *ptr;
+ int l;
+ int frame;
+ float charWidth, charHeight;
+
+ if( !( charWidth = cw ) )
+ charWidth = CHAR_WIDTH;
+
+ if( !( charHeight = ch ) )
+ charWidth = CHAR_HEIGHT;
+
+ if( width < 1 )
+ return;
+
+ // draw number string
+ if( width > 4 )
+ width = 4;
+
+ switch( width )
+ {
+ case 1:
+ value = value > 9 ? 9 : value;
+ value = value < 0 ? 0 : value;
+ break;
+ case 2:
+ value = value > 99 ? 99 : value;
+ value = value < -9 ? -9 : value;
+ break;
+ case 3:
+ value = value > 999 ? 999 : value;
+ value = value < -99 ? -99 : value;
+ break;
+ case 4:
+ value = value > 9999 ? 9999 : value;
+ value = value < -999 ? -999 : value;
+ break;
+ }
+
+ Com_sprintf( num, sizeof( num ), "%d", value );
+ l = strlen( num );
+
+ if( l > width )
+ l = width;
+
+ x += 2 + charWidth * ( width - l );
+
+ ptr = num;
+ while( *ptr && l )
+ {
+ if( *ptr == '-' )
+ frame = STAT_MINUS;
+ else
+ frame = *ptr -'0';
+
+ CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[ frame ] );
+ x += charWidth;
+ ptr++;
+ l--;
+ }
+}
+
+static void CG_DrawProgressBar( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special, float progress )
+{
+ float rimWidth = rect->h / 20.0f;
+ float doneWidth, leftWidth;
+ float tx, ty, tw, th;
+ char textBuffer[ 8 ];
+
+ if( rimWidth < 0.6f )
+ rimWidth = 0.6f;
+
+ if( special >= 0.0f )
+ rimWidth = special;
+
+ if( progress < 0.0f )
+ progress = 0.0f;
+ else if( progress > 1.0f )
+ progress = 1.0f;
+
+ doneWidth = ( rect->w - 2 * rimWidth ) * progress;
+ leftWidth = ( rect->w - 2 * rimWidth ) - doneWidth;
+
+ trap_R_SetColor( color );
+
+ //draw rim and bar
+ if( align == ITEM_ALIGN_RIGHT )
+ {
+ CG_DrawPic( rect->x, rect->y, rimWidth, rect->h, cgs.media.whiteShader );
+ CG_DrawPic( rect->x + rimWidth, rect->y,
+ leftWidth, rimWidth, cgs.media.whiteShader );
+ CG_DrawPic( rect->x + rimWidth, rect->y + rect->h - rimWidth,
+ leftWidth, rimWidth, cgs.media.whiteShader );
+ CG_DrawPic( rect->x + rimWidth + leftWidth, rect->y,
+ rimWidth + doneWidth, rect->h, cgs.media.whiteShader );
+ }
+ else
+ {
+ CG_DrawPic( rect->x, rect->y, rimWidth + doneWidth, rect->h, cgs.media.whiteShader );
+ CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y,
+ leftWidth, rimWidth, cgs.media.whiteShader );
+ CG_DrawPic( rimWidth + rect->x + doneWidth, rect->y + rect->h - rimWidth,
+ leftWidth, rimWidth, cgs.media.whiteShader );
+ CG_DrawPic( rect->x + rect->w - rimWidth, rect->y, rimWidth, rect->h, cgs.media.whiteShader );
+ }
+
+ trap_R_SetColor( NULL );
+
+ //draw text
+ if( scale > 0.0 )
+ {
+ Com_sprintf( textBuffer, sizeof( textBuffer ), "%d%%", (int)( progress * 100 ) );
+ tw = CG_Text_Width( textBuffer, scale, 0 );
+ th = scale * 40.0f;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x + ( rect->w / 10.0f );
+ ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f );
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - ( rect->w / 10.0f ) - tw;
+ ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f );
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( tw / 2.0f );
+ ty = rect->y + ( rect->h / 2.0f ) + ( th / 2.0f );
+ break;
+
+ default:
+ tx = ty = 0.0f;
+ }
+
+ CG_Text_Paint( tx, ty, scale, color, textBuffer, 0, 0, textStyle );
+ }
+}
+
+//=============== TA: was cg_newdraw.c
+
+#define NO_CREDITS_TIME 2000
+
+static void CG_DrawPlayerCreditsValue( rectDef_t *rect, vec4_t color, qboolean padding )
+{
+ int value;
+ playerState_t *ps;
+ centity_t *cent;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ //if the build timer pie is showing don't show this
+ if( ( cent->currentState.weapon == WP_ABUILD ||
+ cent->currentState.weapon == WP_ABUILD2 ) && ps->stats[ STAT_MISC ] )
+ return;
+
+ value = ps->persistant[ PERS_CREDIT ];
+ if( value > -1 )
+ {
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS &&
+ !CG_AtHighestClass( ) )
+ {
+ if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME )
+ {
+ if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 )
+ color[ 3 ] = 0.0f;
+ }
+ }
+
+ trap_R_SetColor( color );
+
+ if( padding )
+ CG_DrawFieldPadded( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ else
+ CG_DrawField( rect->x, rect->y, 1, rect->w, rect->h, value );
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+static void CG_DrawPlayerBankValue( rectDef_t *rect, vec4_t color, qboolean padding )
+{
+ int value;
+ playerState_t *ps;
+
+ ps = &cg.snap->ps;
+
+ value = ps->persistant[ PERS_BANK ];
+ if( value > -1 )
+ {
+ trap_R_SetColor( color );
+
+ if( padding )
+ CG_DrawFieldPadded( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ else
+ CG_DrawField( rect->x, rect->y, 1, rect->w, rect->h, value );
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+#define HH_MIN_ALPHA 0.2f
+#define HH_MAX_ALPHA 0.8f
+#define HH_ALPHA_DIFF (HH_MAX_ALPHA-HH_MIN_ALPHA)
+
+#define AH_MIN_ALPHA 0.2f
+#define AH_MAX_ALPHA 0.8f
+#define AH_ALPHA_DIFF (AH_MAX_ALPHA-AH_MIN_ALPHA)
+
+/*
+==============
+CG_DrawPlayerStamina1
+==============
+*/
+static void CG_DrawPlayerStamina1( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+ float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f;
+ float progress;
+
+ stamina -= ( 2 * (int)maxStaminaBy3 );
+ progress = stamina / maxStaminaBy3;
+
+ if( progress > 1.0f )
+ progress = 1.0f;
+ else if( progress < 0.0f )
+ progress = 0.0f;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerStamina2
+==============
+*/
+static void CG_DrawPlayerStamina2( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+ float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f;
+ float progress;
+
+ stamina -= (int)maxStaminaBy3;
+ progress = stamina / maxStaminaBy3;
+
+ if( progress > 1.0f )
+ progress = 1.0f;
+ else if( progress < 0.0f )
+ progress = 0.0f;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerStamina3
+==============
+*/
+static void CG_DrawPlayerStamina3( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+ float maxStaminaBy3 = (float)MAX_STAMINA / 3.0f;
+ float progress;
+
+ progress = stamina / maxStaminaBy3;
+
+ if( progress > 1.0f )
+ progress = 1.0f;
+ else if( progress < 0.0f )
+ progress = 0.0f;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerStamina4
+==============
+*/
+static void CG_DrawPlayerStamina4( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+ float progress;
+
+ stamina += (float)MAX_STAMINA;
+ progress = stamina / (float)MAX_STAMINA;
+
+ if( progress > 1.0f )
+ progress = 1.0f;
+ else if( progress < 0.0f )
+ progress = 0.0f;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerStaminaBolt
+==============
+*/
+static void CG_DrawPlayerStaminaBolt( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ float stamina = ps->stats[ STAT_STAMINA ];
+
+ if( stamina < 0 )
+ color[ 3 ] = HH_MIN_ALPHA;
+ else
+ color[ 3 ] = HH_MAX_ALPHA;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerClipsRing
+==============
+*/
+static void CG_DrawPlayerClipsRing( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ centity_t *cent;
+ float buildTime = ps->stats[ STAT_MISC ];
+ float progress;
+ float maxDelay;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+
+ switch( cent->currentState.weapon )
+ {
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ maxDelay = (float)BG_FindBuildDelayForWeapon( cent->currentState.weapon );
+
+ if( buildTime > maxDelay )
+ buildTime = maxDelay;
+
+ progress = ( maxDelay - buildTime ) / maxDelay;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+ break;
+
+ default:
+ if( ps->weaponstate == WEAPON_RELOADING )
+ {
+ maxDelay = (float)BG_FindReloadTimeForWeapon( cent->currentState.weapon );
+ progress = ( maxDelay - (float)ps->weaponTime ) / maxDelay;
+
+ color[ 3 ] = HH_MIN_ALPHA + ( progress * HH_ALPHA_DIFF );
+ }
+ break;
+ }
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerBuildTimerRing
+==============
+*/
+static void CG_DrawPlayerBuildTimerRing( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ centity_t *cent;
+ float buildTime = ps->stats[ STAT_MISC ];
+ float progress;
+ float maxDelay;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+
+ maxDelay = (float)BG_FindBuildDelayForWeapon( cent->currentState.weapon );
+
+ if( buildTime > maxDelay )
+ buildTime = maxDelay;
+
+ progress = ( maxDelay - buildTime ) / maxDelay;
+
+ color[ 3 ] = AH_MIN_ALPHA + ( progress * AH_ALPHA_DIFF );
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerBoosted
+==============
+*/
+static void CG_DrawPlayerBoosted( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ qboolean boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED;
+
+ if( boosted )
+ color[ 3 ] = AH_MAX_ALPHA;
+ else
+ color[ 3 ] = AH_MIN_ALPHA;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerBoosterBolt
+==============
+*/
+static void CG_DrawPlayerBoosterBolt( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ qboolean boosted = ps->stats[ STAT_STATE ] & SS_BOOSTED;
+ vec4_t localColor;
+
+ Vector4Copy( color, localColor );
+
+ if( boosted )
+ {
+ if( ps->stats[ STAT_BOOSTTIME ] > BOOST_TIME - 3000 )
+ {
+ qboolean flash = ( ps->stats[ STAT_BOOSTTIME ] / 500 ) % 2;
+
+ if( flash )
+ localColor[ 3 ] = 1.0f;
+ }
+ }
+
+ trap_R_SetColor( localColor );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerPoisonBarbs
+==============
+*/
+static void CG_DrawPlayerPoisonBarbs( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ int x = rect->x;
+ int y = rect->y;
+ int width = rect->w;
+ int height = rect->h;
+ qboolean vertical;
+ int iconsize, numBarbs, i;
+
+ BG_UnpackAmmoArray( ps->weapon, ps->ammo, ps->powerups, &numBarbs, NULL );
+
+ if( height > width )
+ {
+ vertical = qtrue;
+ iconsize = width;
+ }
+ else if( height <= width )
+ {
+ vertical = qfalse;
+ iconsize = height;
+ }
+
+ if( color[ 3 ] != 0.0 )
+ trap_R_SetColor( color );
+
+ for( i = 0; i < numBarbs; i ++ )
+ {
+ if( vertical )
+ y += iconsize;
+ else
+ x += iconsize;
+
+ CG_DrawPic( x, y, iconsize, iconsize, shader );
+ }
+
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawPlayerWallclimbing
+==============
+*/
+static void CG_DrawPlayerWallclimbing( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ qboolean ww = ps->stats[ STAT_STATE ] & SS_WALLCLIMBING;
+
+ if( ww )
+ color[ 3 ] = AH_MAX_ALPHA;
+ else
+ color[ 3 ] = AH_MIN_ALPHA;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+static void CG_DrawPlayerStamina( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ playerState_t *ps = &cg.snap->ps;
+ int stamina = ps->stats[ STAT_STAMINA ];
+ float progress = ( (float)stamina + (float)MAX_STAMINA ) / ( (float)MAX_STAMINA * 2.0f );
+
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, progress );
+}
+
+static void CG_DrawPlayerAmmoValue( rectDef_t *rect, vec4_t color )
+{
+ int value;
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ if( cent->currentState.weapon )
+ {
+ switch( cent->currentState.weapon )
+ {
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ //percentage of BP remaining
+ value = cgs.alienBuildPoints;
+ break;
+
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ //percentage of BP remaining
+ value = cgs.humanBuildPoints;
+ break;
+
+ default:
+ BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups, &value, NULL );
+ break;
+ }
+
+ if( value > 999 )
+ value = 999;
+
+ if( value > -1 )
+ {
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ trap_R_SetColor( NULL );
+ }
+ }
+}
+
+
+/*
+==============
+CG_DrawAlienSense
+==============
+*/
+static void CG_DrawAlienSense( rectDef_t *rect )
+{
+ if( BG_ClassHasAbility( cg.snap->ps.stats[ STAT_PCLASS ], SCA_ALIENSENSE ) )
+ CG_AlienSense( rect );
+}
+
+
+/*
+==============
+CG_DrawHumanScanner
+==============
+*/
+static void CG_DrawHumanScanner( rectDef_t *rect, qhandle_t shader, vec4_t color )
+{
+ if( BG_InventoryContainsUpgrade( UP_HELMET, cg.snap->ps.stats ) )
+ CG_Scanner( rect, shader, color );
+}
+
+
+/*
+==============
+CG_DrawUsableBuildable
+==============
+*/
+static void CG_DrawUsableBuildable( rectDef_t *rect, qhandle_t shader, vec4_t color )
+{
+ vec3_t view, point;
+ trace_t trace;
+ entityState_t *es;
+
+ AngleVectors( cg.refdefViewAngles, view, NULL, NULL );
+ VectorMA( cg.refdef.vieworg, 64, view, point );
+ CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL,
+ point, cg.predictedPlayerState.clientNum, MASK_SHOT );
+
+ es = &cg_entities[ trace.entityNum ].currentState;
+
+ if( es->eType == ET_BUILDABLE && BG_FindUsableForBuildable( es->modelindex ) &&
+ cg.predictedPlayerState.stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) )
+ {
+ //hack to prevent showing the usable buildable when you aren't carrying an energy weapon
+ if( ( es->modelindex == BA_H_REACTOR || es->modelindex == BA_H_REPEATER ) &&
+ ( !BG_FindUsesEnergyForWeapon( cg.snap->ps.weapon ) ||
+ BG_FindInfinteAmmoForWeapon( cg.snap->ps.weapon ) ) )
+ return;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+ }
+}
+
+
+#define BUILD_DELAY_TIME 2000
+
+static void CG_DrawPlayerBuildTimer( rectDef_t *rect, vec4_t color )
+{
+ float progress;
+ int index;
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ if( cent->currentState.weapon )
+ {
+ switch( cent->currentState.weapon )
+ {
+ case WP_ABUILD:
+ progress = (float)ps->stats[ STAT_MISC ] / (float)ABUILDER_BASE_DELAY;
+ break;
+
+ case WP_ABUILD2:
+ progress = (float)ps->stats[ STAT_MISC ] / (float)ABUILDER_ADV_DELAY;
+ break;
+
+ case WP_HBUILD:
+ progress = (float)ps->stats[ STAT_MISC ] / (float)HBUILD_DELAY;
+ break;
+
+ case WP_HBUILD2:
+ progress = (float)ps->stats[ STAT_MISC ] / (float)HBUILD2_DELAY;
+ break;
+
+ default:
+ return;
+ break;
+ }
+
+ if( !ps->stats[ STAT_MISC ] )
+ return;
+
+ index = (int)( progress * 8.0f );
+
+ if( index > 7 )
+ index = 7;
+ else if( index < 0 )
+ index = 0;
+
+ if( cg.time - cg.lastBuildAttempt <= BUILD_DELAY_TIME )
+ {
+ if( ( ( cg.time - cg.lastBuildAttempt ) / 300 ) % 2 )
+ {
+ color[ 0 ] = 1.0f;
+ color[ 1 ] = color[ 2 ] = 0.0f;
+ color[ 3 ] = 1.0f;
+ }
+ }
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h,
+ cgs.media.buildWeaponTimerPie[ index ] );
+ trap_R_SetColor( NULL );
+ }
+}
+
+static void CG_DrawPlayerClipsValue( rectDef_t *rect, vec4_t color )
+{
+ int value;
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ if( cent->currentState.weapon )
+ {
+ switch( cent->currentState.weapon )
+ {
+ case WP_ABUILD:
+ case WP_ABUILD2:
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ break;
+
+ default:
+ BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups, NULL, &value );
+
+ if( value > -1 )
+ {
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ trap_R_SetColor( NULL );
+ }
+ break;
+ }
+ }
+}
+
+static void CG_DrawPlayerHealthValue( rectDef_t *rect, vec4_t color )
+{
+ playerState_t *ps;
+ int value;
+
+ ps = &cg.snap->ps;
+
+ value = ps->stats[ STAT_HEALTH ];
+
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 4, rect->w / 4, rect->h, value );
+ trap_R_SetColor( NULL );
+}
+
+static void CG_DrawPlayerHealthBar( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ playerState_t *ps;
+ float total;
+
+ ps = &cg.snap->ps;
+
+ total = ( (float)ps->stats[ STAT_HEALTH ] / (float)ps->stats[ STAT_MAX_HEALTH ] );
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, total );
+}
+
+/*
+==============
+CG_DrawPlayerHealthCross
+==============
+*/
+static void CG_DrawPlayerHealthCross( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ playerState_t *ps = &cg.snap->ps;
+ int health = ps->stats[ STAT_HEALTH ];
+
+ if( health < 10 )
+ {
+ color[ 0 ] = 1.0f;
+ color[ 1 ] = color[ 2 ] = 0.0f;
+ }
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+static void CG_DrawProgressLabel( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, const char *s, float fraction )
+{
+ vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f };
+ float tx, tw = CG_Text_Width( s, scale, 0 );
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = 0.0f;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->w - tw;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = ( rect->w / 2.0f ) - ( tw / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ if( fraction < 1.0f )
+ CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, scale, white,
+ s, 0, 0, ITEM_TEXTSTYLE_NORMAL );
+ else
+ CG_Text_Paint( rect->x + text_x + tx, rect->y + text_y, scale, color,
+ s, 0, 0, ITEM_TEXTSTYLE_NEON );
+}
+
+static void CG_DrawMediaProgress( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.mediaFraction );
+}
+
+static void CG_DrawMediaProgressLabel( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align )
+{
+ CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Map and Textures", cg.mediaFraction );
+}
+
+static void CG_DrawBuildablesProgress( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.buildablesFraction );
+}
+
+static void CG_DrawBuildablesProgressLabel( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align )
+{
+ CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Buildable Models", cg.buildablesFraction );
+}
+
+static void CG_DrawCharModelProgress( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, cg.charModelFraction );
+}
+
+static void CG_DrawCharModelProgressLabel( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align )
+{
+ CG_DrawProgressLabel( rect, text_x, text_y, color, scale, align, "Character Models", cg.charModelFraction );
+}
+
+static void CG_DrawOverallProgress( rectDef_t *rect, vec4_t color, float scale,
+ int align, int textStyle, int special )
+{
+ float total;
+
+ total = ( cg.charModelFraction + cg.buildablesFraction + cg.mediaFraction ) / 3.0f;
+ CG_DrawProgressBar( rect, color, scale, align, textStyle, special, total );
+}
+
+static void CG_DrawLevelShot( rectDef_t *rect )
+{
+ const char *s;
+ const char *info;
+ qhandle_t levelshot;
+ qhandle_t detail;
+
+ info = CG_ConfigString( CS_SERVERINFO );
+ s = Info_ValueForKey( info, "mapname" );
+ levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) );
+
+ if( !levelshot )
+ levelshot = trap_R_RegisterShaderNoMip( "gfx/2d/load_screen" );
+
+ trap_R_SetColor( NULL );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, levelshot );
+
+ // blend a detail texture over it
+ detail = trap_R_RegisterShader( "gfx/misc/detail" );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, detail );
+}
+
+static void CG_DrawLoadingString( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, int textStyle, const char *s )
+{
+ float tw, th, tx;
+ int pos, i;
+ char buffer[ 1024 ];
+ char *end;
+
+ if( !s[ 0 ] )
+ return;
+
+ strcpy( buffer, s );
+ tw = CG_Text_Width( s, scale, 0 );
+ th = scale * 40.0f;
+
+ pos = i = 0;
+
+ while( pos < strlen( s ) )
+ {
+ strcpy( buffer, &s[ pos ] );
+ tw = CG_Text_Width( buffer, scale, 0 );
+
+ while( tw > rect->w )
+ {
+ end = strrchr( buffer, ' ' );
+
+ if( end == NULL )
+ break;
+
+ *end = '\0';
+ tw = CG_Text_Width( buffer, scale, 0 );
+ }
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - tw;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( tw / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ CG_Text_Paint( tx + text_x, rect->y + text_y + i * ( th + 3 ), scale, color,
+ buffer, 0, 0, textStyle );
+
+ pos += strlen( buffer ) + 1;
+ i++;
+ }
+}
+
+static void CG_DrawLevelName( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align, int textStyle )
+{
+ const char *s;
+
+ s = CG_ConfigString( CS_MESSAGE );
+
+ CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s );
+}
+
+static void CG_DrawMOTD( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align, int textStyle )
+{
+ const char *s;
+
+ s = CG_ConfigString( CS_MOTD );
+
+ CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, s );
+}
+
+static void CG_DrawHostname( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align, int textStyle )
+{
+ char buffer[ 1024 ];
+ const char *info;
+
+ info = CG_ConfigString( CS_SERVERINFO );
+
+ Q_strncpyz( buffer, Info_ValueForKey( info, "sv_hostname" ), 1024 );
+ Q_CleanStr( buffer );
+
+ CG_DrawLoadingString( rect, text_x, text_y, color, scale, align, textStyle, buffer );
+}
+
+/*
+==============
+CG_DrawDemoPlayback
+==============
+*/
+static void CG_DrawDemoPlayback( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ if( !cg_drawDemoState.integer )
+ return;
+
+ if( trap_GetDemoState( ) != DS_PLAYBACK )
+ return;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+==============
+CG_DrawDemoRecording
+==============
+*/
+static void CG_DrawDemoRecording( rectDef_t *rect, vec4_t color, qhandle_t shader )
+{
+ if( !cg_drawDemoState.integer )
+ return;
+
+ if( trap_GetDemoState( ) != DS_RECORDING )
+ return;
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+======================
+CG_UpdateMediaFraction
+
+======================
+*/
+void CG_UpdateMediaFraction( float newFract )
+{
+ cg.mediaFraction = newFract;
+
+ trap_UpdateScreen( );
+}
+
+/*
+====================
+CG_DrawLoadingScreen
+
+Draw all the status / pacifier stuff during level loading
+====================
+*/
+void CG_DrawLoadingScreen( void )
+{
+ Menu_Paint( Menus_FindByName( "Loading" ), qtrue );
+}
+
+float CG_GetValue( int ownerDraw )
+{
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ switch( ownerDraw )
+ {
+ case CG_PLAYER_AMMO_VALUE:
+ if( cent->currentState.weapon )
+ {
+ int value;
+
+ BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups,
+ &value, NULL );
+
+ return value;
+ }
+ break;
+ case CG_PLAYER_CLIPS_VALUE:
+ if( cent->currentState.weapon )
+ {
+ int value;
+
+ BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups,
+ NULL, &value );
+
+ return value;
+ }
+ break;
+ case CG_PLAYER_HEALTH:
+ return ps->stats[ STAT_HEALTH ];
+ break;
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+const char *CG_GetKillerText( )
+{
+ const char *s = "";
+ if( cg.killerName[ 0 ] )
+ s = va( "Fragged by %s", cg.killerName );
+
+ return s;
+}
+
+
+static void CG_DrawKiller( rectDef_t *rect, float scale, vec4_t color,
+ qhandle_t shader, int textStyle )
+{
+ // fragged by ... line
+ if( cg.killerName[ 0 ] )
+ {
+ int x = rect->x + rect->w / 2;
+ CG_Text_Paint( x - CG_Text_Width( CG_GetKillerText( ), scale, 0 ) / 2,
+ rect->y + rect->h, scale, color, CG_GetKillerText( ), 0, 0, textStyle );
+ }
+}
+
+
+static void CG_Text_Paint_Limit( float *maxX, float x, float y, float scale,
+ vec4_t color, const char* text, float adjust, int limit )
+{
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph;
+
+ if( text )
+ {
+// TTimo: FIXME
+// const unsigned char *s = text; // bk001206 - unsigned
+ const char *s = text;
+ float max = *maxX;
+ float useScale;
+ fontInfo_t *font = &cgDC.Assets.textFont;
+
+ if( scale <= cg_smallFont.value )
+ font = &cgDC.Assets.smallFont;
+ else if( scale > cg_bigFont.value )
+ font = &cgDC.Assets.bigFont;
+
+ useScale = scale * font->glyphScale;
+ trap_R_SetColor( color );
+ len = strlen( text );
+
+ if( limit > 0 && len > limit )
+ len = limit;
+
+ count = 0;
+
+ while( s && *s && count < len )
+ {
+ glyph = &font->glyphs[ (int)*s ];
+ //TTimo: FIXME: getting nasty warnings without the cast,
+ //hopefully this doesn't break the VM build
+
+ if( Q_IsColorString( s ) )
+ {
+ memcpy( newColor, g_color_table[ ColorIndex( *(s+1) ) ], sizeof( newColor ) );
+ newColor[ 3 ] = color[ 3 ];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ }
+ else
+ {
+ float yadj = useScale * glyph->top;
+
+ if( CG_Text_Width( s, useScale, 1 ) + x > max )
+ {
+ *maxX = 0;
+ break;
+ }
+
+ CG_Text_PaintChar( x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+ x += ( glyph->xSkip * useScale ) + adjust;
+ *maxX = x;
+ count++;
+ s++;
+ }
+ }
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+static void CG_DrawTeamSpectators( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader )
+{
+ if( cg.spectatorLen )
+ {
+ float maxX;
+
+ if( cg.spectatorWidth == -1 )
+ {
+ cg.spectatorWidth = 0;
+ cg.spectatorPaintX = rect->x + 1;
+ cg.spectatorPaintX2 = -1;
+ }
+
+ if( cg.spectatorOffset > cg.spectatorLen )
+ {
+ cg.spectatorOffset = 0;
+ cg.spectatorPaintX = rect->x + 1;
+ cg.spectatorPaintX2 = -1;
+ }
+
+ if( cg.time > cg.spectatorTime )
+ {
+ cg.spectatorTime = cg.time + 10;
+
+ if( cg.spectatorPaintX <= rect->x + 2 )
+ {
+ if( cg.spectatorOffset < cg.spectatorLen )
+ {
+ //TA: skip colour directives
+ if( Q_IsColorString( &cg.spectatorList[ cg.spectatorOffset ] ) )
+ cg.spectatorOffset += 2;
+ else
+ {
+ cg.spectatorPaintX += CG_Text_Width( &cg.spectatorList[ cg.spectatorOffset ], scale, 1 ) - 1;
+ cg.spectatorOffset++;
+ }
+ }
+ else
+ {
+ cg.spectatorOffset = 0;
+
+ if( cg.spectatorPaintX2 >= 0 )
+ cg.spectatorPaintX = cg.spectatorPaintX2;
+ else
+ cg.spectatorPaintX = rect->x + rect->w - 2;
+
+ cg.spectatorPaintX2 = -1;
+ }
+ }
+ else
+ {
+ cg.spectatorPaintX--;
+
+ if( cg.spectatorPaintX2 >= 0 )
+ cg.spectatorPaintX2--;
+ }
+ }
+
+ maxX = rect->x + rect->w - 2;
+
+ CG_Text_Paint_Limit( &maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color,
+ &cg.spectatorList[ cg.spectatorOffset ], 0, 0 );
+
+ if( cg.spectatorPaintX2 >= 0 )
+ {
+ float maxX2 = rect->x + rect->w - 2;
+ CG_Text_Paint_Limit( &maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale,
+ color, cg.spectatorList, 0, cg.spectatorOffset );
+ }
+
+ if( cg.spectatorOffset && maxX > 0 )
+ {
+ // if we have an offset ( we are skipping the first part of the string ) and we fit the string
+ if( cg.spectatorPaintX2 == -1 )
+ cg.spectatorPaintX2 = rect->x + rect->w - 2;
+ }
+ else
+ cg.spectatorPaintX2 = -1;
+ }
+}
+
+/*
+==================
+CG_DrawStageReport
+==================
+*/
+static void CG_DrawStageReport( rectDef_t *rect, float text_x, float text_y,
+ vec4_t color, float scale, int align, int textStyle )
+{
+ char s[ MAX_TOKEN_CHARS ];
+ int tx, w, kills;
+
+ if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_NONE && !cg.intermissionStarted )
+ return;
+
+ if( cg.intermissionStarted )
+ {
+ Com_sprintf( s, MAX_TOKEN_CHARS,
+ "Stage %d" //PH34R MY MAD-LEET CODING SKILLZ
+ " "
+ "Stage %d",
+ cgs.alienStage + 1, cgs.humanStage + 1 );
+ }
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ kills = cgs.alienNextStageThreshold - cgs.alienKills;
+
+ if( cgs.alienNextStageThreshold < 0 )
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d", cgs.alienStage + 1 );
+ else if( kills == 1 )
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kill for next stage",
+ cgs.alienStage + 1, kills );
+ else
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage",
+ cgs.alienStage + 1, kills );
+ }
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ kills = cgs.humanNextStageThreshold - cgs.humanKills;
+
+ if( cgs.humanNextStageThreshold < 0 )
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d", cgs.humanStage + 1 );
+ else if( kills == 1 )
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kill for next stage",
+ cgs.humanStage + 1, kills );
+ else
+ Com_sprintf( s, MAX_TOKEN_CHARS, "Stage %d, %d kills for next stage",
+ cgs.humanStage + 1, kills );
+ }
+
+ w = CG_Text_Width( s, scale, 0 );
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - w;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( w / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ CG_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle );
+}
+
+/*
+==================
+CG_DrawFPS
+==================
+*/
+//TA: personally i think this should be longer - it should really be a cvar
+#define FPS_FRAMES 20
+#define FPS_STRING "fps"
+static void CG_DrawFPS( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int align, int textStyle,
+ qboolean scalableText )
+{
+ char *s;
+ int tx, w, totalWidth, strLength;
+ static int previousTimes[ FPS_FRAMES ];
+ static int index;
+ int i, total;
+ int fps;
+ static int previous;
+ int t, frameTime;
+
+ if( !cg_drawFPS.integer )
+ return;
+
+ // don't use serverTime, because that will be drifting to
+ // correct for internet lag changes, timescales, timedemos, etc
+ t = trap_Milliseconds( );
+ frameTime = t - previous;
+ previous = t;
+
+ previousTimes[ index % FPS_FRAMES ] = frameTime;
+ index++;
+
+ if( index > FPS_FRAMES )
+ {
+ // average multiple frames together to smooth changes out a bit
+ total = 0;
+
+ for( i = 0 ; i < FPS_FRAMES ; i++ )
+ total += previousTimes[ i ];
+
+ if( !total )
+ total = 1;
+
+ fps = 1000 * FPS_FRAMES / total;
+
+ s = va( "%d", fps );
+ w = CG_Text_Width( "0", scale, 0 );
+ strLength = CG_DrawStrlen( s );
+ totalWidth = CG_Text_Width( FPS_STRING, scale, 0 ) + w * strLength;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - totalWidth;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ if( scalableText )
+ {
+ for( i = 0; i < strLength; i++ )
+ {
+ char c[ 2 ];
+
+ c[ 0 ] = s[ i ];
+ c[ 1 ] = '\0';
+
+ CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle );
+ }
+ }
+ else
+ {
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, fps );
+ trap_R_SetColor( NULL );
+ }
+
+ if( scalableText )
+ CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, FPS_STRING, 0, 0, textStyle );
+ }
+}
+
+
+/*
+=================
+CG_DrawTimerMins
+=================
+*/
+static void CG_DrawTimerMins( rectDef_t *rect, vec4_t color )
+{
+ int mins, seconds;
+ int msec;
+
+ if( !cg_drawTimer.integer )
+ return;
+
+ msec = cg.time - cgs.levelStartTime;
+
+ seconds = msec / 1000;
+ mins = seconds / 60;
+ seconds -= mins * 60;
+
+ trap_R_SetColor( color );
+ CG_DrawField( rect->x, rect->y, 3, rect->w / 3, rect->h, mins );
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+=================
+CG_DrawTimerSecs
+=================
+*/
+static void CG_DrawTimerSecs( rectDef_t *rect, vec4_t color )
+{
+ int mins, seconds;
+ int msec;
+
+ if( !cg_drawTimer.integer )
+ return;
+
+ msec = cg.time - cgs.levelStartTime;
+
+ seconds = msec / 1000;
+ mins = seconds / 60;
+ seconds -= mins * 60;
+
+ trap_R_SetColor( color );
+ CG_DrawFieldPadded( rect->x, rect->y, 2, rect->w / 2, rect->h, seconds );
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+=================
+CG_DrawTimer
+=================
+*/
+static void CG_DrawTimer( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int align, int textStyle )
+{
+ char *s;
+ int i, tx, w, totalWidth, strLength;
+ int mins, seconds, tens;
+ int msec;
+
+ if( !cg_drawTimer.integer )
+ return;
+
+ msec = cg.time - cgs.levelStartTime;
+
+ seconds = msec / 1000;
+ mins = seconds / 60;
+ seconds -= mins * 60;
+ tens = seconds / 10;
+ seconds -= tens * 10;
+
+ s = va( "%d:%d%d", mins, tens, seconds );
+ w = CG_Text_Width( "0", scale, 0 );
+ strLength = CG_DrawStrlen( s );
+ totalWidth = w * strLength;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - totalWidth;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ for( i = 0; i < strLength; i++ )
+ {
+ char c[ 2 ];
+
+ c[ 0 ] = s[ i ];
+ c[ 1 ] = '\0';
+
+ CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle );
+ }
+}
+
+/*
+=================
+CG_DrawClock
+=================
+*/
+static void CG_DrawClock( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int align, int textStyle )
+{
+ char *s;
+ int i, tx, w, totalWidth, strLength;
+ qtime_t qt;
+ int t;
+
+ if( !cg_drawClock.integer )
+ return;
+
+ t = trap_RealTime( &qt );
+
+ if( cg_drawClock.integer == 2 )
+ {
+ s = va( "%02d%s%02d", qt.tm_hour, ( qt.tm_sec % 2 ) ? ":" : " ",
+ qt.tm_min );
+ }
+ else
+ {
+ char *pm = "am";
+ int h = qt.tm_hour;
+
+ if( h == 0 )
+ h = 12;
+ else if( h == 12 )
+ pm = "pm";
+ else if( h > 12 )
+ {
+ h -= 12;
+ pm = "pm";
+ }
+
+ s = va( "%d%s%02d%s", h, ( qt.tm_sec % 2 ) ? ":" : " ", qt.tm_min, pm );
+ }
+ w = CG_Text_Width( "0", scale, 0 );
+ strLength = CG_DrawStrlen( s );
+ totalWidth = w * strLength;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - totalWidth;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( totalWidth / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ for( i = 0; i < strLength; i++ )
+ {
+ char c[ 2 ];
+
+ c[ 0 ] = s[ i ];
+ c[ 1 ] = '\0';
+
+ CG_Text_Paint( text_x + tx + i * w, rect->y + text_y, scale, color, c, 0, 0, textStyle );
+ }
+}
+
+/*
+==================
+CG_DrawSnapshot
+==================
+*/
+static void CG_DrawSnapshot( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int align, int textStyle )
+{
+ char *s;
+ int w, tx;
+
+ if( !cg_drawSnapshot.integer )
+ return;
+
+ s = va( "time:%d snap:%d cmd:%d", cg.snap->serverTime,
+ cg.latestSnapshotNum, cgs.serverCommandSequence );
+ w = CG_Text_Width( s, scale, 0 );
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ tx = rect->x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ tx = rect->x + rect->w - w;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ tx = rect->x + ( rect->w / 2.0f ) - ( w / 2.0f );
+ break;
+
+ default:
+ tx = 0.0f;
+ }
+
+ CG_Text_Paint( text_x + tx, rect->y + text_y, scale, color, s, 0, 0, textStyle );
+}
+
+/*
+===============================================================================
+
+LAGOMETER
+
+===============================================================================
+*/
+
+#define LAG_SAMPLES 128
+
+typedef struct
+{
+ int frameSamples[ LAG_SAMPLES ];
+ int frameCount;
+ int snapshotFlags[ LAG_SAMPLES ];
+ int snapshotSamples[ LAG_SAMPLES ];
+ int snapshotCount;
+} lagometer_t;
+
+lagometer_t lagometer;
+
+/*
+==============
+CG_AddLagometerFrameInfo
+
+Adds the current interpolate / extrapolate bar for this frame
+==============
+*/
+void CG_AddLagometerFrameInfo( void )
+{
+ int offset;
+
+ offset = cg.time - cg.latestSnapshotTime;
+ lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1 ) ] = offset;
+ lagometer.frameCount++;
+}
+
+/*
+==============
+CG_AddLagometerSnapshotInfo
+
+Each time a snapshot is received, log its ping time and
+the number of snapshots that were dropped before it.
+
+Pass NULL for a dropped packet.
+==============
+*/
+#define PING_FRAMES 40
+void CG_AddLagometerSnapshotInfo( snapshot_t *snap )
+{
+ static int previousPings[ PING_FRAMES ];
+ static int index;
+ int i;
+
+ // dropped packet
+ if( !snap )
+ {
+ lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = -1;
+ lagometer.snapshotCount++;
+ return;
+ }
+
+ // add this snapshot's info
+ lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->ping;
+ lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1 ) ] = snap->snapFlags;
+ lagometer.snapshotCount++;
+
+ cg.ping = 0;
+ if( cg.snap )
+ {
+ previousPings[ index++ ] = cg.snap->ping;
+ index = index % PING_FRAMES;
+
+ for( i = 0; i < PING_FRAMES; i++ )
+ {
+ cg.ping += previousPings[ i ];
+ }
+
+ cg.ping /= PING_FRAMES;
+ }
+}
+
+/*
+==============
+CG_DrawDisconnect
+
+Should we draw something differnet for long lag vs no packets?
+==============
+*/
+static void CG_DrawDisconnect( void )
+{
+ float x, y;
+ int cmdNum;
+ usercmd_t cmd;
+ const char *s;
+ int w;
+ vec4_t color = { 1.0f, 1.0f, 1.0f, 1.0f };
+
+ // draw the phone jack if we are completely past our buffers
+ cmdNum = trap_GetCurrentCmdNumber( ) - CMD_BACKUP + 1;
+ trap_GetUserCmd( cmdNum, &cmd );
+
+ // special check for map_restart
+ if( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time )
+ return;
+
+ // also add text in center of screen
+ s = "Connection Interrupted";
+ w = CG_Text_Width( s, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 100, 0.7f, color, s, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+
+ // blink the icon
+ if( ( cg.time >> 9 ) & 1 )
+ return;
+
+ x = 640 - 48;
+ y = 480 - 48;
+
+ CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader( "gfx/2d/net.tga" ) );
+}
+
+#define MAX_LAGOMETER_PING 900
+#define MAX_LAGOMETER_RANGE 300
+
+
+/*
+==============
+CG_DrawLagometer
+==============
+*/
+static void CG_DrawLagometer( rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t textColor )
+{
+ int a, x, y, i;
+ float v;
+ float ax, ay, aw, ah, mid, range;
+ int color;
+ vec4_t adjustedColor;
+ float vscale;
+ vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f };
+
+ if( cg.snap->ps.pm_type == PM_INTERMISSION )
+ return;
+
+ if( !cg_lagometer.integer )
+ return;
+
+ if( cg.demoPlayback )
+ return;
+
+ Vector4Copy( textColor, adjustedColor );
+ adjustedColor[ 3 ] = 0.25f;
+
+ trap_R_SetColor( adjustedColor );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.whiteShader );
+ trap_R_SetColor( NULL );
+
+ //
+ // draw the graph
+ //
+ ax = x = rect->x;
+ ay = y = rect->y;
+ aw = rect->w;
+ ah = rect->h;
+
+ trap_R_SetColor( NULL );
+
+ CG_AdjustFrom640( &ax, &ay, &aw, &ah );
+
+ color = -1;
+ range = ah / 3;
+ mid = ay + range;
+
+ vscale = range / MAX_LAGOMETER_RANGE;
+
+ // draw the frame interpoalte / extrapolate graph
+ for( a = 0 ; a < aw ; a++ )
+ {
+ i = ( lagometer.frameCount - 1 - a ) & ( LAG_SAMPLES - 1 );
+ v = lagometer.frameSamples[ i ];
+ v *= vscale;
+
+ if( v > 0 )
+ {
+ if( color != 1 )
+ {
+ color = 1;
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] );
+ }
+
+ if( v > range )
+ v = range;
+
+ trap_R_DrawStretchPic( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
+ }
+ else if( v < 0 )
+ {
+ if( color != 2 )
+ {
+ color = 2;
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_BLUE ) ] );
+ }
+
+ v = -v;
+ if( v > range )
+ v = range;
+
+ trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
+ }
+ }
+
+ // draw the snapshot latency / drop graph
+ range = ah / 2;
+ vscale = range / MAX_LAGOMETER_PING;
+
+ for( a = 0 ; a < aw ; a++ )
+ {
+ i = ( lagometer.snapshotCount - 1 - a ) & ( LAG_SAMPLES - 1 );
+ v = lagometer.snapshotSamples[ i ];
+
+ if( v > 0 )
+ {
+ if( lagometer.snapshotFlags[ i ] & SNAPFLAG_RATE_DELAYED )
+ {
+ if( color != 5 )
+ {
+ color = 5; // YELLOW for rate delay
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_YELLOW ) ] );
+ }
+ }
+ else
+ {
+ if( color != 3 )
+ {
+ color = 3;
+
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_GREEN ) ] );
+ }
+ }
+
+ v = v * vscale;
+
+ if( v > range )
+ v = range;
+
+ trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
+ }
+ else if( v < 0 )
+ {
+ if( color != 4 )
+ {
+ color = 4; // RED for dropped snapshots
+ trap_R_SetColor( g_color_table[ ColorIndex( COLOR_RED ) ] );
+ }
+
+ trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader );
+ }
+ }
+
+ trap_R_SetColor( NULL );
+
+ if( cg_nopredict.integer || cg_synchronousClients.integer )
+ CG_Text_Paint( ax, ay, 0.5, white, "snc", 0, 0, ITEM_TEXTSTYLE_NORMAL );
+ else
+ {
+ char *s;
+
+ s = va( "%d", cg.ping );
+ ax = rect->x + ( rect->w / 2.0f ) - ( CG_Text_Width( s, scale, 0 ) / 2.0f ) + text_x;
+ ay = rect->y + ( rect->h / 2.0f ) + ( CG_Text_Height( s, scale, 0 ) / 2.0f ) + text_y;
+
+ Vector4Copy( textColor, adjustedColor );
+ adjustedColor[ 3 ] = 0.5f;
+ CG_Text_Paint( ax, ay, scale, adjustedColor, s, 0, 0, ITEM_TEXTSTYLE_NORMAL );
+ }
+
+ CG_DrawDisconnect( );
+}
+
+/*
+==============
+CG_DrawTextBlock
+==============
+*/
+static void CG_DrawTextBlock( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, int textStyle, const char *text,
+ menuDef_t *parent, itemDef_t *textItem )
+{
+ float x, y, w, h;
+
+ //offset the text
+ x = rect->x;
+ y = rect->y;
+ w = rect->w - ( 16 + ( 2 * text_x ) ); //16 to ensure text within frame
+ h = rect->h;
+
+ textItem->text = text;
+
+ textItem->parent = parent;
+ memcpy( textItem->window.foreColor, color, sizeof( vec4_t ) );
+ textItem->window.flags = 0;
+
+ switch( align )
+ {
+ case ITEM_ALIGN_LEFT:
+ textItem->window.rect.x = x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ textItem->window.rect.x = x + w;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ textItem->window.rect.x = x + ( w / 2 );
+ break;
+
+ default:
+ textItem->window.rect.x = x;
+ break;
+ }
+
+ textItem->window.rect.y = y;
+ textItem->window.rect.w = w;
+ textItem->window.rect.h = h;
+ textItem->window.borderSize = 0;
+ textItem->textRect.x = 0;
+ textItem->textRect.y = 0;
+ textItem->textRect.w = 0;
+ textItem->textRect.h = 0;
+ textItem->textalignment = align;
+ textItem->textalignx = text_x;
+ textItem->textaligny = text_y;
+ textItem->textscale = scale;
+ textItem->textStyle = textStyle;
+
+ //hack to utilise existing autowrap code
+ Item_Text_AutoWrapped_Paint( textItem );
+}
+
+/*
+===================
+CG_DrawConsole
+===================
+*/
+static void CG_DrawConsole( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, int textStyle )
+{
+ static menuDef_t dummyParent;
+ static itemDef_t textItem;
+
+ CG_DrawTextBlock( rect, text_x, text_y, color, scale, align, textStyle,
+ cg.consoleText, &dummyParent, &textItem );
+}
+
+/*
+===================
+CG_DrawTutorial
+===================
+*/
+static void CG_DrawTutorial( rectDef_t *rect, float text_x, float text_y, vec4_t color,
+ float scale, int align, int textStyle )
+{
+ static menuDef_t dummyParent;
+ static itemDef_t textItem;
+
+ if( !cg_tutorial.integer )
+ return;
+
+ CG_DrawTextBlock( rect, text_x, text_y, color, scale, align, textStyle,
+ CG_TutorialText( ), &dummyParent, &textItem );
+}
+
+/*
+===================
+CG_DrawWeaponIcon
+===================
+*/
+void CG_DrawWeaponIcon( rectDef_t *rect, vec4_t color )
+{
+ int ammo, clips, maxAmmo;
+ centity_t *cent;
+ playerState_t *ps;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ BG_UnpackAmmoArray( cent->currentState.weapon, ps->ammo, ps->powerups, &ammo, &clips );
+ BG_FindAmmoForWeapon( cent->currentState.weapon, &maxAmmo, NULL );
+
+ // don't display if dead
+ if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ if( cent->currentState.weapon == 0 )
+ return;
+
+ CG_RegisterWeapon( cent->currentState.weapon );
+
+ if( clips == 0 && !BG_FindInfinteAmmoForWeapon( cent->currentState.weapon ) )
+ {
+ float ammoPercent = (float)ammo / (float)maxAmmo;
+
+ if( ammoPercent < 0.33f )
+ {
+ color[ 0 ] = 1.0f;
+ color[ 1 ] = color[ 2 ] = 0.0f;
+ }
+ }
+
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS && CG_AtHighestClass( ) )
+ {
+ if( cg.time - cg.lastEvolveAttempt <= NO_CREDITS_TIME )
+ {
+ if( ( ( cg.time - cg.lastEvolveAttempt ) / 300 ) % 2 )
+ color[ 3 ] = 0.0f;
+ }
+ }
+
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].weaponIcon );
+ trap_R_SetColor( NULL );
+}
+
+
+
+/*
+================================================================================
+
+CROSSHAIR
+
+================================================================================
+*/
+
+
+/*
+=================
+CG_DrawCrosshair
+=================
+*/
+static void CG_DrawCrosshair( void )
+{
+ float w, h;
+ qhandle_t hShader;
+ float x, y;
+ weaponInfo_t *wi;
+
+ if( cg_drawCrosshair.integer == CROSSHAIR_ALWAYSOFF )
+ return;
+
+ if( cg_drawCrosshair.integer == CROSSHAIR_RANGEDONLY &&
+ !BG_FindLongRangedForWeapon( cg.snap->ps.weapon ) )
+ {
+ return;
+ }
+
+ if( ( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) ||
+ ( cg.snap->ps.stats[ STAT_STATE ] & SS_INFESTING ) ||
+ ( cg.snap->ps.stats[ STAT_STATE ] & SS_HOVELING ) )
+ return;
+
+ if( cg.renderingThirdPerson )
+ return;
+
+ wi = &cg_weapons[ cg.snap->ps.weapon ];
+
+ w = h = wi->crossHairSize;
+
+ x = cg_crosshairX.integer;
+ y = cg_crosshairY.integer;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+
+ hShader = wi->crossHair;
+
+ if( hShader != 0 )
+ {
+ trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * ( cg.refdef.width - w ),
+ y + cg.refdef.y + 0.5 * ( cg.refdef.height - h ),
+ w, h, 0, 0, 1, 1, hShader );
+ }
+}
+
+
+
+/*
+=================
+CG_ScanForCrosshairEntity
+=================
+*/
+static void CG_ScanForCrosshairEntity( void )
+{
+ trace_t trace;
+ vec3_t start, end;
+ int content;
+ pTeam_t team;
+
+ VectorCopy( cg.refdef.vieworg, start );
+ VectorMA( start, 131072, cg.refdef.viewaxis[ 0 ], end );
+
+ CG_Trace( &trace, start, vec3_origin, vec3_origin, end,
+ cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY );
+
+ if( trace.entityNum >= MAX_CLIENTS )
+ return;
+
+ // if the player is in fog, don't show it
+ content = trap_CM_PointContents( trace.endpos, 0 );
+ if( content & CONTENTS_FOG )
+ return;
+
+ team = cgs.clientinfo[ trace.entityNum ].team;
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] != TEAM_SPECTATOR )
+ {
+ //only display team names of those on the same team as this player
+ if( team != cg.snap->ps.stats[ STAT_PTEAM ] )
+ return;
+ }
+
+ // update the fade timer
+ cg.crosshairClientNum = trace.entityNum;
+ cg.crosshairClientTime = cg.time;
+}
+
+
+/*
+=====================
+CG_DrawCrosshairNames
+=====================
+*/
+static void CG_DrawCrosshairNames( rectDef_t *rect, float scale, int textStyle )
+{
+ float *color;
+ char *name;
+ float w, x;
+
+ if( !cg_drawCrosshairNames.integer )
+ return;
+
+ if( cg.renderingThirdPerson )
+ return;
+
+ // scan the known entities to see if the crosshair is sighted on one
+ CG_ScanForCrosshairEntity( );
+
+ // draw the name of the player being looked at
+ color = CG_FadeColor( cg.crosshairClientTime, 1000 );
+ if( !color )
+ {
+ trap_R_SetColor( NULL );
+ return;
+ }
+
+ name = cgs.clientinfo[ cg.crosshairClientNum ].name;
+ w = CG_Text_Width( name, scale, 0 );
+ x = rect->x + rect->w / 2;
+ CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle );
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+===============
+CG_OwnerDraw
+
+Draw an owner drawn item
+===============
+*/
+void CG_OwnerDraw( float x, float y, float w, float h, float text_x,
+ float text_y, int ownerDraw, int ownerDrawFlags,
+ int align, float special, float scale, vec4_t color,
+ qhandle_t shader, int textStyle )
+{
+ rectDef_t rect;
+
+ if( cg_drawStatus.integer == 0 )
+ return;
+
+ rect.x = x;
+ rect.y = y;
+ rect.w = w;
+ rect.h = h;
+
+ switch( ownerDraw )
+ {
+ case CG_PLAYER_CREDITS_VALUE:
+ CG_DrawPlayerCreditsValue( &rect, color, qtrue );
+ break;
+ case CG_PLAYER_BANK_VALUE:
+ CG_DrawPlayerBankValue( &rect, color, qtrue );
+ break;
+ case CG_PLAYER_CREDITS_VALUE_NOPAD:
+ CG_DrawPlayerCreditsValue( &rect, color, qfalse );
+ break;
+ case CG_PLAYER_BANK_VALUE_NOPAD:
+ CG_DrawPlayerBankValue( &rect, color, qfalse );
+ break;
+ case CG_PLAYER_STAMINA:
+ CG_DrawPlayerStamina( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_PLAYER_STAMINA_1:
+ CG_DrawPlayerStamina1( &rect, color, shader );
+ break;
+ case CG_PLAYER_STAMINA_2:
+ CG_DrawPlayerStamina2( &rect, color, shader );
+ break;
+ case CG_PLAYER_STAMINA_3:
+ CG_DrawPlayerStamina3( &rect, color, shader );
+ break;
+ case CG_PLAYER_STAMINA_4:
+ CG_DrawPlayerStamina4( &rect, color, shader );
+ break;
+ case CG_PLAYER_STAMINA_BOLT:
+ CG_DrawPlayerStaminaBolt( &rect, color, shader );
+ break;
+ case CG_PLAYER_AMMO_VALUE:
+ CG_DrawPlayerAmmoValue( &rect, color );
+ break;
+ case CG_PLAYER_CLIPS_VALUE:
+ CG_DrawPlayerClipsValue( &rect, color );
+ break;
+ case CG_PLAYER_BUILD_TIMER:
+ CG_DrawPlayerBuildTimer( &rect, color );
+ break;
+ case CG_PLAYER_HEALTH:
+ CG_DrawPlayerHealthValue( &rect, color );
+ break;
+ case CG_PLAYER_HEALTH_BAR:
+ CG_DrawPlayerHealthBar( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_PLAYER_HEALTH_CROSS:
+ CG_DrawPlayerHealthCross( &rect, color, shader );
+ break;
+ case CG_PLAYER_CLIPS_RING:
+ CG_DrawPlayerClipsRing( &rect, color, shader );
+ break;
+ case CG_PLAYER_BUILD_TIMER_RING:
+ CG_DrawPlayerBuildTimerRing( &rect, color, shader );
+ break;
+ case CG_PLAYER_WALLCLIMBING:
+ CG_DrawPlayerWallclimbing( &rect, color, shader );
+ break;
+ case CG_PLAYER_BOOSTED:
+ CG_DrawPlayerBoosted( &rect, color, shader );
+ break;
+ case CG_PLAYER_BOOST_BOLT:
+ CG_DrawPlayerBoosterBolt( &rect, color, shader );
+ break;
+ case CG_PLAYER_POISON_BARBS:
+ CG_DrawPlayerPoisonBarbs( &rect, color, shader );
+ break;
+ case CG_PLAYER_ALIEN_SENSE:
+ CG_DrawAlienSense( &rect );
+ break;
+ case CG_PLAYER_HUMAN_SCANNER:
+ CG_DrawHumanScanner( &rect, shader, color );
+ break;
+ case CG_PLAYER_USABLE_BUILDABLE:
+ CG_DrawUsableBuildable( &rect, shader, color );
+ break;
+ case CG_KILLER:
+ CG_DrawKiller( &rect, scale, color, shader, textStyle );
+ break;
+ case CG_PLAYER_SELECT:
+ CG_DrawItemSelect( &rect, color );
+ break;
+ case CG_PLAYER_WEAPONICON:
+ CG_DrawWeaponIcon( &rect, color );
+ break;
+ case CG_PLAYER_SELECTTEXT:
+ CG_DrawItemSelectText( &rect, scale, textStyle );
+ break;
+ case CG_SPECTATORS:
+ CG_DrawTeamSpectators( &rect, scale, color, shader );
+ break;
+ case CG_PLAYER_CROSSHAIRNAMES:
+ CG_DrawCrosshairNames( &rect, scale, textStyle );
+ break;
+ case CG_STAGE_REPORT_TEXT:
+ CG_DrawStageReport( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+
+ //loading screen
+ case CG_LOAD_LEVELSHOT:
+ CG_DrawLevelShot( &rect );
+ break;
+ case CG_LOAD_MEDIA:
+ CG_DrawMediaProgress( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_LOAD_MEDIA_LABEL:
+ CG_DrawMediaProgressLabel( &rect, text_x, text_y, color, scale, align );
+ break;
+ case CG_LOAD_BUILDABLES:
+ CG_DrawBuildablesProgress( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_LOAD_BUILDABLES_LABEL:
+ CG_DrawBuildablesProgressLabel( &rect, text_x, text_y, color, scale, align );
+ break;
+ case CG_LOAD_CHARMODEL:
+ CG_DrawCharModelProgress( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_LOAD_CHARMODEL_LABEL:
+ CG_DrawCharModelProgressLabel( &rect, text_x, text_y, color, scale, align );
+ break;
+ case CG_LOAD_OVERALL:
+ CG_DrawOverallProgress( &rect, color, scale, align, textStyle, special );
+ break;
+ case CG_LOAD_LEVELNAME:
+ CG_DrawLevelName( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+ case CG_LOAD_MOTD:
+ CG_DrawMOTD( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+ case CG_LOAD_HOSTNAME:
+ CG_DrawHostname( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+
+ case CG_FPS:
+ CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qtrue );
+ break;
+ case CG_FPS_FIXED:
+ CG_DrawFPS( &rect, text_x, text_y, scale, color, align, textStyle, qfalse );
+ break;
+ case CG_TIMER:
+ CG_DrawTimer( &rect, text_x, text_y, scale, color, align, textStyle );
+ break;
+ case CG_CLOCK:
+ CG_DrawClock( &rect, text_x, text_y, scale, color, align, textStyle );
+ break;
+ case CG_TIMER_MINS:
+ CG_DrawTimerMins( &rect, color );
+ break;
+ case CG_TIMER_SECS:
+ CG_DrawTimerSecs( &rect, color );
+ break;
+ case CG_SNAPSHOT:
+ CG_DrawSnapshot( &rect, text_x, text_y, scale, color, align, textStyle );
+ break;
+ case CG_LAGOMETER:
+ CG_DrawLagometer( &rect, text_x, text_y, scale, color );
+ break;
+
+ case CG_DEMO_PLAYBACK:
+ CG_DrawDemoPlayback( &rect, color, shader );
+ break;
+ case CG_DEMO_RECORDING:
+ CG_DrawDemoRecording( &rect, color, shader );
+ break;
+
+ case CG_CONSOLE:
+ CG_DrawConsole( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+
+ case CG_TUTORIAL:
+ CG_DrawTutorial( &rect, text_x, text_y, color, scale, align, textStyle );
+ break;
+
+ default:
+ break;
+ }
+}
+
+void CG_MouseEvent( int x, int y )
+{
+ int n;
+
+ if( ( cg.predictedPlayerState.pm_type == PM_NORMAL ||
+ cg.predictedPlayerState.pm_type == PM_SPECTATOR ) &&
+ cg.showScores == qfalse )
+ {
+ trap_Key_SetCatcher( 0 );
+ return;
+ }
+
+ cgs.cursorX += x;
+ if( cgs.cursorX < 0 )
+ cgs.cursorX = 0;
+ else if( cgs.cursorX > 640 )
+ cgs.cursorX = 640;
+
+ cgs.cursorY += y;
+ if( cgs.cursorY < 0 )
+ cgs.cursorY = 0;
+ else if( cgs.cursorY > 480 )
+ cgs.cursorY = 480;
+
+ n = Display_CursorType( cgs.cursorX, cgs.cursorY );
+ cgs.activeCursor = 0;
+ if( n == CURSOR_ARROW )
+ cgs.activeCursor = cgs.media.selectCursor;
+ else if( n == CURSOR_SIZER )
+ cgs.activeCursor = cgs.media.sizeCursor;
+
+ if( cgs.capturedItem )
+ Display_MouseMove( cgs.capturedItem, x, y );
+ else
+ Display_MouseMove( NULL, cgs.cursorX, cgs.cursorY );
+}
+
+/*
+==================
+CG_HideTeamMenus
+==================
+
+*/
+void CG_HideTeamMenu( void )
+{
+ Menus_CloseByName( "teamMenu" );
+ Menus_CloseByName( "getMenu" );
+}
+
+/*
+==================
+CG_ShowTeamMenus
+==================
+
+*/
+void CG_ShowTeamMenu( void )
+{
+ Menus_OpenByName( "teamMenu" );
+}
+
+/*
+==================
+CG_EventHandling
+==================
+ type 0 - no event handling
+ 1 - team menu
+ 2 - hud editor
+
+*/
+void CG_EventHandling( int type )
+{
+ cgs.eventHandling = type;
+
+ if( type == CGAME_EVENT_NONE )
+ CG_HideTeamMenu( );
+}
+
+
+
+void CG_KeyEvent( int key, qboolean down )
+{
+ if( !down )
+ return;
+
+ if( cg.predictedPlayerState.pm_type == PM_NORMAL ||
+ ( cg.predictedPlayerState.pm_type == PM_SPECTATOR &&
+ cg.showScores == qfalse ) )
+ {
+ CG_EventHandling( CGAME_EVENT_NONE );
+ trap_Key_SetCatcher( 0 );
+ return;
+ }
+
+ Display_HandleKey( key, down, cgs.cursorX, cgs.cursorY );
+
+ if( cgs.capturedItem )
+ cgs.capturedItem = NULL;
+ else
+ {
+ if( key == K_MOUSE2 && down )
+ cgs.capturedItem = Display_CaptureItem( cgs.cursorX, cgs.cursorY );
+ }
+}
+
+int CG_ClientNumFromName( const char *p )
+{
+ int i;
+
+ for( i = 0; i < cgs.maxclients; i++ )
+ {
+ if( cgs.clientinfo[ i ].infoValid &&
+ Q_stricmp( cgs.clientinfo[ i ].name, p ) == 0 )
+ return i;
+ }
+
+ return -1;
+}
+
+void CG_RunMenuScript( char **args )
+{
+}
+
+
+void CG_GetTeamColor( vec4_t *color )
+{
+ (*color)[ 0 ] = (*color)[ 2 ] = 0.0f;
+ (*color)[ 1 ] = 0.17f;
+ (*color)[ 3 ] = 0.25f;
+}
+//END TA UI
+
+
+/*
+================
+CG_DrawLighting
+
+================
+*/
+static void CG_DrawLighting( void )
+{
+ centity_t *cent;
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+
+ //fade to black if stamina is low
+ if( ( cg.snap->ps.stats[ STAT_STAMINA ] < -800 ) &&
+ ( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) )
+ {
+ vec4_t black = { 0, 0, 0, 0 };
+ black[ 3 ] = 1.0 - ( (float)( cg.snap->ps.stats[ STAT_STAMINA ] + 1000 ) / 200.0f );
+ trap_R_SetColor( black );
+ CG_DrawPic( 0, 0, 640, 480, cgs.media.whiteShader );
+ trap_R_SetColor( NULL );
+ }
+}
+
+/*
+===============================================================================
+
+CENTER PRINTING
+
+===============================================================================
+*/
+
+
+/*
+==============
+CG_CenterPrint
+
+Called for important messages that should stay in the center of the screen
+for a few moments
+==============
+*/
+void CG_CenterPrint( const char *str, int y, int charWidth )
+{
+ char *s;
+
+ Q_strncpyz( cg.centerPrint, str, sizeof( cg.centerPrint ) );
+
+ cg.centerPrintTime = cg.time;
+ cg.centerPrintY = y;
+ cg.centerPrintCharWidth = charWidth;
+
+ // count the number of lines for centering
+ cg.centerPrintLines = 1;
+ s = cg.centerPrint;
+ while( *s )
+ {
+ if( *s == '\n' )
+ cg.centerPrintLines++;
+
+ s++;
+ }
+}
+
+
+/*
+===================
+CG_DrawCenterString
+===================
+*/
+static void CG_DrawCenterString( void )
+{
+ char *start;
+ int l;
+ int x, y, w;
+ int h;
+ float *color;
+
+ if( !cg.centerPrintTime )
+ return;
+
+ color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value );
+ if( !color )
+ return;
+
+ trap_R_SetColor( color );
+
+ start = cg.centerPrint;
+
+ y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2;
+
+ while( 1 )
+ {
+ char linebuffer[ 1024 ];
+
+ for( l = 0; l < 50; l++ )
+ {
+ if( !start[ l ] || start[ l ] == '\n' )
+ break;
+
+ linebuffer[ l ] = start[ l ];
+ }
+
+ linebuffer[ l ] = 0;
+
+ w = CG_Text_Width( linebuffer, 0.5, 0 );
+ h = CG_Text_Height( linebuffer, 0.5, 0 );
+ x = ( SCREEN_WIDTH - w ) / 2;
+ CG_Text_Paint( x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE );
+ y += h + 6;
+
+ while( *start && ( *start != '\n' ) )
+ start++;
+
+ if( !*start )
+ break;
+
+ start++;
+ }
+
+ trap_R_SetColor( NULL );
+}
+
+
+
+
+
+//==============================================================================
+
+//FIXME: both vote notes are hardcoded, change to ownerdrawn?
+
+/*
+=================
+CG_DrawVote
+=================
+*/
+static void CG_DrawVote( void )
+{
+ char *s;
+ int sec;
+ vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f };
+ char yeskey[ 32 ], nokey[ 32 ];
+
+ if( !cgs.voteTime )
+ return;
+
+ // play a talk beep whenever it is modified
+ if( cgs.voteModified )
+ {
+ cgs.voteModified = qfalse;
+ trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
+ }
+
+ sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000;
+
+ if( sec < 0 )
+ sec = 0;
+ Q_strncpyz( yeskey, CG_KeyBinding( "vote yes" ), sizeof( yeskey ) );
+ Q_strncpyz( nokey, CG_KeyBinding( "vote no" ), sizeof( nokey ) );
+ s = va( "VOTE(%i): \"%s\" [%s]Yes:%i [%s]No:%i", sec, cgs.voteString,
+ yeskey, cgs.voteYes, nokey, cgs.voteNo );
+ CG_Text_Paint( 8, 340, 0.3f, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL );
+}
+
+/*
+=================
+CG_DrawTeamVote
+=================
+*/
+static void CG_DrawTeamVote( void )
+{
+ char *s;
+ int sec, cs_offset;
+ vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f };
+ char yeskey[ 32 ], nokey[ 32 ];
+
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ cs_offset = 0;
+ else if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ cs_offset = 1;
+ else
+ return;
+
+ if( !cgs.teamVoteTime[ cs_offset ] )
+ return;
+
+ // play a talk beep whenever it is modified
+ if ( cgs.teamVoteModified[ cs_offset ] )
+ {
+ cgs.teamVoteModified[ cs_offset ] = qfalse;
+ trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
+ }
+
+ sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[ cs_offset ] ) ) / 1000;
+
+ if( sec < 0 )
+ sec = 0;
+
+ Q_strncpyz( yeskey, CG_KeyBinding( "teamvote yes" ), sizeof( yeskey ) );
+ Q_strncpyz( nokey, CG_KeyBinding( "teamvote no" ), sizeof( nokey ) );
+ s = va( "TEAMVOTE(%i): \"%s\" [%s]Yes:%i [%s]No:%i", sec,
+ cgs.teamVoteString[ cs_offset ],
+ yeskey, cgs.teamVoteYes[cs_offset],
+ nokey, cgs.teamVoteNo[ cs_offset ] );
+
+ CG_Text_Paint( 8, 360, 0.3f, white, s, 0, 0, ITEM_TEXTSTYLE_NORMAL );
+}
+
+
+static qboolean CG_DrawScoreboard( void )
+{
+ static qboolean firstTime = qtrue;
+ float fade, *fadeColor;
+
+ if( menuScoreboard )
+ menuScoreboard->window.flags &= ~WINDOW_FORCED;
+
+ if( cg_paused.integer )
+ {
+ cg.deferredPlayerLoading = 0;
+ firstTime = qtrue;
+ return qfalse;
+ }
+
+ if( cg.showScores ||
+ cg.predictedPlayerState.pm_type == PM_INTERMISSION )
+ {
+ fade = 1.0;
+ fadeColor = colorWhite;
+ }
+ else
+ {
+ cg.deferredPlayerLoading = 0;
+ cg.killerName[ 0 ] = 0;
+ firstTime = qtrue;
+ return qfalse;
+ }
+
+
+ if( menuScoreboard == NULL )
+ menuScoreboard = Menus_FindByName( "teamscore_menu" );
+
+ if( menuScoreboard )
+ {
+ if( firstTime )
+ {
+ CG_SetScoreSelection( menuScoreboard );
+ firstTime = qfalse;
+ }
+
+ Menu_Paint( menuScoreboard, qtrue );
+ }
+
+ return qtrue;
+}
+
+/*
+=================
+CG_DrawIntermission
+=================
+*/
+static void CG_DrawIntermission( void )
+{
+ if( cg_drawStatus.integer )
+ Menu_Paint( Menus_FindByName( "default_hud" ), qtrue );
+
+ cg.scoreFadeTime = cg.time;
+ cg.scoreBoardShowing = CG_DrawScoreboard( );
+}
+
+#define FOLLOWING_STRING "following "
+
+/*
+=================
+CG_DrawFollow
+=================
+*/
+static qboolean CG_DrawFollow( void )
+{
+ float w;
+ vec4_t color;
+ char buffer[ MAX_STRING_CHARS ];
+
+ if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ return qfalse;
+
+ color[ 0 ] = 1;
+ color[ 1 ] = 1;
+ color[ 2 ] = 1;
+ color[ 3 ] = 1;
+
+ strcpy( buffer, FOLLOWING_STRING );
+ strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name );
+
+ w = CG_Text_Width( buffer, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+
+ return qtrue;
+}
+
+/*
+=================
+CG_DrawQueue
+=================
+*/
+static qboolean CG_DrawQueue( void )
+{
+ float w;
+ vec4_t color;
+ char buffer[ MAX_STRING_CHARS ];
+
+ if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) )
+ return qfalse;
+
+ color[ 0 ] = 1;
+ color[ 1 ] = 1;
+ color[ 2 ] = 1;
+ color[ 3 ] = 1;
+
+ Com_sprintf( buffer, MAX_STRING_CHARS, "You are in position %d of the spawn queue.",
+ cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1 );
+
+ w = CG_Text_Width( buffer, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 360, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+
+ if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ if( cgs.numAlienSpawns == 1 )
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." );
+ else
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.",
+ cgs.numAlienSpawns );
+ }
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( cgs.numHumanSpawns == 1 )
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." );
+ else
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.",
+ cgs.numHumanSpawns );
+ }
+
+ w = CG_Text_Width( buffer, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+
+ return qtrue;
+}
+
+//==================================================================================
+
+#define SPECTATOR_STRING "SPECTATOR"
+/*
+=================
+CG_Draw2D
+=================
+*/
+static void CG_Draw2D( void )
+{
+ vec4_t color;
+ float w;
+ menuDef_t *menu = NULL, *defaultMenu;
+
+ color[ 0 ] = color[ 1 ] = color[ 2 ] = color[ 3 ] = 1.0f;
+
+ // if we are taking a levelshot for the menu, don't draw anything
+ if( cg.levelShot )
+ return;
+
+ if( cg_draw2D.integer == 0 )
+ return;
+
+ if( cg.snap->ps.pm_type == PM_INTERMISSION )
+ {
+ CG_DrawIntermission( );
+ return;
+ }
+
+ //TA: draw the lighting effects e.g. nvg
+ CG_DrawLighting( );
+
+
+ defaultMenu = Menus_FindByName( "default_hud" );
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ {
+ w = CG_Text_Width( SPECTATOR_STRING, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 440, 0.7f, color, SPECTATOR_STRING, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+ }
+ else
+ menu = Menus_FindByName( BG_FindHudNameForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] ) );
+
+ if( !( cg.snap->ps.stats[ STAT_STATE ] & SS_INFESTING ) &&
+ !( cg.snap->ps.stats[ STAT_STATE ] & SS_HOVELING ) && menu &&
+ ( cg.snap->ps.stats[ STAT_HEALTH ] > 0 ) )
+ {
+ CG_DrawBuildableStatus( );
+ if( cg_drawStatus.integer )
+ Menu_Paint( menu, qtrue );
+
+ CG_DrawCrosshair( );
+ }
+ else if( cg_drawStatus.integer )
+ Menu_Paint( defaultMenu, qtrue );
+
+ CG_DrawVote( );
+ CG_DrawTeamVote( );
+ CG_DrawFollow( );
+ CG_DrawQueue( );
+
+ // don't draw center string if scoreboard is up
+ cg.scoreBoardShowing = CG_DrawScoreboard( );
+
+ if( !cg.scoreBoardShowing )
+ CG_DrawCenterString( );
+}
+
+/*
+===============
+CG_ScalePainBlendTCs
+===============
+*/
+static void CG_ScalePainBlendTCs( float* s1, float *t1, float *s2, float *t2 )
+{
+ *s1 -= 0.5f;
+ *t1 -= 0.5f;
+ *s2 -= 0.5f;
+ *t2 -= 0.5f;
+
+ *s1 *= cg_painBlendZoom.value;
+ *t1 *= cg_painBlendZoom.value;
+ *s2 *= cg_painBlendZoom.value;
+ *t2 *= cg_painBlendZoom.value;
+
+ *s1 += 0.5f;
+ *t1 += 0.5f;
+ *s2 += 0.5f;
+ *t2 += 0.5f;
+}
+
+#define PAINBLEND_BORDER 0.15f
+
+/*
+===============
+CG_PainBlend
+===============
+*/
+static void CG_PainBlend( void )
+{
+ vec4_t color;
+ int damage;
+ float damageAsFracOfMax;
+ qhandle_t shader = cgs.media.viewBloodShader;
+ float x, y, w, h;
+ float s1, t1, s2, t2;
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR || cg.intermissionStarted )
+ return;
+
+ damage = cg.lastHealth - cg.snap->ps.stats[ STAT_HEALTH ];
+
+ if( damage < 0 )
+ damage = 0;
+
+ damageAsFracOfMax = (float)damage / cg.snap->ps.stats[ STAT_MAX_HEALTH ];
+ cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ];
+
+ cg.painBlendValue += damageAsFracOfMax * cg_painBlendScale.value;
+
+ if( cg.painBlendValue > 0.0f )
+ {
+ cg.painBlendValue -= ( cg.frametime / 1000.0f ) *
+ cg_painBlendDownRate.value;
+ }
+
+ if( cg.painBlendValue > 1.0f )
+ cg.painBlendValue = 1.0f;
+ else if( cg.painBlendValue <= 0.0f )
+ {
+ cg.painBlendValue = 0.0f;
+ return;
+ }
+
+ if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ VectorSet( color, 0.43f, 0.8f, 0.37f );
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ VectorSet( color, 0.8f, 0.0f, 0.0f );
+
+ if( cg.painBlendValue > cg.painBlendTarget )
+ {
+ cg.painBlendTarget += ( cg.frametime / 1000.0f ) *
+ cg_painBlendUpRate.value;
+ }
+ else if( cg.painBlendValue < cg.painBlendTarget )
+ cg.painBlendTarget = cg.painBlendValue;
+
+ if( cg.painBlendTarget > cg_painBlendMax.value )
+ cg.painBlendTarget = cg_painBlendMax.value;
+
+ color[ 3 ] = cg.painBlendTarget;
+
+ trap_R_SetColor( color );
+
+ //left
+ x = 0.0f; y = 0.0f;
+ w = PAINBLEND_BORDER * 640.0f; h = 480.0f;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ s1 = 0.0f; t1 = 0.0f;
+ s2 = PAINBLEND_BORDER; t2 = 1.0f;
+ CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );
+
+ //right
+ x = 640.0f - ( PAINBLEND_BORDER * 640.0f ); y = 0.0f;
+ w = PAINBLEND_BORDER * 640.0f; h = 480.0f;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ s1 = 1.0f - PAINBLEND_BORDER; t1 = 0.0f;
+ s2 = 1.0f; t2 =1.0f;
+ CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );
+
+ //top
+ x = PAINBLEND_BORDER * 640.0f; y = 0.0f;
+ w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ s1 = PAINBLEND_BORDER; t1 = 0.0f;
+ s2 = 1.0f - PAINBLEND_BORDER; t2 = PAINBLEND_BORDER;
+ CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );
+
+ //bottom
+ x = PAINBLEND_BORDER * 640.0f; y = 480.0f - ( PAINBLEND_BORDER * 480.0f );
+ w = 640.0f - ( 2 * PAINBLEND_BORDER * 640.0f ); h = PAINBLEND_BORDER * 480.0f;
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ s1 = PAINBLEND_BORDER; t1 = 1.0f - PAINBLEND_BORDER;
+ s2 = 1.0f - PAINBLEND_BORDER; t2 = 1.0f;
+ CG_ScalePainBlendTCs( &s1, &t1, &s2, &t2 );
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );
+
+ trap_R_SetColor( NULL );
+}
+
+/*
+=====================
+CG_ResetPainBlend
+=====================
+*/
+void CG_ResetPainBlend( void )
+{
+ cg.painBlendValue = 0.0f;
+ cg.painBlendTarget = 0.0f;
+ cg.lastHealth = cg.snap->ps.stats[ STAT_HEALTH ];
+}
+
+/*
+=====================
+CG_DrawActive
+
+Perform all drawing needed to completely fill the screen
+=====================
+*/
+void CG_DrawActive( stereoFrame_t stereoView )
+{
+ float separation;
+ vec3_t baseOrg;
+
+ // optionally draw the info screen instead
+ if( !cg.snap )
+ return;
+
+ switch ( stereoView )
+ {
+ case STEREO_CENTER:
+ separation = 0;
+ break;
+ case STEREO_LEFT:
+ separation = -cg_stereoSeparation.value / 2;
+ break;
+ case STEREO_RIGHT:
+ separation = cg_stereoSeparation.value / 2;
+ break;
+ default:
+ separation = 0;
+ CG_Error( "CG_DrawActive: Undefined stereoView" );
+ }
+
+ // clear around the rendered view if sized down
+ CG_TileClear( );
+
+ // offset vieworg appropriately if we're doing stereo separation
+ VectorCopy( cg.refdef.vieworg, baseOrg );
+
+ if( separation != 0 )
+ VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[ 1 ],
+ cg.refdef.vieworg );
+
+ // draw 3D view
+ trap_R_RenderScene( &cg.refdef );
+
+ // restore original viewpoint if running stereo
+ if( separation != 0 )
+ VectorCopy( baseOrg, cg.refdef.vieworg );
+
+ // first person blend blobs, done after AnglesToAxis
+ if( !cg.renderingThirdPerson )
+ CG_PainBlend( );
+
+ // draw status bar and other floating elements
+ CG_Draw2D( );
+}
+
+
+
diff --git a/src/cgame/cg_drawtools.c b/src/cgame/cg_drawtools.c
new file mode 100644
index 0000000..06ae071
--- /dev/null
+++ b/src/cgame/cg_drawtools.c
@@ -0,0 +1,378 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc
+
+
+#include "cg_local.h"
+
+/*
+===============
+CG_DrawPlane
+
+Draw a quad in 3 space - basically CG_DrawPic in 3 space
+===============
+*/
+void CG_DrawPlane( vec3_t origin, vec3_t down, vec3_t right, qhandle_t shader )
+{
+ polyVert_t verts[ 4 ];
+ vec3_t temp;
+
+ VectorCopy( origin, verts[ 0 ].xyz );
+ verts[ 0 ].st[ 0 ] = 0;
+ verts[ 0 ].st[ 1 ] = 0;
+ verts[ 0 ].modulate[ 0 ] = 255;
+ verts[ 0 ].modulate[ 1 ] = 255;
+ verts[ 0 ].modulate[ 2 ] = 255;
+ verts[ 0 ].modulate[ 3 ] = 255;
+
+ VectorAdd( origin, right, temp );
+ VectorCopy( temp, verts[ 1 ].xyz );
+ verts[ 1 ].st[ 0 ] = 1;
+ verts[ 1 ].st[ 1 ] = 0;
+ verts[ 1 ].modulate[ 0 ] = 255;
+ verts[ 1 ].modulate[ 1 ] = 255;
+ verts[ 1 ].modulate[ 2 ] = 255;
+ verts[ 1 ].modulate[ 3 ] = 255;
+
+ VectorAdd( origin, right, temp );
+ VectorAdd( temp, down, temp );
+ VectorCopy( temp, verts[ 2 ].xyz );
+ verts[ 2 ].st[ 0 ] = 1;
+ verts[ 2 ].st[ 1 ] = 1;
+ verts[ 2 ].modulate[ 0 ] = 255;
+ verts[ 2 ].modulate[ 1 ] = 255;
+ verts[ 2 ].modulate[ 2 ] = 255;
+ verts[ 2 ].modulate[ 3 ] = 255;
+
+ VectorAdd( origin, down, temp );
+ VectorCopy( temp, verts[ 3 ].xyz );
+ verts[ 3 ].st[ 0 ] = 0;
+ verts[ 3 ].st[ 1 ] = 1;
+ verts[ 3 ].modulate[ 0 ] = 255;
+ verts[ 3 ].modulate[ 1 ] = 255;
+ verts[ 3 ].modulate[ 2 ] = 255;
+ verts[ 3 ].modulate[ 3 ] = 255;
+
+ trap_R_AddPolyToScene( shader, 4, verts );
+}
+
+/*
+================
+CG_AdjustFrom640
+
+Adjusted for resolution and screen aspect ratio
+================
+*/
+void CG_AdjustFrom640( float *x, float *y, float *w, float *h )
+{
+#if 0
+ // adjust for wide screens
+ if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) {
+ *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) );
+ }
+#endif
+ // scale for screen sizes
+ *x *= cgs.screenXScale;
+ *y *= cgs.screenYScale;
+ *w *= cgs.screenXScale;
+ *h *= cgs.screenYScale;
+}
+
+/*
+================
+CG_FillRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void CG_FillRect( float x, float y, float width, float height, const float *color )
+{
+ trap_R_SetColor( color );
+
+ CG_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader );
+
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+================
+CG_DrawSides
+
+Coords are virtual 640x480
+================
+*/
+void CG_DrawSides( float x, float y, float w, float h, float size )
+{
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ size *= cgs.screenXScale;
+ trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader );
+ trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader );
+}
+
+void CG_DrawTopBottom( float x, float y, float w, float h, float size )
+{
+ CG_AdjustFrom640( &x, &y, &w, &h );
+ size *= cgs.screenYScale;
+ trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader );
+ trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader );
+}
+
+
+/*
+================
+CG_DrawRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void CG_DrawRect( float x, float y, float width, float height, float size, const float *color )
+{
+ trap_R_SetColor( color );
+
+ CG_DrawTopBottom( x, y, width, height, size );
+ CG_DrawSides( x, y, width, height, size );
+
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+================
+CG_DrawPic
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader )
+{
+ CG_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader );
+}
+
+
+
+/*
+================
+CG_DrawFadePic
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void CG_DrawFadePic( float x, float y, float width, float height, vec4_t fcolor,
+ vec4_t tcolor, float amount, qhandle_t hShader )
+{
+ vec4_t finalcolor;
+ float inverse;
+
+ inverse = 100 - amount;
+
+ CG_AdjustFrom640( &x, &y, &width, &height );
+
+ finalcolor[ 0 ] = ( ( inverse * fcolor[ 0 ] ) + ( amount * tcolor[ 0 ] ) ) / 100;
+ finalcolor[ 1 ] = ( ( inverse * fcolor[ 1 ] ) + ( amount * tcolor[ 1 ] ) ) / 100;
+ finalcolor[ 2 ] = ( ( inverse * fcolor[ 2 ] ) + ( amount * tcolor[ 2 ] ) ) / 100;
+ finalcolor[ 3 ] = ( ( inverse * fcolor[ 3 ] ) + ( amount * tcolor[ 3 ] ) ) / 100;
+
+ trap_R_SetColor( finalcolor );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+=================
+CG_DrawStrlen
+
+Returns character count, skiping color escape codes
+=================
+*/
+int CG_DrawStrlen( const char *str )
+{
+ const char *s = str;
+ int count = 0;
+
+ while( *s )
+ {
+ if( Q_IsColorString( s ) )
+ s += 2;
+ else
+ {
+ count++;
+ s++;
+ }
+ }
+
+ return count;
+}
+
+/*
+=============
+CG_TileClearBox
+
+This repeats a 64*64 tile graphic to fill the screen around a sized down
+refresh window.
+=============
+*/
+static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader )
+{
+ float s1, t1, s2, t2;
+
+ s1 = x / 64.0;
+ t1 = y / 64.0;
+ s2 = ( x + w ) / 64.0;
+ t2 = ( y + h ) / 64.0;
+ trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader );
+}
+
+
+
+/*
+==============
+CG_TileClear
+
+Clear around a sized down screen
+==============
+*/
+void CG_TileClear( void )
+{
+ int top, bottom, left, right;
+ int w, h;
+
+ w = cgs.glconfig.vidWidth;
+ h = cgs.glconfig.vidHeight;
+
+ if( cg.refdef.x == 0 && cg.refdef.y == 0 &&
+ cg.refdef.width == w && cg.refdef.height == h )
+ return; // full screen rendering
+
+ top = cg.refdef.y;
+ bottom = top + cg.refdef.height - 1;
+ left = cg.refdef.x;
+ right = left + cg.refdef.width - 1;
+
+ // clear above view screen
+ CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader );
+
+ // clear below view screen
+ CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader );
+
+ // clear left of view screen
+ CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader );
+
+ // clear right of view screen
+ CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader );
+}
+
+/*
+================
+CG_FadeColor
+================
+*/
+float *CG_FadeColor( int startMsec, int totalMsec )
+{
+ static vec4_t color;
+ int t;
+
+ if( startMsec == 0 )
+ return NULL;
+
+ t = cg.time - startMsec;
+
+ if( t >= totalMsec )
+ return NULL;
+
+ // fade out
+ if( totalMsec - t < FADE_TIME )
+ color[ 3 ] = ( totalMsec - t ) * 1.0 / FADE_TIME;
+ else
+ color[ 3 ] = 1.0;
+
+ color[ 0 ] = color[ 1 ] = color[ 2 ] = 1;
+
+ return color;
+}
+
+/*
+================
+CG_WorldToScreen
+================
+*/
+qboolean CG_WorldToScreen( vec3_t point, float *x, float *y )
+{
+ vec3_t trans;
+ float xc, yc;
+ float px, py;
+ float z;
+
+ px = tan( cg.refdef.fov_x * M_PI / 360.0 );
+ py = tan( cg.refdef.fov_y * M_PI / 360.0 );
+
+ VectorSubtract( point, cg.refdef.vieworg, trans );
+
+ xc = 640.0f / 2.0f;
+ yc = 480.0f / 2.0f;
+
+ z = DotProduct( trans, cg.refdef.viewaxis[ 0 ] );
+ if( z <= 0.001f )
+ return qfalse;
+
+ if( x )
+ *x = xc - DotProduct( trans, cg.refdef.viewaxis[ 1 ] ) * xc / ( z * px );
+
+ if( y )
+ *y = yc - DotProduct( trans, cg.refdef.viewaxis[ 2 ] ) * yc / ( z * py );
+
+ return qtrue;
+}
+
+/*
+================
+CG_KeyBinding
+================
+*/
+char *CG_KeyBinding( const char *bind )
+{
+ static char key[ 32 ];
+ char bindbuff[ MAX_CVAR_VALUE_STRING ];
+ int i;
+
+ key[ 0 ] = '\0';
+ // NOTE: change K_LAST_KEY to MAX_KEYS for full key support (eventually)
+ for( i = 0; i < K_LAST_KEY; i++ )
+ {
+ trap_Key_GetBindingBuf( i, bindbuff, sizeof( bindbuff ) );
+ if( !Q_stricmp( bindbuff, bind ) )
+ {
+ trap_Key_KeynumToStringBuf( i, key, sizeof( key ) );
+ break;
+ }
+ }
+ if( !key[ 0 ] )
+ {
+ Q_strncpyz( key, "\\", sizeof( key ) );
+ Q_strcat( key, sizeof( key ), bind );
+ }
+ return key;
+}
diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c
new file mode 100644
index 0000000..2d2e808
--- /dev/null
+++ b/src/cgame/cg_ents.c
@@ -0,0 +1,1256 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_ents.c -- present snapshot entities, happens every single frame
+
+
+#include "cg_local.h"
+
+/*
+======================
+CG_DrawBoxFace
+
+Draws a bounding box face
+======================
+*/
+static void CG_DrawBoxFace( vec3_t a, vec3_t b, vec3_t c, vec3_t d )
+{
+ polyVert_t verts[ 4 ];
+ vec4_t color = { 255.0f, 0.0f, 0.0f, 128.0f };
+
+ VectorCopy( d, verts[ 0 ].xyz );
+ verts[ 0 ].st[ 0 ] = 1;
+ verts[ 0 ].st[ 1 ] = 1;
+ Vector4Copy( color, verts[ 0 ].modulate );
+
+ VectorCopy( c, verts[ 1 ].xyz );
+ verts[ 1 ].st[ 0 ] = 1;
+ verts[ 1 ].st[ 1 ] = 0;
+ Vector4Copy( color, verts[ 1 ].modulate );
+
+ VectorCopy( b, verts[ 2 ].xyz );
+ verts[ 2 ].st[ 0 ] = 0;
+ verts[ 2 ].st[ 1 ] = 0;
+ Vector4Copy( color, verts[ 2 ].modulate );
+
+ VectorCopy( a, verts[ 3 ].xyz );
+ verts[ 3 ].st[ 0 ] = 0;
+ verts[ 3 ].st[ 1 ] = 1;
+ Vector4Copy( color, verts[ 3 ].modulate );
+
+ trap_R_AddPolyToScene( cgs.media.outlineShader, 4, verts );
+}
+
+/*
+======================
+CG_DrawBoundingBox
+
+Draws a bounding box
+======================
+*/
+void CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs )
+{
+ vec3_t ppp, mpp, mmp, pmp;
+ vec3_t mmm, pmm, ppm, mpm;
+
+ ppp[ 0 ] = origin[ 0 ] + maxs[ 0 ];
+ ppp[ 1 ] = origin[ 1 ] + maxs[ 1 ];
+ ppp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
+
+ mpp[ 0 ] = origin[ 0 ] + mins[ 0 ];
+ mpp[ 1 ] = origin[ 1 ] + maxs[ 1 ];
+ mpp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
+
+ mmp[ 0 ] = origin[ 0 ] + mins[ 0 ];
+ mmp[ 1 ] = origin[ 1 ] + mins[ 1 ];
+ mmp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
+
+ pmp[ 0 ] = origin[ 0 ] + maxs[ 0 ];
+ pmp[ 1 ] = origin[ 1 ] + mins[ 1 ];
+ pmp[ 2 ] = origin[ 2 ] + maxs[ 2 ];
+
+ ppm[ 0 ] = origin[ 0 ] + maxs[ 0 ];
+ ppm[ 1 ] = origin[ 1 ] + maxs[ 1 ];
+ ppm[ 2 ] = origin[ 2 ] + mins[ 2 ];
+
+ mpm[ 0 ] = origin[ 0 ] + mins[ 0 ];
+ mpm[ 1 ] = origin[ 1 ] + maxs[ 1 ];
+ mpm[ 2 ] = origin[ 2 ] + mins[ 2 ];
+
+ mmm[ 0 ] = origin[ 0 ] + mins[ 0 ];
+ mmm[ 1 ] = origin[ 1 ] + mins[ 1 ];
+ mmm[ 2 ] = origin[ 2 ] + mins[ 2 ];
+
+ pmm[ 0 ] = origin[ 0 ] + maxs[ 0 ];
+ pmm[ 1 ] = origin[ 1 ] + mins[ 1 ];
+ pmm[ 2 ] = origin[ 2 ] + mins[ 2 ];
+
+ //phew!
+
+ CG_DrawBoxFace( ppp, mpp, mmp, pmp );
+ CG_DrawBoxFace( ppp, pmp, pmm, ppm );
+ CG_DrawBoxFace( mpp, ppp, ppm, mpm );
+ CG_DrawBoxFace( mmp, mpp, mpm, mmm );
+ CG_DrawBoxFace( pmp, mmp, mmm, pmm );
+ CG_DrawBoxFace( mmm, mpm, ppm, pmm );
+}
+
+
+/*
+======================
+CG_PositionEntityOnTag
+
+Modifies the entities position and axis by the given
+tag location
+======================
+*/
+void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ qhandle_t parentModel, char *tagName )
+{
+ int i;
+ orientation_t lerped;
+
+ // lerp the tag
+ trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
+ 1.0 - parent->backlerp, tagName );
+
+ // FIXME: allow origin offsets along tag?
+ VectorCopy( parent->origin, entity->origin );
+ for( i = 0; i < 3; i++ )
+ VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin );
+
+ // had to cast away the const to avoid compiler problems...
+ MatrixMultiply( lerped.axis, ( (refEntity_t *)parent )->axis, entity->axis );
+ entity->backlerp = parent->backlerp;
+}
+
+
+/*
+======================
+CG_PositionRotatedEntityOnTag
+
+Modifies the entities position and axis by the given
+tag location
+======================
+*/
+void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ qhandle_t parentModel, char *tagName )
+{
+ int i;
+ orientation_t lerped;
+ vec3_t tempAxis[ 3 ];
+
+//AxisClear( entity->axis );
+ // lerp the tag
+ trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
+ 1.0 - parent->backlerp, tagName );
+
+ // FIXME: allow origin offsets along tag?
+ VectorCopy( parent->origin, entity->origin );
+ for( i = 0; i < 3; i++ )
+ VectorMA( entity->origin, lerped.origin[ i ], parent->axis[ i ], entity->origin );
+
+ // had to cast away the const to avoid compiler problems...
+ MatrixMultiply( entity->axis, lerped.axis, tempAxis );
+ MatrixMultiply( tempAxis, ( (refEntity_t *)parent )->axis, entity->axis );
+}
+
+
+
+/*
+==========================================================================
+
+FUNCTIONS CALLED EACH FRAME
+
+==========================================================================
+*/
+
+/*
+======================
+CG_SetEntitySoundPosition
+
+Also called by event processing code
+======================
+*/
+void CG_SetEntitySoundPosition( centity_t *cent )
+{
+ if( cent->currentState.solid == SOLID_BMODEL )
+ {
+ vec3_t origin;
+ float *v;
+
+ v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ];
+ VectorAdd( cent->lerpOrigin, v, origin );
+ trap_S_UpdateEntityPosition( cent->currentState.number, origin );
+ }
+ else
+ trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin );
+}
+
+/*
+==================
+CG_EntityEffects
+
+Add continuous entity effects, like local entity emission and lighting
+==================
+*/
+static void CG_EntityEffects( centity_t *cent )
+{
+ // update sound origins
+ CG_SetEntitySoundPosition( cent );
+
+ // add loop sound
+ if( cent->currentState.loopSound )
+ {
+ if( cent->currentState.eType != ET_SPEAKER )
+ {
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
+ cgs.gameSounds[ cent->currentState.loopSound ] );
+ }
+ else
+ {
+ trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
+ cgs.gameSounds[ cent->currentState.loopSound ] );
+ }
+ }
+
+
+ // constant light glow
+ if ( cent->currentState.constantLight )
+ {
+ int cl;
+ int i, r, g, b;
+
+ cl = cent->currentState.constantLight;
+ r = cl & 255;
+ g = ( cl >> 8 ) & 255;
+ b = ( cl >> 16 ) & 255;
+ i = ( ( cl >> 24 ) & 255 ) * 4;
+ trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b );
+ }
+
+ if( CG_IsTrailSystemValid( &cent->muzzleTS ) )
+ {
+ //FIXME hack to prevent tesla trails reaching too far
+ if( cent->currentState.eType == ET_BUILDABLE )
+ {
+ vec3_t front, back;
+
+ CG_AttachmentPoint( &cent->muzzleTS->frontAttachment, front );
+ CG_AttachmentPoint( &cent->muzzleTS->backAttachment, back );
+
+ if( Distance( front, back ) > ( TESLAGEN_RANGE * M_ROOT3 ) )
+ CG_DestroyTrailSystem( &cent->muzzleTS );
+ }
+
+ if( cg.time > cent->muzzleTSDeathTime && CG_IsTrailSystemValid( &cent->muzzleTS ) )
+ CG_DestroyTrailSystem( &cent->muzzleTS );
+ }
+}
+
+
+/*
+==================
+CG_General
+==================
+*/
+static void CG_General( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *s1;
+
+ s1 = &cent->currentState;
+
+ // if set to invisible, skip
+ if( !s1->modelindex )
+ return;
+
+ memset( &ent, 0, sizeof( ent ) );
+
+ // set frame
+
+ ent.frame = s1->frame;
+ ent.oldframe = ent.frame;
+ ent.backlerp = 0;
+
+ VectorCopy( cent->lerpOrigin, ent.origin);
+ VectorCopy( cent->lerpOrigin, ent.oldorigin);
+
+ ent.hModel = cgs.gameModels[ s1->modelindex ];
+
+ // player model
+ if( s1->number == cg.snap->ps.clientNum )
+ ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors
+
+ // convert angles to axis
+ AnglesToAxis( cent->lerpAngles, ent.axis );
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+/*
+==================
+CG_Speaker
+
+Speaker entities can automatically play sounds
+==================
+*/
+static void CG_Speaker( centity_t *cent )
+{
+ if( ! cent->currentState.clientNum )
+ { // FIXME: use something other than clientNum...
+ return; // not auto triggering
+ }
+
+ if( cg.time < cent->miscTime )
+ return;
+
+ trap_S_StartSound( NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[ cent->currentState.eventParm ] );
+
+ // ent->s.frame = ent->wait * 10;
+ // ent->s.clientNum = ent->random * 10;
+ cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom( );
+}
+
+
+//============================================================================
+
+/*
+===============
+CG_LaunchMissile
+===============
+*/
+static void CG_LaunchMissile( centity_t *cent )
+{
+ entityState_t *es;
+ const weaponInfo_t *wi;
+ particleSystem_t *ps;
+ trailSystem_t *ts;
+ weapon_t weapon;
+ weaponMode_t weaponMode;
+
+ es = &cent->currentState;
+
+ weapon = es->weapon;
+ if( weapon > WP_NUM_WEAPONS )
+ weapon = WP_NONE;
+
+ wi = &cg_weapons[ weapon ];
+ weaponMode = es->generic1;
+
+ if( wi->wim[ weaponMode ].missileParticleSystem )
+ {
+ ps = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].missileParticleSystem );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentCent( &ps->attachment, cent );
+ CG_AttachToCent( &ps->attachment );
+ }
+ }
+
+ if( wi->wim[ weaponMode ].missileTrailSystem )
+ {
+ ts = CG_SpawnNewTrailSystem( wi->wim[ weaponMode ].missileTrailSystem );
+
+ if( CG_IsTrailSystemValid( &ts ) )
+ {
+ CG_SetAttachmentCent( &ts->frontAttachment, cent );
+ CG_AttachToCent( &ts->frontAttachment );
+ }
+ }
+}
+
+/*
+===============
+CG_Missile
+===============
+*/
+static void CG_Missile( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *es;
+ const weaponInfo_t *wi;
+ weapon_t weapon;
+ weaponMode_t weaponMode;
+ const weaponInfoMode_t *wim;
+
+ es = &cent->currentState;
+
+ weapon = es->weapon;
+ if( weapon > WP_NUM_WEAPONS )
+ weapon = WP_NONE;
+
+ wi = &cg_weapons[ weapon ];
+ weaponMode = es->generic1;
+
+ wim = &wi->wim[ weaponMode ];
+
+ // calculate the axis
+ VectorCopy( es->angles, cent->lerpAngles );
+
+ // add dynamic light
+ if( wim->missileDlight )
+ {
+ trap_R_AddLightToScene( cent->lerpOrigin, wim->missileDlight,
+ wim->missileDlightColor[ 0 ],
+ wim->missileDlightColor[ 1 ],
+ wim->missileDlightColor[ 2 ] );
+ }
+
+ // add missile sound
+ if( wim->missileSound )
+ {
+ vec3_t velocity;
+
+ BG_EvaluateTrajectoryDelta( &cent->currentState.pos, cg.time, velocity );
+
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, wim->missileSound );
+ }
+
+ // create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( cent->lerpOrigin, ent.oldorigin );
+
+ if( wim->usesSpriteMissle )
+ {
+ ent.reType = RT_SPRITE;
+ ent.radius = wim->missileSpriteSize;
+ ent.rotation = 0;
+ ent.customShader = wim->missileSprite;
+ ent.shaderRGBA[ 0 ] = 0xFF;
+ ent.shaderRGBA[ 1 ] = 0xFF;
+ ent.shaderRGBA[ 2 ] = 0xFF;
+ ent.shaderRGBA[ 3 ] = 0xFF;
+ }
+ else
+ {
+ ent.hModel = wim->missileModel;
+ ent.renderfx = wim->missileRenderfx | RF_NOSHADOW;
+
+ // convert direction of travel into axis
+ if( VectorNormalize2( es->pos.trDelta, ent.axis[ 0 ] ) == 0 )
+ ent.axis[ 0 ][ 2 ] = 1;
+
+ // spin as it moves
+ if( es->pos.trType != TR_STATIONARY && wim->missileRotates )
+ RotateAroundDirection( ent.axis, cg.time / 4 );
+ else
+ RotateAroundDirection( ent.axis, es->time );
+
+ if( wim->missileAnimates )
+ {
+ int timeSinceStart = cg.time - es->time;
+
+ if( wim->missileAnimLooping )
+ {
+ ent.frame = wim->missileAnimStartFrame +
+ (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate ) %
+ wim->missileAnimNumFrames;
+ }
+ else
+ {
+ ent.frame = wim->missileAnimStartFrame +
+ (int)( ( timeSinceStart / 1000.0f ) * wim->missileAnimFrameRate );
+
+ if( ent.frame > ( wim->missileAnimStartFrame + wim->missileAnimNumFrames ) )
+ ent.frame = wim->missileAnimStartFrame + wim->missileAnimNumFrames;
+ }
+ }
+ }
+
+ //only refresh if there is something to display
+ if( wim->missileSprite || wim->missileModel )
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+/*
+===============
+CG_Mover
+===============
+*/
+static void CG_Mover( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *s1;
+
+ s1 = &cent->currentState;
+
+ // create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( cent->lerpOrigin, ent.oldorigin );
+ AnglesToAxis( cent->lerpAngles, ent.axis );
+
+ ent.renderfx = RF_NOSHADOW;
+
+ // flicker between two skins (FIXME?)
+ ent.skinNum = ( cg.time >> 6 ) & 1;
+
+ // get the model, either as a bmodel or a modelindex
+ if( s1->solid == SOLID_BMODEL )
+ ent.hModel = cgs.inlineDrawModel[ s1->modelindex ];
+ else
+ ent.hModel = cgs.gameModels[ s1->modelindex ];
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+
+ // add the secondary model
+ if( s1->modelindex2 )
+ {
+ ent.skinNum = 0;
+ ent.hModel = cgs.gameModels[ s1->modelindex2 ];
+ trap_R_AddRefEntityToScene( &ent );
+ }
+
+}
+
+/*
+===============
+CG_Beam
+
+Also called as an event
+===============
+*/
+void CG_Beam( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *s1;
+
+ s1 = &cent->currentState;
+
+ // create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( s1->pos.trBase, ent.origin );
+ VectorCopy( s1->origin2, ent.oldorigin );
+ AxisClear( ent.axis );
+ ent.reType = RT_BEAM;
+
+ ent.renderfx = RF_NOSHADOW;
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+
+/*
+===============
+CG_Portal
+===============
+*/
+static void CG_Portal( centity_t *cent )
+{
+ refEntity_t ent;
+ entityState_t *s1;
+
+ s1 = &cent->currentState;
+
+ // create the render entity
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ VectorCopy( s1->origin2, ent.oldorigin );
+ ByteToDir( s1->eventParm, ent.axis[ 0 ] );
+ PerpendicularVector( ent.axis[ 1 ], ent.axis[ 0 ] );
+
+ // negating this tends to get the directions like they want
+ // we really should have a camera roll value
+ VectorSubtract( vec3_origin, ent.axis[ 1 ], ent.axis[ 1 ] );
+
+ CrossProduct( ent.axis[ 0 ], ent.axis[ 1 ], ent.axis[ 2 ] );
+ ent.reType = RT_PORTALSURFACE;
+ ent.oldframe = s1->powerups;
+ ent.frame = s1->frame; // rotation speed
+ ent.skinNum = s1->clientNum / 256.0 * 360; // roll offset
+
+ // add to refresh list
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+//============================================================================
+
+#define SETBOUNDS(v1,v2,r) ((v1)[0]=(-r/2),(v1)[1]=(-r/2),(v1)[2]=(-r/2),\
+ (v2)[0]=(r/2),(v2)[1]=(r/2),(v2)[2]=(r/2))
+#define RADIUSSTEP 0.5f
+
+#define FLARE_OFF 0
+#define FLARE_NOFADE 1
+#define FLARE_TIMEFADE 2
+#define FLARE_REALFADE 3
+
+/*
+=========================
+CG_LightFlare
+=========================
+*/
+static void CG_LightFlare( centity_t *cent )
+{
+ refEntity_t flare;
+ entityState_t *es;
+ vec3_t forward, delta;
+ float len;
+ trace_t tr;
+ float maxAngle;
+ vec3_t mins, maxs, start, end;
+ float srcRadius, srLocal, ratio = 1.0f;
+ int entityNum;
+
+ es = &cent->currentState;
+
+ if( cg.renderingThirdPerson )
+ entityNum = MAGIC_TRACE_HACK;
+ else
+ entityNum = cg.predictedPlayerState.clientNum;
+
+ //don't draw light flares
+ if( cg_lightFlare.integer == FLARE_OFF )
+ return;
+
+ //flare is "off"
+ if( es->eFlags & EF_NODRAW )
+ return;
+
+ CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, es->angles2,
+ entityNum, MASK_SHOT );
+
+ //if there is no los between the view and the flare source
+ //it definately cannot be seen
+ if( tr.fraction < 1.0f || tr.allsolid )
+ return;
+
+ memset( &flare, 0, sizeof( flare ) );
+
+ flare.reType = RT_SPRITE;
+ flare.customShader = cgs.gameShaders[ es->modelindex ];
+ flare.shaderRGBA[ 0 ] = 0xFF;
+ flare.shaderRGBA[ 1 ] = 0xFF;
+ flare.shaderRGBA[ 2 ] = 0xFF;
+ flare.shaderRGBA[ 3 ] = 0xFF;
+
+ //flares always drawn before the rest of the scene
+ flare.renderfx |= RF_DEPTHHACK;
+
+ //bunch of geometry
+ AngleVectors( es->angles, forward, NULL, NULL );
+ VectorCopy( cent->lerpOrigin, flare.origin );
+ VectorSubtract( flare.origin, cg.refdef.vieworg, delta );
+ len = VectorLength( delta );
+ VectorNormalize( delta );
+
+ //flare is too close to camera to be drawn
+ if( len < es->generic1 )
+ return;
+
+ //don't bother for flares behind the view plane
+ if( DotProduct( delta, cg.refdef.viewaxis[ 0 ] ) < 0.0 )
+ return;
+
+ //only recalculate radius and ratio every three frames
+ if( !( cg.clientFrame % 2 ) )
+ {
+ //can only see the flare when in front of it
+ flare.radius = len / es->origin2[ 0 ];
+
+ if( es->origin2[ 2 ] == 0 )
+ srcRadius = srLocal = flare.radius / 2.0f;
+ else
+ srcRadius = srLocal = len / es->origin2[ 2 ];
+
+ maxAngle = es->origin2[ 1 ];
+
+ if( maxAngle > 0.0f )
+ {
+ float radiusMod = 1.0f - ( 180.0f - RAD2DEG(
+ acos( DotProduct( delta, forward ) ) ) ) / maxAngle;
+
+ if( radiusMod < 0.0f )
+ radiusMod = 0.0f;
+
+ flare.radius *= radiusMod;
+ }
+
+ if( flare.radius < 0.0f )
+ flare.radius = 0.0f;
+
+ VectorMA( flare.origin, -flare.radius, delta, end );
+ VectorMA( cg.refdef.vieworg, flare.radius, delta, start );
+
+ if( cg_lightFlare.integer == FLARE_REALFADE )
+ {
+ //"correct" flares
+ CG_BiSphereTrace( &tr, cg.refdef.vieworg, end,
+ 1.0f, srcRadius, entityNum, MASK_SHOT );
+
+ if( tr.fraction < 1.0f )
+ ratio = tr.lateralFraction;
+ else
+ ratio = 1.0f;
+ }
+ else if( cg_lightFlare.integer == FLARE_TIMEFADE )
+ {
+ //draw timed flares
+ SETBOUNDS( mins, maxs, srcRadius );
+ CG_Trace( &tr, start, mins, maxs, end,
+ entityNum, MASK_SHOT );
+
+ if( ( tr.fraction < 1.0f || tr.startsolid ) && cent->lfs.status )
+ {
+ cent->lfs.status = qfalse;
+ cent->lfs.lastTime = cg.time;
+ }
+ else if( ( tr.fraction == 1.0f && !tr.startsolid ) && !cent->lfs.status )
+ {
+ cent->lfs.status = qtrue;
+ cent->lfs.lastTime = cg.time;
+ }
+
+ //fade flare up
+ if( cent->lfs.status )
+ {
+ if( cent->lfs.lastTime + es->time > cg.time )
+ ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time;
+ }
+
+ //fade flare down
+ if( !cent->lfs.status )
+ {
+ if( cent->lfs.lastTime + es->time > cg.time )
+ {
+ ratio = (float)( cg.time - cent->lfs.lastTime ) / es->time;
+ ratio = 1.0f - ratio;
+ }
+ else
+ ratio = 0.0f;
+ }
+ }
+ else if( cg_lightFlare.integer == FLARE_NOFADE )
+ {
+ //draw nofade flares
+ SETBOUNDS( mins, maxs, srcRadius );
+ CG_Trace( &tr, start, mins, maxs, end,
+ entityNum, MASK_SHOT );
+
+ //flare source occluded
+ if( ( tr.fraction < 1.0f || tr.startsolid ) )
+ ratio = 0.0f;
+ }
+ }
+ else
+ {
+ ratio = cent->lfs.lastRatio;
+ flare.radius = cent->lfs.lastRadius;
+ }
+
+ cent->lfs.lastRatio = ratio;
+ cent->lfs.lastRadius = flare.radius;
+
+ if( ratio < 1.0f )
+ {
+ flare.radius *= ratio;
+ flare.shaderRGBA[ 3 ] = (byte)( (float)flare.shaderRGBA[ 3 ] * ratio );
+ }
+
+ if( flare.radius <= 0.0f )
+ return;
+
+ trap_R_AddRefEntityToScene( &flare );
+}
+
+/*
+=========================
+CG_Lev2ZapChain
+=========================
+*/
+static void CG_Lev2ZapChain( centity_t *cent )
+{
+ int i;
+ entityState_t *es;
+ centity_t *source = NULL, *target = NULL;
+
+ es = &cent->currentState;
+
+ for( i = 0; i <= 2; i++ )
+ {
+ switch( i )
+ {
+ case 0:
+ if( es->time <= 0 )
+ continue;
+
+ source = &cg_entities[ es->powerups ];
+ target = &cg_entities[ es->time ];
+ break;
+
+ case 1:
+ if( es->time2 <= 0 )
+ continue;
+
+ source = &cg_entities[ es->time ];
+ target = &cg_entities[ es->time2 ];
+ break;
+
+ case 2:
+ if( es->constantLight <= 0 )
+ continue;
+
+ source = &cg_entities[ es->time2 ];
+ target = &cg_entities[ es->constantLight ];
+ break;
+ }
+
+ if( !CG_IsTrailSystemValid( &cent->level2ZapTS[ i ] ) )
+ cent->level2ZapTS[ i ] = CG_SpawnNewTrailSystem( cgs.media.level2ZapTS );
+
+ if( CG_IsTrailSystemValid( &cent->level2ZapTS[ i ] ) )
+ {
+ CG_SetAttachmentCent( &cent->level2ZapTS[ i ]->frontAttachment, source );
+ CG_SetAttachmentCent( &cent->level2ZapTS[ i ]->backAttachment, target );
+ CG_AttachToCent( &cent->level2ZapTS[ i ]->frontAttachment );
+ CG_AttachToCent( &cent->level2ZapTS[ i ]->backAttachment );
+ }
+ }
+}
+
+/*
+=========================
+CG_AdjustPositionForMover
+
+Also called by client movement prediction code
+=========================
+*/
+void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out )
+{
+ centity_t *cent;
+ vec3_t oldOrigin, origin, deltaOrigin;
+ vec3_t oldAngles, angles, deltaAngles;
+
+ if( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL )
+ {
+ VectorCopy( in, out );
+ return;
+ }
+
+ cent = &cg_entities[ moverNum ];
+
+ if( cent->currentState.eType != ET_MOVER )
+ {
+ VectorCopy( in, out );
+ return;
+ }
+
+ BG_EvaluateTrajectory( &cent->currentState.pos, fromTime, oldOrigin );
+ BG_EvaluateTrajectory( &cent->currentState.apos, fromTime, oldAngles );
+
+ BG_EvaluateTrajectory( &cent->currentState.pos, toTime, origin );
+ BG_EvaluateTrajectory( &cent->currentState.apos, toTime, angles );
+
+ VectorSubtract( origin, oldOrigin, deltaOrigin );
+ VectorSubtract( angles, oldAngles, deltaAngles );
+
+ VectorAdd( in, deltaOrigin, out );
+
+ // FIXME: origin change when on a rotating object
+}
+
+
+/*
+=============================
+CG_InterpolateEntityPosition
+=============================
+*/
+static void CG_InterpolateEntityPosition( centity_t *cent )
+{
+ vec3_t current, next;
+ float f;
+
+ // it would be an internal error to find an entity that interpolates without
+ // a snapshot ahead of the current one
+ if( cg.nextSnap == NULL )
+ CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" );
+
+ f = cg.frameInterpolation;
+
+ // this will linearize a sine or parabolic curve, but it is important
+ // to not extrapolate player positions if more recent data is available
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, current );
+ BG_EvaluateTrajectory( &cent->nextState.pos, cg.nextSnap->serverTime, next );
+
+ cent->lerpOrigin[ 0 ] = current[ 0 ] + f * ( next[ 0 ] - current[ 0 ] );
+ cent->lerpOrigin[ 1 ] = current[ 1 ] + f * ( next[ 1 ] - current[ 1 ] );
+ cent->lerpOrigin[ 2 ] = current[ 2 ] + f * ( next[ 2 ] - current[ 2 ] );
+
+ BG_EvaluateTrajectory( &cent->currentState.apos, cg.snap->serverTime, current );
+ BG_EvaluateTrajectory( &cent->nextState.apos, cg.nextSnap->serverTime, next );
+
+ cent->lerpAngles[ 0 ] = LerpAngle( current[ 0 ], next[ 0 ], f );
+ cent->lerpAngles[ 1 ] = LerpAngle( current[ 1 ], next[ 1 ], f );
+ cent->lerpAngles[ 2 ] = LerpAngle( current[ 2 ], next[ 2 ], f );
+
+}
+
+/*
+===============
+CG_CalcEntityLerpPositions
+
+===============
+*/
+static void CG_CalcEntityLerpPositions( centity_t *cent )
+{
+ // this will be set to how far forward projectiles will be extrapolated
+ int timeshift = 0;
+
+ // if this player does not want to see extrapolated players
+ if( !cg_smoothClients.integer )
+ {
+ // make sure the clients use TR_INTERPOLATE
+ if( cent->currentState.number < MAX_CLIENTS )
+ {
+ cent->currentState.pos.trType = TR_INTERPOLATE;
+ cent->nextState.pos.trType = TR_INTERPOLATE;
+ }
+ }
+
+ if( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE )
+ {
+ CG_InterpolateEntityPosition( cent );
+ return;
+ }
+
+ // first see if we can interpolate between two snaps for
+ // linear extrapolated clients
+ if( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP &&
+ cent->currentState.number < MAX_CLIENTS )
+ {
+ CG_InterpolateEntityPosition( cent );
+ return;
+ }
+
+ if( cg_projectileNudge.integer > 0 &&
+ cent->currentState.eType == ET_MISSILE &&
+ !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ {
+ timeshift = cg.ping;
+ }
+
+ // just use the current frame and evaluate as best we can
+ BG_EvaluateTrajectory( &cent->currentState.pos,
+ ( cg.time + timeshift ), cent->lerpOrigin );
+ BG_EvaluateTrajectory( &cent->currentState.apos,
+ ( cg.time + timeshift ), cent->lerpAngles );
+
+ if( timeshift )
+ {
+ trace_t tr;
+ vec3_t lastOrigin;
+
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.time, lastOrigin );
+
+ CG_Trace( &tr, lastOrigin, vec3_origin, vec3_origin, cent->lerpOrigin,
+ cent->currentState.number, MASK_SHOT );
+
+ // don't let the projectile go through the floor
+ if( tr.fraction < 1.0f )
+ VectorLerp( tr.fraction, lastOrigin, cent->lerpOrigin, cent->lerpOrigin );
+ }
+
+ // adjust for riding a mover if it wasn't rolled into the predicted
+ // player state
+ if( cent != &cg.predictedPlayerEntity )
+ {
+ CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum,
+ cg.snap->serverTime, cg.time, cent->lerpOrigin );
+ }
+}
+
+
+/*
+===============
+CG_CEntityPVSEnter
+
+===============
+*/
+static void CG_CEntityPVSEnter( centity_t *cent )
+{
+ entityState_t *es = &cent->currentState;
+
+ if( cg_debugPVS.integer )
+ CG_Printf( "Entity %d entered PVS\n", cent->currentState.number );
+
+ switch( es->eType )
+ {
+ case ET_MISSILE:
+ CG_LaunchMissile( cent );
+ break;
+ }
+
+ //clear any particle systems from previous uses of this centity_t
+ cent->muzzlePS = NULL;
+ cent->muzzlePsTrigger = qfalse;
+ cent->jetPackPS = NULL;
+ cent->jetPackState = JPS_OFF;
+ cent->buildablePS = NULL;
+ cent->entityPS = NULL;
+ cent->entityPSMissing = qfalse;
+
+ //make sure that the buildable animations are in a consistent state
+ //when a buildable enters the PVS
+ cent->buildableAnim = cent->lerpFrame.animationNumber = BANIM_NONE;
+ cent->oldBuildableAnim = es->legsAnim;
+}
+
+
+/*
+===============
+CG_CEntityPVSLeave
+
+===============
+*/
+static void CG_CEntityPVSLeave( centity_t *cent )
+{
+ int i;
+ entityState_t *es = &cent->currentState;
+
+ if( cg_debugPVS.integer )
+ CG_Printf( "Entity %d left PVS\n", cent->currentState.number );
+
+ switch( es->eType )
+ {
+ case ET_LEV2_ZAP_CHAIN:
+ for( i = 0; i <= 2; i++ )
+ {
+ if( CG_IsTrailSystemValid( &cent->level2ZapTS[ i ] ) )
+ CG_DestroyTrailSystem( &cent->level2ZapTS[ i ] );
+ }
+ break;
+ }
+}
+
+
+/*
+===============
+CG_AddCEntity
+
+===============
+*/
+static void CG_AddCEntity( centity_t *cent )
+{
+ // event-only entities will have been dealt with already
+ if( cent->currentState.eType >= ET_EVENTS )
+ return;
+
+ // calculate the current origin
+ CG_CalcEntityLerpPositions( cent );
+
+ // add automatic effects
+ CG_EntityEffects( cent );
+
+ switch( cent->currentState.eType )
+ {
+ default:
+ CG_Error( "Bad entity type: %i\n", cent->currentState.eType );
+ break;
+
+ case ET_INVISIBLE:
+ case ET_PUSH_TRIGGER:
+ case ET_TELEPORT_TRIGGER:
+ break;
+
+ case ET_GENERAL:
+ CG_General( cent );
+ break;
+
+ case ET_CORPSE:
+ CG_Corpse( cent );
+ break;
+
+ case ET_PLAYER:
+ CG_Player( cent );
+ break;
+
+ case ET_BUILDABLE:
+ CG_Buildable( cent );
+ break;
+
+ case ET_MISSILE:
+ CG_Missile( cent );
+ break;
+
+ case ET_MOVER:
+ CG_Mover( cent );
+ break;
+
+ case ET_BEAM:
+ CG_Beam( cent );
+ break;
+
+ case ET_PORTAL:
+ CG_Portal( cent );
+ break;
+
+ case ET_SPEAKER:
+ CG_Speaker( cent );
+ break;
+
+ case ET_PARTICLE_SYSTEM:
+ CG_ParticleSystemEntity( cent );
+ break;
+
+ case ET_ANIMMAPOBJ:
+ CG_AnimMapObj( cent );
+ break;
+
+ case ET_MODELDOOR:
+ CG_ModelDoor( cent );
+ break;
+
+ case ET_LIGHTFLARE:
+ CG_LightFlare( cent );
+ break;
+
+ case ET_LEV2_ZAP_CHAIN:
+ CG_Lev2ZapChain( cent );
+ break;
+ }
+}
+
+/*
+===============
+CG_AddPacketEntities
+
+===============
+*/
+void CG_AddPacketEntities( void )
+{
+ int num;
+ centity_t *cent;
+ playerState_t *ps;
+
+ // set cg.frameInterpolation
+ if( cg.nextSnap )
+ {
+ int delta;
+
+ delta = ( cg.nextSnap->serverTime - cg.snap->serverTime );
+
+ if( delta == 0 )
+ cg.frameInterpolation = 0;
+ else
+ cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta;
+ }
+ else
+ {
+ cg.frameInterpolation = 0; // actually, it should never be used, because
+ // no entities should be marked as interpolating
+ }
+
+ // the auto-rotating items will all have the same axis
+ cg.autoAngles[ 0 ] = 0;
+ cg.autoAngles[ 1 ] = ( cg.time & 2047 ) * 360 / 2048.0;
+ cg.autoAngles[ 2 ] = 0;
+
+ cg.autoAnglesFast[ 0 ] = 0;
+ cg.autoAnglesFast[ 1 ] = ( cg.time & 1023 ) * 360 / 1024.0f;
+ cg.autoAnglesFast[ 2 ] = 0;
+
+ AnglesToAxis( cg.autoAngles, cg.autoAxis );
+ AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast );
+
+ // generate and add the entity from the playerstate
+ ps = &cg.predictedPlayerState;
+ BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse );
+ cg.predictedPlayerEntity.valid = qtrue;
+ CG_AddCEntity( &cg.predictedPlayerEntity );
+
+ // lerp the non-predicted value for lightning gun origins
+ CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] );
+
+ // scanner
+ CG_UpdateEntityPositions( );
+
+ for( num = 0; num < MAX_GENTITIES; num++ )
+ cg_entities[ num ].valid = qfalse;
+
+ // add each entity sent over by the server
+ for( num = 0; num < cg.snap->numEntities; num++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ num ].number ];
+ cent->valid = qtrue;
+ }
+
+ for( num = 0; num < MAX_GENTITIES; num++ )
+ {
+ cent = &cg_entities[ num ];
+
+ if( cent->valid && !cent->oldValid )
+ CG_CEntityPVSEnter( cent );
+ else if( !cent->valid && cent->oldValid )
+ CG_CEntityPVSLeave( cent );
+
+ cent->oldValid = cent->valid;
+ }
+
+ // add each entity sent over by the server
+ for( num = 0; num < cg.snap->numEntities; num++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ num ].number ];
+ CG_AddCEntity( cent );
+ }
+
+ //make an attempt at drawing bounding boxes of selected entity types
+ if( cg_drawBBOX.integer )
+ {
+ for( num = 0; num < cg.snap->numEntities; num++ )
+ {
+ float x, zd, zu;
+ vec3_t mins, maxs;
+ entityState_t *es;
+
+ cent = &cg_entities[ cg.snap->entities[ num ].number ];
+ es = &cent->currentState;
+
+ switch( es->eType )
+ {
+ case ET_BUILDABLE:
+ case ET_MISSILE:
+ case ET_CORPSE:
+ x = ( es->solid & 255 );
+ zd = ( ( es->solid >> 8 ) & 255 );
+ zu = ( ( es->solid >> 16 ) & 255 ) - 32;
+
+ mins[ 0 ] = mins[ 1 ] = -x;
+ maxs[ 0 ] = maxs[ 1 ] = x;
+ mins[ 2 ] = -zd;
+ maxs[ 2 ] = zu;
+
+ CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs );
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
diff --git a/src/cgame/cg_event.c b/src/cgame/cg_event.c
new file mode 100644
index 0000000..07bcea9
--- /dev/null
+++ b/src/cgame/cg_event.c
@@ -0,0 +1,1034 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_event.c -- handle entity events at snapshot or playerstate transitions
+
+
+#include "cg_local.h"
+
+/*
+=============
+CG_Obituary
+=============
+*/
+static void CG_Obituary( entityState_t *ent )
+{
+ int mod;
+ int target, attacker;
+ char *message;
+ char *message2;
+ const char *targetInfo;
+ const char *attackerInfo;
+ char targetName[ 32 ];
+ char attackerName[ 32 ];
+ char className[ 64 ];
+ gender_t gender;
+ clientInfo_t *ci;
+ qboolean teamKill = qfalse;
+
+ target = ent->otherEntityNum;
+ attacker = ent->otherEntityNum2;
+ mod = ent->eventParm;
+
+ if( target < 0 || target >= MAX_CLIENTS )
+ CG_Error( "CG_Obituary: target out of range" );
+
+ ci = &cgs.clientinfo[ target ];
+
+ if( attacker < 0 || attacker >= MAX_CLIENTS )
+ {
+ attacker = ENTITYNUM_WORLD;
+ attackerInfo = NULL;
+ }
+ else
+ {
+ attackerInfo = CG_ConfigString( CS_PLAYERS + attacker );
+ if( ci && cgs.clientinfo[ attacker ].team == ci->team )
+ teamKill = qtrue;
+ }
+
+ targetInfo = CG_ConfigString( CS_PLAYERS + target );
+
+ if( !targetInfo )
+ return;
+
+ Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof( targetName ) - 2 );
+ strcat( targetName, S_COLOR_WHITE );
+
+ message2 = "";
+
+ // check for single client messages
+
+ switch( mod )
+ {
+ case MOD_SUICIDE:
+ message = "suicides";
+ break;
+ case MOD_FALLING:
+ message = "fell fowl to gravity";
+ break;
+ case MOD_CRUSH:
+ message = "was squished";
+ break;
+ case MOD_WATER:
+ message = "forgot to pack a snorkel";
+ break;
+ case MOD_SLIME:
+ message = "melted";
+ break;
+ case MOD_LAVA:
+ message = "does a back flip into the lava";
+ break;
+ case MOD_TARGET_LASER:
+ message = "saw the light";
+ break;
+ case MOD_TRIGGER_HURT:
+ message = "was in the wrong place";
+ break;
+ case MOD_HSPAWN:
+ message = "should have run further";
+ break;
+ case MOD_ASPAWN:
+ message = "shouldn't have trod in the acid";
+ break;
+ case MOD_MGTURRET:
+ message = "was gunned down by a turret";
+ break;
+ case MOD_TESLAGEN:
+ message = "was zapped by a tesla generator";
+ break;
+ case MOD_ATUBE:
+ message = "was melted by an acid tube";
+ break;
+ case MOD_OVERMIND:
+ message = "got too close to the overmind";
+ break;
+ case MOD_REACTOR:
+ message = "got too close to the reactor";
+ break;
+ case MOD_SLOWBLOB:
+ message = "should have visited a medical station";
+ break;
+ case MOD_SWARM:
+ message = "was hunted down by the swarm";
+ break;
+ default:
+ message = NULL;
+ break;
+ }
+
+ if( attacker == target )
+ {
+ gender = ci->gender;
+ switch( mod )
+ {
+ case MOD_FLAMER_SPLASH:
+ if( gender == GENDER_FEMALE )
+ message = "toasted herself";
+ else if( gender == GENDER_NEUTER )
+ message = "toasted itself";
+ else
+ message = "toasted himself";
+ break;
+
+ case MOD_LCANNON_SPLASH:
+ if( gender == GENDER_FEMALE )
+ message = "irradiated herself";
+ else if( gender == GENDER_NEUTER )
+ message = "irradiated itself";
+ else
+ message = "irradiated himself";
+ break;
+
+ case MOD_GRENADE:
+ if( gender == GENDER_FEMALE )
+ message = "blew herself up";
+ else if( gender == GENDER_NEUTER )
+ message = "blew itself up";
+ else
+ message = "blew himself up";
+ break;
+
+ default:
+ if( gender == GENDER_FEMALE )
+ message = "killed herself";
+ else if( gender == GENDER_NEUTER )
+ message = "killed itself";
+ else
+ message = "killed himself";
+ break;
+ }
+ }
+
+ if( message )
+ {
+ CG_Printf( "%s %s.\n", targetName, message );
+ return;
+ }
+
+ // check for double client messages
+ if( !attackerInfo )
+ {
+ attacker = ENTITYNUM_WORLD;
+ strcpy( attackerName, "noname" );
+ }
+ else
+ {
+ Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof( attackerName ) - 2);
+ strcat( attackerName, S_COLOR_WHITE );
+ // check for kill messages about the current clientNum
+ if( target == cg.snap->ps.clientNum )
+ Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) );
+ }
+
+ if( attacker != ENTITYNUM_WORLD )
+ {
+ switch( mod )
+ {
+ case MOD_PAINSAW:
+ message = "was sawn by";
+ break;
+ case MOD_BLASTER:
+ message = "was blasted by";
+ break;
+ case MOD_MACHINEGUN:
+ message = "was machinegunned by";
+ break;
+ case MOD_CHAINGUN:
+ message = "was chaingunned by";
+ break;
+ case MOD_SHOTGUN:
+ message = "was gunned down by";
+ break;
+ case MOD_PRIFLE:
+ message = "was pulse rifled by";
+ break;
+ case MOD_MDRIVER:
+ message = "was mass driven by";
+ break;
+ case MOD_LASGUN:
+ message = "was lasgunned by";
+ break;
+ case MOD_FLAMER:
+ message = "was grilled by";
+ message2 = "'s flamer";
+ break;
+ case MOD_FLAMER_SPLASH:
+ message = "was toasted by";
+ message2 = "'s flamer";
+ break;
+ case MOD_LCANNON:
+ message = "felt the full force of";
+ message2 = "'s lucifer cannon";
+ break;
+ case MOD_LCANNON_SPLASH:
+ message = "was caught in the fallout of";
+ message2 = "'s lucifer cannon";
+ break;
+ case MOD_GRENADE:
+ message = "couldn't escape";
+ message2 = "'s grenade";
+ break;
+
+ case MOD_ABUILDER_CLAW:
+ message = "should leave";
+ message2 = "'s buildings alone";
+ break;
+ case MOD_LEVEL0_BITE:
+ message = "was bitten by";
+ break;
+ case MOD_LEVEL1_CLAW:
+ message = "was swiped by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL1 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL2_CLAW:
+ message = "was clawed by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL2_ZAP:
+ message = "was zapped by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL2 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL3_CLAW:
+ message = "was chomped by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL3_POUNCE:
+ message = "was pounced upon by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL3_BOUNCEBALL:
+ message = "was sniped by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL3 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL4_CLAW:
+ message = "was mauled by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) );
+ message2 = className;
+ break;
+ case MOD_LEVEL4_CHARGE:
+ message = "should have gotten out of the way of";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL4 ) );
+ message2 = className;
+ break;
+
+ case MOD_POISON:
+ message = "should have used a medkit against";
+ message2 = "'s poison";
+ break;
+ case MOD_LEVEL1_PCLOUD:
+ message = "was gassed by";
+ Com_sprintf( className, 64, "'s %s",
+ BG_FindHumanNameForClassNum( PCL_ALIEN_LEVEL1 ) );
+ message2 = className;
+ break;
+
+
+ case MOD_TELEFRAG:
+ message = "tried to invade";
+ message2 = "'s personal space";
+ break;
+ default:
+ message = "was killed by";
+ break;
+ }
+
+ if( message )
+ {
+ CG_Printf( "%s %s %s%s%s\n",
+ targetName, message,
+ ( teamKill ) ? S_COLOR_RED "TEAMMATE " S_COLOR_WHITE : "",
+ attackerName, message2 );
+ if( teamKill && attacker == cg.clientNum )
+ {
+ CG_CenterPrint( va ( "You killed " S_COLOR_RED "TEAMMATE "
+ S_COLOR_WHITE "%s", targetName ),
+ SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
+ }
+ return;
+ }
+ }
+
+ // we don't know what it was
+ CG_Printf( "%s died.\n", targetName );
+}
+
+//==========================================================================
+
+/*
+================
+CG_PainEvent
+
+Also called by playerstate transition
+================
+*/
+void CG_PainEvent( centity_t *cent, int health )
+{
+ char *snd;
+
+ // don't do more than two pain sounds a second
+ if( cg.time - cent->pe.painTime < 500 )
+ return;
+
+ if( health < 25 )
+ snd = "*pain25_1.wav";
+ else if( health < 50 )
+ snd = "*pain50_1.wav";
+ else if( health < 75 )
+ snd = "*pain75_1.wav";
+ else
+ snd = "*pain100_1.wav";
+
+ trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE,
+ CG_CustomSound( cent->currentState.number, snd ) );
+
+ // save pain time for programitic twitch animation
+ cent->pe.painTime = cg.time;
+ cent->pe.painDirection ^= 1;
+}
+
+/*
+==============
+CG_EntityEvent
+
+An entity has an event value
+also called by CG_CheckPlayerstateEvents
+==============
+*/
+#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");}
+void CG_EntityEvent( centity_t *cent, vec3_t position )
+{
+ entityState_t *es;
+ int event;
+ vec3_t dir;
+ const char *s;
+ int clientNum;
+ clientInfo_t *ci;
+ int steptime;
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ steptime = 200;
+ else
+ steptime = BG_FindSteptimeForClass( cg.snap->ps.stats[ STAT_PCLASS ] );
+
+ es = &cent->currentState;
+ event = es->event & ~EV_EVENT_BITS;
+
+ if( cg_debugEvents.integer )
+ CG_Printf( "ent:%3i event:%3i ", es->number, event );
+
+ if( !event )
+ {
+ DEBUGNAME("ZEROEVENT");
+ return;
+ }
+
+ clientNum = es->clientNum;
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS )
+ clientNum = 0;
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ switch( event )
+ {
+ //
+ // movement generated events
+ //
+ case EV_FOOTSTEP:
+ DEBUGNAME( "EV_FOOTSTEP" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ if( ci->footsteps == FOOTSTEP_CUSTOM )
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ ci->customFootsteps[ rand( ) & 3 ] );
+ else
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ ci->footsteps ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_FOOTSTEP_METAL:
+ DEBUGNAME( "EV_FOOTSTEP_METAL" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ if( ci->footsteps == FOOTSTEP_CUSTOM )
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ ci->customMetalFootsteps[ rand( ) & 3 ] );
+ else
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ FOOTSTEP_METAL ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_FOOTSTEP_SQUELCH:
+ DEBUGNAME( "EV_FOOTSTEP_SQUELCH" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ FOOTSTEP_FLESH ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_FOOTSPLASH:
+ DEBUGNAME( "EV_FOOTSPLASH" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_FOOTWADE:
+ DEBUGNAME( "EV_FOOTWADE" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] );
+ }
+ break;
+
+ case EV_SWIM:
+ DEBUGNAME( "EV_SWIM" );
+ if( cg_footsteps.integer && ci->footsteps != FOOTSTEP_NONE )
+ {
+ trap_S_StartSound( NULL, es->number, CHAN_BODY,
+ cgs.media.footsteps[ FOOTSTEP_SPLASH ][ rand( ) & 3 ] );
+ }
+ break;
+
+
+ case EV_FALL_SHORT:
+ DEBUGNAME( "EV_FALL_SHORT" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.landSound );
+
+ if( clientNum == cg.predictedPlayerState.clientNum )
+ {
+ // smooth landing z changes
+ cg.landChange = -8;
+ cg.landTime = cg.time;
+ }
+ break;
+
+ case EV_FALL_MEDIUM:
+ DEBUGNAME( "EV_FALL_MEDIUM" );
+ // use normal pain sound
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) );
+
+ if( clientNum == cg.predictedPlayerState.clientNum )
+ {
+ // smooth landing z changes
+ cg.landChange = -16;
+ cg.landTime = cg.time;
+ }
+ break;
+
+ case EV_FALL_FAR:
+ DEBUGNAME( "EV_FALL_FAR" );
+ trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) );
+ cent->pe.painTime = cg.time; // don't play a pain sound right after this
+
+ if( clientNum == cg.predictedPlayerState.clientNum )
+ {
+ // smooth landing z changes
+ cg.landChange = -24;
+ cg.landTime = cg.time;
+ }
+ break;
+
+ case EV_FALLING:
+ DEBUGNAME( "EV_FALLING" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*falling1.wav" ) );
+ break;
+
+ case EV_STEP_4:
+ case EV_STEP_8:
+ case EV_STEP_12:
+ case EV_STEP_16: // smooth out step up transitions
+ case EV_STEPDN_4:
+ case EV_STEPDN_8:
+ case EV_STEPDN_12:
+ case EV_STEPDN_16: // smooth out step down transitions
+ DEBUGNAME( "EV_STEP" );
+ {
+ float oldStep;
+ int delta;
+ int step;
+
+ if( clientNum != cg.predictedPlayerState.clientNum )
+ break;
+
+ // if we are interpolating, we don't need to smooth steps
+ if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) ||
+ cg_nopredict.integer || cg_synchronousClients.integer )
+ break;
+
+ // check for stepping up before a previous step is completed
+ delta = cg.time - cg.stepTime;
+
+ if( delta < steptime )
+ oldStep = cg.stepChange * ( steptime - delta ) / steptime;
+ else
+ oldStep = 0;
+
+ // add this amount
+ if( event >= EV_STEPDN_4 )
+ {
+ step = 4 * ( event - EV_STEPDN_4 + 1 );
+ cg.stepChange = oldStep - step;
+ }
+ else
+ {
+ step = 4 * ( event - EV_STEP_4 + 1 );
+ cg.stepChange = oldStep + step;
+ }
+
+ if( cg.stepChange > MAX_STEP_CHANGE )
+ cg.stepChange = MAX_STEP_CHANGE;
+ else if( cg.stepChange < -MAX_STEP_CHANGE )
+ cg.stepChange = -MAX_STEP_CHANGE;
+
+ cg.stepTime = cg.time;
+ break;
+ }
+
+ case EV_JUMP:
+ DEBUGNAME( "EV_JUMP" );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) );
+
+ if( BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_WALLJUMPER ) )
+ {
+ vec3_t surfNormal, refNormal = { 0.0f, 0.0f, 1.0f };
+ vec3_t rotAxis;
+
+ if( clientNum != cg.predictedPlayerState.clientNum )
+ break;
+
+ //set surfNormal
+ VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal );
+
+ //if we are moving from one surface to another smooth the transition
+ if( !VectorCompare( surfNormal, cg.lastNormal ) && surfNormal[ 2 ] != 1.0f )
+ {
+ CrossProduct( refNormal, surfNormal, rotAxis );
+ VectorNormalize( rotAxis );
+
+ //add the op
+ CG_addSmoothOp( rotAxis, 15.0f, 1.0f );
+ }
+
+ //copy the current normal to the lastNormal
+ VectorCopy( surfNormal, cg.lastNormal );
+ }
+
+ break;
+
+ case EV_LEV1_GRAB:
+ DEBUGNAME( "EV_LEV1_GRAB" );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL1Grab );
+ break;
+
+ case EV_LEV4_CHARGE_PREPARE:
+ DEBUGNAME( "EV_LEV4_CHARGE_PREPARE" );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargePrepare );
+ break;
+
+ case EV_LEV4_CHARGE_START:
+ DEBUGNAME( "EV_LEV4_CHARGE_START" );
+ //FIXME: stop cgs.media.alienL4ChargePrepare playing here
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.media.alienL4ChargeStart );
+ break;
+
+ case EV_TAUNT:
+ DEBUGNAME( "EV_TAUNT" );
+ if( !cg_noTaunt.integer )
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) );
+ break;
+
+ case EV_WATER_TOUCH:
+ DEBUGNAME( "EV_WATER_TOUCH" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrInSound );
+ break;
+
+ case EV_WATER_LEAVE:
+ DEBUGNAME( "EV_WATER_LEAVE" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound );
+ break;
+
+ case EV_WATER_UNDER:
+ DEBUGNAME( "EV_WATER_UNDER" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound );
+ break;
+
+ case EV_WATER_CLEAR:
+ DEBUGNAME( "EV_WATER_CLEAR" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) );
+ break;
+
+ //
+ // weapon events
+ //
+ case EV_NOAMMO:
+ DEBUGNAME( "EV_NOAMMO" );
+ {
+ }
+ break;
+
+ case EV_CHANGE_WEAPON:
+ DEBUGNAME( "EV_CHANGE_WEAPON" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.selectSound );
+ break;
+
+ case EV_FIRE_WEAPON:
+ DEBUGNAME( "EV_FIRE_WEAPON" );
+ CG_FireWeapon( cent, WPM_PRIMARY );
+ break;
+
+ case EV_FIRE_WEAPON2:
+ DEBUGNAME( "EV_FIRE_WEAPON2" );
+ CG_FireWeapon( cent, WPM_SECONDARY );
+ break;
+
+ case EV_FIRE_WEAPON3:
+ DEBUGNAME( "EV_FIRE_WEAPON3" );
+ CG_FireWeapon( cent, WPM_TERTIARY );
+ break;
+
+ //=================================================================
+
+ //
+ // other events
+ //
+ case EV_PLAYER_TELEPORT_IN:
+ DEBUGNAME( "EV_PLAYER_TELEPORT_IN" );
+ //deprecated
+ break;
+
+ case EV_PLAYER_TELEPORT_OUT:
+ DEBUGNAME( "EV_PLAYER_TELEPORT_OUT" );
+ CG_PlayerDisconnect( position );
+ break;
+
+ case EV_BUILD_CONSTRUCT:
+ DEBUGNAME( "EV_BUILD_CONSTRUCT" );
+ //do something useful here
+ break;
+
+ case EV_BUILD_DESTROY:
+ DEBUGNAME( "EV_BUILD_DESTROY" );
+ //do something useful here
+ break;
+
+ case EV_RPTUSE_SOUND:
+ DEBUGNAME( "EV_RPTUSE_SOUND" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.repeaterUseSound );
+ break;
+
+ case EV_GRENADE_BOUNCE:
+ DEBUGNAME( "EV_GRENADE_BOUNCE" );
+ if( rand( ) & 1 )
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound1 );
+ else
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.hardBounceSound2 );
+ break;
+
+ //
+ // missile impacts
+ //
+ case EV_MISSILE_HIT:
+ DEBUGNAME( "EV_MISSILE_HIT" );
+ ByteToDir( es->eventParm, dir );
+ CG_MissileHitPlayer( es->weapon, es->generic1, position, dir, es->otherEntityNum );
+ break;
+
+ case EV_MISSILE_MISS:
+ DEBUGNAME( "EV_MISSILE_MISS" );
+ ByteToDir( es->eventParm, dir );
+ CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_DEFAULT );
+ break;
+
+ case EV_MISSILE_MISS_METAL:
+ DEBUGNAME( "EV_MISSILE_MISS_METAL" );
+ ByteToDir( es->eventParm, dir );
+ CG_MissileHitWall( es->weapon, es->generic1, 0, position, dir, IMPACTSOUND_METAL );
+ break;
+
+ case EV_HUMAN_BUILDABLE_EXPLOSION:
+ DEBUGNAME( "EV_HUMAN_BUILDABLE_EXPLOSION" );
+ ByteToDir( es->eventParm, dir );
+ CG_HumanBuildableExplosion( position, dir );
+ break;
+
+ case EV_ALIEN_BUILDABLE_EXPLOSION:
+ DEBUGNAME( "EV_ALIEN_BUILDABLE_EXPLOSION" );
+ ByteToDir( es->eventParm, dir );
+ CG_AlienBuildableExplosion( position, dir );
+ break;
+
+ case EV_TESLATRAIL:
+ DEBUGNAME( "EV_TESLATRAIL" );
+ cent->currentState.weapon = WP_TESLAGEN;
+ {
+ centity_t *source = &cg_entities[ es->generic1 ];
+ centity_t *target = &cg_entities[ es->clientNum ];
+ vec3_t sourceOffset = { 0.0f, 0.0f, 28.0f };
+
+ if( !CG_IsTrailSystemValid( &source->muzzleTS ) )
+ {
+ source->muzzleTS = CG_SpawnNewTrailSystem( cgs.media.teslaZapTS );
+
+ if( CG_IsTrailSystemValid( &source->muzzleTS ) )
+ {
+ CG_SetAttachmentCent( &source->muzzleTS->frontAttachment, source );
+ CG_SetAttachmentCent( &source->muzzleTS->backAttachment, target );
+ CG_AttachToCent( &source->muzzleTS->frontAttachment );
+ CG_AttachToCent( &source->muzzleTS->backAttachment );
+ CG_SetAttachmentOffset( &source->muzzleTS->frontAttachment, sourceOffset );
+
+ source->muzzleTSDeathTime = cg.time + cg_teslaTrailTime.integer;
+ }
+ }
+ }
+ break;
+
+ case EV_BULLET_HIT_WALL:
+ DEBUGNAME( "EV_BULLET_HIT_WALL" );
+ ByteToDir( es->eventParm, dir );
+ CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD );
+ break;
+
+ case EV_BULLET_HIT_FLESH:
+ DEBUGNAME( "EV_BULLET_HIT_FLESH" );
+ CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm );
+ break;
+
+ case EV_SHOTGUN:
+ DEBUGNAME( "EV_SHOTGUN" );
+ CG_ShotgunFire( es );
+ break;
+
+ case EV_GENERAL_SOUND:
+ DEBUGNAME( "EV_GENERAL_SOUND" );
+ if( cgs.gameSounds[ es->eventParm ] )
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] );
+ else
+ {
+ s = CG_ConfigString( CS_SOUNDS + es->eventParm );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) );
+ }
+ break;
+
+ case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes
+ DEBUGNAME( "EV_GLOBAL_SOUND" );
+ if( cgs.gameSounds[ es->eventParm ] )
+ trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] );
+ else
+ {
+ s = CG_ConfigString( CS_SOUNDS + es->eventParm );
+ trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) );
+ }
+ break;
+
+ case EV_PAIN:
+ // local player sounds are triggered in CG_CheckLocalSounds,
+ // so ignore events on the player
+ DEBUGNAME( "EV_PAIN" );
+ if( cent->currentState.number != cg.snap->ps.clientNum )
+ CG_PainEvent( cent, es->eventParm );
+ break;
+
+ case EV_DEATH1:
+ case EV_DEATH2:
+ case EV_DEATH3:
+ DEBUGNAME( "EV_DEATHx" );
+ trap_S_StartSound( NULL, es->number, CHAN_VOICE,
+ CG_CustomSound( es->number, va( "*death%i.wav", event - EV_DEATH1 + 1 ) ) );
+ break;
+
+ case EV_OBITUARY:
+ DEBUGNAME( "EV_OBITUARY" );
+ CG_Obituary( es );
+ break;
+
+ case EV_GIB_PLAYER:
+ DEBUGNAME( "EV_GIB_PLAYER" );
+ // no gibbing
+ break;
+
+ case EV_STOPLOOPINGSOUND:
+ DEBUGNAME( "EV_STOPLOOPINGSOUND" );
+ trap_S_StopLoopingSound( es->number );
+ es->loopSound = 0;
+ break;
+
+ case EV_DEBUG_LINE:
+ DEBUGNAME( "EV_DEBUG_LINE" );
+ CG_Beam( cent );
+ break;
+
+ case EV_BUILD_DELAY:
+ DEBUGNAME( "EV_BUILD_DELAY" );
+ if( clientNum == cg.predictedPlayerState.clientNum )
+ {
+ trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND );
+ cg.lastBuildAttempt = cg.time;
+ }
+ break;
+
+ case EV_BUILD_REPAIR:
+ DEBUGNAME( "EV_BUILD_REPAIR" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairSound );
+ break;
+
+ case EV_BUILD_REPAIRED:
+ DEBUGNAME( "EV_BUILD_REPAIRED" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.buildableRepairedSound );
+ break;
+
+ case EV_OVERMIND_ATTACK:
+ DEBUGNAME( "EV_OVERMIND_ATTACK" );
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ trap_S_StartLocalSound( cgs.media.alienOvermindAttack, CHAN_ANNOUNCER );
+ CG_CenterPrint( "The Overmind is under attack!", 200, GIANTCHAR_WIDTH * 4 );
+ }
+ break;
+
+ case EV_OVERMIND_DYING:
+ DEBUGNAME( "EV_OVERMIND_DYING" );
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ trap_S_StartLocalSound( cgs.media.alienOvermindDying, CHAN_ANNOUNCER );
+ CG_CenterPrint( "The Overmind is dying!", 200, GIANTCHAR_WIDTH * 4 );
+ }
+ break;
+
+ case EV_DCC_ATTACK:
+ DEBUGNAME( "EV_DCC_ATTACK" );
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ //trap_S_StartLocalSound( cgs.media.humanDCCAttack, CHAN_ANNOUNCER );
+ CG_CenterPrint( "Our base is under attack!", 200, GIANTCHAR_WIDTH * 4 );
+ }
+ break;
+
+ case EV_OVERMIND_SPAWNS:
+ DEBUGNAME( "EV_OVERMIND_SPAWNS" );
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ trap_S_StartLocalSound( cgs.media.alienOvermindSpawns, CHAN_ANNOUNCER );
+ CG_CenterPrint( "The Overmind needs spawns!", 200, GIANTCHAR_WIDTH * 4 );
+ }
+ break;
+
+ case EV_ALIEN_EVOLVE:
+ DEBUGNAME( "EV_ALIEN_EVOLVE" );
+ trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.alienEvolveSound );
+ {
+ particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienEvolvePS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentCent( &ps->attachment, cent );
+ CG_AttachToCent( &ps->attachment );
+ }
+ }
+
+ if( es->number == cg.clientNum )
+ {
+ CG_ResetPainBlend( );
+ cg.spawnTime = cg.time;
+ }
+ break;
+
+ case EV_ALIEN_EVOLVE_FAILED:
+ DEBUGNAME( "EV_ALIEN_EVOLVE_FAILED" );
+ if( clientNum == cg.predictedPlayerState.clientNum )
+ {
+ //FIXME: change to "negative" sound
+ trap_S_StartLocalSound( cgs.media.buildableRepairedSound, CHAN_LOCAL_SOUND );
+ cg.lastEvolveAttempt = cg.time;
+ }
+ break;
+
+ case EV_ALIEN_ACIDTUBE:
+ DEBUGNAME( "EV_ALIEN_ACIDTUBE" );
+ {
+ particleSystem_t *ps = CG_SpawnNewParticleSystem( cgs.media.alienAcidTubePS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentCent( &ps->attachment, cent );
+ ByteToDir( es->eventParm, dir );
+ CG_SetParticleSystemNormal( ps, dir );
+ CG_AttachToCent( &ps->attachment );
+ }
+ }
+ break;
+
+ case EV_MEDKIT_USED:
+ DEBUGNAME( "EV_MEDKIT_USED" );
+ trap_S_StartSound( NULL, es->number, CHAN_AUTO, cgs.media.medkitUseSound );
+ break;
+
+ case EV_PLAYER_RESPAWN:
+ DEBUGNAME( "EV_PLAYER_RESPAWN" );
+ if( es->number == cg.clientNum )
+ cg.spawnTime = cg.time;
+ break;
+
+ default:
+ DEBUGNAME( "UNKNOWN" );
+ CG_Error( "Unknown event: %i", event );
+ break;
+ }
+}
+
+
+/*
+==============
+CG_CheckEvents
+
+==============
+*/
+void CG_CheckEvents( centity_t *cent )
+{
+ entity_event_t event;
+ entity_event_t oldEvent = EV_NONE;
+
+ // check for event-only entities
+ if( cent->currentState.eType > ET_EVENTS )
+ {
+ event = cent->currentState.eType - ET_EVENTS;
+
+ if( cent->previousEvent )
+ return; // already fired
+
+ cent->previousEvent = 1;
+
+ cent->currentState.event = cent->currentState.eType - ET_EVENTS;
+
+ // Move the pointer to the entity that the
+ // event was originally attached to
+ if( cent->currentState.eFlags & EF_PLAYER_EVENT )
+ {
+ cent = &cg_entities[ cent->currentState.otherEntityNum ];
+ oldEvent = cent->currentState.event;
+ cent->currentState.event = event;
+ }
+ }
+ else
+ {
+ // check for events riding with another entity
+ if( cent->currentState.event == cent->previousEvent )
+ return;
+
+ cent->previousEvent = cent->currentState.event;
+ if( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 )
+ return;
+ }
+
+ // calculate the position at exactly the frame time
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.snap->serverTime, cent->lerpOrigin );
+ CG_SetEntitySoundPosition( cent );
+
+ CG_EntityEvent( cent, cent->lerpOrigin );
+
+ // If this was a reattached spilled event, restore the original event
+ if( oldEvent != EV_NONE )
+ cent->currentState.event = oldEvent;
+}
+
diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h
new file mode 100644
index 0000000..74b7a4d
--- /dev/null
+++ b/src/cgame/cg_local.h
@@ -0,0 +1,2072 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#include "../qcommon/q_shared.h"
+#include "../renderer/tr_types.h"
+#include "../game/bg_public.h"
+#include "cg_public.h"
+#include "../ui/ui_shared.h"
+
+// The entire cgame module is unloaded and reloaded on each level change,
+// so there is NO persistant data between levels on the client side.
+// If you absolutely need something stored, it can either be kept
+// by the server in the server stored userinfos, or stashed in a cvar.
+
+#define CG_FONT_THRESHOLD 0.1
+
+#define POWERUP_BLINKS 5
+
+#define POWERUP_BLINK_TIME 1000
+#define FADE_TIME 200
+#define PULSE_TIME 200
+#define DAMAGE_DEFLECT_TIME 100
+#define DAMAGE_RETURN_TIME 400
+#define DAMAGE_TIME 500
+#define LAND_DEFLECT_TIME 150
+#define LAND_RETURN_TIME 300
+#define DUCK_TIME 100
+#define PAIN_TWITCH_TIME 200
+#define WEAPON_SELECT_TIME 1400
+#define ITEM_SCALEUP_TIME 1000
+#define ZOOM_TIME 150
+#define ITEM_BLOB_TIME 200
+#define MUZZLE_FLASH_TIME 20
+#define SINK_TIME 1000 // time for fragments to sink into ground before going away
+#define ATTACKER_HEAD_TIME 10000
+#define REWARD_TIME 3000
+
+#define PULSE_SCALE 1.5 // amount to scale up the icons when activating
+
+#define MAX_STEP_CHANGE 32
+
+#define MAX_VERTS_ON_POLY 10
+#define MAX_MARK_POLYS 256
+
+#define STAT_MINUS 10 // num frame for '-' stats digit
+
+#define ICON_SIZE 48
+#define CHAR_WIDTH 32
+#define CHAR_HEIGHT 48
+#define TEXT_ICON_SPACE 4
+
+// very large characters
+#define GIANT_WIDTH 32
+#define GIANT_HEIGHT 48
+
+#define NUM_CROSSHAIRS 10
+
+#define TEAM_OVERLAY_MAXNAME_WIDTH 12
+#define TEAM_OVERLAY_MAXLOCATION_WIDTH 16
+
+#define DEFAULT_MODEL "sarge"
+#define DEFAULT_TEAM_MODEL "sarge"
+#define DEFAULT_TEAM_HEAD "sarge"
+
+typedef enum
+{
+ FOOTSTEP_NORMAL,
+ FOOTSTEP_FLESH,
+ FOOTSTEP_METAL,
+ FOOTSTEP_SPLASH,
+ FOOTSTEP_CUSTOM,
+ FOOTSTEP_NONE,
+
+ FOOTSTEP_TOTAL
+} footstep_t;
+
+typedef enum
+{
+ IMPACTSOUND_DEFAULT,
+ IMPACTSOUND_METAL,
+ IMPACTSOUND_FLESH
+} impactSound_t;
+
+typedef enum
+{
+ JPS_OFF,
+ JPS_DESCENDING,
+ JPS_HOVERING,
+ JPS_ASCENDING
+} jetPackState_t;
+
+//======================================================================
+
+// when changing animation, set animationTime to frameTime + lerping time
+// The current lerp will finish out, then it will lerp to the new animation
+typedef struct
+{
+ int oldFrame;
+ int oldFrameTime; // time when ->oldFrame was exactly on
+
+ int frame;
+ int frameTime; // time when ->frame will be exactly on
+
+ float backlerp;
+
+ float yawAngle;
+ qboolean yawing;
+ float pitchAngle;
+ qboolean pitching;
+
+ int animationNumber; // may include ANIM_TOGGLEBIT
+ animation_t *animation;
+ int animationTime; // time when the first frame of the animation will be exact
+} lerpFrame_t;
+
+//======================================================================
+
+//attachment system
+typedef enum
+{
+ AT_STATIC,
+ AT_TAG,
+ AT_CENT,
+ AT_PARTICLE
+} attachmentType_t;
+
+//forward declaration for particle_t
+struct particle_s;
+
+typedef struct attachment_s
+{
+ attachmentType_t type;
+ qboolean attached;
+
+ qboolean staticValid;
+ qboolean tagValid;
+ qboolean centValid;
+ qboolean particleValid;
+
+ qboolean hasOffset;
+ vec3_t offset;
+
+ vec3_t lastValidAttachmentPoint;
+
+ //AT_STATIC
+ vec3_t origin;
+
+ //AT_TAG
+ refEntity_t re; //FIXME: should be pointers?
+ refEntity_t parent; //
+ qhandle_t model;
+ char tagName[ MAX_STRING_CHARS ];
+
+ //AT_CENT
+ int centNum;
+
+ //AT_PARTICLE
+ struct particle_s *particle;
+} attachment_t;
+
+//======================================================================
+
+//particle system stuff
+#define MAX_PARTICLE_FILES 128
+
+#define MAX_PS_SHADER_FRAMES 32
+#define MAX_PS_MODELS 8
+#define MAX_EJECTORS_PER_SYSTEM 4
+#define MAX_PARTICLES_PER_EJECTOR 4
+
+#define MAX_BASEPARTICLE_SYSTEMS 192
+#define MAX_BASEPARTICLE_EJECTORS MAX_BASEPARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM
+#define MAX_BASEPARTICLES MAX_BASEPARTICLE_EJECTORS*MAX_PARTICLES_PER_EJECTOR
+
+#define MAX_PARTICLE_SYSTEMS 48
+#define MAX_PARTICLE_EJECTORS MAX_PARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM
+#define MAX_PARTICLES MAX_PARTICLE_EJECTORS*5
+
+#define PARTICLES_INFINITE -1
+#define PARTICLES_SAME_AS_INITIAL -2
+
+//COMPILE TIME STRUCTURES
+typedef enum
+{
+ PMT_STATIC,
+ PMT_STATIC_TRANSFORM,
+ PMT_TAG,
+ PMT_CENT_ANGLES,
+ PMT_NORMAL
+} pMoveType_t;
+
+typedef enum
+{
+ PMD_LINEAR,
+ PMD_POINT
+} pDirType_t;
+
+typedef struct pMoveValues_u
+{
+ pDirType_t dirType;
+
+ //PMD_LINEAR
+ vec3_t dir;
+ float dirRandAngle;
+
+ //PMD_POINT
+ vec3_t point;
+ float pointRandAngle;
+
+ float mag;
+ float magRandFrac;
+
+ float parentVelFrac;
+ float parentVelFracRandFrac;
+} pMoveValues_t;
+
+typedef struct pLerpValues_s
+{
+ int delay;
+ float delayRandFrac;
+
+ float initial;
+ float initialRandFrac;
+
+ float final;
+ float finalRandFrac;
+
+ float randFrac;
+} pLerpValues_t;
+
+//particle template
+typedef struct baseParticle_s
+{
+ vec3_t displacement;
+ float randDisplacement;
+ float normalDisplacement;
+
+ pMoveType_t velMoveType;
+ pMoveValues_t velMoveValues;
+
+ pMoveType_t accMoveType;
+ pMoveValues_t accMoveValues;
+
+ int lifeTime;
+ float lifeTimeRandFrac;
+
+ float bounceFrac;
+ float bounceFracRandFrac;
+ qboolean bounceCull;
+
+ char bounceMarkName[ MAX_QPATH ];
+ qhandle_t bounceMark;
+ float bounceMarkRadius;
+ float bounceMarkRadiusRandFrac;
+ float bounceMarkCount;
+ float bounceMarkCountRandFrac;
+
+ char bounceSoundName[ MAX_QPATH ];
+ qhandle_t bounceSound;
+ float bounceSoundCount;
+ float bounceSoundCountRandFrac;
+
+ pLerpValues_t radius;
+ pLerpValues_t alpha;
+ pLerpValues_t rotation;
+
+ qboolean dynamicLight;
+ pLerpValues_t dLightRadius;
+ byte dLightColor[ 3 ];
+
+ int colorDelay;
+ float colorDelayRandFrac;
+ byte initialColor[ 3 ];
+ byte finalColor[ 3 ];
+
+ char childSystemName[ MAX_QPATH ];
+ qhandle_t childSystemHandle;
+
+ char onDeathSystemName[ MAX_QPATH ];
+ qhandle_t onDeathSystemHandle;
+
+ char childTrailSystemName[ MAX_QPATH ];
+ qhandle_t childTrailSystemHandle;
+
+ //particle invariant stuff
+ char shaderNames[ MAX_PS_SHADER_FRAMES ][ MAX_QPATH ];
+ qhandle_t shaders[ MAX_PS_SHADER_FRAMES ];
+ int numFrames;
+ float framerate;
+
+ char modelNames[ MAX_PS_MODELS ][ MAX_QPATH ];
+ qhandle_t models[ MAX_PS_MODELS ];
+ int numModels;
+ animation_t modelAnimation;
+
+ qboolean overdrawProtection;
+ qboolean realLight;
+ qboolean cullOnStartSolid;
+} baseParticle_t;
+
+
+//ejector template
+typedef struct baseParticleEjector_s
+{
+ baseParticle_t *particles[ MAX_PARTICLES_PER_EJECTOR ];
+ int numParticles;
+
+ pLerpValues_t eject; //zero period indicates creation of all particles at once
+
+ int totalParticles; //can be infinite
+ float totalParticlesRandFrac;
+} baseParticleEjector_t;
+
+
+//particle system template
+typedef struct baseParticleSystem_s
+{
+ char name[ MAX_QPATH ];
+ baseParticleEjector_t *ejectors[ MAX_EJECTORS_PER_SYSTEM ];
+ int numEjectors;
+
+ qboolean thirdPersonOnly;
+ qboolean registered; //whether or not the assets for this particle have been loaded
+} baseParticleSystem_t;
+
+
+//RUN TIME STRUCTURES
+typedef struct particleSystem_s
+{
+ baseParticleSystem_t *class;
+
+ attachment_t attachment;
+
+ qboolean valid;
+ qboolean lazyRemove; //mark this system for later removal
+
+ //for PMT_NORMAL
+ qboolean normalValid;
+ vec3_t normal;
+} particleSystem_t;
+
+
+typedef struct particleEjector_s
+{
+ baseParticleEjector_t *class;
+ particleSystem_t *parent;
+
+ pLerpValues_t ejectPeriod;
+
+ int count;
+ int totalParticles;
+
+ int nextEjectionTime;
+
+ qboolean valid;
+} particleEjector_t;
+
+
+//used for actual particle evaluation
+typedef struct particle_s
+{
+ baseParticle_t *class;
+ particleEjector_t *parent;
+
+ int birthTime;
+ int lifeTime;
+
+ float bounceMarkRadius;
+ int bounceMarkCount;
+ int bounceSoundCount;
+ qboolean atRest;
+
+ vec3_t origin;
+ vec3_t velocity;
+
+ pMoveType_t accMoveType;
+ pMoveValues_t accMoveValues;
+
+ int lastEvalTime;
+
+ int nextChildTime;
+
+ pLerpValues_t radius;
+ pLerpValues_t alpha;
+ pLerpValues_t rotation;
+
+ pLerpValues_t dLightRadius;
+
+ int colorDelay;
+
+ qhandle_t model;
+ lerpFrame_t lf;
+ vec3_t lastAxis[ 3 ];
+
+ qboolean valid;
+ int frameWhenInvalidated;
+
+ int sortKey;
+} particle_t;
+
+//======================================================================
+
+//trail system stuff
+#define MAX_TRAIL_FILES 128
+
+#define MAX_BEAMS_PER_SYSTEM 4
+
+#define MAX_BASETRAIL_SYSTEMS 64
+#define MAX_BASETRAIL_BEAMS MAX_BASETRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM
+
+#define MAX_TRAIL_SYSTEMS 32
+#define MAX_TRAIL_BEAMS MAX_TRAIL_SYSTEMS*MAX_BEAMS_PER_SYSTEM
+#define MAX_TRAIL_BEAM_NODES 128
+
+#define MAX_TRAIL_BEAM_JITTERS 4
+
+typedef enum
+{
+ TBTT_STRETCH,
+ TBTT_REPEAT
+} trailBeamTextureType_t;
+
+typedef struct baseTrailJitter_s
+{
+ float magnitude;
+ int period;
+} baseTrailJitter_t;
+
+//beam template
+typedef struct baseTrailBeam_s
+{
+ int numSegments;
+ float frontWidth;
+ float backWidth;
+ float frontAlpha;
+ float backAlpha;
+ byte frontColor[ 3 ];
+ byte backColor[ 3 ];
+
+ // the time it takes for a segment to vanish (single attached only)
+ int segmentTime;
+
+ // the time it takes for a beam to fade out (double attached only)
+ int fadeOutTime;
+
+ char shaderName[ MAX_QPATH ];
+ qhandle_t shader;
+
+ trailBeamTextureType_t textureType;
+
+ //TBTT_STRETCH
+ float frontTextureCoord;
+ float backTextureCoord;
+
+ //TBTT_REPEAT
+ float repeatLength;
+ qboolean clampToBack;
+
+ qboolean realLight;
+
+ int numJitters;
+ baseTrailJitter_t jitters[ MAX_TRAIL_BEAM_JITTERS ];
+ qboolean jitterAttachments;
+} baseTrailBeam_t;
+
+
+//trail system template
+typedef struct baseTrailSystem_s
+{
+ char name[ MAX_QPATH ];
+ baseTrailBeam_t *beams[ MAX_BEAMS_PER_SYSTEM ];
+ int numBeams;
+
+ qboolean thirdPersonOnly;
+ qboolean registered; //whether or not the assets for this trail have been loaded
+} baseTrailSystem_t;
+
+typedef struct trailSystem_s
+{
+ baseTrailSystem_t *class;
+
+ attachment_t frontAttachment;
+ attachment_t backAttachment;
+
+ int destroyTime;
+ qboolean valid;
+} trailSystem_t;
+
+typedef struct trailBeamNode_s
+{
+ vec3_t refPosition;
+ vec3_t position;
+
+ int timeLeft;
+
+ float textureCoord;
+ float halfWidth;
+ byte alpha;
+ byte color[ 3 ];
+
+ vec2_t jitters[ MAX_TRAIL_BEAM_JITTERS ];
+
+ struct trailBeamNode_s *prev;
+ struct trailBeamNode_s *next;
+
+ qboolean used;
+} trailBeamNode_t;
+
+typedef struct trailBeam_s
+{
+ baseTrailBeam_t *class;
+ trailSystem_t *parent;
+
+ trailBeamNode_t nodePool[ MAX_TRAIL_BEAM_NODES ];
+ trailBeamNode_t *nodes;
+
+ int lastEvalTime;
+
+ qboolean valid;
+
+ int nextJitterTimes[ MAX_TRAIL_BEAM_JITTERS ];
+} trailBeam_t;
+
+//======================================================================
+
+// player entities need to track more information
+// than any other type of entity.
+
+// note that not every player entity is a client entity,
+// because corpses after respawn are outside the normal
+// client numbering range
+
+//TA: smoothing of view and model for WW transitions
+#define MAXSMOOTHS 32
+
+typedef struct
+{
+ float time;
+ float timeMod;
+
+ vec3_t rotAxis;
+ float rotAngle;
+} smooth_t;
+
+
+typedef struct
+{
+ lerpFrame_t legs, torso, flag, nonseg;
+ int painTime;
+ int painDirection; // flip from 0 to 1
+
+ // machinegun spinning
+ float barrelAngle;
+ int barrelTime;
+ qboolean barrelSpinning;
+
+ vec3_t lastNormal;
+ vec3_t lastAxis[ 3 ];
+ smooth_t sList[ MAXSMOOTHS ];
+} playerEntity_t;
+
+typedef struct lightFlareStatus_s
+{
+ float lastRadius; //caching of likely flare radius
+ float lastRatio; //caching of likely flare ratio
+ int lastTime; //last time flare was visible/occluded
+ qboolean status; //flare is visble?
+} lightFlareStatus_t;
+
+typedef struct buildableStatus_s
+{
+ int lastTime; // Last time status was visible
+ qboolean visible; // Status is visble?
+} buildableStatus_t;
+
+//=================================================
+
+// centity_t have a direct corespondence with gentity_t in the game, but
+// only the entityState_t is directly communicated to the cgame
+typedef struct centity_s
+{
+ entityState_t currentState; // from cg.frame
+ entityState_t nextState; // from cg.nextFrame, if available
+ qboolean interpolate; // true if next is valid to interpolate to
+ qboolean currentValid; // true if cg.frame holds this entity
+
+ int muzzleFlashTime; // move to playerEntity?
+ int muzzleFlashTime2; // move to playerEntity?
+ int muzzleFlashTime3; // move to playerEntity?
+ int previousEvent;
+ int teleportFlag;
+
+ int trailTime; // so missile trails can handle dropped initial packets
+ int dustTrailTime;
+ int miscTime;
+ int snapShotTime; // last time this entity was found in a snapshot
+
+ playerEntity_t pe;
+
+ int errorTime; // decay the error from this time
+ vec3_t errorOrigin;
+ vec3_t errorAngles;
+
+ qboolean extrapolated; // false if origin / angles is an interpolation
+ vec3_t rawOrigin;
+ vec3_t rawAngles;
+
+ vec3_t beamEnd;
+
+ // exact interpolated position of entity on this frame
+ vec3_t lerpOrigin;
+ vec3_t lerpAngles;
+
+ lerpFrame_t lerpFrame;
+
+ //TA:
+ buildableAnimNumber_t buildableAnim; //persistant anim number
+ buildableAnimNumber_t oldBuildableAnim; //to detect when new anims are set
+ particleSystem_t *buildablePS;
+ buildableStatus_t buildableStatus;
+ float lastBuildableHealthScale;
+ int lastBuildableDamageSoundTime;
+
+ lightFlareStatus_t lfs;
+
+ qboolean doorState;
+
+ qboolean animInit;
+ qboolean animPlaying;
+ qboolean animLastState;
+
+ particleSystem_t *muzzlePS;
+ qboolean muzzlePsTrigger;
+
+ particleSystem_t *jetPackPS;
+ jetPackState_t jetPackState;
+
+ particleSystem_t *entityPS;
+ qboolean entityPSMissing;
+
+ trailSystem_t *level2ZapTS[ 3 ];
+
+ trailSystem_t *muzzleTS; //used for the tesla and reactor
+ int muzzleTSDeathTime;
+
+ qboolean valid;
+ qboolean oldValid;
+} centity_t;
+
+
+//======================================================================
+
+typedef struct markPoly_s
+{
+ struct markPoly_s *prevMark, *nextMark;
+ int time;
+ qhandle_t markShader;
+ qboolean alphaFade; // fade alpha instead of rgb
+ float color[ 4 ];
+ poly_t poly;
+ polyVert_t verts[ MAX_VERTS_ON_POLY ];
+} markPoly_t;
+
+//======================================================================
+
+
+typedef struct
+{
+ int client;
+ int score;
+ int ping;
+ int time;
+ int team;
+ weapon_t weapon;
+ upgrade_t upgrade;
+} score_t;
+
+// each client has an associated clientInfo_t
+// that contains media references necessary to present the
+// client model and other color coded effects
+// this is regenerated each time a client's configstring changes,
+// usually as a result of a userinfo (name, model, etc) change
+#define MAX_CUSTOM_SOUNDS 32
+typedef struct
+{
+ qboolean infoValid;
+
+ char name[ MAX_QPATH ];
+ pTeam_t team;
+
+ int botSkill; // 0 = not bot, 1-5 = bot
+
+ vec3_t color1;
+ vec3_t color2;
+
+ int score; // updated by score servercmds
+ int location; // location index for team mode
+ int health; // you only get this info about your teammates
+ int armor;
+ int curWeapon;
+
+ int handicap;
+ int wins, losses; // in tourney mode
+
+ int teamTask; // task in teamplay (offence/defence)
+ qboolean teamLeader; // true when this is a team leader
+
+ int powerups; // so can display quad/flag status
+
+ int medkitUsageTime;
+ int invulnerabilityStartTime;
+ int invulnerabilityStopTime;
+
+ int breathPuffTime;
+
+ // when clientinfo is changed, the loading of models/skins/sounds
+ // can be deferred until you are dead, to prevent hitches in
+ // gameplay
+ char modelName[ MAX_QPATH ];
+ char skinName[ MAX_QPATH ];
+ char headModelName[ MAX_QPATH ];
+ char headSkinName[ MAX_QPATH ];
+
+ qboolean newAnims; // true if using the new mission pack animations
+ qboolean fixedlegs; // true if legs yaw is always the same as torso yaw
+ qboolean fixedtorso; // true if torso never changes yaw
+ qboolean nonsegmented; // true if model is Q2 style nonsegmented
+
+ vec3_t headOffset; // move head in icon views
+ footstep_t footsteps;
+ gender_t gender; // from model
+
+ qhandle_t legsModel;
+ qhandle_t legsSkin;
+
+ qhandle_t torsoModel;
+ qhandle_t torsoSkin;
+
+ qhandle_t headModel;
+ qhandle_t headSkin;
+
+ qhandle_t nonSegModel; //non-segmented model system
+ qhandle_t nonSegSkin; //non-segmented model system
+
+ qhandle_t modelIcon;
+
+ animation_t animations[ MAX_PLAYER_TOTALANIMATIONS ];
+
+ sfxHandle_t sounds[ MAX_CUSTOM_SOUNDS ];
+
+ sfxHandle_t customFootsteps[ 4 ];
+ sfxHandle_t customMetalFootsteps[ 4 ];
+} clientInfo_t;
+
+
+typedef struct weaponInfoMode_s
+{
+ float flashDlight;
+ vec3_t flashDlightColor;
+ sfxHandle_t flashSound[ 4 ]; // fast firing weapons randomly choose
+ qboolean continuousFlash;
+
+ qhandle_t missileModel;
+ sfxHandle_t missileSound;
+ float missileDlight;
+ vec3_t missileDlightColor;
+ int missileRenderfx;
+ qboolean usesSpriteMissle;
+ qhandle_t missileSprite;
+ int missileSpriteSize;
+ qhandle_t missileParticleSystem;
+ qhandle_t missileTrailSystem;
+ qboolean missileRotates;
+ qboolean missileAnimates;
+ int missileAnimStartFrame;
+ int missileAnimNumFrames;
+ int missileAnimFrameRate;
+ int missileAnimLooping;
+
+ sfxHandle_t firingSound;
+ qboolean loopFireSound;
+
+ qhandle_t muzzleParticleSystem;
+
+ qboolean alwaysImpact;
+ qhandle_t impactParticleSystem;
+ qhandle_t impactMark;
+ qhandle_t impactMarkSize;
+ sfxHandle_t impactSound[ 4 ]; //random impact sound
+ sfxHandle_t impactFleshSound[ 4 ]; //random impact sound
+} weaponInfoMode_t;
+
+// each WP_* weapon enum has an associated weaponInfo_t
+// that contains media references necessary to present the
+// weapon and its effects
+typedef struct weaponInfo_s
+{
+ qboolean registered;
+ char *humanName;
+
+ qhandle_t handsModel; // the hands don't actually draw, they just position the weapon
+ qhandle_t weaponModel;
+ qhandle_t barrelModel;
+ qhandle_t flashModel;
+
+ vec3_t weaponMidpoint; // so it will rotate centered instead of by tag
+
+ qhandle_t weaponIcon;
+ qhandle_t ammoIcon;
+
+ qhandle_t crossHair;
+ int crossHairSize;
+
+ sfxHandle_t readySound;
+
+ qboolean disableIn3rdPerson;
+
+ weaponInfoMode_t wim[ WPM_NUM_WEAPONMODES ];
+} weaponInfo_t;
+
+typedef struct upgradeInfo_s
+{
+ qboolean registered;
+ char *humanName;
+
+ qhandle_t upgradeIcon;
+} upgradeInfo_t;
+
+typedef struct
+{
+ qboolean looped;
+ qboolean enabled;
+
+ sfxHandle_t sound;
+} sound_t;
+
+typedef struct
+{
+ qhandle_t models[ MAX_BUILDABLE_MODELS ];
+ animation_t animations[ MAX_BUILDABLE_ANIMATIONS ];
+
+ //same number of sounds as animations
+ sound_t sounds[ MAX_BUILDABLE_ANIMATIONS ];
+} buildableInfo_t;
+
+#define MAX_REWARDSTACK 10
+#define MAX_SOUNDBUFFER 20
+
+//======================================================================
+
+//TA:
+typedef struct
+{
+ vec3_t alienBuildablePos[ MAX_GENTITIES ];
+ int alienBuildableTimes[ MAX_GENTITIES ];
+ int numAlienBuildables;
+
+ vec3_t humanBuildablePos[ MAX_GENTITIES ];
+ int numHumanBuildables;
+
+ vec3_t alienClientPos[ MAX_CLIENTS ];
+ int numAlienClients;
+
+ vec3_t humanClientPos[ MAX_CLIENTS ];
+ int numHumanClients;
+
+ int lastUpdateTime;
+ vec3_t origin;
+ vec3_t vangles;
+} entityPos_t;
+
+typedef struct
+{
+ int time;
+ int length;
+} consoleLine_t;
+
+#define MAX_CONSOLE_TEXT 8192
+#define MAX_CONSOLE_LINES 32
+
+// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action
+// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after
+
+#define MAX_PREDICTED_EVENTS 16
+
+#define NUM_SAVED_STATES ( CMD_BACKUP + 2 )
+
+typedef struct
+{
+ int clientFrame; // incremented each frame
+
+ int clientNum;
+
+ qboolean demoPlayback;
+ qboolean levelShot; // taking a level menu screenshot
+ int deferredPlayerLoading;
+ qboolean loading; // don't defer players at initial startup
+ qboolean intermissionStarted; // don't play voice rewards, because game will end shortly
+
+ // there are only one or two snapshot_t that are relevent at a time
+ int latestSnapshotNum; // the number of snapshots the client system has received
+ int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet
+
+ snapshot_t *snap; // cg.snap->serverTime <= cg.time
+ snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL
+ snapshot_t activeSnapshots[ 2 ];
+
+ float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) /
+ // (cg.nextFrame->serverTime - cg.frame->serverTime)
+
+ qboolean thisFrameTeleport;
+ qboolean nextFrameTeleport;
+
+ int frametime; // cg.time - cg.oldTime
+
+ int time; // this is the time value that the client
+ // is rendering at.
+ int oldTime; // time at last frame, used for missile trails and prediction checking
+
+ int physicsTime; // either cg.snap->time or cg.nextSnap->time
+
+ int timelimitWarnings; // 5 min, 1 min, overtime
+ int fraglimitWarnings;
+
+ qboolean mapRestart; // set on a map restart to set back the weapon
+
+ qboolean renderingThirdPerson; // during deaths, chasecams, etc
+
+ // prediction state
+ qboolean hyperspace; // true if prediction has hit a trigger_teleport
+ playerState_t predictedPlayerState;
+ pmoveExt_t pmext;
+ centity_t predictedPlayerEntity;
+ qboolean validPPS; // clear until the first call to CG_PredictPlayerState
+ int predictedErrorTime;
+ vec3_t predictedError;
+
+ int eventSequence;
+ int predictableEvents[MAX_PREDICTED_EVENTS];
+
+ float stepChange; // for stair up smoothing
+ int stepTime;
+
+ float duckChange; // for duck viewheight smoothing
+ int duckTime;
+
+ float landChange; // for landing hard
+ int landTime;
+
+ // input state sent to server
+ int weaponSelect;
+
+ // auto rotating items
+ vec3_t autoAngles;
+ vec3_t autoAxis[ 3 ];
+ vec3_t autoAnglesFast;
+ vec3_t autoAxisFast[ 3 ];
+
+ // view rendering
+ refdef_t refdef;
+ vec3_t refdefViewAngles; // will be converted to refdef.viewaxis
+
+ // zoom key
+ qboolean zoomed;
+ int zoomTime;
+ float zoomSensitivity;
+
+ // information screen text during loading
+ char infoScreenText[ MAX_STRING_CHARS ];
+
+ // scoreboard
+ int scoresRequestTime;
+ int numScores;
+ int selectedScore;
+ int teamScores[ 2 ];
+ score_t scores[MAX_CLIENTS];
+ qboolean showScores;
+ qboolean scoreBoardShowing;
+ int scoreFadeTime;
+ char killerName[ MAX_NAME_LENGTH ];
+ char spectatorList[ MAX_STRING_CHARS ]; // list of names
+ int spectatorLen; // length of list
+ float spectatorWidth; // width in device units
+ int spectatorTime; // next time to offset
+ int spectatorPaintX; // current paint x
+ int spectatorPaintX2; // current paint x
+ int spectatorOffset; // current offset from start
+ int spectatorPaintLen; // current offset from start
+
+ // centerprinting
+ int centerPrintTime;
+ int centerPrintCharWidth;
+ int centerPrintY;
+ char centerPrint[ 1024 ];
+ int centerPrintLines;
+
+ // low ammo warning state
+ int lowAmmoWarning; // 1 = low, 2 = empty
+
+ // kill timers for carnage reward
+ int lastKillTime;
+
+ // crosshair client ID
+ int crosshairClientNum;
+ int crosshairClientTime;
+
+ // powerup active flashing
+ int powerupActive;
+ int powerupTime;
+
+ // attacking player
+ int attackerTime;
+ int voiceTime;
+
+ // reward medals
+ int rewardStack;
+ int rewardTime;
+ int rewardCount[ MAX_REWARDSTACK ];
+ qhandle_t rewardShader[ MAX_REWARDSTACK ];
+ qhandle_t rewardSound[ MAX_REWARDSTACK ];
+
+ // sound buffer mainly for announcer sounds
+ int soundBufferIn;
+ int soundBufferOut;
+ int soundTime;
+ qhandle_t soundBuffer[ MAX_SOUNDBUFFER ];
+
+ // for voice chat buffer
+ int voiceChatTime;
+ int voiceChatBufferIn;
+ int voiceChatBufferOut;
+
+ // warmup countdown
+ int warmup;
+ int warmupCount;
+
+ //==========================
+
+ int itemPickup;
+ int itemPickupTime;
+ int itemPickupBlendTime; // the pulse around the crosshair is timed seperately
+
+ int weaponSelectTime;
+ int weaponAnimation;
+ int weaponAnimationTime;
+
+ // blend blobs
+ float damageTime;
+ float damageX, damageY, damageValue;
+
+ // status bar head
+ float headYaw;
+ float headEndPitch;
+ float headEndYaw;
+ int headEndTime;
+ float headStartPitch;
+ float headStartYaw;
+ int headStartTime;
+
+ // view movement
+ float v_dmg_time;
+ float v_dmg_pitch;
+ float v_dmg_roll;
+
+ vec3_t kick_angles; // weapon kicks
+ vec3_t kick_origin;
+
+ // temp working variables for player view
+ float bobfracsin;
+ int bobcycle;
+ float xyspeed;
+ int nextOrbitTime;
+
+ // development tool
+ refEntity_t testModelEntity;
+ refEntity_t testModelBarrelEntity;
+ char testModelName[MAX_QPATH];
+ char testModelBarrelName[MAX_QPATH];
+ qboolean testGun;
+
+ int spawnTime; //TA: fovwarp
+ int weapon1Time; //TA: time when BUTTON_ATTACK went t->f f->t
+ int weapon2Time; //TA: time when BUTTON_ATTACK2 went t->f f->t
+ int weapon3Time; //TA: time when BUTTON_USE_HOLDABLE went t->f f->t
+ qboolean weapon1Firing;
+ qboolean weapon2Firing;
+ qboolean weapon3Firing;
+
+ int poisonedTime;
+
+ vec3_t lastNormal; //TA: view smoothage
+ vec3_t lastVangles; //TA: view smoothage
+ smooth_t sList[ MAXSMOOTHS ]; //TA: WW smoothing
+
+ int forwardMoveTime; //TA: for struggling
+ int rightMoveTime;
+ int upMoveTime;
+
+ float charModelFraction; //TA: loading percentages
+ float mediaFraction;
+ float buildablesFraction;
+
+ int lastBuildAttempt;
+ int lastEvolveAttempt;
+
+ char consoleText[ MAX_CONSOLE_TEXT ];
+ consoleLine_t consoleLines[ MAX_CONSOLE_LINES ];
+ int numConsoleLines;
+
+ particleSystem_t *poisonCloudPS;
+
+ float painBlendValue;
+ float painBlendTarget;
+ int lastHealth;
+
+ int lastPredictedCommand;
+ int lastServerTime;
+ playerState_t savedPmoveStates[ NUM_SAVED_STATES ];
+ int stateHead, stateTail;
+ int ping;
+} cg_t;
+
+
+// all of the model, shader, and sound references that are
+// loaded at gamestate time are stored in cgMedia_t
+// Other media that can be tied to clients, weapons, or items are
+// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t
+typedef struct
+{
+ qhandle_t charsetShader;
+ qhandle_t whiteShader;
+ qhandle_t outlineShader;
+
+ qhandle_t level2ZapTS;
+
+ qhandle_t balloonShader;
+ qhandle_t connectionShader;
+
+ qhandle_t viewBloodShader;
+ qhandle_t tracerShader;
+ qhandle_t crosshairShader[ WP_NUM_WEAPONS ];
+ qhandle_t backTileShader;
+
+ qhandle_t creepShader;
+
+ qhandle_t scannerShader;
+ qhandle_t scannerBlipShader;
+ qhandle_t scannerLineShader;
+
+
+ qhandle_t numberShaders[ 11 ];
+
+ qhandle_t shadowMarkShader;
+ qhandle_t wakeMarkShader;
+
+ // buildable shaders
+ qhandle_t greenBuildShader;
+ qhandle_t redBuildShader;
+ qhandle_t humanSpawningShader;
+
+ // disconnect
+ qhandle_t disconnectPS;
+ qhandle_t disconnectSound;
+
+ // sounds
+ sfxHandle_t tracerSound;
+ sfxHandle_t selectSound;
+ sfxHandle_t footsteps[ FOOTSTEP_TOTAL ][ 4 ];
+ sfxHandle_t talkSound;
+ sfxHandle_t alienTalkSound;
+ sfxHandle_t humanTalkSound;
+ sfxHandle_t landSound;
+ sfxHandle_t fallSound;
+
+ sfxHandle_t hardBounceSound1;
+ sfxHandle_t hardBounceSound2;
+
+ sfxHandle_t voteNow;
+ sfxHandle_t votePassed;
+ sfxHandle_t voteFailed;
+
+ sfxHandle_t watrInSound;
+ sfxHandle_t watrOutSound;
+ sfxHandle_t watrUnSound;
+
+ sfxHandle_t jetpackDescendSound;
+ sfxHandle_t jetpackIdleSound;
+ sfxHandle_t jetpackAscendSound;
+
+ qhandle_t jetPackDescendPS;
+ qhandle_t jetPackHoverPS;
+ qhandle_t jetPackAscendPS;
+
+ sfxHandle_t medkitUseSound;
+
+ sfxHandle_t alienStageTransition;
+ sfxHandle_t humanStageTransition;
+
+ sfxHandle_t alienOvermindAttack;
+ sfxHandle_t alienOvermindDying;
+ sfxHandle_t alienOvermindSpawns;
+
+ sfxHandle_t alienBuildableExplosion;
+ sfxHandle_t alienBuildableDamage;
+ sfxHandle_t alienBuildablePrebuild;
+ sfxHandle_t humanBuildableExplosion;
+ sfxHandle_t humanBuildablePrebuild;
+ sfxHandle_t humanBuildableDamage[ 4 ];
+
+ sfxHandle_t alienL1Grab;
+ sfxHandle_t alienL4ChargePrepare;
+ sfxHandle_t alienL4ChargeStart;
+
+ qhandle_t cursor;
+ qhandle_t selectCursor;
+ qhandle_t sizeCursor;
+
+ //light armour
+ qhandle_t larmourHeadSkin;
+ qhandle_t larmourLegsSkin;
+ qhandle_t larmourTorsoSkin;
+
+ qhandle_t jetpackModel;
+ qhandle_t jetpackFlashModel;
+ qhandle_t battpackModel;
+
+ sfxHandle_t repeaterUseSound;
+
+ sfxHandle_t buildableRepairSound;
+ sfxHandle_t buildableRepairedSound;
+
+ qhandle_t poisonCloudPS;
+ qhandle_t alienEvolvePS;
+ qhandle_t alienAcidTubePS;
+
+ sfxHandle_t alienEvolveSound;
+
+ qhandle_t humanBuildableDamagedPS;
+ qhandle_t humanBuildableDestroyedPS;
+ qhandle_t alienBuildableDamagedPS;
+ qhandle_t alienBuildableDestroyedPS;
+
+ qhandle_t alienBleedPS;
+ qhandle_t humanBleedPS;
+
+ qhandle_t teslaZapTS;
+
+ sfxHandle_t lCannonWarningSound;
+
+ qhandle_t buildWeaponTimerPie[ 8 ];
+ qhandle_t upgradeClassIconShader;
+} cgMedia_t;
+
+typedef struct
+{
+ qhandle_t frameShader;
+ qhandle_t overlayShader;
+ qhandle_t noPowerShader;
+ qhandle_t markedShader;
+ vec4_t healthSevereColor;
+ vec4_t healthHighColor;
+ vec4_t healthElevatedColor;
+ vec4_t healthGuardedColor;
+ vec4_t healthLowColor;
+ int frameHeight;
+ int frameWidth;
+ int healthPadding;
+ int overlayHeight;
+ int overlayWidth;
+ float verticalMargin;
+ float horizontalMargin;
+ vec4_t foreColor;
+ vec4_t backColor;
+ qboolean loaded;
+} buildStat_t;
+
+
+// The client game static (cgs) structure hold everything
+// loaded or calculated from the gamestate. It will NOT
+// be cleared when a tournement restart is done, allowing
+// all clients to begin playing instantly
+typedef struct
+{
+ gameState_t gameState; // gamestate from server
+ glconfig_t glconfig; // rendering configuration
+ float screenXScale; // derived from glconfig
+ float screenYScale;
+ float screenXBias;
+
+ int serverCommandSequence; // reliable command stream counter
+ int processedSnapshotNum; // the number of snapshots cgame has requested
+
+ qboolean localServer; // detected on startup by checking sv_running
+
+ // parsed from serverinfo
+ int timelimit;
+ int maxclients;
+ char mapname[ MAX_QPATH ];
+ qboolean markDeconstruct; // Whether or not buildables are marked
+
+ int voteTime;
+ int voteYes;
+ int voteNo;
+ qboolean voteModified; // beep whenever changed
+ char voteString[ MAX_STRING_TOKENS ];
+
+ int teamVoteTime[ 2 ];
+ int teamVoteYes[ 2 ];
+ int teamVoteNo[ 2 ];
+ qboolean teamVoteModified[ 2 ]; // beep whenever changed
+ char teamVoteString[ 2 ][ MAX_STRING_TOKENS ];
+
+ int levelStartTime;
+
+ int scores1, scores2; // from configstrings
+
+ qboolean newHud;
+
+ int alienBuildPoints;
+ int alienBuildPointsTotal;
+ int humanBuildPoints;
+ int humanBuildPointsTotal;
+ int humanBuildPointsPowered;
+
+ int alienStage;
+ int humanStage;
+ int alienKills;
+ int humanKills;
+ int alienNextStageThreshold;
+ int humanNextStageThreshold;
+
+ int numAlienSpawns;
+ int numHumanSpawns;
+
+ //
+ // locally derived information from gamestate
+ //
+ qhandle_t gameModels[ MAX_MODELS ];
+ qhandle_t gameShaders[ MAX_GAME_SHADERS ];
+ qhandle_t gameParticleSystems[ MAX_GAME_PARTICLE_SYSTEMS ];
+ sfxHandle_t gameSounds[ MAX_SOUNDS ];
+
+ int numInlineModels;
+ qhandle_t inlineDrawModel[ MAX_MODELS ];
+ vec3_t inlineModelMidpoints[ MAX_MODELS ];
+
+ clientInfo_t clientinfo[ MAX_CLIENTS ];
+
+ //TA: corpse info
+ clientInfo_t corpseinfo[ MAX_CLIENTS ];
+
+ int cursorX;
+ int cursorY;
+ qboolean eventHandling;
+ qboolean mouseCaptured;
+ qboolean sizingHud;
+ void *capturedItem;
+ qhandle_t activeCursor;
+
+ buildStat_t alienBuildStat;
+ buildStat_t humanBuildStat;
+
+ // media
+ cgMedia_t media;
+} cgs_t;
+
+//==============================================================================
+
+extern cgs_t cgs;
+extern cg_t cg;
+extern centity_t cg_entities[ MAX_GENTITIES ];
+
+//TA: weapon limit expanded:
+//extern weaponInfo_t cg_weapons[MAX_WEAPONS];
+extern weaponInfo_t cg_weapons[ 32 ];
+//TA: upgrade infos:
+extern upgradeInfo_t cg_upgrades[ 32 ];
+
+//TA: buildable infos:
+extern buildableInfo_t cg_buildables[ BA_NUM_BUILDABLES ];
+
+extern markPoly_t cg_markPolys[ MAX_MARK_POLYS ];
+
+extern vmCvar_t cg_centertime;
+extern vmCvar_t cg_runpitch;
+extern vmCvar_t cg_runroll;
+extern vmCvar_t cg_bobup;
+extern vmCvar_t cg_bobpitch;
+extern vmCvar_t cg_bobroll;
+extern vmCvar_t cg_swingSpeed;
+extern vmCvar_t cg_shadows;
+extern vmCvar_t cg_gibs;
+extern vmCvar_t cg_drawTimer;
+extern vmCvar_t cg_drawClock;
+extern vmCvar_t cg_drawFPS;
+extern vmCvar_t cg_drawDemoState;
+extern vmCvar_t cg_drawSnapshot;
+extern vmCvar_t cg_draw3dIcons;
+extern vmCvar_t cg_drawIcons;
+extern vmCvar_t cg_drawAmmoWarning;
+extern vmCvar_t cg_drawCrosshair;
+extern vmCvar_t cg_drawCrosshairNames;
+extern vmCvar_t cg_drawRewards;
+extern vmCvar_t cg_drawTeamOverlay;
+extern vmCvar_t cg_teamOverlayUserinfo;
+extern vmCvar_t cg_crosshairX;
+extern vmCvar_t cg_crosshairY;
+extern vmCvar_t cg_drawStatus;
+extern vmCvar_t cg_draw2D;
+extern vmCvar_t cg_animSpeed;
+extern vmCvar_t cg_debugAnim;
+extern vmCvar_t cg_debugPosition;
+extern vmCvar_t cg_debugEvents;
+extern vmCvar_t cg_teslaTrailTime;
+extern vmCvar_t cg_railTrailTime;
+extern vmCvar_t cg_errorDecay;
+extern vmCvar_t cg_nopredict;
+extern vmCvar_t cg_debugMove;
+extern vmCvar_t cg_noPlayerAnims;
+extern vmCvar_t cg_showmiss;
+extern vmCvar_t cg_footsteps;
+extern vmCvar_t cg_addMarks;
+extern vmCvar_t cg_brassTime;
+extern vmCvar_t cg_gun_frame;
+extern vmCvar_t cg_gun_x;
+extern vmCvar_t cg_gun_y;
+extern vmCvar_t cg_gun_z;
+extern vmCvar_t cg_drawGun;
+extern vmCvar_t cg_viewsize;
+extern vmCvar_t cg_tracerChance;
+extern vmCvar_t cg_tracerWidth;
+extern vmCvar_t cg_tracerLength;
+extern vmCvar_t cg_autoswitch;
+extern vmCvar_t cg_ignore;
+extern vmCvar_t cg_simpleItems;
+extern vmCvar_t cg_fov;
+extern vmCvar_t cg_zoomFov;
+extern vmCvar_t cg_thirdPersonRange;
+extern vmCvar_t cg_thirdPersonAngle;
+extern vmCvar_t cg_thirdPerson;
+extern vmCvar_t cg_stereoSeparation;
+extern vmCvar_t cg_lagometer;
+extern vmCvar_t cg_drawAttacker;
+extern vmCvar_t cg_synchronousClients;
+extern vmCvar_t cg_stats;
+extern vmCvar_t cg_forceModel;
+extern vmCvar_t cg_buildScript;
+extern vmCvar_t cg_paused;
+extern vmCvar_t cg_blood;
+extern vmCvar_t cg_predictItems;
+extern vmCvar_t cg_deferPlayers;
+extern vmCvar_t cg_drawFriend;
+extern vmCvar_t cg_teamChatsOnly;
+extern vmCvar_t cg_noVoiceChats;
+extern vmCvar_t cg_noVoiceText;
+extern vmCvar_t cg_scorePlum;
+extern vmCvar_t cg_smoothClients;
+extern vmCvar_t pmove_fixed;
+extern vmCvar_t pmove_msec;
+//extern vmCvar_t cg_pmove_fixed;
+extern vmCvar_t cg_cameraOrbit;
+extern vmCvar_t cg_cameraOrbitDelay;
+extern vmCvar_t cg_timescaleFadeEnd;
+extern vmCvar_t cg_timescaleFadeSpeed;
+extern vmCvar_t cg_timescale;
+extern vmCvar_t cg_cameraMode;
+extern vmCvar_t cg_smallFont;
+extern vmCvar_t cg_bigFont;
+extern vmCvar_t cg_noTaunt;
+extern vmCvar_t cg_noProjectileTrail;
+extern vmCvar_t cg_oldRail;
+extern vmCvar_t cg_oldRocket;
+extern vmCvar_t cg_oldPlasma;
+extern vmCvar_t cg_trueLightning;
+extern vmCvar_t cg_creepRes;
+extern vmCvar_t cg_drawSurfNormal;
+extern vmCvar_t cg_drawBBOX;
+extern vmCvar_t cg_debugAlloc;
+extern vmCvar_t cg_wwSmoothTime;
+extern vmCvar_t cg_wwFollow;
+extern vmCvar_t cg_wwToggle;
+extern vmCvar_t cg_depthSortParticles;
+extern vmCvar_t cg_bounceParticles;
+extern vmCvar_t cg_consoleLatency;
+extern vmCvar_t cg_lightFlare;
+extern vmCvar_t cg_debugParticles;
+extern vmCvar_t cg_debugTrails;
+extern vmCvar_t cg_debugPVS;
+extern vmCvar_t cg_disableWarningDialogs;
+extern vmCvar_t cg_disableScannerPlane;
+extern vmCvar_t cg_tutorial;
+
+extern vmCvar_t cg_painBlendUpRate;
+extern vmCvar_t cg_painBlendDownRate;
+extern vmCvar_t cg_painBlendMax;
+extern vmCvar_t cg_painBlendScale;
+extern vmCvar_t cg_painBlendZoom;
+
+//TA: hack to get class an carriage through to UI module
+extern vmCvar_t ui_currentClass;
+extern vmCvar_t ui_carriage;
+extern vmCvar_t ui_stages;
+extern vmCvar_t ui_dialog;
+extern vmCvar_t ui_loading;
+extern vmCvar_t ui_voteActive;
+extern vmCvar_t ui_alienTeamVoteActive;
+extern vmCvar_t ui_humanTeamVoteActive;
+
+extern vmCvar_t cg_debugRandom;
+
+extern vmCvar_t cg_optimizePrediction;
+extern vmCvar_t cg_projectileNudge;
+extern vmCvar_t cg_unlagged;
+
+//
+// cg_main.c
+//
+const char *CG_ConfigString( int index );
+const char *CG_Argv( int arg );
+
+void QDECL CG_Printf( const char *msg, ... );
+void QDECL CG_Error( const char *msg, ... );
+
+void CG_StartMusic( void );
+int CG_PlayerCount( void );
+
+void CG_UpdateCvars( void );
+
+int CG_CrosshairPlayer( void );
+int CG_LastAttacker( void );
+void CG_LoadMenus( const char *menuFile );
+void CG_KeyEvent( int key, qboolean down );
+void CG_MouseEvent( int x, int y );
+void CG_EventHandling( int type );
+void CG_SetScoreSelection( void *menu );
+void CG_BuildSpectatorString( void );
+
+qboolean CG_FileExists( char *filename );
+void CG_RemoveNotifyLine( void );
+void CG_AddNotifyText( void );
+
+
+//
+// cg_view.c
+//
+void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod ); //TA
+void CG_TestModel_f( void );
+void CG_TestGun_f( void );
+void CG_TestModelNextFrame_f( void );
+void CG_TestModelPrevFrame_f( void );
+void CG_TestModelNextSkin_f( void );
+void CG_TestModelPrevSkin_f( void );
+void CG_AddBufferedSound( sfxHandle_t sfx );
+void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback );
+
+
+//
+// cg_drawtools.c
+//
+void CG_DrawPlane( vec3_t origin, vec3_t down, vec3_t right, qhandle_t shader );
+void CG_AdjustFrom640( float *x, float *y, float *w, float *h );
+void CG_FillRect( float x, float y, float width, float height, const float *color );
+void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader );
+void CG_DrawFadePic( float x, float y, float width, float height, vec4_t fcolor,
+ vec4_t tcolor, float amount, qhandle_t hShader );
+
+int CG_DrawStrlen( const char *str );
+
+float *CG_FadeColor( int startMsec, int totalMsec );
+void CG_TileClear( void );
+void CG_ColorForHealth( vec4_t hcolor );
+void CG_GetColorForHealth( int health, int armor, vec4_t hcolor );
+
+void CG_DrawRect( float x, float y, float width, float height, float size, const float *color );
+void CG_DrawSides(float x, float y, float w, float h, float size);
+void CG_DrawTopBottom(float x, float y, float w, float h, float size);
+qboolean CG_WorldToScreen( vec3_t point, float *x, float *y );
+char *CG_KeyBinding( const char *bind );
+
+
+//
+// cg_draw.c
+//
+extern int sortedTeamPlayers[ TEAM_MAXOVERLAY ];
+extern int numSortedTeamPlayers;
+
+void CG_AddLagometerFrameInfo( void );
+void CG_AddLagometerSnapshotInfo( snapshot_t *snap );
+void CG_CenterPrint( const char *str, int y, int charWidth );
+void CG_DrawActive( stereoFrame_t stereoView );
+void CG_OwnerDraw( float x, float y, float w, float h, float text_x, float text_y,
+ int ownerDraw, int ownerDrawFlags, int align, float special,
+ float scale, vec4_t color, qhandle_t shader, int textStyle);
+void CG_Text_Paint( float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style );
+int CG_Text_Width( const char *text, float scale, int limit );
+int CG_Text_Height( const char *text, float scale, int limit );
+float CG_GetValue(int ownerDraw);
+void CG_RunMenuScript(char **args);
+void CG_SetPrintString( int type, const char *p );
+void CG_GetTeamColor( vec4_t *color );
+const char *CG_GetKillerText( void );
+void CG_Text_PaintChar( float x, float y, float width, float height, float scale,
+ float s, float t, float s2, float t2, qhandle_t hShader );
+void CG_DrawLoadingScreen( void );
+void CG_UpdateMediaFraction( float newFract );
+void CG_ResetPainBlend( void );
+void CG_DrawField( float x, float y, int width, float cw, float ch, int value );
+
+//
+// cg_players.c
+//
+void CG_Player( centity_t *cent );
+void CG_Corpse( centity_t *cent );
+void CG_ResetPlayerEntity( centity_t *cent );
+void CG_AddRefEntityWithPowerups( refEntity_t *ent, int powerups, int team );
+void CG_NewClientInfo( int clientNum );
+void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin );
+sfxHandle_t CG_CustomSound( int clientNum, const char *soundName );
+void CG_PlayerDisconnect( vec3_t org );
+void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum );
+qboolean CG_AtHighestClass( void );
+
+//
+// cg_buildable.c
+//
+void CG_GhostBuildable( buildable_t buildable );
+void CG_Buildable( centity_t *cent );
+void CG_BuildableStatusParse( const char *filename, buildStat_t *bs );
+void CG_DrawBuildableStatus( void );
+void CG_InitBuildables( void );
+void CG_HumanBuildableExplosion( vec3_t origin, vec3_t dir );
+void CG_AlienBuildableExplosion( vec3_t origin, vec3_t dir );
+
+//
+// cg_animation.c
+//
+void CG_RunLerpFrame( lerpFrame_t *lf );
+
+//
+// cg_animmapobj.c
+//
+void CG_AnimMapObj( centity_t *cent );
+void CG_ModelDoor( centity_t *cent );
+
+//
+// cg_predict.c
+//
+
+#define MAGIC_TRACE_HACK -2
+
+void CG_BuildSolidList( void );
+int CG_PointContents( const vec3_t point, int passEntityNum );
+void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs,
+ const vec3_t end, int skipNumber, int mask );
+void CG_CapTrace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs,
+ const vec3_t end, int skipNumber, int mask );
+void CG_BiSphereTrace( trace_t *result, const vec3_t start, const vec3_t end,
+ const float startRadius, const float endRadius, int skipNumber, int mask );
+void CG_PredictPlayerState( void );
+
+
+//
+// cg_events.c
+//
+void CG_CheckEvents( centity_t *cent );
+void CG_EntityEvent( centity_t *cent, vec3_t position );
+void CG_PainEvent( centity_t *cent, int health );
+
+
+//
+// cg_ents.c
+//
+void CG_DrawBoundingBox( vec3_t origin, vec3_t mins, vec3_t maxs );
+void CG_SetEntitySoundPosition( centity_t *cent );
+void CG_AddPacketEntities( void );
+void CG_Beam( centity_t *cent );
+void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out );
+
+void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ qhandle_t parentModel, char *tagName );
+void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ qhandle_t parentModel, char *tagName );
+
+
+
+
+//
+// cg_weapons.c
+//
+void CG_NextWeapon_f( void );
+void CG_PrevWeapon_f( void );
+void CG_Weapon_f( void );
+
+void CG_InitUpgrades( void );
+void CG_RegisterUpgrade( int upgradeNum );
+void CG_InitWeapons( void );
+void CG_RegisterWeapon( int weaponNum );
+
+void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode );
+void CG_MissileHitWall( weapon_t weapon, weaponMode_t weaponMode, int clientNum,
+ vec3_t origin, vec3_t dir, impactSound_t soundType );
+void CG_MissileHitPlayer( weapon_t weapon, weaponMode_t weaponMode, vec3_t origin, vec3_t dir, int entityNum );
+void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum );
+void CG_ShotgunFire( entityState_t *es );
+
+void CG_AddViewWeapon (playerState_t *ps);
+void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent );
+void CG_DrawItemSelect( rectDef_t *rect, vec4_t color );
+void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle );
+
+
+//
+// cg_scanner.c
+//
+void CG_UpdateEntityPositions( void );
+void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color );
+void CG_AlienSense( rectDef_t *rect );
+
+//
+// cg_marks.c
+//
+void CG_InitMarkPolys( void );
+void CG_AddMarks( void );
+void CG_ImpactMark( qhandle_t markShader,
+ const vec3_t origin, const vec3_t dir,
+ float orientation,
+ float r, float g, float b, float a,
+ qboolean alphaFade,
+ float radius, qboolean temporary );
+
+//
+// cg_snapshot.c
+//
+void CG_ProcessSnapshots( void );
+
+//
+// cg_consolecmds.c
+//
+qboolean CG_ConsoleCommand( void );
+void CG_InitConsoleCommands( void );
+qboolean CG_RequestScores( void );
+
+//
+// cg_servercmds.c
+//
+void CG_ExecuteNewServerCommands( int latestSequence );
+void CG_ParseServerinfo( void );
+void CG_SetConfigValues( void );
+void CG_ShaderStateChanged(void);
+
+//
+// cg_playerstate.c
+//
+void CG_Respawn( void );
+void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops );
+void CG_CheckChangedPredictableEvents( playerState_t *ps );
+
+//
+// cg_mem.c
+//
+void CG_InitMemory( void );
+void *CG_Alloc( int size );
+void CG_Free( void *ptr );
+void CG_DefragmentMemory( void );
+
+//
+// cg_attachment.c
+//
+qboolean CG_AttachmentPoint( attachment_t *a, vec3_t v );
+qboolean CG_AttachmentDir( attachment_t *a, vec3_t v );
+qboolean CG_AttachmentAxis( attachment_t *a, vec3_t axis[ 3 ] );
+qboolean CG_AttachmentVelocity( attachment_t *a, vec3_t v );
+int CG_AttachmentCentNum( attachment_t *a );
+
+qboolean CG_Attached( attachment_t *a );
+
+void CG_AttachToPoint( attachment_t *a );
+void CG_AttachToCent( attachment_t *a );
+void CG_AttachToTag( attachment_t *a );
+void CG_AttachToParticle( attachment_t *a );
+void CG_SetAttachmentPoint( attachment_t *a, vec3_t v );
+void CG_SetAttachmentCent( attachment_t *a, centity_t *cent );
+void CG_SetAttachmentTag( attachment_t *a, refEntity_t parent,
+ qhandle_t model, char *tagName );
+void CG_SetAttachmentParticle( attachment_t *a, particle_t *p );
+
+void CG_SetAttachmentOffset( attachment_t *a, vec3_t v );
+
+//
+// cg_particles.c
+//
+void CG_LoadParticleSystems( void );
+qhandle_t CG_RegisterParticleSystem( char *name );
+
+particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle );
+void CG_DestroyParticleSystem( particleSystem_t **ps );
+
+qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps );
+qboolean CG_IsParticleSystemValid( particleSystem_t **ps );
+
+void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal );
+
+void CG_AddParticles( void );
+
+void CG_ParticleSystemEntity( centity_t *cent );
+
+void CG_TestPS_f( void );
+void CG_DestroyTestPS_f( void );
+
+//
+// cg_trails.c
+//
+void CG_LoadTrailSystems( void );
+qhandle_t CG_RegisterTrailSystem( char *name );
+
+trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle );
+void CG_DestroyTrailSystem( trailSystem_t **ts );
+
+qboolean CG_IsTrailSystemValid( trailSystem_t **ts );
+
+void CG_AddTrails( void );
+
+void CG_TestTS_f( void );
+void CG_DestroyTestTS_f( void );
+
+//
+// cg_ptr.c
+//
+int CG_ReadPTRCode( void );
+void CG_WritePTRCode( int code );
+
+//
+// cg_tutorial.c
+//
+const char *CG_TutorialText( void );
+
+//
+//===============================================
+
+//
+// system traps
+// These functions are how the cgame communicates with the main game system
+//
+
+
+// print message on the local console
+void trap_Print( const char *fmt );
+
+// abort the game
+void trap_Error( const char *fmt );
+
+// milliseconds should only be used for performance tuning, never
+// for anything game related. Get time from the CG_DrawActiveFrame parameter
+int trap_Milliseconds( void );
+
+// console variable interaction
+void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags );
+void trap_Cvar_Update( vmCvar_t *vmCvar );
+void trap_Cvar_Set( const char *var_name, const char *value );
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+
+// ServerCommand and ConsoleCommand parameter access
+int trap_Argc( void );
+void trap_Argv( int n, char *buffer, int bufferLength );
+void trap_Args( char *buffer, int bufferLength );
+void trap_LiteralArgs( char *buffer, int bufferLength );
+
+// filesystem access
+// returns length of file
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode );
+void trap_FS_Read( void *buffer, int len, fileHandle_t f );
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f );
+void trap_FS_FCloseFile( fileHandle_t f );
+void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin ); // fsOrigin_t
+int trap_FS_GetFileList( const char *path, const char *extension,
+ char *listbuf, int bufsize );
+
+// add commands to the local console as if they were typed in
+// for map changing, etc. The command is not executed immediately,
+// but will be executed in order the next time console commands
+// are processed
+void trap_SendConsoleCommand( const char *text );
+
+// register a command name so the console can perform command completion.
+// FIXME: replace this with a normal console command "defineCommand"?
+void trap_AddCommand( const char *cmdName );
+
+// send a string to the server over the network
+void trap_SendClientCommand( const char *s );
+
+// force a screen update, only used during gamestate load
+void trap_UpdateScreen( void );
+
+// model collision
+void trap_CM_LoadMap( const char *mapname );
+int trap_CM_NumInlineModels( void );
+clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels
+clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs );
+int trap_CM_PointContents( const vec3_t p, clipHandle_t model );
+int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles );
+void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask );
+void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles );
+void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask );
+void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles );
+void trap_CM_BiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask );
+void trap_CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask,
+ const vec3_t origin );
+
+// Returns the projection of a polygon onto the solid brushes in the world
+int trap_CM_MarkFragments( int numPoints, const vec3_t *points,
+ const vec3_t projection,
+ int maxPoints, vec3_t pointBuffer,
+ int maxFragments, markFragment_t *fragmentBuffer );
+
+// normal sounds will have their volume dynamically changed as their entity
+// moves and the listener moves
+void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx );
+void trap_S_StopLoopingSound( int entnum );
+
+// a local sound is always played full volume
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum );
+void trap_S_ClearLoopingSounds( qboolean killall );
+void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx );
+void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin );
+
+// respatialize recalculates the volumes of sound as they should be heard by the
+// given entityNum and position
+void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater );
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); // returns buzz if not found
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music
+void trap_S_StopBackgroundTrack( void );
+
+
+void trap_R_LoadWorldMap( const char *mapname );
+
+// all media should be registered during level startup to prevent
+// hitches during gameplay
+qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found
+qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found
+qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found
+qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found
+
+// a scene is built up by calls to R_ClearScene and the various R_Add functions.
+// Nothing is drawn until R_RenderScene is called.
+void trap_R_ClearScene( void );
+void trap_R_AddRefEntityToScene( const refEntity_t *re );
+
+// polys are intended for simple wall marks, not really for doing
+// significant construction
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts );
+void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys );
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir );
+void trap_R_RenderScene( const refdef_t *fd );
+void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1
+void trap_R_DrawStretchPic( float x, float y, float w, float h,
+ float s1, float t1, float s2, float t2, qhandle_t hShader );
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs );
+int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame,
+ float frac, const char *tagName );
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset );
+
+// The glconfig_t will not change during the life of a cgame.
+// If it needs to change, the entire cgame will be restarted, because
+// all the qhandle_t are then invalid.
+void trap_GetGlconfig( glconfig_t *glconfig );
+
+// the gamestate should be grabbed at startup, and whenever a
+// configstring changes
+void trap_GetGameState( gameState_t *gamestate );
+
+// cgame will poll each frame to see if a newer snapshot has arrived
+// that it is interested in. The time is returned seperately so that
+// snapshot latency can be calculated.
+void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime );
+
+// a snapshot get can fail if the snapshot (or the entties it holds) is so
+// old that it has fallen out of the client system queue
+qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot );
+
+// retrieve a text command from the server stream
+// the current snapshot will hold the number of the most recent command
+// qfalse can be returned if the client system handled the command
+// argc() / argv() can be used to examine the parameters of the command
+qboolean trap_GetServerCommand( int serverCommandNumber );
+
+// returns the most recent command number that can be passed to GetUserCmd
+// this will always be at least one higher than the number in the current
+// snapshot, and it may be quite a few higher if it is a fast computer on
+// a lagged connection
+int trap_GetCurrentCmdNumber( void );
+
+qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd );
+
+// used for the weapon select and zoom
+void trap_SetUserCmdValue( int stateValue, float sensitivityScale );
+
+// aids for VM testing
+void testPrintInt( char *string, int i );
+void testPrintFloat( char *string, float f );
+
+int trap_MemoryRemaining( void );
+void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font);
+qboolean trap_Key_IsDown( int keynum );
+int trap_Key_GetCatcher( void );
+void trap_Key_SetCatcher( int catcher );
+int trap_Key_GetKey( const char *binding );
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen );
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen );
+void trap_Key_SetBinding( int keynum, const char *binding );
+
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits );
+e_status trap_CIN_StopCinematic( int handle );
+e_status trap_CIN_RunCinematic( int handle );
+void trap_CIN_DrawCinematic( int handle );
+void trap_CIN_SetExtents( int handle, int x, int y, int w, int h );
+
+void trap_SnapVector( float *v );
+int trap_RealTime( qtime_t *tm );
+
+qboolean trap_loadCamera( const char *name );
+void trap_startCamera( int time );
+qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles );
+
+qboolean trap_GetEntityToken( char *buffer, int bufferSize );
+
+int trap_GetDemoState( void );
+int trap_GetDemoPos( void );
+void trap_GetDemoName( char *buffer, int size );
+
+// cg_drawCrosshair settings
+#define CROSSHAIR_ALWAYSOFF 0
+#define CROSSHAIR_RANGEDONLY 1
+#define CROSSHAIR_ALWAYSON 2
+
diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c
new file mode 100644
index 0000000..d303b30
--- /dev/null
+++ b/src/cgame/cg_main.c
@@ -0,0 +1,1842 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_main.c -- initialization and primary entry point for cgame
+
+
+#include "cg_local.h"
+
+#include "../ui/ui_shared.h"
+// display context for new ui stuff
+displayContextDef_t cgDC;
+
+int forceModelModificationCount = -1;
+
+void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum );
+void CG_Shutdown( void );
+
+/*
+================
+vmMain
+
+This is the only way control passes into the module.
+This must be the very first function compiled into the .q3vm file
+================
+*/
+intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3,
+ int arg4, int arg5, int arg6, int arg7,
+ int arg8, int arg9, int arg10, int arg11 )
+{
+ switch( command )
+ {
+ case CG_INIT:
+ CG_Init( arg0, arg1, arg2 );
+ return 0;
+
+ case CG_SHUTDOWN:
+ CG_Shutdown( );
+ return 0;
+
+ case CG_CONSOLE_COMMAND:
+ return CG_ConsoleCommand( );
+
+ case CG_CONSOLE_TEXT:
+ CG_AddNotifyText( );
+ return 0;
+
+ case CG_DRAW_ACTIVE_FRAME:
+ CG_DrawActiveFrame( arg0, arg1, arg2 );
+ return 0;
+
+ case CG_CROSSHAIR_PLAYER:
+ return CG_CrosshairPlayer( );
+
+ case CG_LAST_ATTACKER:
+ return CG_LastAttacker( );
+
+ case CG_KEY_EVENT:
+ CG_KeyEvent( arg0, arg1 );
+ return 0;
+
+ case CG_MOUSE_EVENT:
+ cgDC.cursorx = cgs.cursorX;
+ cgDC.cursory = cgs.cursorY;
+ CG_MouseEvent( arg0, arg1 );
+ return 0;
+
+ case CG_EVENT_HANDLING:
+ CG_EventHandling( arg0 );
+ return 0;
+
+ default:
+ CG_Error( "vmMain: unknown command %i", command );
+ break;
+ }
+
+ return -1;
+}
+
+
+cg_t cg;
+cgs_t cgs;
+centity_t cg_entities[ MAX_GENTITIES ];
+
+//TA: weapons limit expanded:
+//weaponInfo_t cg_weapons[MAX_WEAPONS];
+weaponInfo_t cg_weapons[ 32 ];
+upgradeInfo_t cg_upgrades[ 32 ];
+
+buildableInfo_t cg_buildables[ BA_NUM_BUILDABLES ];
+
+vmCvar_t cg_teslaTrailTime;
+vmCvar_t cg_railTrailTime;
+vmCvar_t cg_centertime;
+vmCvar_t cg_runpitch;
+vmCvar_t cg_runroll;
+vmCvar_t cg_bobup;
+vmCvar_t cg_bobpitch;
+vmCvar_t cg_bobroll;
+vmCvar_t cg_swingSpeed;
+vmCvar_t cg_shadows;
+vmCvar_t cg_gibs;
+vmCvar_t cg_drawTimer;
+vmCvar_t cg_drawClock;
+vmCvar_t cg_drawFPS;
+vmCvar_t cg_drawDemoState;
+vmCvar_t cg_drawSnapshot;
+vmCvar_t cg_draw3dIcons;
+vmCvar_t cg_drawIcons;
+vmCvar_t cg_drawAmmoWarning;
+vmCvar_t cg_drawCrosshair;
+vmCvar_t cg_drawCrosshairNames;
+vmCvar_t cg_drawRewards;
+vmCvar_t cg_crosshairX;
+vmCvar_t cg_crosshairY;
+vmCvar_t cg_draw2D;
+vmCvar_t cg_drawStatus;
+vmCvar_t cg_animSpeed;
+vmCvar_t cg_debugAnim;
+vmCvar_t cg_debugPosition;
+vmCvar_t cg_debugEvents;
+vmCvar_t cg_errorDecay;
+vmCvar_t cg_nopredict;
+vmCvar_t cg_debugMove;
+vmCvar_t cg_noPlayerAnims;
+vmCvar_t cg_showmiss;
+vmCvar_t cg_footsteps;
+vmCvar_t cg_addMarks;
+vmCvar_t cg_brassTime;
+vmCvar_t cg_viewsize;
+vmCvar_t cg_drawGun;
+vmCvar_t cg_gun_frame;
+vmCvar_t cg_gun_x;
+vmCvar_t cg_gun_y;
+vmCvar_t cg_gun_z;
+vmCvar_t cg_tracerChance;
+vmCvar_t cg_tracerWidth;
+vmCvar_t cg_tracerLength;
+vmCvar_t cg_autoswitch;
+vmCvar_t cg_ignore;
+vmCvar_t cg_simpleItems;
+vmCvar_t cg_fov;
+vmCvar_t cg_zoomFov;
+vmCvar_t cg_thirdPerson;
+vmCvar_t cg_thirdPersonRange;
+vmCvar_t cg_thirdPersonAngle;
+vmCvar_t cg_stereoSeparation;
+vmCvar_t cg_lagometer;
+vmCvar_t cg_drawAttacker;
+vmCvar_t cg_synchronousClients;
+vmCvar_t cg_stats;
+vmCvar_t cg_buildScript;
+vmCvar_t cg_forceModel;
+vmCvar_t cg_paused;
+vmCvar_t cg_blood;
+vmCvar_t cg_predictItems;
+vmCvar_t cg_deferPlayers;
+vmCvar_t cg_drawTeamOverlay;
+vmCvar_t cg_teamOverlayUserinfo;
+vmCvar_t cg_drawFriend;
+vmCvar_t cg_teamChatsOnly;
+vmCvar_t cg_noVoiceChats;
+vmCvar_t cg_noVoiceText;
+vmCvar_t cg_hudFiles;
+vmCvar_t cg_scorePlum;
+vmCvar_t cg_smoothClients;
+vmCvar_t pmove_fixed;
+//vmCvar_t cg_pmove_fixed;
+vmCvar_t pmove_msec;
+vmCvar_t cg_pmove_msec;
+vmCvar_t cg_cameraMode;
+vmCvar_t cg_cameraOrbit;
+vmCvar_t cg_cameraOrbitDelay;
+vmCvar_t cg_timescaleFadeEnd;
+vmCvar_t cg_timescaleFadeSpeed;
+vmCvar_t cg_timescale;
+vmCvar_t cg_smallFont;
+vmCvar_t cg_bigFont;
+vmCvar_t cg_noTaunt;
+vmCvar_t cg_noProjectileTrail;
+vmCvar_t cg_oldRail;
+vmCvar_t cg_oldRocket;
+vmCvar_t cg_oldPlasma;
+vmCvar_t cg_trueLightning;
+vmCvar_t cg_creepRes;
+vmCvar_t cg_drawSurfNormal;
+vmCvar_t cg_drawBBOX;
+vmCvar_t cg_debugAlloc;
+vmCvar_t cg_wwSmoothTime;
+vmCvar_t cg_wwFollow;
+vmCvar_t cg_wwToggle;
+vmCvar_t cg_depthSortParticles;
+vmCvar_t cg_bounceParticles;
+vmCvar_t cg_consoleLatency;
+vmCvar_t cg_lightFlare;
+vmCvar_t cg_debugParticles;
+vmCvar_t cg_debugTrails;
+vmCvar_t cg_debugPVS;
+vmCvar_t cg_disableWarningDialogs;
+vmCvar_t cg_disableScannerPlane;
+vmCvar_t cg_tutorial;
+
+vmCvar_t cg_painBlendUpRate;
+vmCvar_t cg_painBlendDownRate;
+vmCvar_t cg_painBlendMax;
+vmCvar_t cg_painBlendScale;
+vmCvar_t cg_painBlendZoom;
+
+//TA: hack to get class and carriage through to UI module
+vmCvar_t ui_currentClass;
+vmCvar_t ui_carriage;
+vmCvar_t ui_stages;
+vmCvar_t ui_dialog;
+vmCvar_t ui_loading;
+vmCvar_t ui_voteActive;
+vmCvar_t ui_alienTeamVoteActive;
+vmCvar_t ui_humanTeamVoteActive;
+
+vmCvar_t cg_debugRandom;
+
+vmCvar_t cg_optimizePrediction;
+vmCvar_t cg_projectileNudge;
+vmCvar_t cg_unlagged;
+
+
+typedef struct
+{
+ vmCvar_t *vmCvar;
+ char *cvarName;
+ char *defaultString;
+ int cvarFlags;
+} cvarTable_t;
+
+static cvarTable_t cvarTable[ ] =
+{
+ { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging
+ { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE },
+ { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE },
+ { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE },
+ { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE },
+ { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE },
+ { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE },
+ { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE },
+ { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE },
+ { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE },
+ { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE },
+ { &cg_drawTimer, "cg_drawTimer", "1", CVAR_ARCHIVE },
+ { &cg_drawClock, "cg_drawClock", "0", CVAR_ARCHIVE },
+ { &cg_drawFPS, "cg_drawFPS", "1", CVAR_ARCHIVE },
+ { &cg_drawDemoState, "cg_drawDemoState", "1", CVAR_ARCHIVE },
+ { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE },
+ { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE },
+ { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE },
+ { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE },
+ { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE },
+ { &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE },
+ { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE },
+ { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE },
+ { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE },
+ { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE },
+ { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE },
+ { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE },
+ { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE },
+ { &cg_lagometer, "cg_lagometer", "0", CVAR_ARCHIVE },
+ { &cg_teslaTrailTime, "cg_teslaTrailTime", "250", CVAR_ARCHIVE },
+ { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE },
+ { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT },
+ { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT },
+ { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT },
+ { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT },
+ { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE},
+ { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE },
+ { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT },
+ { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE },
+ { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE },
+ { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT },
+ { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT },
+ { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT },
+ { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT },
+ { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT },
+ { &cg_errorDecay, "cg_errordecay", "100", 0 },
+ { &cg_nopredict, "cg_nopredict", "0", 0 },
+ { &cg_debugMove, "cg_debugMove", "0", 0 },
+ { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT },
+ { &cg_showmiss, "cg_showmiss", "0", 0 },
+ { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT },
+ { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT },
+ { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT },
+ { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT },
+ { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT },
+ { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT },
+ { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_CHEAT },
+ { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE },
+ { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE },
+ { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE },
+ { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE },
+ { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO },
+ { &cg_stats, "cg_stats", "0", 0 },
+ { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE },
+ { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE },
+ { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE },
+ { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE },
+ { &cg_creepRes, "cg_creepRes", "16", CVAR_ARCHIVE },
+ { &cg_drawSurfNormal, "cg_drawSurfNormal", "0", CVAR_CHEAT },
+ { &cg_drawBBOX, "cg_drawBBOX", "0", CVAR_CHEAT },
+ { &cg_debugAlloc, "cg_debugAlloc", "0", 0 },
+ { &cg_wwSmoothTime, "cg_wwSmoothTime", "300", CVAR_ARCHIVE },
+ { &cg_wwFollow, "cg_wwFollow", "1", CVAR_ARCHIVE|CVAR_USERINFO },
+ { &cg_wwToggle, "cg_wwToggle", "1", CVAR_ARCHIVE|CVAR_USERINFO },
+ { &cg_unlagged, "cg_unlagged", "1", CVAR_ARCHIVE|CVAR_USERINFO },
+ { &cg_depthSortParticles, "cg_depthSortParticles", "1", CVAR_ARCHIVE },
+ { &cg_bounceParticles, "cg_bounceParticles", "0", CVAR_ARCHIVE },
+ { &cg_consoleLatency, "cg_consoleLatency", "3000", CVAR_ARCHIVE },
+ { &cg_lightFlare, "cg_lightFlare", "3", CVAR_ARCHIVE },
+ { &cg_debugParticles, "cg_debugParticles", "0", CVAR_CHEAT },
+ { &cg_debugTrails, "cg_debugTrails", "0", CVAR_CHEAT },
+ { &cg_debugPVS, "cg_debugPVS", "0", CVAR_CHEAT },
+ { &cg_disableWarningDialogs, "cg_disableWarningDialogs", "0", CVAR_ARCHIVE },
+ { &cg_disableScannerPlane, "cg_disableScannerPlane", "0", CVAR_ARCHIVE },
+ { &cg_tutorial, "cg_tutorial", "1", CVAR_ARCHIVE },
+ { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE},
+
+ { &cg_painBlendUpRate, "cg_painBlendUpRate", "10.0", 0 },
+ { &cg_painBlendDownRate, "cg_painBlendDownRate", "0.5", 0 },
+ { &cg_painBlendMax, "cg_painBlendMax", "0.7", 0 },
+ { &cg_painBlendScale, "cg_painBlendScale", "7.0", 0 },
+ { &cg_painBlendZoom, "cg_painBlendZoom", "0.65", 0 },
+
+ { &ui_currentClass, "ui_currentClass", "0", 0 },
+ { &ui_carriage, "ui_carriage", "", 0 },
+ { &ui_stages, "ui_stages", "0 0", 0 },
+ { &ui_dialog, "ui_dialog", "Text not set", 0 },
+ { &ui_loading, "ui_loading", "0", 0 },
+ { &ui_voteActive, "ui_voteActive", "0", 0 },
+ { &ui_humanTeamVoteActive, "ui_humanTeamVoteActive", "0", 0 },
+ { &ui_alienTeamVoteActive, "ui_alienTeamVoteActive", "0", 0 },
+
+ { &cg_debugRandom, "cg_debugRandom", "0", 0 },
+
+ { &cg_optimizePrediction, "cg_optimizePrediction", "1", CVAR_ARCHIVE },
+ { &cg_projectileNudge, "cg_projectileNudge", "1", CVAR_ARCHIVE },
+
+ // the following variables are created in other parts of the system,
+ // but we also reference them here
+
+ { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures
+ { &cg_paused, "cl_paused", "0", CVAR_ROM },
+ { &cg_blood, "com_blood", "1", CVAR_ARCHIVE },
+ { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo
+ { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT},
+ { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE},
+ { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0},
+ { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0},
+ { &cg_timescale, "timescale", "1", 0},
+ { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE},
+ { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE},
+ { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT},
+
+ { &pmove_fixed, "pmove_fixed", "0", 0},
+ { &pmove_msec, "pmove_msec", "8", 0},
+ { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE},
+ { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE},
+ { &cg_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE},
+ { &cg_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE},
+ { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE},
+ { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE},
+ { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE},
+ { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE}
+// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE }
+};
+
+static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] );
+
+/*
+=================
+CG_RegisterCvars
+=================
+*/
+void CG_RegisterCvars( void )
+{
+ int i;
+ cvarTable_t *cv;
+ char var[ MAX_TOKEN_CHARS ];
+
+ for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ )
+ {
+ trap_Cvar_Register( cv->vmCvar, cv->cvarName,
+ cv->defaultString, cv->cvarFlags );
+ }
+
+ //repress standard Q3 console
+ trap_Cvar_Set( "con_notifytime", "-2" );
+
+ // see if we are also running the server on this machine
+ trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) );
+ cgs.localServer = atoi( var );
+ forceModelModificationCount = cg_forceModel.modificationCount;
+
+ trap_Cvar_Register( NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE );
+ trap_Cvar_Register( NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE );
+ trap_Cvar_Register( NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE );
+ trap_Cvar_Register( NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE );
+}
+
+
+/*
+===================
+CG_ForceModelChange
+===================
+*/
+static void CG_ForceModelChange( void )
+{
+ int i;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ const char *clientInfo;
+
+ clientInfo = CG_ConfigString( CS_PLAYERS + i );
+
+ if( !clientInfo[ 0 ] )
+ continue;
+
+ CG_NewClientInfo( i );
+ }
+}
+
+
+/*
+=================
+CG_UpdateCvars
+=================
+*/
+void CG_UpdateCvars( void )
+{
+ int i;
+ cvarTable_t *cv;
+
+ for( i = 0, cv = cvarTable; i < cvarTableSize; i++, cv++ )
+ trap_Cvar_Update( cv->vmCvar );
+
+ // check for modications here
+
+ // if force model changed
+ if( forceModelModificationCount != cg_forceModel.modificationCount )
+ {
+ forceModelModificationCount = cg_forceModel.modificationCount;
+ CG_ForceModelChange( );
+ }
+}
+
+
+int CG_CrosshairPlayer( void )
+{
+ if( cg.time > ( cg.crosshairClientTime + 1000 ) )
+ return -1;
+
+ return cg.crosshairClientNum;
+}
+
+
+int CG_LastAttacker( void )
+{
+ if( !cg.attackerTime )
+ return -1;
+
+ return cg.snap->ps.persistant[ PERS_ATTACKER ];
+}
+
+/*
+=================
+CG_RemoveNotifyLine
+=================
+*/
+void CG_RemoveNotifyLine( void )
+{
+ int i, offset, totalLength;
+
+ if( cg.numConsoleLines == 0 )
+ return;
+
+ offset = cg.consoleLines[ 0 ].length;
+ totalLength = strlen( cg.consoleText ) - offset;
+
+ //slide up consoleText
+ for( i = 0; i <= totalLength; i++ )
+ cg.consoleText[ i ] = cg.consoleText[ i + offset ];
+
+ //pop up the first consoleLine
+ for( i = 0; i < cg.numConsoleLines; i++ )
+ cg.consoleLines[ i ] = cg.consoleLines[ i + 1 ];
+
+ cg.numConsoleLines--;
+}
+
+/*
+=================
+CG_AddNotifyText
+=================
+*/
+void CG_AddNotifyText( void )
+{
+ char buffer[ BIG_INFO_STRING ];
+
+ trap_LiteralArgs( buffer, BIG_INFO_STRING );
+
+ if( !buffer[ 0 ] )
+ {
+ cg.consoleText[ 0 ] = '\0';
+ cg.numConsoleLines = 0;
+ return;
+ }
+
+ if( cg.numConsoleLines == MAX_CONSOLE_LINES )
+ CG_RemoveNotifyLine( );
+
+ Q_strcat( cg.consoleText, MAX_CONSOLE_TEXT, buffer );
+ cg.consoleLines[ cg.numConsoleLines ].time = cg.time;
+ cg.consoleLines[ cg.numConsoleLines ].length = strlen( buffer );
+ cg.numConsoleLines++;
+}
+
+void QDECL CG_Printf( const char *msg, ... )
+{
+ va_list argptr;
+ char text[ 1024 ];
+
+ va_start( argptr, msg );
+ vsprintf( text, msg, argptr );
+ va_end( argptr );
+
+ trap_Print( text );
+}
+
+void QDECL CG_Error( const char *msg, ... )
+{
+ va_list argptr;
+ char text[ 1024 ];
+
+ va_start( argptr, msg );
+ vsprintf( text, msg, argptr );
+ va_end( argptr );
+
+ trap_Error( text );
+}
+
+void QDECL Com_Error( int level, const char *error, ... )
+{
+ va_list argptr;
+ char text[1024];
+
+ va_start( argptr, error );
+ vsprintf( text, error, argptr );
+ va_end( argptr );
+
+ CG_Error( "%s", text );
+}
+
+void QDECL Com_Printf( const char *msg, ... ) {
+ va_list argptr;
+ char text[1024];
+
+ va_start (argptr, msg);
+ vsprintf (text, msg, argptr);
+ va_end (argptr);
+
+ CG_Printf ("%s", text);
+}
+
+
+
+/*
+================
+CG_Argv
+================
+*/
+const char *CG_Argv( int arg )
+{
+ static char buffer[ MAX_STRING_CHARS ];
+
+ trap_Argv( arg, buffer, sizeof( buffer ) );
+
+ return buffer;
+}
+
+
+//========================================================================
+
+/*
+=================
+CG_FileExists
+
+Test if a specific file exists or not
+=================
+*/
+qboolean CG_FileExists( char *filename )
+{
+ fileHandle_t f;
+
+ if( trap_FS_FOpenFile( filename, &f, FS_READ ) > 0 )
+ {
+ //file exists so close it
+ trap_FS_FCloseFile( f );
+
+ return qtrue;
+ }
+ else
+ return qfalse;
+}
+
+/*
+=================
+CG_RegisterSounds
+
+called during a precache command
+=================
+*/
+static void CG_RegisterSounds( void )
+{
+ int i;
+ char name[ MAX_QPATH ];
+ const char *soundName;
+
+ cgs.media.alienStageTransition = trap_S_RegisterSound( "sound/announcements/overmindevolved.wav", qtrue );
+ cgs.media.humanStageTransition = trap_S_RegisterSound( "sound/announcements/reinforcement.wav", qtrue );
+
+ cgs.media.alienOvermindAttack = trap_S_RegisterSound( "sound/announcements/overmindattack.wav", qtrue );
+ cgs.media.alienOvermindDying = trap_S_RegisterSound( "sound/announcements/overminddying.wav", qtrue );
+ cgs.media.alienOvermindSpawns = trap_S_RegisterSound( "sound/announcements/overmindspawns.wav", qtrue );
+
+ cgs.media.alienL1Grab = trap_S_RegisterSound( "sound/player/level1/grab.wav", qtrue );
+ cgs.media.alienL4ChargePrepare = trap_S_RegisterSound( "sound/player/level4/charge_prepare.wav", qtrue );
+ cgs.media.alienL4ChargeStart = trap_S_RegisterSound( "sound/player/level4/charge_start.wav", qtrue );
+
+ cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/tracer.wav", qfalse );
+ cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse );
+
+ cgs.media.talkSound = trap_S_RegisterSound( "sound/misc/talk.wav", qfalse );
+ cgs.media.alienTalkSound = trap_S_RegisterSound( "sound/misc/alien_talk.wav", qfalse );
+ cgs.media.humanTalkSound = trap_S_RegisterSound( "sound/misc/human_talk.wav", qfalse );
+ cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse );
+
+ cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse );
+ cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse );
+ cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse );
+
+ cgs.media.disconnectSound = trap_S_RegisterSound( "sound/misc/disconnect.wav", qfalse );
+
+ for( i = 0; i < 4; i++ )
+ {
+ Com_sprintf( name, sizeof( name ), "sound/player/footsteps/step%i.wav", i + 1 );
+ cgs.media.footsteps[ FOOTSTEP_NORMAL ][ i ] = trap_S_RegisterSound( name, qfalse );
+
+ Com_sprintf( name, sizeof( name ), "sound/player/footsteps/flesh%i.wav", i + 1 );
+ cgs.media.footsteps[ FOOTSTEP_FLESH ][ i ] = trap_S_RegisterSound( name, qfalse );
+
+ Com_sprintf( name, sizeof( name ), "sound/player/footsteps/splash%i.wav", i + 1 );
+ cgs.media.footsteps[ FOOTSTEP_SPLASH ][ i ] = trap_S_RegisterSound( name, qfalse );
+
+ Com_sprintf( name, sizeof( name ), "sound/player/footsteps/clank%i.wav", i + 1 );
+ cgs.media.footsteps[ FOOTSTEP_METAL ][ i ] = trap_S_RegisterSound( name, qfalse );
+ }
+
+ for( i = 1 ; i < MAX_SOUNDS ; i++ )
+ {
+ soundName = CG_ConfigString( CS_SOUNDS + i );
+
+ if( !soundName[ 0 ] )
+ break;
+
+ if( soundName[ 0 ] == '*' )
+ continue; // custom sound
+
+ cgs.gameSounds[ i ] = trap_S_RegisterSound( soundName, qfalse );
+ }
+
+ cgs.media.jetpackDescendSound = trap_S_RegisterSound( "sound/upgrades/jetpack/low.wav", qfalse );
+ cgs.media.jetpackIdleSound = trap_S_RegisterSound( "sound/upgrades/jetpack/idle.wav", qfalse );
+ cgs.media.jetpackAscendSound = trap_S_RegisterSound( "sound/upgrades/jetpack/hi.wav", qfalse );
+
+ cgs.media.medkitUseSound = trap_S_RegisterSound( "sound/upgrades/medkit/medkit.wav", qfalse );
+
+ cgs.media.alienEvolveSound = trap_S_RegisterSound( "sound/player/alienevolve.wav", qfalse );
+
+ cgs.media.alienBuildableExplosion = trap_S_RegisterSound( "sound/buildables/alien/explosion.wav", qfalse );
+ cgs.media.alienBuildableDamage = trap_S_RegisterSound( "sound/buildables/alien/damage.wav", qfalse );
+ cgs.media.alienBuildablePrebuild = trap_S_RegisterSound( "sound/buildables/alien/prebuild.wav", qfalse );
+
+ cgs.media.humanBuildableExplosion = trap_S_RegisterSound( "sound/buildables/human/explosion.wav", qfalse );
+ cgs.media.humanBuildablePrebuild = trap_S_RegisterSound( "sound/buildables/human/prebuild.wav", qfalse );
+
+ for( i = 0; i < 4; i++ )
+ cgs.media.humanBuildableDamage[ i ] = trap_S_RegisterSound(
+ va( "sound/buildables/human/damage%d.wav", i ), qfalse );
+
+ cgs.media.hardBounceSound1 = trap_S_RegisterSound( "sound/misc/hard_bounce1.wav", qfalse );
+ cgs.media.hardBounceSound2 = trap_S_RegisterSound( "sound/misc/hard_bounce2.wav", qfalse );
+
+ cgs.media.repeaterUseSound = trap_S_RegisterSound( "sound/buildables/repeater/use.wav", qfalse );
+
+ cgs.media.buildableRepairSound = trap_S_RegisterSound( "sound/buildables/human/repair.wav", qfalse );
+ cgs.media.buildableRepairedSound = trap_S_RegisterSound( "sound/buildables/human/repaired.wav", qfalse );
+
+ cgs.media.lCannonWarningSound = trap_S_RegisterSound( "models/weapons/lcannon/warning.wav", qfalse );
+}
+
+
+//===================================================================================
+
+
+/*
+=================
+CG_RegisterGraphics
+
+This function may execute for a couple of minutes with a slow disk.
+=================
+*/
+static void CG_RegisterGraphics( void )
+{
+ int i;
+ static char *sb_nums[ 11 ] =
+ {
+ "gfx/2d/numbers/zero_32b",
+ "gfx/2d/numbers/one_32b",
+ "gfx/2d/numbers/two_32b",
+ "gfx/2d/numbers/three_32b",
+ "gfx/2d/numbers/four_32b",
+ "gfx/2d/numbers/five_32b",
+ "gfx/2d/numbers/six_32b",
+ "gfx/2d/numbers/seven_32b",
+ "gfx/2d/numbers/eight_32b",
+ "gfx/2d/numbers/nine_32b",
+ "gfx/2d/numbers/minus_32b",
+ };
+ static char *buildWeaponTimerPieShaders[ 8 ] =
+ {
+ "ui/assets/neutral/1_5pie",
+ "ui/assets/neutral/3_0pie",
+ "ui/assets/neutral/4_5pie",
+ "ui/assets/neutral/6_0pie",
+ "ui/assets/neutral/7_5pie",
+ "ui/assets/neutral/9_0pie",
+ "ui/assets/neutral/10_5pie",
+ "ui/assets/neutral/12_0pie",
+ };
+
+ // clear any references to old media
+ memset( &cg.refdef, 0, sizeof( cg.refdef ) );
+ trap_R_ClearScene( );
+
+ trap_R_LoadWorldMap( cgs.mapname );
+ CG_UpdateMediaFraction( 0.66f );
+
+ for( i = 0; i < 11; i++ )
+ cgs.media.numberShaders[ i ] = trap_R_RegisterShader( sb_nums[ i ] );
+
+ cgs.media.viewBloodShader = trap_R_RegisterShader( "gfx/damage/fullscreen_painblend" );
+
+ cgs.media.connectionShader = trap_R_RegisterShader( "gfx/2d/net" );
+
+ cgs.media.creepShader = trap_R_RegisterShader( "creep" );
+
+ cgs.media.scannerBlipShader = trap_R_RegisterShader( "gfx/2d/blip" );
+ cgs.media.scannerLineShader = trap_R_RegisterShader( "gfx/2d/stalk" );
+
+ cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" );
+
+ cgs.media.backTileShader = trap_R_RegisterShader( "console" );
+
+
+ //TA: building shaders
+ cgs.media.greenBuildShader = trap_R_RegisterShader("gfx/misc/greenbuild" );
+ cgs.media.redBuildShader = trap_R_RegisterShader("gfx/misc/redbuild" );
+ cgs.media.humanSpawningShader = trap_R_RegisterShader("models/buildables/telenode/rep_cyl" );
+
+ for( i = 0; i < 8; i++ )
+ cgs.media.buildWeaponTimerPie[ i ] = trap_R_RegisterShader( buildWeaponTimerPieShaders[ i ] );
+
+ cgs.media.upgradeClassIconShader = trap_R_RegisterShader( "icons/icona_upgrade.tga" );
+
+ cgs.media.balloonShader = trap_R_RegisterShader( "gfx/sprites/chatballoon" );
+
+ cgs.media.disconnectPS = CG_RegisterParticleSystem( "disconnectPS" );
+
+ CG_UpdateMediaFraction( 0.7f );
+
+ memset( cg_weapons, 0, sizeof( cg_weapons ) );
+ memset( cg_upgrades, 0, sizeof( cg_upgrades ) );
+
+ cgs.media.shadowMarkShader = trap_R_RegisterShader( "gfx/marks/shadow" );
+ cgs.media.wakeMarkShader = trap_R_RegisterShader( "gfx/marks/wake" );
+
+ cgs.media.poisonCloudPS = CG_RegisterParticleSystem( "firstPersonPoisonCloudPS" );
+ cgs.media.alienEvolvePS = CG_RegisterParticleSystem( "alienEvolvePS" );
+ cgs.media.alienAcidTubePS = CG_RegisterParticleSystem( "alienAcidTubePS" );
+
+ cgs.media.jetPackDescendPS = CG_RegisterParticleSystem( "jetPackDescendPS" );
+ cgs.media.jetPackHoverPS = CG_RegisterParticleSystem( "jetPackHoverPS" );
+ cgs.media.jetPackAscendPS = CG_RegisterParticleSystem( "jetPackAscendPS" );
+
+ cgs.media.humanBuildableDamagedPS = CG_RegisterParticleSystem( "humanBuildableDamagedPS" );
+ cgs.media.alienBuildableDamagedPS = CG_RegisterParticleSystem( "alienBuildableDamagedPS" );
+ cgs.media.humanBuildableDestroyedPS = CG_RegisterParticleSystem( "humanBuildableDestroyedPS" );
+ cgs.media.alienBuildableDestroyedPS = CG_RegisterParticleSystem( "alienBuildableDestroyedPS" );
+
+ cgs.media.alienBleedPS = CG_RegisterParticleSystem( "alienBleedPS" );
+ cgs.media.humanBleedPS = CG_RegisterParticleSystem( "humanBleedPS" );
+
+ CG_BuildableStatusParse( "ui/assets/human/buildstat.cfg", &cgs.humanBuildStat );
+ CG_BuildableStatusParse( "ui/assets/alien/buildstat.cfg", &cgs.alienBuildStat );
+
+ // register the inline models
+ cgs.numInlineModels = trap_CM_NumInlineModels( );
+
+ for( i = 1; i < cgs.numInlineModels; i++ )
+ {
+ char name[ 10 ];
+ vec3_t mins, maxs;
+ int j;
+
+ Com_sprintf( name, sizeof( name ), "*%i", i );
+
+ cgs.inlineDrawModel[ i ] = trap_R_RegisterModel( name );
+ trap_R_ModelBounds( cgs.inlineDrawModel[ i ], mins, maxs );
+
+ for( j = 0 ; j < 3 ; j++ )
+ cgs.inlineModelMidpoints[ i ][ j ] = mins[ j ] + 0.5 * ( maxs[ j ] - mins[ j ] );
+ }
+
+ // register all the server specified models
+ for( i = 1; i < MAX_MODELS; i++ )
+ {
+ const char *modelName;
+
+ modelName = CG_ConfigString( CS_MODELS + i );
+
+ if( !modelName[ 0 ] )
+ break;
+
+ cgs.gameModels[ i ] = trap_R_RegisterModel( modelName );
+ }
+
+ CG_UpdateMediaFraction( 0.8f );
+
+ // register all the server specified shaders
+ for( i = 1; i < MAX_GAME_SHADERS; i++ )
+ {
+ const char *shaderName;
+
+ shaderName = CG_ConfigString( CS_SHADERS + i );
+
+ if( !shaderName[ 0 ] )
+ break;
+
+ cgs.gameShaders[ i ] = trap_R_RegisterShader( shaderName );
+ }
+
+ CG_UpdateMediaFraction( 0.9f );
+
+ // register all the server specified particle systems
+ for( i = 1; i < MAX_GAME_PARTICLE_SYSTEMS; i++ )
+ {
+ const char *psName;
+
+ psName = CG_ConfigString( CS_PARTICLE_SYSTEMS + i );
+
+ if( !psName[ 0 ] )
+ break;
+
+ cgs.gameParticleSystems[ i ] = CG_RegisterParticleSystem( (char *)psName );
+ }
+}
+
+
+/*
+=======================
+CG_BuildSpectatorString
+
+=======================
+*/
+void CG_BuildSpectatorString( void )
+{
+ int i;
+
+ cg.spectatorList[ 0 ] = 0;
+
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ if( cgs.clientinfo[ i ].infoValid && cgs.clientinfo[ i ].team == PTE_NONE )
+ Q_strcat( cg.spectatorList, sizeof( cg.spectatorList ), va( "%s " S_COLOR_WHITE, cgs.clientinfo[ i ].name ) );
+ }
+
+ i = strlen( cg.spectatorList );
+
+ if( i != cg.spectatorLen )
+ {
+ cg.spectatorLen = i;
+ cg.spectatorWidth = -1;
+ }
+}
+
+
+
+/*
+===================
+CG_RegisterClients
+
+===================
+*/
+static void CG_RegisterClients( void )
+{
+ int i;
+
+ cg.charModelFraction = 0.0f;
+
+ //precache all the models/sounds/etc
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ CG_PrecacheClientInfo( i, BG_FindModelNameForClass( i ),
+ BG_FindSkinNameForClass( i ) );
+
+ cg.charModelFraction = (float)i / (float)PCL_NUM_CLASSES;
+ trap_UpdateScreen( );
+ }
+
+ cgs.media.larmourHeadSkin = trap_R_RegisterSkin( "models/players/human_base/head_light.skin" );
+ cgs.media.larmourLegsSkin = trap_R_RegisterSkin( "models/players/human_base/lower_light.skin" );
+ cgs.media.larmourTorsoSkin = trap_R_RegisterSkin( "models/players/human_base/upper_light.skin" );
+
+ cgs.media.jetpackModel = trap_R_RegisterModel( "models/players/human_base/jetpack.md3" );
+ cgs.media.jetpackFlashModel = trap_R_RegisterModel( "models/players/human_base/jetpack_flash.md3" );
+ cgs.media.battpackModel = trap_R_RegisterModel( "models/players/human_base/battpack.md3" );
+
+ cg.charModelFraction = 1.0f;
+ trap_UpdateScreen( );
+
+ //load all the clientinfos of clients already connected to the server
+ for( i = 0; i < MAX_CLIENTS; i++ )
+ {
+ const char *clientInfo;
+
+ clientInfo = CG_ConfigString( CS_PLAYERS + i );
+ if( !clientInfo[ 0 ] )
+ continue;
+
+ CG_NewClientInfo( i );
+ }
+
+ CG_BuildSpectatorString( );
+}
+
+//===========================================================================
+
+/*
+=================
+CG_ConfigString
+=================
+*/
+const char *CG_ConfigString( int index )
+{
+ if( index < 0 || index >= MAX_CONFIGSTRINGS )
+ CG_Error( "CG_ConfigString: bad index: %i", index );
+
+ return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ];
+}
+
+//==================================================================
+
+/*
+======================
+CG_StartMusic
+
+======================
+*/
+void CG_StartMusic( void )
+{
+ char *s;
+ char parm1[ MAX_QPATH ], parm2[ MAX_QPATH ];
+
+ // start the background music
+ s = (char *)CG_ConfigString( CS_MUSIC );
+ Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) );
+ Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) );
+
+ trap_S_StartBackgroundTrack( parm1, parm2 );
+}
+
+/*
+======================
+CG_PlayerCount
+======================
+*/
+int CG_PlayerCount( void )
+{
+ int i, count = 0;
+
+ CG_RequestScores( );
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == PTE_ALIENS ||
+ cg.scores[ i ].team == PTE_HUMANS )
+ count++;
+ }
+
+ return count;
+}
+
+//
+// ==============================
+// new hud stuff ( mission pack )
+// ==============================
+//
+char *CG_GetMenuBuffer( const char *filename )
+{
+ int len;
+ fileHandle_t f;
+ static char buf[ MAX_MENUFILE ];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+
+ if( !f )
+ {
+ trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) );
+ return NULL;
+ }
+
+ if( len >= MAX_MENUFILE )
+ {
+ trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i",
+ filename, len, MAX_MENUFILE ) );
+ trap_FS_FCloseFile( f );
+ return NULL;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ return buf;
+}
+
+qboolean CG_Asset_Parse( int handle )
+{
+ pc_token_t token;
+ const char *tempStr;
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( Q_stricmp( token.string, "{" ) != 0 )
+ return qfalse;
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ return qfalse;
+
+ if( Q_stricmp( token.string, "}" ) == 0 )
+ return qtrue;
+
+ // font
+ if( Q_stricmp( token.string, "font" ) == 0 )
+ {
+ int pointSize;
+
+ if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) )
+ return qfalse;
+
+ cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.textFont );
+ continue;
+ }
+
+ // smallFont
+ if( Q_stricmp( token.string, "smallFont" ) == 0 )
+ {
+ int pointSize;
+
+ if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) )
+ return qfalse;
+
+ cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.smallFont );
+ continue;
+ }
+
+ // font
+ if( Q_stricmp( token.string, "bigfont" ) == 0 )
+ {
+ int pointSize;
+
+ if( !PC_String_Parse( handle, &tempStr ) || !PC_Int_Parse( handle, &pointSize ) )
+ return qfalse;
+
+ cgDC.registerFont( tempStr, pointSize, &cgDC.Assets.bigFont );
+ continue;
+ }
+
+ // gradientbar
+ if( Q_stricmp( token.string, "gradientbar" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( tempStr );
+ continue;
+ }
+
+ // enterMenuSound
+ if( Q_stricmp( token.string, "menuEnterSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // exitMenuSound
+ if( Q_stricmp( token.string, "menuExitSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // itemFocusSound
+ if( Q_stricmp( token.string, "itemFocusSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // menuBuzzSound
+ if( Q_stricmp( token.string, "menuBuzzSound" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &tempStr ) )
+ return qfalse;
+
+ cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "cursor" ) == 0 )
+ {
+ if( !PC_String_Parse( handle, &cgDC.Assets.cursorStr ) )
+ return qfalse;
+
+ cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr );
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "fadeClamp" ) == 0 )
+ {
+ if( !PC_Float_Parse( handle, &cgDC.Assets.fadeClamp ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "fadeCycle" ) == 0 )
+ {
+ if( !PC_Int_Parse( handle, &cgDC.Assets.fadeCycle ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "fadeAmount" ) == 0 )
+ {
+ if( !PC_Float_Parse( handle, &cgDC.Assets.fadeAmount ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "shadowX" ) == 0 )
+ {
+ if( !PC_Float_Parse( handle, &cgDC.Assets.shadowX ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "shadowY" ) == 0 )
+ {
+ if( !PC_Float_Parse( handle, &cgDC.Assets.shadowY ) )
+ return qfalse;
+
+ continue;
+ }
+
+ if( Q_stricmp( token.string, "shadowColor" ) == 0 )
+ {
+ if( !PC_Color_Parse( handle, &cgDC.Assets.shadowColor ) )
+ return qfalse;
+
+ cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[ 3 ];
+ continue;
+ }
+ }
+
+ return qfalse; // bk001204 - why not?
+}
+
+void CG_ParseMenu( const char *menuFile )
+{
+ pc_token_t token;
+ int handle;
+
+ handle = trap_Parse_LoadSource( menuFile );
+
+ if( !handle )
+ handle = trap_Parse_LoadSource( "ui/testhud.menu" );
+
+ if( !handle )
+ return;
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ //if ( Q_stricmp( token, "{" ) ) {
+ // Com_Printf( "Missing { in menu file\n" );
+ // break;
+ //}
+
+ //if ( menuCount == MAX_MENUS ) {
+ // Com_Printf( "Too many menus!\n" );
+ // break;
+ //}
+
+ if( token.string[ 0 ] == '}' )
+ break;
+
+ if( Q_stricmp( token.string, "assetGlobalDef" ) == 0 )
+ {
+ if( CG_Asset_Parse( handle ) )
+ continue;
+ else
+ break;
+ }
+
+
+ if( Q_stricmp( token.string, "menudef" ) == 0 )
+ {
+ // start a new menu
+ Menu_New( handle );
+ }
+ }
+
+ trap_Parse_FreeSource( handle );
+}
+
+qboolean CG_Load_Menu( char **p )
+{
+ char *token;
+
+ token = COM_ParseExt( p, qtrue );
+
+ if( token[ 0 ] != '{' )
+ return qfalse;
+
+ while( 1 )
+ {
+ token = COM_ParseExt( p, qtrue );
+
+ if( Q_stricmp( token, "}" ) == 0 )
+ return qtrue;
+
+ if( !token || token[ 0 ] == 0 )
+ return qfalse;
+
+ CG_ParseMenu( token );
+ }
+ return qfalse;
+}
+
+
+
+void CG_LoadMenus( const char *menuFile )
+{
+ char *token;
+ char *p;
+ int len, start;
+ fileHandle_t f;
+ static char buf[ MAX_MENUDEFFILE ];
+
+ start = trap_Milliseconds( );
+
+ len = trap_FS_FOpenFile( menuFile, &f, FS_READ );
+
+ if( !f )
+ {
+ trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) );
+ len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ );
+
+ if( !f )
+ trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n" ) );
+ }
+
+ if( len >= MAX_MENUDEFFILE )
+ {
+ trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i",
+ menuFile, len, MAX_MENUDEFFILE ) );
+ trap_FS_FCloseFile( f );
+ return;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ COM_Compress( buf );
+
+ Menu_Reset( );
+
+ p = buf;
+
+ while( 1 )
+ {
+ token = COM_ParseExt( &p, qtrue );
+
+ if( !token || token[ 0 ] == 0 || token[ 0 ] == '}' )
+ break;
+
+ if( Q_stricmp( token, "}" ) == 0 )
+ break;
+
+ if( Q_stricmp( token, "loadmenu" ) == 0 )
+ {
+ if( CG_Load_Menu( &p ) )
+ continue;
+ else
+ break;
+ }
+ }
+
+ Com_Printf( "UI menu load time = %d milli seconds\n", trap_Milliseconds( ) - start );
+}
+
+
+
+static qboolean CG_OwnerDrawHandleKey( int ownerDraw, int flags, float *special, int key )
+{
+ return qfalse;
+}
+
+
+static int CG_FeederCount( float feederID )
+{
+ int i, count = 0;
+
+ if( feederID == FEEDER_ALIENTEAM_LIST )
+ {
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == PTE_ALIENS )
+ count++;
+ }
+ }
+ else if( feederID == FEEDER_HUMANTEAM_LIST )
+ {
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == PTE_HUMANS )
+ count++;
+ }
+ }
+
+ return count;
+}
+
+
+void CG_SetScoreSelection( void *p )
+{
+ menuDef_t *menu = (menuDef_t*)p;
+ playerState_t *ps = &cg.snap->ps;
+ int i, alien, human;
+ int feeder;
+
+ alien = human = 0;
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == PTE_ALIENS )
+ alien++;
+ else if( cg.scores[ i ].team == PTE_HUMANS )
+ human++;
+
+ if( ps->clientNum == cg.scores[ i ].client )
+ cg.selectedScore = i;
+ }
+
+ if( menu == NULL )
+ // just interested in setting the selected score
+ return;
+
+ feeder = FEEDER_ALIENTEAM_LIST;
+ i = alien;
+
+ if( cg.scores[ cg.selectedScore ].team == PTE_HUMANS )
+ {
+ feeder = FEEDER_HUMANTEAM_LIST;
+ i = human;
+ }
+
+ Menu_SetFeederSelection(menu, feeder, i, NULL);
+}
+
+// FIXME: might need to cache this info
+static clientInfo_t * CG_InfoFromScoreIndex( int index, int team, int *scoreIndex )
+{
+ int i, count;
+ count = 0;
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == team )
+ {
+ if( count == index )
+ {
+ *scoreIndex = i;
+ return &cgs.clientinfo[ cg.scores[ i ].client ];
+ }
+ count++;
+ }
+ }
+
+ *scoreIndex = index;
+ return &cgs.clientinfo[ cg.scores[ index ].client ];
+}
+
+static const char *CG_FeederItemText( float feederID, int index, int column, qhandle_t *handle )
+{
+ int scoreIndex = 0;
+ clientInfo_t *info = NULL;
+ int team = -1;
+ score_t *sp = NULL;
+ qboolean showIcons = qfalse;
+
+ *handle = -1;
+
+ if( feederID == FEEDER_ALIENTEAM_LIST )
+ team = PTE_ALIENS;
+ else if( feederID == FEEDER_HUMANTEAM_LIST )
+ team = PTE_HUMANS;
+
+ info = CG_InfoFromScoreIndex( index, team, &scoreIndex );
+ sp = &cg.scores[ scoreIndex ];
+
+ if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) &&
+ cg.intermissionStarted )
+ showIcons = qfalse;
+ else if( cg.snap->ps.pm_type == PM_SPECTATOR || cg.snap->ps.pm_flags & PMF_FOLLOW ||
+ team == cg.snap->ps.stats[ STAT_PTEAM ] || cg.intermissionStarted )
+ showIcons = qtrue;
+
+ if( info && info->infoValid )
+ {
+ switch( column )
+ {
+ case 0:
+ if( showIcons )
+ {
+ if( sp->weapon != WP_NONE )
+ *handle = cg_weapons[ sp->weapon ].weaponIcon;
+ }
+ break;
+
+ case 1:
+ if( showIcons )
+ {
+ if( sp->team == PTE_HUMANS && sp->upgrade != UP_NONE )
+ *handle = cg_upgrades[ sp->upgrade ].upgradeIcon;
+ else if( sp->team == PTE_ALIENS )
+ {
+ switch( sp->weapon )
+ {
+ case WP_ABUILD2:
+ case WP_ALEVEL1_UPG:
+ case WP_ALEVEL2_UPG:
+ case WP_ALEVEL3_UPG:
+ *handle = cgs.media.upgradeClassIconShader;
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ break;
+
+ case 2:
+ if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) &&
+ cg.intermissionStarted )
+ return "Ready";
+ break;
+
+ case 3:
+ return info->name;
+ break;
+
+ case 4:
+ return va( "%d", info->score );
+ break;
+
+ case 5:
+ return va( "%4d", sp->time );
+ break;
+
+ case 6:
+ if( sp->ping == -1 )
+ return "connecting";
+
+ return va( "%4d", sp->ping );
+ break;
+ }
+ }
+
+ return "";
+}
+
+static qhandle_t CG_FeederItemImage( float feederID, int index )
+{
+ return 0;
+}
+
+static void CG_FeederSelection( float feederID, int index )
+{
+ int i, count;
+ int team = ( feederID == FEEDER_ALIENTEAM_LIST ) ? PTE_ALIENS : PTE_HUMANS;
+ count = 0;
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ if( cg.scores[ i ].team == team )
+ {
+ if( index == count )
+ cg.selectedScore = i;
+
+ count++;
+ }
+ }
+}
+
+static float CG_Cvar_Get( const char *cvar )
+{
+ char buff[ 128 ];
+
+ memset( buff, 0, sizeof( buff ) );
+ trap_Cvar_VariableStringBuffer( cvar, buff, sizeof( buff ) );
+ return atof( buff );
+}
+
+void CG_Text_PaintWithCursor( float x, float y, float scale, vec4_t color, const char *text,
+ int cursorPos, char cursor, int limit, int style )
+{
+ CG_Text_Paint( x, y, scale, color, text, 0, limit, style );
+}
+
+static int CG_OwnerDrawWidth( int ownerDraw, float scale )
+{
+ switch( ownerDraw )
+ {
+ case CG_KILLER:
+ return CG_Text_Width( CG_GetKillerText( ), scale, 0 );
+ break;
+ }
+
+ return 0;
+}
+
+static int CG_PlayCinematic( const char *name, float x, float y, float w, float h )
+{
+ return trap_CIN_PlayCinematic( name, x, y, w, h, CIN_loop );
+}
+
+static void CG_StopCinematic( int handle )
+{
+ trap_CIN_StopCinematic( handle );
+}
+
+static void CG_DrawCinematic( int handle, float x, float y, float w, float h )
+{
+ trap_CIN_SetExtents( handle, x, y, w, h );
+ trap_CIN_DrawCinematic( handle );
+}
+
+static void CG_RunCinematicFrame( int handle )
+{
+ trap_CIN_RunCinematic( handle );
+}
+
+//TA: hack to prevent warning
+static qboolean CG_OwnerDrawVisible( int parameter )
+{
+ return qfalse;
+}
+
+/*
+=================
+CG_LoadHudMenu
+=================
+*/
+void CG_LoadHudMenu( void )
+{
+ char buff[ 1024 ];
+ const char *hudSet;
+
+ cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip;
+ cgDC.setColor = &trap_R_SetColor;
+ cgDC.drawHandlePic = &CG_DrawPic;
+ cgDC.drawStretchPic = &trap_R_DrawStretchPic;
+ cgDC.drawText = &CG_Text_Paint;
+ cgDC.textWidth = &CG_Text_Width;
+ cgDC.textHeight = &CG_Text_Height;
+ cgDC.registerModel = &trap_R_RegisterModel;
+ cgDC.modelBounds = &trap_R_ModelBounds;
+ cgDC.fillRect = &CG_FillRect;
+ cgDC.drawRect = &CG_DrawRect;
+ cgDC.drawSides = &CG_DrawSides;
+ cgDC.drawTopBottom = &CG_DrawTopBottom;
+ cgDC.clearScene = &trap_R_ClearScene;
+ cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene;
+ cgDC.renderScene = &trap_R_RenderScene;
+ cgDC.registerFont = &trap_R_RegisterFont;
+ cgDC.ownerDrawItem = &CG_OwnerDraw;
+ cgDC.getValue = &CG_GetValue;
+ cgDC.ownerDrawVisible = &CG_OwnerDrawVisible;
+ cgDC.runScript = &CG_RunMenuScript;
+ cgDC.getTeamColor = &CG_GetTeamColor;
+ cgDC.setCVar = trap_Cvar_Set;
+ cgDC.getCVarString = trap_Cvar_VariableStringBuffer;
+ cgDC.getCVarValue = CG_Cvar_Get;
+ cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor;
+ //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode;
+ //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode;
+ cgDC.startLocalSound = &trap_S_StartLocalSound;
+ cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey;
+ cgDC.feederCount = &CG_FeederCount;
+ cgDC.feederItemImage = &CG_FeederItemImage;
+ cgDC.feederItemText = &CG_FeederItemText;
+ cgDC.feederSelection = &CG_FeederSelection;
+ //cgDC.setBinding = &trap_Key_SetBinding;
+ //cgDC.getBindingBuf = &trap_Key_GetBindingBuf;
+ //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf;
+ //cgDC.executeText = &trap_Cmd_ExecuteText;
+ cgDC.Error = &Com_Error;
+ cgDC.Print = &Com_Printf;
+ cgDC.ownerDrawWidth = &CG_OwnerDrawWidth;
+ //cgDC.Pause = &CG_Pause;
+ cgDC.registerSound = &trap_S_RegisterSound;
+ cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack;
+ cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack;
+ cgDC.playCinematic = &CG_PlayCinematic;
+ cgDC.stopCinematic = &CG_StopCinematic;
+ cgDC.drawCinematic = &CG_DrawCinematic;
+ cgDC.runCinematicFrame = &CG_RunCinematicFrame;
+
+ Init_Display( &cgDC );
+
+ Menu_Reset( );
+
+ trap_Cvar_VariableStringBuffer( "cg_hudFiles", buff, sizeof( buff ) );
+ hudSet = buff;
+
+ if( hudSet[ 0 ] == '\0' )
+ hudSet = "ui/hud.txt";
+
+ CG_LoadMenus( hudSet );
+}
+
+void CG_AssetCache( void )
+{
+ cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR );
+ cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR );
+ cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN );
+ cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP );
+ cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT );
+ cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT );
+ cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB );
+ cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR );
+ cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB );
+}
+
+/*
+=================
+CG_Init
+
+Called after every level change or subsystem restart
+Will perform callbacks to make the loading info screen update.
+=================
+*/
+void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum )
+{
+ const char *s;
+
+ // clear everything
+ memset( &cgs, 0, sizeof( cgs ) );
+ memset( &cg, 0, sizeof( cg ) );
+ memset( &cg.pmext, 0, sizeof( cg.pmext ) );
+ memset( cg_entities, 0, sizeof( cg_entities ) );
+
+ cg.clientNum = clientNum;
+
+ cgs.processedSnapshotNum = serverMessageNum;
+ cgs.serverCommandSequence = serverCommandSequence;
+
+ // load a few needed things before we do any screen updates
+ cgs.media.whiteShader = trap_R_RegisterShader( "white" );
+ cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" );
+ cgs.media.outlineShader = trap_R_RegisterShader( "outline" );
+
+ //inform UI to repress cursor whilst loading
+ trap_Cvar_Set( "ui_loading", "1" );
+
+ //TA: load overrides
+ BG_InitClassOverrides( );
+ BG_InitBuildableOverrides( );
+ BG_InitAllowedGameElements( );
+
+ //TA: dyn memory
+ CG_InitMemory( );
+
+ CG_RegisterCvars( );
+
+ CG_InitConsoleCommands( );
+
+ //TA: moved up for LoadHudMenu
+ String_Init( );
+
+ //TA: TA UI
+ CG_AssetCache( );
+ CG_LoadHudMenu( ); // load new hud stuff
+
+ cg.weaponSelect = WP_NONE;
+
+ // old servers
+
+ // get the rendering configuration from the client system
+ trap_GetGlconfig( &cgs.glconfig );
+ cgs.screenXScale = cgs.glconfig.vidWidth / 640.0;
+ cgs.screenYScale = cgs.glconfig.vidHeight / 480.0;
+
+ // get the gamestate from the client system
+ trap_GetGameState( &cgs.gameState );
+
+ // check version
+ s = CG_ConfigString( CS_GAME_VERSION );
+
+ if( strcmp( s, GAME_VERSION ) )
+ CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s );
+
+ s = CG_ConfigString( CS_LEVEL_START_TIME );
+ cgs.levelStartTime = atoi( s );
+
+ CG_ParseServerinfo( );
+
+ // load the new map
+ trap_CM_LoadMap( cgs.mapname );
+
+ cg.loading = qtrue; // force players to load instead of defer
+
+ CG_LoadTrailSystems( );
+ CG_UpdateMediaFraction( 0.05f );
+
+ CG_LoadParticleSystems( );
+ CG_UpdateMediaFraction( 0.05f );
+
+ CG_RegisterSounds( );
+ CG_UpdateMediaFraction( 0.60f );
+
+ CG_RegisterGraphics( );
+ CG_UpdateMediaFraction( 0.90f );
+
+ CG_InitWeapons( );
+ CG_UpdateMediaFraction( 0.95f );
+
+ CG_InitUpgrades( );
+ CG_UpdateMediaFraction( 1.0f );
+
+ //TA:
+ CG_InitBuildables( );
+
+ CG_RegisterClients( ); // if low on memory, some clients will be deferred
+
+ cg.loading = qfalse; // future players will be deferred
+
+ CG_InitMarkPolys( );
+
+ // remove the last loading update
+ cg.infoScreenText[ 0 ] = 0;
+
+ // Make sure we have update values (scores)
+ CG_SetConfigValues( );
+
+ CG_StartMusic( );
+
+ CG_ShaderStateChanged( );
+
+ trap_S_ClearLoopingSounds( qtrue );
+
+ trap_Cvar_Set( "ui_loading", "0" );
+}
+
+/*
+=================
+CG_Shutdown
+
+Called before every level change or subsystem restart
+=================
+*/
+void CG_Shutdown( void )
+{
+ // some mods may need to do cleanup work here,
+ // like closing files or archiving session data
+}
diff --git a/src/cgame/cg_marks.c b/src/cgame/cg_marks.c
new file mode 100644
index 0000000..380f1f0
--- /dev/null
+++ b/src/cgame/cg_marks.c
@@ -0,0 +1,289 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_marks.c -- wall marks
+
+
+#include "cg_local.h"
+
+/*
+===================================================================
+
+MARK POLYS
+
+===================================================================
+*/
+
+
+markPoly_t cg_activeMarkPolys; // double linked list
+markPoly_t *cg_freeMarkPolys; // single linked list
+markPoly_t cg_markPolys[ MAX_MARK_POLYS ];
+static int markTotal;
+
+/*
+===================
+CG_InitMarkPolys
+
+This is called at startup and for tournement restarts
+===================
+*/
+void CG_InitMarkPolys( void )
+{
+ int i;
+
+ memset( cg_markPolys, 0, sizeof( cg_markPolys ) );
+
+ cg_activeMarkPolys.nextMark = &cg_activeMarkPolys;
+ cg_activeMarkPolys.prevMark = &cg_activeMarkPolys;
+ cg_freeMarkPolys = cg_markPolys;
+
+ for( i = 0; i < MAX_MARK_POLYS - 1; i++ )
+ cg_markPolys[ i ].nextMark = &cg_markPolys[ i + 1 ];
+}
+
+
+/*
+==================
+CG_FreeMarkPoly
+==================
+*/
+void CG_FreeMarkPoly( markPoly_t *le )
+{
+ if( !le->prevMark )
+ CG_Error( "CG_FreeLocalEntity: not active" );
+
+ // remove from the doubly linked active list
+ le->prevMark->nextMark = le->nextMark;
+ le->nextMark->prevMark = le->prevMark;
+
+ // the free list is only singly linked
+ le->nextMark = cg_freeMarkPolys;
+ cg_freeMarkPolys = le;
+}
+
+/*
+===================
+CG_AllocMark
+
+Will allways succeed, even if it requires freeing an old active mark
+===================
+*/
+markPoly_t *CG_AllocMark( void )
+{
+ markPoly_t *le;
+ int time;
+
+ if( !cg_freeMarkPolys )
+ {
+ // no free entities, so free the one at the end of the chain
+ // remove the oldest active entity
+ time = cg_activeMarkPolys.prevMark->time;
+
+ while( cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time )
+ CG_FreeMarkPoly( cg_activeMarkPolys.prevMark );
+ }
+
+ le = cg_freeMarkPolys;
+ cg_freeMarkPolys = cg_freeMarkPolys->nextMark;
+
+ memset( le, 0, sizeof( *le ) );
+
+ // link into the active list
+ le->nextMark = cg_activeMarkPolys.nextMark;
+ le->prevMark = &cg_activeMarkPolys;
+ cg_activeMarkPolys.nextMark->prevMark = le;
+ cg_activeMarkPolys.nextMark = le;
+ return le;
+}
+
+
+
+/*
+=================
+CG_ImpactMark
+
+origin should be a point within a unit of the plane
+dir should be the plane normal
+
+temporary marks will not be stored or randomly oriented, but immediately
+passed to the renderer.
+=================
+*/
+#define MAX_MARK_FRAGMENTS 128
+#define MAX_MARK_POINTS 384
+
+void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir,
+ float orientation, float red, float green, float blue, float alpha,
+ qboolean alphaFade, float radius, qboolean temporary )
+{
+ vec3_t axis[ 3 ];
+ float texCoordScale;
+ vec3_t originalPoints[ 4 ];
+ byte colors[ 4 ];
+ int i, j;
+ int numFragments;
+ markFragment_t markFragments[ MAX_MARK_FRAGMENTS ], *mf;
+ vec3_t markPoints[ MAX_MARK_POINTS ];
+ vec3_t projection;
+
+ if( !cg_addMarks.integer )
+ return;
+
+ if( radius <= 0 )
+ CG_Error( "CG_ImpactMark called with <= 0 radius" );
+
+ //if ( markTotal >= MAX_MARK_POLYS ) {
+ // return;
+ //}
+
+ // create the texture axis
+ VectorNormalize2( dir, axis[ 0 ] );
+ PerpendicularVector( axis[ 1 ], axis[ 0 ] );
+ RotatePointAroundVector( axis[ 2 ], axis[ 0 ], axis[ 1 ], orientation );
+ CrossProduct( axis[ 0 ], axis[ 2 ], axis[ 1 ] );
+
+ texCoordScale = 0.5 * 1.0 / radius;
+
+ // create the full polygon
+ for( i = 0; i < 3; i++ )
+ {
+ originalPoints[ 0 ][ i ] = origin[ i ] - radius * axis[ 1 ][ i ] - radius * axis[ 2 ][ i ];
+ originalPoints[ 1 ][ i ] = origin[ i ] + radius * axis[ 1 ][ i ] - radius * axis[ 2 ][ i ];
+ originalPoints[ 2 ][ i ] = origin[ i ] + radius * axis[ 1 ][ i ] + radius * axis[ 2 ][ i ];
+ originalPoints[ 3 ][ i ] = origin[ i ] - radius * axis[ 1 ][ i ] + radius * axis[ 2 ][ i ];
+ }
+
+ // get the fragments
+ VectorScale( dir, -20, projection );
+ numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints,
+ projection, MAX_MARK_POINTS, markPoints[ 0 ],
+ MAX_MARK_FRAGMENTS, markFragments );
+
+ colors[ 0 ] = red * 255;
+ colors[ 1 ] = green * 255;
+ colors[ 2 ] = blue * 255;
+ colors[ 3 ] = alpha * 255;
+
+ for( i = 0, mf = markFragments; i < numFragments; i++, mf++ )
+ {
+ polyVert_t *v;
+ polyVert_t verts[ MAX_VERTS_ON_POLY ];
+ markPoly_t *mark;
+
+ // we have an upper limit on the complexity of polygons
+ // that we store persistantly
+ if( mf->numPoints > MAX_VERTS_ON_POLY )
+ mf->numPoints = MAX_VERTS_ON_POLY;
+
+ for( j = 0, v = verts; j < mf->numPoints; j++, v++ )
+ {
+ vec3_t delta;
+
+ VectorCopy( markPoints[ mf->firstPoint + j ], v->xyz );
+
+ VectorSubtract( v->xyz, origin, delta );
+ v->st[ 0 ] = 0.5 + DotProduct( delta, axis[ 1 ] ) * texCoordScale;
+ v->st[ 1 ] = 0.5 + DotProduct( delta, axis[ 2 ] ) * texCoordScale;
+ *(int *)v->modulate = *(int *)colors;
+ }
+
+ // if it is a temporary (shadow) mark, add it immediately and forget about it
+ if( temporary )
+ {
+ trap_R_AddPolyToScene( markShader, mf->numPoints, verts );
+ continue;
+ }
+
+ // otherwise save it persistantly
+ mark = CG_AllocMark( );
+ mark->time = cg.time;
+ mark->alphaFade = alphaFade;
+ mark->markShader = markShader;
+ mark->poly.numVerts = mf->numPoints;
+ mark->color[ 0 ] = red;
+ mark->color[ 1 ] = green;
+ mark->color[ 2 ] = blue;
+ mark->color[ 3 ] = alpha;
+ memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[ 0 ] ) );
+ markTotal++;
+ }
+}
+
+
+/*
+===============
+CG_AddMarks
+===============
+*/
+#define MARK_TOTAL_TIME 10000
+#define MARK_FADE_TIME 1000
+
+void CG_AddMarks( void )
+{
+ int j;
+ markPoly_t *mp, *next;
+ int t;
+ int fade;
+
+ if( !cg_addMarks.integer )
+ return;
+
+ mp = cg_activeMarkPolys.nextMark;
+ for ( ; mp != &cg_activeMarkPolys; mp = next )
+ {
+ // grab next now, so if the local entity is freed we
+ // still have it
+ next = mp->nextMark;
+
+ // see if it is time to completely remove it
+ if( cg.time > mp->time + MARK_TOTAL_TIME )
+ {
+ CG_FreeMarkPoly( mp );
+ continue;
+ }
+
+ // fade all marks out with time
+ t = mp->time + MARK_TOTAL_TIME - cg.time;
+ if( t < MARK_FADE_TIME )
+ {
+ fade = 255 * t / MARK_FADE_TIME;
+ if( mp->alphaFade )
+ {
+ for( j = 0; j < mp->poly.numVerts; j++ )
+ mp->verts[ j ].modulate[ 3 ] = fade;
+ }
+ else
+ {
+ for( j = 0; j < mp->poly.numVerts; j++ )
+ {
+ mp->verts[ j ].modulate[ 0 ] = mp->color[ 0 ] * fade;
+ mp->verts[ j ].modulate[ 1 ] = mp->color[ 1 ] * fade;
+ mp->verts[ j ].modulate[ 2 ] = mp->color[ 2 ] * fade;
+ }
+ }
+ }
+
+ trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts );
+ }
+}
+
diff --git a/src/cgame/cg_mem.c b/src/cgame/cg_mem.c
new file mode 100644
index 0000000..6cf5ddd
--- /dev/null
+++ b/src/cgame/cg_mem.c
@@ -0,0 +1,202 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "cg_local.h"
+
+#define POOLSIZE (256 * 1024)
+#define FREEMEMCOOKIE ((int)0xDEADBE3F) // Any unlikely to be used value
+#define ROUNDBITS 31 // Round to 32 bytes
+
+struct freememnode
+{
+ // Size of ROUNDBITS
+ int cookie, size; // Size includes node (obviously)
+ struct freememnode *prev, *next;
+};
+
+static char memoryPool[ POOLSIZE ];
+static struct freememnode *freehead;
+static int freemem;
+
+void *CG_Alloc( int size )
+{
+ // Find a free block and allocate.
+ // Does two passes, attempts to fill same-sized free slot first.
+
+ struct freememnode *fmn, *prev, *next, *smallest;
+ int allocsize, smallestsize;
+ char *endptr;
+ int *ptr;
+
+ allocsize = ( size + sizeof(int) + ROUNDBITS ) & ~ROUNDBITS; // Round to 32-byte boundary
+ ptr = NULL;
+
+ smallest = NULL;
+ smallestsize = POOLSIZE + 1; // Guaranteed not to miss any slots :)
+ for( fmn = freehead; fmn; fmn = fmn->next )
+ {
+ if( fmn->cookie != FREEMEMCOOKIE )
+ CG_Error( "CG_Alloc: Memory corruption detected!\n" );
+
+ if( fmn->size >= allocsize )
+ {
+ // We've got a block
+ if( fmn->size == allocsize )
+ {
+ // Same size, just remove
+
+ prev = fmn->prev;
+ next = fmn->next;
+ if( prev )
+ prev->next = next; // Point previous node to next
+ if( next )
+ next->prev = prev; // Point next node to previous
+ if( fmn == freehead )
+ freehead = next; // Set head pointer to next
+ ptr = (int *) fmn;
+ break; // Stop the loop, this is fine
+ }
+ else
+ {
+ // Keep track of the smallest free slot
+ if( fmn->size < smallestsize )
+ {
+ smallest = fmn;
+ smallestsize = fmn->size;
+ }
+ }
+ }
+ }
+
+ if( !ptr && smallest )
+ {
+ // We found a slot big enough
+ smallest->size -= allocsize;
+ endptr = (char *) smallest + smallest->size;
+ ptr = (int *) endptr;
+ }
+
+ if( ptr )
+ {
+ freemem -= allocsize;
+ if( cg_debugAlloc.integer )
+ CG_Printf( "CG_Alloc of %i bytes (%i left)\n", allocsize, freemem );
+ memset( ptr, 0, allocsize );
+ *ptr++ = allocsize; // Store a copy of size for deallocation
+ return( (void *) ptr );
+ }
+
+ CG_Error( "CG_Alloc: failed on allocation of %i bytes\n", size );
+ return( NULL );
+}
+
+void CG_Free( void *ptr )
+{
+ // Release allocated memory, add it to the free list.
+
+ struct freememnode *fmn;
+ char *freeend;
+ int *freeptr;
+
+ freeptr = ptr;
+ freeptr--;
+
+ freemem += *freeptr;
+ if( cg_debugAlloc.integer )
+ CG_Printf( "CG_Free of %i bytes (%i left)\n", *freeptr, freemem );
+
+ for( fmn = freehead; fmn; fmn = fmn->next )
+ {
+ freeend = ((char *) fmn) + fmn->size;
+ if( freeend == (char *) freeptr )
+ {
+ // Released block can be merged to an existing node
+
+ fmn->size += *freeptr; // Add size of node.
+ return;
+ }
+ }
+ // No merging, add to head of list
+
+ fmn = (struct freememnode *) freeptr;
+ fmn->size = *freeptr; // Set this first to avoid corrupting *freeptr
+ fmn->cookie = FREEMEMCOOKIE;
+ fmn->prev = NULL;
+ fmn->next = freehead;
+ freehead->prev = fmn;
+ freehead = fmn;
+}
+
+void CG_InitMemory( void )
+{
+ // Set up the initial node
+
+ freehead = (struct freememnode *) memoryPool;
+ freehead->cookie = FREEMEMCOOKIE;
+ freehead->size = POOLSIZE;
+ freehead->next = NULL;
+ freehead->prev = NULL;
+ freemem = sizeof(memoryPool);
+}
+
+void CG_DefragmentMemory( void )
+{
+ // If there's a frenzy of deallocation and we want to
+ // allocate something big, this is useful. Otherwise...
+ // not much use.
+
+ struct freememnode *startfmn, *endfmn, *fmn;
+
+ for( startfmn = freehead; startfmn; )
+ {
+ endfmn = (struct freememnode *)(((char *) startfmn) + startfmn->size);
+ for( fmn = freehead; fmn; )
+ {
+ if( fmn->cookie != FREEMEMCOOKIE )
+ CG_Error( "CG_DefragmentMemory: Memory corruption detected!\n" );
+
+ if( fmn == endfmn )
+ {
+ // We can add fmn onto startfmn.
+
+ if( fmn->prev )
+ fmn->prev->next = fmn->next;
+ if( fmn->next )
+ {
+ if( !(fmn->next->prev = fmn->prev) )
+ freehead = fmn->next; // We're removing the head node
+ }
+ startfmn->size += fmn->size;
+ memset( fmn, 0, sizeof(struct freememnode) ); // A redundant call, really.
+
+ startfmn = freehead;
+ endfmn = fmn = NULL; // Break out of current loop
+ }
+ else
+ fmn = fmn->next;
+ }
+
+ if( endfmn )
+ startfmn = startfmn->next; // endfmn acts as a 'restart' flag here
+ }
+}
diff --git a/src/cgame/cg_particles.c b/src/cgame/cg_particles.c
new file mode 100644
index 0000000..80c4b23
--- /dev/null
+++ b/src/cgame/cg_particles.c
@@ -0,0 +1,2567 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_particles.c -- the particle system
+
+
+#include "cg_local.h"
+
+static baseParticleSystem_t baseParticleSystems[ MAX_BASEPARTICLE_SYSTEMS ];
+static baseParticleEjector_t baseParticleEjectors[ MAX_BASEPARTICLE_EJECTORS ];
+static baseParticle_t baseParticles[ MAX_BASEPARTICLES ];
+static int numBaseParticleSystems = 0;
+static int numBaseParticleEjectors = 0;
+static int numBaseParticles = 0;
+
+static particleSystem_t particleSystems[ MAX_PARTICLE_SYSTEMS ];
+static particleEjector_t particleEjectors[ MAX_PARTICLE_EJECTORS ];
+static particle_t particles[ MAX_PARTICLES ];
+static particle_t *sortedParticles[ MAX_PARTICLES ];
+static particle_t *radixBuffer[ MAX_PARTICLES ];
+
+/*
+===============
+CG_LerpValues
+
+Lerp between two values
+===============
+*/
+static float CG_LerpValues( float a, float b, float f )
+{
+ if( b == PARTICLES_SAME_AS_INITIAL )
+ return a;
+ else
+ return ( (a) + (f) * ( (b) - (a) ) );
+}
+
+/*
+===============
+CG_RandomiseValue
+
+Randomise some value by some variance
+===============
+*/
+static float CG_RandomiseValue( float value, float variance )
+{
+ if( value != 0.0f )
+ return value * ( 1.0f + ( random( ) * variance ) );
+ else
+ return random( ) * variance;
+}
+
+/*
+===============
+CG_SpreadVector
+
+Randomly spread a vector by some amount
+===============
+*/
+static void CG_SpreadVector( vec3_t v, float spread )
+{
+ vec3_t p, r1, r2;
+ float randomSpread = crandom( ) * spread;
+ float randomRotation = random( ) * 360.0f;
+
+ PerpendicularVector( p, v );
+
+ RotatePointAroundVector( r1, p, v, randomSpread );
+ RotatePointAroundVector( r2, v, r1, randomRotation );
+
+ VectorCopy( r2, v );
+}
+
+/*
+===============
+CG_DestroyParticle
+
+Destroy an individual particle
+===============
+*/
+static void CG_DestroyParticle( particle_t *p, vec3_t impactNormal )
+{
+ //this particle has an onDeath particle system attached
+ if( p->class->onDeathSystemName[ 0 ] != '\0' )
+ {
+ particleSystem_t *ps;
+
+ ps = CG_SpawnNewParticleSystem( p->class->onDeathSystemHandle );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ if( impactNormal )
+ CG_SetParticleSystemNormal( ps, impactNormal );
+
+ CG_SetAttachmentPoint( &ps->attachment, p->origin );
+ CG_AttachToPoint( &ps->attachment );
+ }
+ }
+
+ p->valid = qfalse;
+
+ //this gives other systems a couple of
+ //frames to realise the particle is gone
+ p->frameWhenInvalidated = cg.clientFrame;
+}
+
+/*
+===============
+CG_SpawnNewParticle
+
+Introduce a new particle into the world
+===============
+*/
+static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *parent )
+{
+ int i, j;
+ particle_t *p = NULL;
+ particleEjector_t *pe = parent;
+ particleSystem_t *ps = parent->parent;
+ vec3_t attachmentPoint, attachmentVelocity;
+ vec3_t transform[ 3 ];
+
+ for( i = 0; i < MAX_PARTICLES; i++ )
+ {
+ p = &particles[ i ];
+
+ //FIXME: the + 1 may be unnecessary
+ if( !p->valid && cg.clientFrame > p->frameWhenInvalidated + 1 )
+ {
+ memset( p, 0, sizeof( particle_t ) );
+
+ //found a free slot
+ p->class = bp;
+ p->parent = pe;
+
+ p->birthTime = cg.time;
+ p->lifeTime = (int)CG_RandomiseValue( (float)bp->lifeTime, bp->lifeTimeRandFrac );
+
+ p->radius.delay = (int)CG_RandomiseValue( (float)bp->radius.delay, bp->radius.delayRandFrac );
+ p->radius.initial = CG_RandomiseValue( bp->radius.initial, bp->radius.initialRandFrac );
+ p->radius.final = CG_RandomiseValue( bp->radius.final, bp->radius.finalRandFrac );
+
+ p->alpha.delay = (int)CG_RandomiseValue( (float)bp->alpha.delay, bp->alpha.delayRandFrac );
+ p->alpha.initial = CG_RandomiseValue( bp->alpha.initial, bp->alpha.initialRandFrac );
+ p->alpha.final = CG_RandomiseValue( bp->alpha.final, bp->alpha.finalRandFrac );
+
+ p->rotation.delay = (int)CG_RandomiseValue( (float)bp->rotation.delay, bp->rotation.delayRandFrac );
+ p->rotation.initial = CG_RandomiseValue( bp->rotation.initial, bp->rotation.initialRandFrac );
+ p->rotation.final = CG_RandomiseValue( bp->rotation.final, bp->rotation.finalRandFrac );
+
+ p->dLightRadius.delay =
+ (int)CG_RandomiseValue( (float)bp->dLightRadius.delay, bp->dLightRadius.delayRandFrac );
+ p->dLightRadius.initial =
+ CG_RandomiseValue( bp->dLightRadius.initial, bp->dLightRadius.initialRandFrac );
+ p->dLightRadius.final =
+ CG_RandomiseValue( bp->dLightRadius.final, bp->dLightRadius.finalRandFrac );
+
+ p->colorDelay = CG_RandomiseValue( bp->colorDelay, bp->colorDelayRandFrac );
+
+ p->bounceMarkRadius = CG_RandomiseValue( bp->bounceMarkRadius, bp->bounceMarkRadiusRandFrac );
+ p->bounceMarkCount =
+ rint( CG_RandomiseValue( (float)bp->bounceMarkCount, bp->bounceMarkCountRandFrac ) );
+ p->bounceSoundCount =
+ rint( CG_RandomiseValue( (float)bp->bounceSoundCount, bp->bounceSoundCountRandFrac ) );
+
+ if( bp->numModels )
+ {
+ p->model = bp->models[ rand( ) % bp->numModels ];
+
+ if( bp->modelAnimation.frameLerp < 0 )
+ {
+ bp->modelAnimation.frameLerp = p->lifeTime / bp->modelAnimation.numFrames;
+ bp->modelAnimation.initialLerp = p->lifeTime / bp->modelAnimation.numFrames;
+ }
+ }
+
+ if( !CG_AttachmentPoint( &ps->attachment, attachmentPoint ) )
+ return NULL;
+
+ VectorCopy( attachmentPoint, p->origin );
+
+ if( CG_AttachmentAxis( &ps->attachment, transform ) )
+ {
+ vec3_t transDisplacement;
+
+ VectorMatrixMultiply( bp->displacement, transform, transDisplacement );
+ VectorAdd( p->origin, transDisplacement, p->origin );
+ }
+ else
+ VectorAdd( p->origin, bp->displacement, p->origin );
+
+ for( j = 0; j <= 2; j++ )
+ p->origin[ j ] += ( crandom( ) * bp->randDisplacement );
+
+ switch( bp->velMoveType )
+ {
+ case PMT_STATIC:
+ if( bp->velMoveValues.dirType == PMD_POINT )
+ VectorSubtract( bp->velMoveValues.point, p->origin, p->velocity );
+ else if( bp->velMoveValues.dirType == PMD_LINEAR )
+ VectorCopy( bp->velMoveValues.dir, p->velocity );
+ break;
+
+ case PMT_STATIC_TRANSFORM:
+ if( !CG_AttachmentAxis( &ps->attachment, transform ) )
+ return NULL;
+
+ if( bp->velMoveValues.dirType == PMD_POINT )
+ {
+ vec3_t transPoint;
+
+ VectorMatrixMultiply( bp->velMoveValues.point, transform, transPoint );
+ VectorSubtract( transPoint, p->origin, p->velocity );
+ }
+ else if( bp->velMoveValues.dirType == PMD_LINEAR )
+ VectorMatrixMultiply( bp->velMoveValues.dir, transform, p->velocity );
+ break;
+
+ case PMT_TAG:
+ case PMT_CENT_ANGLES:
+ if( bp->velMoveValues.dirType == PMD_POINT )
+ VectorSubtract( attachmentPoint, p->origin, p->velocity );
+ else if( bp->velMoveValues.dirType == PMD_LINEAR )
+ {
+ if( !CG_AttachmentDir( &ps->attachment, p->velocity ) )
+ return NULL;
+ }
+ break;
+
+ case PMT_NORMAL:
+ if( !ps->normalValid )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a particle with velocityType "
+ "normal has no normal\n" );
+ return NULL;
+ }
+
+ VectorCopy( ps->normal, p->velocity );
+
+ //normal displacement
+ VectorNormalize( p->velocity );
+ VectorMA( p->origin, bp->normalDisplacement, p->velocity, p->origin );
+ break;
+ }
+
+ VectorNormalize( p->velocity );
+ CG_SpreadVector( p->velocity, bp->velMoveValues.dirRandAngle );
+ VectorScale( p->velocity,
+ CG_RandomiseValue( bp->velMoveValues.mag, bp->velMoveValues.magRandFrac ),
+ p->velocity );
+
+ if( CG_AttachmentVelocity( &ps->attachment, attachmentVelocity ) )
+ {
+ VectorMA( p->velocity,
+ CG_RandomiseValue( bp->velMoveValues.parentVelFrac,
+ bp->velMoveValues.parentVelFracRandFrac ), attachmentVelocity, p->velocity );
+ }
+
+ p->lastEvalTime = cg.time;
+
+ p->valid = qtrue;
+
+ //this particle has a child particle system attached
+ if( bp->childSystemName[ 0 ] != '\0' )
+ {
+ particleSystem_t *ps = CG_SpawnNewParticleSystem( bp->childSystemHandle );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentParticle( &ps->attachment, p );
+ CG_AttachToParticle( &ps->attachment );
+ }
+ }
+
+ //this particle has a child trail system attached
+ if( bp->childTrailSystemName[ 0 ] != '\0' )
+ {
+ trailSystem_t *ts = CG_SpawnNewTrailSystem( bp->childTrailSystemHandle );
+
+ if( CG_IsTrailSystemValid( &ts ) )
+ {
+ CG_SetAttachmentParticle( &ts->frontAttachment, p );
+ CG_AttachToParticle( &ts->frontAttachment );
+ }
+ }
+
+ break;
+ }
+ }
+
+ return p;
+}
+
+
+/*
+===============
+CG_SpawnNewParticles
+
+Check if there are any ejectors that should be
+introducing new particles
+===============
+*/
+static void CG_SpawnNewParticles( void )
+{
+ int i, j;
+ particle_t *p;
+ particleSystem_t *ps;
+ particleEjector_t *pe;
+ baseParticleEjector_t *bpe;
+ float lerpFrac;
+ int count;
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ {
+ pe = &particleEjectors[ i ];
+ ps = pe->parent;
+
+ if( pe->valid )
+ {
+ //a non attached particle system can't make particles
+ if( !CG_Attached( &ps->attachment ) )
+ continue;
+
+ bpe = particleEjectors[ i ].class;
+
+ //if this system is scheduled for removal don't make any new particles
+ if( !ps->lazyRemove )
+ {
+ while( pe->nextEjectionTime <= cg.time &&
+ ( pe->count > 0 || pe->totalParticles == PARTICLES_INFINITE ) )
+ {
+ for( j = 0; j < bpe->numParticles; j++ )
+ CG_SpawnNewParticle( bpe->particles[ j ], pe );
+
+ if( pe->count > 0 )
+ pe->count--;
+
+ //calculate next ejection time
+ lerpFrac = 1.0 - ( (float)pe->count / (float)pe->totalParticles );
+ pe->nextEjectionTime = cg.time + (int)CG_RandomiseValue(
+ CG_LerpValues( pe->ejectPeriod.initial,
+ pe->ejectPeriod.final,
+ lerpFrac ),
+ pe->ejectPeriod.randFrac );
+ }
+ }
+
+ if( pe->count == 0 || ps->lazyRemove )
+ {
+ count = 0;
+
+ //wait for child particles to die before declaring this pe invalid
+ for( j = 0; j < MAX_PARTICLES; j++ )
+ {
+ p = &particles[ j ];
+
+ if( p->valid && p->parent == pe )
+ count++;
+ }
+
+ if( !count )
+ pe->valid = qfalse;
+ }
+ }
+ }
+}
+
+
+/*
+===============
+CG_SpawnNewParticleEjector
+
+Allocate a new particle ejector
+===============
+*/
+static particleEjector_t *CG_SpawnNewParticleEjector( baseParticleEjector_t *bpe,
+ particleSystem_t *parent )
+{
+ int i;
+ particleEjector_t *pe = NULL;
+ particleSystem_t *ps = parent;
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ {
+ pe = &particleEjectors[ i ];
+
+ if( !pe->valid )
+ {
+ memset( pe, 0, sizeof( particleEjector_t ) );
+
+ //found a free slot
+ pe->class = bpe;
+ pe->parent = ps;
+
+ pe->ejectPeriod.initial = bpe->eject.initial;
+ pe->ejectPeriod.final = bpe->eject.final;
+ pe->ejectPeriod.randFrac = bpe->eject.randFrac;
+
+ pe->nextEjectionTime = cg.time +
+ (int)CG_RandomiseValue( (float)bpe->eject.delay, bpe->eject.delayRandFrac );
+ pe->count = pe->totalParticles =
+ (int)rint( CG_RandomiseValue( (float)bpe->totalParticles, bpe->totalParticlesRandFrac ) );
+
+ pe->valid = qtrue;
+
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "PE %s created\n", ps->class->name );
+
+ break;
+ }
+ }
+
+ return pe;
+}
+
+
+/*
+===============
+CG_SpawnNewParticleSystem
+
+Allocate a new particle system
+===============
+*/
+particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle )
+{
+ int i, j;
+ particleSystem_t *ps = NULL;
+ baseParticleSystem_t *bps = &baseParticleSystems[ psHandle - 1 ];
+
+ if( !bps->registered )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a particle system has not been registered yet\n" );
+ return NULL;
+ }
+
+ for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ )
+ {
+ ps = &particleSystems[ i ];
+
+ if( !ps->valid )
+ {
+ memset( ps, 0, sizeof( particleSystem_t ) );
+
+ //found a free slot
+ ps->class = bps;
+
+ ps->valid = qtrue;
+ ps->lazyRemove = qfalse;
+
+ for( j = 0; j < bps->numEjectors; j++ )
+ CG_SpawnNewParticleEjector( bps->ejectors[ j ], ps );
+
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "PS %s created\n", bps->name );
+
+ break;
+ }
+ }
+
+ return ps;
+}
+
+/*
+===============
+CG_RegisterParticleSystem
+
+Load the shaders required for a particle system
+===============
+*/
+qhandle_t CG_RegisterParticleSystem( char *name )
+{
+ int i, j, k, l;
+ baseParticleSystem_t *bps;
+ baseParticleEjector_t *bpe;
+ baseParticle_t *bp;
+
+ for( i = 0; i < MAX_BASEPARTICLE_SYSTEMS; i++ )
+ {
+ bps = &baseParticleSystems[ i ];
+
+ if( !Q_stricmpn( bps->name, name, MAX_QPATH ) )
+ {
+ //already registered
+ if( bps->registered )
+ return i + 1;
+
+ for( j = 0; j < bps->numEjectors; j++ )
+ {
+ bpe = bps->ejectors[ j ];
+
+ for( l = 0; l < bpe->numParticles; l++ )
+ {
+ bp = bpe->particles[ l ];
+
+ for( k = 0; k < bp->numFrames; k++ )
+ bp->shaders[ k ] = trap_R_RegisterShader( bp->shaderNames[ k ] );
+
+ for( k = 0; k < bp->numModels; k++ )
+ bp->models[ k ] = trap_R_RegisterModel( bp->modelNames[ k ] );
+
+ if( bp->bounceMarkName[ 0 ] != '\0' )
+ bp->bounceMark = trap_R_RegisterShader( bp->bounceMarkName );
+
+ if( bp->bounceSoundName[ 0 ] != '\0' )
+ bp->bounceSound = trap_S_RegisterSound( bp->bounceSoundName, qfalse );
+
+ //recursively register any children
+ if( bp->childSystemName[ 0 ] != '\0' )
+ {
+ //don't care about a handle for children since
+ //the system deals with it
+ CG_RegisterParticleSystem( bp->childSystemName );
+ }
+
+ if( bp->onDeathSystemName[ 0 ] != '\0' )
+ {
+ //don't care about a handle for children since
+ //the system deals with it
+ CG_RegisterParticleSystem( bp->onDeathSystemName );
+ }
+
+ if( bp->childTrailSystemName[ 0 ] != '\0' )
+ bp->childTrailSystemHandle = CG_RegisterTrailSystem( bp->childTrailSystemName );
+ }
+ }
+
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "Registered particle system %s\n", name );
+
+ bps->registered = qtrue;
+
+ //avoid returning 0
+ return i + 1;
+ }
+ }
+
+ CG_Printf( S_COLOR_RED "ERROR: failed to register particle system %s\n", name );
+ return 0;
+}
+
+
+/*
+===============
+CG_ParseValueAndVariance
+
+Parse a value and its random variance
+===============
+*/
+static void CG_ParseValueAndVariance( char *token, float *value, float *variance, qboolean allowNegative )
+{
+ char valueBuffer[ 16 ];
+ char varianceBuffer[ 16 ];
+ char *variancePtr = NULL, *varEndPointer = NULL;
+ float localValue = 0.0f;
+ float localVariance = 0.0f;
+
+ Q_strncpyz( valueBuffer, token, sizeof( valueBuffer ) );
+ Q_strncpyz( varianceBuffer, token, sizeof( varianceBuffer ) );
+
+ variancePtr = strchr( valueBuffer, '~' );
+
+ //variance included
+ if( variancePtr )
+ {
+ variancePtr[ 0 ] = '\0';
+ variancePtr++;
+
+ localValue = atof_neg( valueBuffer, allowNegative );
+
+ varEndPointer = strchr( variancePtr, '%' );
+
+ if( varEndPointer )
+ {
+ varEndPointer[ 0 ] = '\0';
+ localVariance = atof_neg( variancePtr, qfalse ) / 100.0f;
+ }
+ else
+ {
+ if( localValue != 0.0f )
+ localVariance = atof_neg( variancePtr, qfalse ) / localValue;
+ else
+ localVariance = atof_neg( variancePtr, qfalse );
+ }
+ }
+ else
+ localValue = atof_neg( valueBuffer, allowNegative );
+
+ if( value != NULL )
+ *value = localValue;
+
+ if( variance != NULL )
+ *variance = localVariance;
+}
+
+/*
+===============
+CG_ParseColor
+===============
+*/
+static qboolean CG_ParseColor( byte *c, char **text_p )
+{
+ char *token;
+ int i;
+
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) );
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_ParseParticle
+
+Parse a particle section
+===============
+*/
+static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p )
+{
+ char *token;
+ float number, randFrac;
+ int i;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "bounce" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "cull" ) )
+ {
+ bp->bounceCull = qtrue;
+
+ bp->bounceFrac = -1.0f;
+ bp->bounceFracRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->bounceFrac = number;
+ bp->bounceFracRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "bounceMark" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->bounceMarkCount = number;
+ bp->bounceMarkCountRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->bounceMarkRadius = number;
+ bp->bounceMarkRadiusRandFrac = randFrac;
+
+ token = COM_ParseExt( text_p, qfalse );
+ if( !*token )
+ break;
+
+ Q_strncpyz( bp->bounceMarkName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "bounceSound" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->bounceSoundCount = number;
+ bp->bounceSoundCountRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ Q_strncpyz( bp->bounceSoundName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "shader" ) )
+ {
+ if( bp->numModels > 0 )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: 'shader' not allowed in "
+ "conjunction with 'model'\n", token );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "sync" ) )
+ bp->framerate = 0.0f;
+ else
+ bp->framerate = atof_neg( token, qfalse );
+
+ token = COM_ParseExt( text_p, qfalse );
+ if( !*token )
+ break;
+
+ while( *token && bp->numFrames < MAX_PS_SHADER_FRAMES )
+ {
+ Q_strncpyz( bp->shaderNames[ bp->numFrames++ ], token, MAX_QPATH );
+ token = COM_ParseExt( text_p, qfalse );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "model" ) )
+ {
+ if( bp->numFrames > 0 )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: 'model' not allowed in "
+ "conjunction with 'shader'\n", token );
+ break;
+ }
+
+ token = COM_ParseExt( text_p, qfalse );
+ if( !*token )
+ break;
+
+ while( *token && bp->numModels < MAX_PS_MODELS )
+ {
+ Q_strncpyz( bp->modelNames[ bp->numModels++ ], token, MAX_QPATH );
+ token = COM_ParseExt( text_p, qfalse );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "modelAnimation" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ bp->modelAnimation.firstFrame = atoi_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ bp->modelAnimation.numFrames = atoi( token );
+ bp->modelAnimation.reversed = qfalse;
+ bp->modelAnimation.flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( bp->modelAnimation.numFrames < 0 )
+ {
+ bp->modelAnimation.numFrames = -bp->modelAnimation.numFrames;
+ bp->modelAnimation.reversed = qtrue;
+ }
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ bp->modelAnimation.loopFrames = atoi( token );
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "sync" ) )
+ {
+ bp->modelAnimation.frameLerp = -1;
+ bp->modelAnimation.initialLerp = -1;
+ }
+ else
+ {
+ float fps = atof_neg( token, qfalse );
+
+ if( fps == 0.0f )
+ fps = 1.0f;
+
+ bp->modelAnimation.frameLerp = 1000 / fps;
+ bp->modelAnimation.initialLerp = 1000 / fps;
+ }
+
+ continue;
+ }
+ ///
+ else if( !Q_stricmp( token, "velocityType" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "static" ) )
+ bp->velMoveType = PMT_STATIC;
+ else if( !Q_stricmp( token, "static_transform" ) )
+ bp->velMoveType = PMT_STATIC_TRANSFORM;
+ else if( !Q_stricmp( token, "tag" ) )
+ bp->velMoveType = PMT_TAG;
+ else if( !Q_stricmp( token, "cent" ) )
+ bp->velMoveType = PMT_CENT_ANGLES;
+ else if( !Q_stricmp( token, "normal" ) )
+ bp->velMoveType = PMT_NORMAL;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "velocityDir" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "linear" ) )
+ bp->velMoveValues.dirType = PMD_LINEAR;
+ else if( !Q_stricmp( token, "point" ) )
+ bp->velMoveValues.dirType = PMD_POINT;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "velocityMagnitude" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->velMoveValues.mag = number;
+ bp->velMoveValues.magRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "parentVelocityFraction" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->velMoveValues.parentVelFrac = number;
+ bp->velMoveValues.parentVelFracRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "velocity" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->velMoveValues.dir[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->velMoveValues.dirRandAngle = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "velocityPoint" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->velMoveValues.point[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->velMoveValues.pointRandAngle = randFrac;
+
+ continue;
+ }
+ ///
+ else if( !Q_stricmp( token, "accelerationType" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "static" ) )
+ bp->accMoveType = PMT_STATIC;
+ else if( !Q_stricmp( token, "static_transform" ) )
+ bp->accMoveType = PMT_STATIC_TRANSFORM;
+ else if( !Q_stricmp( token, "tag" ) )
+ bp->accMoveType = PMT_TAG;
+ else if( !Q_stricmp( token, "cent" ) )
+ bp->accMoveType = PMT_CENT_ANGLES;
+ else if( !Q_stricmp( token, "normal" ) )
+ bp->accMoveType = PMT_NORMAL;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "accelerationDir" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "linear" ) )
+ bp->accMoveValues.dirType = PMD_LINEAR;
+ else if( !Q_stricmp( token, "point" ) )
+ bp->accMoveValues.dirType = PMD_POINT;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "accelerationMagnitude" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->accMoveValues.mag = number;
+ bp->accMoveValues.magRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "acceleration" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->accMoveValues.dir[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->accMoveValues.dirRandAngle = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "accelerationPoint" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->accMoveValues.point[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->accMoveValues.pointRandAngle = randFrac;
+
+ continue;
+ }
+ ///
+ else if( !Q_stricmp( token, "displacement" ) )
+ {
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->displacement[ i ] = atof_neg( token, qtrue );
+ }
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &randFrac, qfalse );
+
+ bp->randDisplacement = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "normalDisplacement" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bp->normalDisplacement = atof_neg( token, qtrue );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "overdrawProtection" ) )
+ {
+ bp->overdrawProtection = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "realLight" ) )
+ {
+ bp->realLight = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "dynamicLight" ) )
+ {
+ bp->dynamicLight = qtrue;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->dLightRadius.delay = (int)number;
+ bp->dLightRadius.delayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->dLightRadius.initial = number;
+ bp->dLightRadius.initialRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->dLightRadius.final = PARTICLES_SAME_AS_INITIAL;
+ bp->dLightRadius.finalRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->dLightRadius.final = number;
+ bp->dLightRadius.finalRandFrac = randFrac;
+ }
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseColor( bp->dLightColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "cullOnStartSolid" ) )
+ {
+ bp->cullOnStartSolid = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "radius" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->radius.delay = (int)number;
+ bp->radius.delayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->radius.initial = number;
+ bp->radius.initialRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->radius.final = PARTICLES_SAME_AS_INITIAL;
+ bp->radius.finalRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->radius.final = number;
+ bp->radius.finalRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "alpha" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->alpha.delay = (int)number;
+ bp->alpha.delayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->alpha.initial = number;
+ bp->alpha.initialRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->alpha.final = PARTICLES_SAME_AS_INITIAL;
+ bp->alpha.finalRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->alpha.final = number;
+ bp->alpha.finalRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "color" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->colorDelay = (int)number;
+ bp->colorDelayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseColor( bp->initialColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !*token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->finalColor[ 0 ] = bp->initialColor[ 0 ];
+ bp->finalColor[ 1 ] = bp->initialColor[ 1 ];
+ bp->finalColor[ 2 ] = bp->initialColor[ 2 ];
+ }
+ else if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseColor( bp->finalColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" );
+ break;
+ }
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" );
+ break;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "rotation" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->rotation.delay = (int)number;
+ bp->rotation.delayRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qtrue );
+
+ bp->rotation.initial = number;
+ bp->rotation.initialRandFrac = randFrac;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ bp->rotation.final = PARTICLES_SAME_AS_INITIAL;
+ bp->rotation.finalRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qtrue );
+
+ bp->rotation.final = number;
+ bp->rotation.finalRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "lifeTime" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bp->lifeTime = (int)number;
+ bp->lifeTimeRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "childSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( bp->childSystemName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "onDeathSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( bp->onDeathSystemName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "childTrailSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ Q_strncpyz( bp->childTrailSystemName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this particle
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle\n", token );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_InitialiseBaseParticle
+===============
+*/
+static void CG_InitialiseBaseParticle( baseParticle_t *bp )
+{
+ memset( bp, 0, sizeof( baseParticle_t ) );
+
+ memset( bp->initialColor, 0xFF, sizeof( bp->initialColor ) );
+ memset( bp->finalColor, 0xFF, sizeof( bp->finalColor ) );
+}
+
+/*
+===============
+CG_ParseParticleEjector
+
+Parse a particle ejector section
+===============
+*/
+static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text_p )
+{
+ char *token;
+ float number, randFrac;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ CG_InitialiseBaseParticle( &baseParticles[ numBaseParticles ] );
+
+ if( !CG_ParseParticle( &baseParticles[ numBaseParticles ], text_p ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: failed to parse particle\n" );
+ return qfalse;
+ }
+
+ if( bpe->numParticles == MAX_PARTICLES_PER_EJECTOR )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: ejector has > %d particles\n", MAX_PARTICLES_PER_EJECTOR );
+ return qfalse;
+ }
+ else if( numBaseParticles == MAX_BASEPARTICLES )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of particles (%d) reached\n", MAX_BASEPARTICLES );
+ return qfalse;
+ }
+ else
+ {
+ //start parsing particles again
+ bpe->particles[ bpe->numParticles ] = &baseParticles[ numBaseParticles ];
+ bpe->numParticles++;
+ numBaseParticles++;
+ }
+ continue;
+ }
+ else if( !Q_stricmp( token, "delay" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bpe->eject.delay = (int)number;
+ bpe->eject.delayRandFrac = randFrac;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "period" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ bpe->eject.initial = atoi_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ bpe->eject.final = PARTICLES_SAME_AS_INITIAL;
+ else
+ bpe->eject.final = atoi_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ CG_ParseValueAndVariance( token, NULL, &bpe->eject.randFrac, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "count" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "infinite" ) )
+ {
+ bpe->totalParticles = PARTICLES_INFINITE;
+ bpe->totalParticlesRandFrac = 0.0f;
+ }
+ else
+ {
+ CG_ParseValueAndVariance( token, &number, &randFrac, qfalse );
+
+ bpe->totalParticles = (int)number;
+ bpe->totalParticlesRandFrac = randFrac;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "particle" ) ) //acceptable text
+ continue;
+ else if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this particle ejector
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle ejector\n", token );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+
+/*
+===============
+CG_ParseParticleSystem
+
+Parse a particle system section
+===============
+*/
+static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p, const char *name )
+{
+ char *token;
+ baseParticleEjector_t *bpe;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseParticleEjector( &baseParticleEjectors[ numBaseParticleEjectors ], text_p ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: failed to parse particle ejector\n" );
+ return qfalse;
+ }
+
+ bpe = &baseParticleEjectors[ numBaseParticleEjectors ];
+
+ //check for infinite count + zero period
+ if( bpe->totalParticles == PARTICLES_INFINITE &&
+ ( bpe->eject.initial == 0.0f || bpe->eject.final == 0.0f ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: ejector with 'count infinite' potentially has zero period\n" );
+ return qfalse;
+ }
+
+ if( bps->numEjectors == MAX_EJECTORS_PER_SYSTEM )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: particle system has > %d ejectors\n", MAX_EJECTORS_PER_SYSTEM );
+ return qfalse;
+ }
+ else if( numBaseParticleEjectors == MAX_BASEPARTICLE_EJECTORS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of particle ejectors (%d) reached\n",
+ MAX_BASEPARTICLE_EJECTORS );
+ return qfalse;
+ }
+ else
+ {
+ //start parsing ejectors again
+ bps->ejectors[ bps->numEjectors ] = &baseParticleEjectors[ numBaseParticleEjectors ];
+ bps->numEjectors++;
+ numBaseParticleEjectors++;
+ }
+ continue;
+ }
+ else if( !Q_stricmp( token, "thirdPersonOnly" ) )
+ bps->thirdPersonOnly = qtrue;
+ else if( !Q_stricmp( token, "ejector" ) ) //acceptable text
+ continue;
+ else if( !Q_stricmp( token, "}" ) )
+ {
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "Parsed particle system %s\n", name );
+
+ return qtrue; //reached the end of this particle system
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle system %s\n", token, bps->name );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_ParseParticleFile
+
+Load the particle systems from a particle file
+===============
+*/
+static qboolean CG_ParseParticleFile( const char *fileName )
+{
+ char *text_p;
+ int i;
+ int len;
+ char *token;
+ char text[ 32000 ];
+ char psName[ MAX_QPATH ];
+ qboolean psNameSet = qfalse;
+ fileHandle_t f;
+
+ // load the file
+ len = trap_FS_FOpenFile( fileName, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: particle file %s too long\n", fileName );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( psNameSet )
+ {
+ //check for name space clashes
+ for( i = 0; i < numBaseParticleSystems; i++ )
+ {
+ if( !Q_stricmp( baseParticleSystems[ i ].name, psName ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a particle system is already named %s\n", psName );
+ return qfalse;
+ }
+ }
+
+ Q_strncpyz( baseParticleSystems[ numBaseParticleSystems ].name, psName, MAX_QPATH );
+
+ if( !CG_ParseParticleSystem( &baseParticleSystems[ numBaseParticleSystems ], &text_p, psName ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse particle system %s\n", fileName, psName );
+ return qfalse;
+ }
+
+ //start parsing particle systems again
+ psNameSet = qfalse;
+
+ if( numBaseParticleSystems == MAX_BASEPARTICLE_SYSTEMS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of particle systems (%d) reached\n",
+ MAX_BASEPARTICLE_SYSTEMS );
+ return qfalse;
+ }
+ else
+ numBaseParticleSystems++;
+
+ continue;
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unamed particle system\n" );
+ return qfalse;
+ }
+ }
+
+ if( !psNameSet )
+ {
+ Q_strncpyz( psName, token, sizeof( psName ) );
+ psNameSet = qtrue;
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: particle system already named\n" );
+ return qfalse;
+ }
+ }
+
+ return qtrue;
+}
+
+
+/*
+===============
+CG_LoadParticleSystems
+
+Load particle systems from .particle files
+===============
+*/
+void CG_LoadParticleSystems( void )
+{
+ int i, j, numFiles, fileLen;
+ char fileList[ MAX_PARTICLE_FILES * MAX_QPATH ];
+ char fileName[ MAX_QPATH ];
+ char *filePtr;
+
+ //clear out the old
+ numBaseParticleSystems = 0;
+ numBaseParticleEjectors = 0;
+ numBaseParticles = 0;
+
+ for( i = 0; i < MAX_BASEPARTICLE_SYSTEMS; i++ )
+ {
+ baseParticleSystem_t *bps = &baseParticleSystems[ i ];
+ memset( bps, 0, sizeof( baseParticleSystem_t ) );
+ }
+
+ for( i = 0; i < MAX_BASEPARTICLE_EJECTORS; i++ )
+ {
+ baseParticleEjector_t *bpe = &baseParticleEjectors[ i ];
+ memset( bpe, 0, sizeof( baseParticleEjector_t ) );
+ }
+
+ for( i = 0; i < MAX_BASEPARTICLES; i++ )
+ {
+ baseParticle_t *bp = &baseParticles[ i ];
+ memset( bp, 0, sizeof( baseParticle_t ) );
+ }
+
+
+ //and bring in the new
+ numFiles = trap_FS_GetFileList( "scripts", ".particle",
+ fileList, MAX_PARTICLE_FILES * MAX_QPATH );
+ filePtr = fileList;
+
+ for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 )
+ {
+ fileLen = strlen( filePtr );
+ strcpy( fileName, "scripts/" );
+ strcat( fileName, filePtr );
+ CG_Printf( "...loading '%s'\n", fileName );
+ CG_ParseParticleFile( fileName );
+ }
+
+ //connect any child systems to their psHandle
+ for( i = 0; i < numBaseParticles; i++ )
+ {
+ baseParticle_t *bp = &baseParticles[ i ];
+
+ if( bp->childSystemName[ 0 ] )
+ {
+ //particle class has a child, resolve the name
+ for( j = 0; j < numBaseParticleSystems; j++ )
+ {
+ baseParticleSystem_t *bps = &baseParticleSystems[ j ];
+
+ if( !Q_stricmp( bps->name, bp->childSystemName ) )
+ {
+ //FIXME: add checks for cycles and infinite children
+
+ bp->childSystemHandle = j + 1;
+
+ break;
+ }
+ }
+
+ if( j == numBaseParticleSystems )
+ {
+ //couldn't find named particle system
+ CG_Printf( S_COLOR_YELLOW "WARNING: failed to find child %s\n", bp->childSystemName );
+ bp->childSystemName[ 0 ] = '\0';
+ }
+ }
+
+ if( bp->onDeathSystemName[ 0 ] )
+ {
+ //particle class has a child, resolve the name
+ for( j = 0; j < numBaseParticleSystems; j++ )
+ {
+ baseParticleSystem_t *bps = &baseParticleSystems[ j ];
+
+ if( !Q_stricmp( bps->name, bp->onDeathSystemName ) )
+ {
+ //FIXME: add checks for cycles and infinite children
+
+ bp->onDeathSystemHandle = j + 1;
+
+ break;
+ }
+ }
+
+ if( j == numBaseParticleSystems )
+ {
+ //couldn't find named particle system
+ CG_Printf( S_COLOR_YELLOW "WARNING: failed to find onDeath system %s\n", bp->onDeathSystemName );
+ bp->onDeathSystemName[ 0 ] = '\0';
+ }
+ }
+ }
+}
+
+/*
+===============
+CG_SetParticleSystemNormal
+===============
+*/
+void CG_SetParticleSystemNormal( particleSystem_t *ps, vec3_t normal )
+{
+ if( ps == NULL || !ps->valid )
+ {
+ CG_Printf( S_COLOR_YELLOW "WARNING: tried to modify a NULL particle system\n" );
+ return;
+ }
+
+ ps->normalValid = qtrue;
+ VectorCopy( normal, ps->normal );
+ VectorNormalize( ps->normal );
+}
+
+
+/*
+===============
+CG_DestroyParticleSystem
+
+Destroy a particle system
+
+This doesn't actually invalidate anything, it just stops
+particle ejectors from producing new particles so the
+garbage collector will eventually remove this system.
+However is does set the pointer to NULL so the user is
+unable to manipulate this particle system any longer.
+===============
+*/
+void CG_DestroyParticleSystem( particleSystem_t **ps )
+{
+ int i;
+ particleEjector_t *pe;
+
+ if( *ps == NULL || !(*ps)->valid )
+ {
+ CG_Printf( S_COLOR_YELLOW "WARNING: tried to destroy a NULL particle system\n" );
+ return;
+ }
+
+ if( cg_debugParticles.integer >= 1 )
+ CG_Printf( "PS destroyed\n" );
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ {
+ pe = &particleEjectors[ i ];
+
+ if( pe->valid && pe->parent == *ps )
+ pe->totalParticles = pe->count = 0;
+ }
+
+ *ps = NULL;
+}
+
+/*
+===============
+CG_IsParticleSystemInfinite
+
+Test a particle system for 'count infinite' ejectors
+===============
+*/
+qboolean CG_IsParticleSystemInfinite( particleSystem_t *ps )
+{
+ int i;
+ particleEjector_t *pe;
+
+ if( ps == NULL )
+ {
+ CG_Printf( S_COLOR_YELLOW "WARNING: tried to test a NULL particle system\n" );
+ return qfalse;
+ }
+
+ if( !ps->valid )
+ {
+ CG_Printf( S_COLOR_YELLOW "WARNING: tried to test an invalid particle system\n" );
+ return qfalse;
+ }
+
+ //don't bother checking already invalid systems
+ if( !ps->valid )
+ return qfalse;
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ {
+ pe = &particleEjectors[ i ];
+
+ if( pe->valid && pe->parent == ps )
+ {
+ if( pe->totalParticles == PARTICLES_INFINITE )
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_IsParticleSystemValid
+
+Test a particle system for validity
+===============
+*/
+qboolean CG_IsParticleSystemValid( particleSystem_t **ps )
+{
+ if( *ps == NULL || ( *ps && !(*ps)->valid ) )
+ {
+ if( *ps && !(*ps)->valid )
+ *ps = NULL;
+
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_GarbageCollectParticleSystems
+
+Destroy inactive particle systems
+===============
+*/
+static void CG_GarbageCollectParticleSystems( void )
+{
+ int i, j, count;
+ particleSystem_t *ps;
+ particleEjector_t *pe;
+ int centNum;
+
+ for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ )
+ {
+ ps = &particleSystems[ i ];
+ count = 0;
+
+ //don't bother checking already invalid systems
+ if( !ps->valid )
+ continue;
+
+ for( j = 0; j < MAX_PARTICLE_EJECTORS; j++ )
+ {
+ pe = &particleEjectors[ j ];
+
+ if( pe->valid && pe->parent == ps )
+ count++;
+ }
+
+ if( !count )
+ ps->valid = qfalse;
+
+ //check systems where the parent cent has left the PVS
+ //( local player entity is always valid )
+ if( ( centNum = CG_AttachmentCentNum( &ps->attachment ) ) >= 0 &&
+ centNum != cg.snap->ps.clientNum )
+ {
+ if( !cg_entities[ centNum ].valid )
+ ps->lazyRemove = qtrue;
+ }
+
+ if( cg_debugParticles.integer >= 1 && !ps->valid )
+ CG_Printf( "PS %s garbage collected\n", ps->class->name );
+ }
+}
+
+
+/*
+===============
+CG_CalculateTimeFrac
+
+Calculate the fraction of time passed
+===============
+*/
+static float CG_CalculateTimeFrac( int birth, int life, int delay )
+{
+ float frac;
+
+ frac = ( (float)cg.time - (float)( birth + delay ) ) / (float)( life - delay );
+
+ if( frac < 0.0f )
+ frac = 0.0f;
+ else if( frac > 1.0f )
+ frac = 1.0f;
+
+ return frac;
+}
+
+/*
+===============
+CG_EvaluateParticlePhysics
+
+Compute the physics on a specific particle
+===============
+*/
+static void CG_EvaluateParticlePhysics( particle_t *p )
+{
+ particleSystem_t *ps = p->parent->parent;
+ baseParticle_t *bp = p->class;
+ vec3_t acceleration, newOrigin;
+ vec3_t mins, maxs;
+ float deltaTime, bounce, radius, dot;
+ trace_t trace;
+ vec3_t transform[ 3 ];
+
+ if( p->atRest )
+ {
+ VectorClear( p->velocity );
+ return;
+ }
+
+ switch( bp->accMoveType )
+ {
+ case PMT_STATIC:
+ if( bp->accMoveValues.dirType == PMD_POINT )
+ VectorSubtract( bp->accMoveValues.point, p->origin, acceleration );
+ else if( bp->accMoveValues.dirType == PMD_LINEAR )
+ VectorCopy( bp->accMoveValues.dir, acceleration );
+
+ break;
+
+ case PMT_STATIC_TRANSFORM:
+ if( !CG_AttachmentAxis( &ps->attachment, transform ) )
+ return;
+
+ if( bp->accMoveValues.dirType == PMD_POINT )
+ {
+ vec3_t transPoint;
+
+ VectorMatrixMultiply( bp->accMoveValues.point, transform, transPoint );
+ VectorSubtract( transPoint, p->origin, acceleration );
+ }
+ else if( bp->accMoveValues.dirType == PMD_LINEAR )
+ VectorMatrixMultiply( bp->accMoveValues.dir, transform, acceleration );
+ break;
+
+ case PMT_TAG:
+ case PMT_CENT_ANGLES:
+ if( bp->accMoveValues.dirType == PMD_POINT )
+ {
+ vec3_t point;
+
+ if( !CG_AttachmentPoint( &ps->attachment, point ) )
+ return;
+
+ VectorSubtract( point, p->origin, acceleration );
+ }
+ else if( bp->accMoveValues.dirType == PMD_LINEAR )
+ {
+ if( !CG_AttachmentDir( &ps->attachment, acceleration ) )
+ return;
+ }
+ break;
+
+ case PMT_NORMAL:
+ if( !ps->normalValid )
+ return;
+
+ VectorCopy( ps->normal, acceleration );
+
+ break;
+ }
+
+#define MAX_ACC_RADIUS 1000.0f
+
+ if( bp->accMoveValues.dirType == PMD_POINT )
+ {
+ //FIXME: so this fall off is a bit... odd -- it works..
+ float r2 = DotProduct( acceleration, acceleration ); // = radius^2
+ float scale = ( MAX_ACC_RADIUS - r2 ) / MAX_ACC_RADIUS;
+
+ if( scale > 1.0f )
+ scale = 1.0f;
+ else if( scale < 0.1f )
+ scale = 0.1f;
+
+ scale *= CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac );
+
+ VectorNormalize( acceleration );
+ CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle );
+ VectorScale( acceleration, scale, acceleration );
+ }
+ else if( bp->accMoveValues.dirType == PMD_LINEAR )
+ {
+ VectorNormalize( acceleration );
+ CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle );
+ VectorScale( acceleration,
+ CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac ),
+ acceleration );
+ }
+
+ radius = CG_LerpValues( p->radius.initial,
+ p->radius.final,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->radius.delay ) );
+
+ VectorSet( mins, -radius, -radius, -radius );
+ VectorSet( maxs, radius, radius, radius );
+
+ bounce = CG_RandomiseValue( bp->bounceFrac, bp->bounceFracRandFrac );
+
+ deltaTime = (float)( cg.time - p->lastEvalTime ) * 0.001;
+ VectorMA( p->velocity, deltaTime, acceleration, p->velocity );
+ VectorMA( p->origin, deltaTime, p->velocity, newOrigin );
+ p->lastEvalTime = cg.time;
+
+ // we're not doing particle physics, but at least cull them in solids
+ if( !cg_bounceParticles.integer )
+ {
+ int contents = trap_CM_PointContents( newOrigin, 0 );
+
+ if( ( contents & CONTENTS_SOLID ) || ( contents & CONTENTS_NODROP ) )
+ CG_DestroyParticle( p, NULL );
+ else
+ VectorCopy( newOrigin, p->origin );
+ return;
+ }
+
+ CG_Trace( &trace, p->origin, mins, maxs, newOrigin,
+ CG_AttachmentCentNum( &ps->attachment ), CONTENTS_SOLID );
+
+ //not hit anything or not a collider
+ if( trace.fraction == 1.0f || bounce == 0.0f )
+ {
+ VectorCopy( newOrigin, p->origin );
+ return;
+ }
+
+ //remove particles that get into a CONTENTS_NODROP brush
+ if( ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) ||
+ ( bp->cullOnStartSolid && trace.startsolid ) )
+ {
+ CG_DestroyParticle( p, NULL );
+ return;
+ }
+ else if( bp->bounceCull )
+ {
+ CG_DestroyParticle( p, trace.plane.normal );
+ return;
+ }
+
+ //reflect the velocity on the trace plane
+ dot = DotProduct( p->velocity, trace.plane.normal );
+ VectorMA( p->velocity, -2.0f * dot, trace.plane.normal, p->velocity );
+
+ VectorScale( p->velocity, bounce, p->velocity );
+
+ if( trace.plane.normal[ 2 ] > 0.5f &&
+ ( p->velocity[ 2 ] < 40.0f ||
+ p->velocity[ 2 ] < -cg.frametime * p->velocity[ 2 ] ) )
+ p->atRest = qtrue;
+
+ if( bp->bounceMarkName[ 0 ] && p->bounceMarkCount > 0 )
+ {
+ CG_ImpactMark( bp->bounceMark, trace.endpos, trace.plane.normal,
+ random( ) * 360, 1, 1, 1, 1, qtrue, bp->bounceMarkRadius, qfalse );
+ p->bounceMarkCount--;
+ }
+
+ if( bp->bounceSoundName[ 0 ] && p->bounceSoundCount > 0 )
+ {
+ trap_S_StartSound( trace.endpos, ENTITYNUM_WORLD, CHAN_AUTO, bp->bounceSound );
+ p->bounceSoundCount--;
+ }
+
+ VectorCopy( trace.endpos, p->origin );
+}
+
+
+#define GETKEY(x,y) (((x)>>y)&0xFF)
+
+/*
+===============
+CG_Radix
+===============
+*/
+static void CG_Radix( int bits, int size, particle_t **source, particle_t **dest )
+{
+ int count[ 256 ];
+ int index[ 256 ];
+ int i;
+
+ memset( count, 0, sizeof( count ) );
+
+ for( i = 0; i < size; i++ )
+ count[ GETKEY( source[ i ]->sortKey, bits ) ]++;
+
+ index[ 0 ] = 0;
+
+ for( i = 1; i < 256; i++ )
+ index[ i ] = index[ i - 1 ] + count[ i - 1 ];
+
+ for( i = 0; i < size; i++ )
+ dest[ index[ GETKEY( source[ i ]->sortKey, bits ) ]++ ] = source[ i ];
+}
+
+/*
+===============
+CG_RadixSort
+
+Radix sort with 4 byte size buckets
+===============
+*/
+static void CG_RadixSort( particle_t **source, particle_t **temp, int size )
+{
+ CG_Radix( 0, size, source, temp );
+ CG_Radix( 8, size, temp, source );
+ CG_Radix( 16, size, source, temp );
+ CG_Radix( 24, size, temp, source );
+}
+
+/*
+===============
+CG_CompactAndSortParticles
+
+Depth sort the particles
+===============
+*/
+static void CG_CompactAndSortParticles( void )
+{
+ int i, j = 0;
+ int numParticles;
+ vec3_t delta;
+
+ for( i = 0; i < MAX_PARTICLES; i++ )
+ sortedParticles[ i ] = &particles[ i ];
+
+ if( !cg_depthSortParticles.integer )
+ return;
+
+ for( i = MAX_PARTICLES - 1; i >= 0; i-- )
+ {
+ if( sortedParticles[ i ]->valid )
+ {
+ //find the first hole
+ while( j < MAX_PARTICLES && sortedParticles[ j ]->valid )
+ j++;
+
+ //no more holes
+ if( j >= i )
+ break;
+
+ sortedParticles[ j ] = sortedParticles[ i ];
+ }
+ }
+
+ numParticles = i;
+
+ //set sort keys
+ for( i = 0; i < numParticles; i++ )
+ {
+ VectorSubtract( sortedParticles[ i ]->origin, cg.refdef.vieworg, delta );
+ sortedParticles[ i ]->sortKey = (int)DotProduct( delta, delta );
+ }
+
+ CG_RadixSort( sortedParticles, radixBuffer, numParticles );
+
+ //FIXME: wtf?
+ //reverse order of particles array
+ for( i = 0; i < numParticles; i++ )
+ radixBuffer[ i ] = sortedParticles[ numParticles - i - 1 ];
+
+ for( i = 0; i < numParticles; i++ )
+ sortedParticles[ i ] = radixBuffer[ i ];
+}
+
+/*
+===============
+CG_RenderParticle
+
+Actually render a particle
+===============
+*/
+static void CG_RenderParticle( particle_t *p )
+{
+ refEntity_t re;
+ float timeFrac, scale;
+ int index;
+ baseParticle_t *bp = p->class;
+ particleSystem_t *ps = p->parent->parent;
+ baseParticleSystem_t *bps = ps->class;
+ vec3_t alight, dlight, lightdir;
+ int i;
+ vec3_t up = { 0.0f, 0.0f, 1.0f };
+
+ memset( &re, 0, sizeof( refEntity_t ) );
+
+ timeFrac = CG_CalculateTimeFrac( p->birthTime, p->lifeTime, 0 );
+
+ scale = CG_LerpValues( p->radius.initial,
+ p->radius.final,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->radius.delay ) );
+
+ re.shaderTime = p->birthTime / 1000.0f;
+
+ if( bp->numFrames ) //shader based
+ {
+ re.reType = RT_SPRITE;
+
+ //apply environmental lighting to the particle
+ if( bp->realLight )
+ {
+ trap_R_LightForPoint( p->origin, alight, dlight, lightdir );
+ for( i = 0; i <= 2; i++ )
+ re.shaderRGBA[ i ] = (byte)alight[ i ];
+ }
+ else
+ {
+ vec3_t colorRange;
+
+ VectorSubtract( bp->finalColor,
+ bp->initialColor, colorRange );
+
+ VectorMA( bp->initialColor,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->colorDelay ),
+ colorRange, re.shaderRGBA );
+ }
+
+ re.shaderRGBA[ 3 ] = (byte)( (float)0xFF *
+ CG_LerpValues( p->alpha.initial,
+ p->alpha.final,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->alpha.delay ) ) );
+
+ re.radius = scale;
+
+ re.rotation = CG_LerpValues( p->rotation.initial,
+ p->rotation.final,
+ CG_CalculateTimeFrac( p->birthTime,
+ p->lifeTime,
+ p->rotation.delay ) );
+
+ // if the view would be "inside" the sprite, kill the sprite
+ // so it doesn't add too much overdraw
+ if( Distance( p->origin, cg.refdef.vieworg ) < re.radius && bp->overdrawProtection )
+ return;
+
+ if( bp->framerate == 0.0f )
+ {
+ //sync animation time to lifeTime of particle
+ index = (int)( timeFrac * ( bp->numFrames + 1 ) );
+
+ if( index >= bp->numFrames )
+ index = bp->numFrames - 1;
+
+ re.customShader = bp->shaders[ index ];
+ }
+ else
+ {
+ //looping animation
+ index = (int)( bp->framerate * timeFrac * p->lifeTime * 0.001 ) % bp->numFrames;
+ re.customShader = bp->shaders[ index ];
+ }
+
+ }
+ else if( bp->numModels ) //model based
+ {
+ re.reType = RT_MODEL;
+
+ re.hModel = p->model;
+
+ if( p->atRest )
+ AxisCopy( p->lastAxis, re.axis );
+ else
+ {
+ // convert direction of travel into axis
+ VectorNormalize2( p->velocity, re.axis[ 0 ] );
+
+ if( re.axis[ 0 ][ 0 ] == 0.0f && re.axis[ 0 ][ 1 ] == 0.0f )
+ AxisCopy( axisDefault, re.axis );
+ else
+ {
+ ProjectPointOnPlane( re.axis[ 2 ], up, re.axis[ 0 ] );
+ VectorNormalize( re.axis[ 2 ] );
+ CrossProduct( re.axis[ 2 ], re.axis[ 0 ], re.axis[ 1 ] );
+ }
+
+ AxisCopy( re.axis, p->lastAxis );
+ }
+
+ if( scale != 1.0f )
+ {
+ VectorScale( re.axis[ 0 ], scale, re.axis[ 0 ] );
+ VectorScale( re.axis[ 1 ], scale, re.axis[ 1 ] );
+ VectorScale( re.axis[ 2 ], scale, re.axis[ 2 ] );
+ re.nonNormalizedAxes = qtrue;
+ }
+ else
+ re.nonNormalizedAxes = qfalse;
+
+ p->lf.animation = &bp->modelAnimation;
+
+ //run animation
+ CG_RunLerpFrame( &p->lf );
+
+ re.oldframe = p->lf.oldFrame;
+ re.frame = p->lf.frame;
+ re.backlerp = p->lf.backlerp;
+ }
+
+ if( bps->thirdPersonOnly &&
+ CG_AttachmentCentNum( &ps->attachment ) == cg.snap->ps.clientNum &&
+ !cg.renderingThirdPerson )
+ re.renderfx |= RF_THIRD_PERSON;
+
+ if( bp->dynamicLight && !( re.renderfx & RF_THIRD_PERSON ) )
+ {
+ trap_R_AddLightToScene( p->origin,
+ CG_LerpValues( p->dLightRadius.initial, p->dLightRadius.final,
+ CG_CalculateTimeFrac( p->birthTime, p->lifeTime, p->dLightRadius.delay ) ),
+ (float)bp->dLightColor[ 0 ] / (float)0xFF,
+ (float)bp->dLightColor[ 1 ] / (float)0xFF,
+ (float)bp->dLightColor[ 2 ] / (float)0xFF );
+ }
+
+ VectorCopy( p->origin, re.origin );
+
+ trap_R_AddRefEntityToScene( &re );
+}
+
+/*
+===============
+CG_AddParticles
+
+Add particles to the scene
+===============
+*/
+void CG_AddParticles( void )
+{
+ int i;
+ particle_t *p;
+ int numPS = 0, numPE = 0, numP = 0;
+
+ //remove expired particle systems
+ CG_GarbageCollectParticleSystems( );
+
+ //check each ejector and introduce any new particles
+ CG_SpawnNewParticles( );
+
+ //sorting
+ CG_CompactAndSortParticles( );
+
+ for( i = 0; i < MAX_PARTICLES; i++ )
+ {
+ p = sortedParticles[ i ];
+
+ if( p->valid )
+ {
+ if( p->birthTime + p->lifeTime > cg.time )
+ {
+ //particle is active
+ CG_EvaluateParticlePhysics( p );
+ CG_RenderParticle( p );
+ }
+ else
+ CG_DestroyParticle( p, NULL );
+ }
+ }
+
+ if( cg_debugParticles.integer >= 2 )
+ {
+ for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ )
+ if( particleSystems[ i ].valid )
+ numPS++;
+
+ for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ )
+ if( particleEjectors[ i ].valid )
+ numPE++;
+
+ for( i = 0; i < MAX_PARTICLES; i++ )
+ if( particles[ i ].valid )
+ numP++;
+
+ CG_Printf( "PS: %d PE: %d P: %d\n", numPS, numPE, numP );
+ }
+}
+
+/*
+===============
+CG_ParticleSystemEntity
+
+Particle system entity client code
+===============
+*/
+void CG_ParticleSystemEntity( centity_t *cent )
+{
+ entityState_t *es;
+
+ es = &cent->currentState;
+
+ if( es->eFlags & EF_NODRAW )
+ {
+ if( CG_IsParticleSystemValid( &cent->entityPS ) && CG_IsParticleSystemInfinite( cent->entityPS ) )
+ CG_DestroyParticleSystem( &cent->entityPS );
+
+ return;
+ }
+
+ if( !CG_IsParticleSystemValid( &cent->entityPS ) && !cent->entityPSMissing )
+ {
+ cent->entityPS = CG_SpawnNewParticleSystem( cgs.gameParticleSystems[ es->modelindex ] );
+
+ if( CG_IsParticleSystemValid( &cent->entityPS ) )
+ {
+ CG_SetAttachmentPoint( &cent->entityPS->attachment, cent->lerpOrigin );
+ CG_SetAttachmentCent( &cent->entityPS->attachment, cent );
+ CG_AttachToPoint( &cent->entityPS->attachment );
+ }
+ else
+ cent->entityPSMissing = qtrue;
+ }
+}
+
+static particleSystem_t *testPS;
+static qhandle_t testPSHandle;
+
+/*
+===============
+CG_DestroyTestPS_f
+
+Destroy the test a particle system
+===============
+*/
+void CG_DestroyTestPS_f( void )
+{
+ if( CG_IsParticleSystemValid( &testPS ) )
+ CG_DestroyParticleSystem( &testPS );
+}
+
+/*
+===============
+CG_TestPS_f
+
+Test a particle system
+===============
+*/
+void CG_TestPS_f( void )
+{
+ vec3_t origin;
+ vec3_t up = { 0.0f, 0.0f, 1.0f };
+ char psName[ MAX_QPATH ];
+
+ if( trap_Argc( ) < 2 )
+ return;
+
+ Q_strncpyz( psName, CG_Argv( 1 ), MAX_QPATH );
+ testPSHandle = CG_RegisterParticleSystem( psName );
+
+ if( testPSHandle )
+ {
+ CG_DestroyTestPS_f( );
+
+ testPS = CG_SpawnNewParticleSystem( testPSHandle );
+
+ VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[ 0 ], origin );
+
+ if( CG_IsParticleSystemValid( &testPS ) )
+ {
+ CG_SetAttachmentPoint( &testPS->attachment, origin );
+ CG_SetParticleSystemNormal( testPS, up );
+ CG_AttachToPoint( &testPS->attachment );
+ }
+ }
+}
diff --git a/src/cgame/cg_players.c b/src/cgame/cg_players.c
new file mode 100644
index 0000000..b594330
--- /dev/null
+++ b/src/cgame/cg_players.c
@@ -0,0 +1,2565 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_players.c -- handle the media and animation for player entities
+
+
+#include "cg_local.h"
+
+char *cg_customSoundNames[ MAX_CUSTOM_SOUNDS ] =
+{
+ "*death1.wav",
+ "*death2.wav",
+ "*death3.wav",
+ "*jump1.wav",
+ "*pain25_1.wav",
+ "*pain50_1.wav",
+ "*pain75_1.wav",
+ "*pain100_1.wav",
+ "*falling1.wav",
+ "*gasp.wav",
+ "*drown.wav",
+ "*fall1.wav",
+ "*taunt.wav"
+};
+
+
+/*
+================
+CG_CustomSound
+
+================
+*/
+sfxHandle_t CG_CustomSound( int clientNum, const char *soundName )
+{
+ clientInfo_t *ci;
+ int i;
+
+ if( soundName[ 0 ] != '*' )
+ return trap_S_RegisterSound( soundName, qfalse );
+
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS )
+ clientNum = 0;
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ for( i = 0; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[ i ]; i++ )
+ {
+ if( !strcmp( soundName, cg_customSoundNames[ i ] ) )
+ return ci->sounds[ i ];
+ }
+
+ CG_Error( "Unknown custom sound: %s", soundName );
+ return 0;
+}
+
+
+
+/*
+=============================================================================
+
+CLIENT INFO
+
+=============================================================================
+*/
+
+/*
+======================
+CG_ParseAnimationFile
+
+Read a configuration file containing animation coutns and rates
+models/players/visor/animation.cfg, etc
+======================
+*/
+static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci )
+{
+ char *text_p, *prev;
+ int len;
+ int i;
+ char *token;
+ float fps;
+ int skip;
+ char text[ 20000 ];
+ fileHandle_t f;
+ animation_t *animations;
+
+ animations = ci->animations;
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( "File %s too long\n", filename );
+ trap_FS_FCloseFile( f );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+ skip = 0; // quite the compiler warning
+
+ ci->footsteps = FOOTSTEP_NORMAL;
+ VectorClear( ci->headOffset );
+ ci->gender = GENDER_MALE;
+ ci->fixedlegs = qfalse;
+ ci->fixedtorso = qfalse;
+ ci->nonsegmented = qfalse;
+
+ // read optional parameters
+ while( 1 )
+ {
+ prev = text_p; // so we can unget
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "footsteps" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) )
+ ci->footsteps = FOOTSTEP_NORMAL;
+ else if( !Q_stricmp( token, "flesh" ) )
+ ci->footsteps = FOOTSTEP_FLESH;
+ else if( !Q_stricmp( token, "none" ) )
+ ci->footsteps = FOOTSTEP_NONE;
+ else if( !Q_stricmp( token, "custom" ) )
+ ci->footsteps = FOOTSTEP_CUSTOM;
+ else
+ CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "headoffset" ) )
+ {
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ ci->headOffset[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "sex" ) )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( token[ 0 ] == 'f' || token[ 0 ] == 'F' )
+ ci->gender = GENDER_FEMALE;
+ else if( token[ 0 ] == 'n' || token[ 0 ] == 'N' )
+ ci->gender = GENDER_NEUTER;
+ else
+ ci->gender = GENDER_MALE;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "fixedlegs" ) )
+ {
+ ci->fixedlegs = qtrue;
+ continue;
+ }
+ else if( !Q_stricmp( token, "fixedtorso" ) )
+ {
+ ci->fixedtorso = qtrue;
+ continue;
+ }
+ else if( !Q_stricmp( token, "nonsegmented" ) )
+ {
+ ci->nonsegmented = qtrue;
+ continue;
+ }
+
+ // if it is a number, start parsing animations
+ if( token[ 0 ] >= '0' && token[ 0 ] <= '9' )
+ {
+ text_p = prev; // unget the token
+ break;
+ }
+
+ Com_Printf( "unknown token '%s' is %s\n", token, filename );
+ }
+
+ if( !ci->nonsegmented )
+ {
+ // read information for each frame
+ for( i = 0; i < MAX_PLAYER_ANIMATIONS; i++ )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ {
+ if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE )
+ {
+ animations[ i ].firstFrame = animations[ TORSO_GESTURE ].firstFrame;
+ animations[ i ].frameLerp = animations[ TORSO_GESTURE ].frameLerp;
+ animations[ i ].initialLerp = animations[ TORSO_GESTURE ].initialLerp;
+ animations[ i ].loopFrames = animations[ TORSO_GESTURE ].loopFrames;
+ animations[ i ].numFrames = animations[ TORSO_GESTURE ].numFrames;
+ animations[ i ].reversed = qfalse;
+ animations[ i ].flipflop = qfalse;
+ continue;
+ }
+
+ break;
+ }
+
+ animations[ i ].firstFrame = atoi( token );
+
+ // leg only frames are adjusted to not count the upper body only frames
+ if( i == LEGS_WALKCR )
+ skip = animations[ LEGS_WALKCR ].firstFrame - animations[ TORSO_GESTURE ].firstFrame;
+
+ if( i >= LEGS_WALKCR && i<TORSO_GETFLAG )
+ animations[ i ].firstFrame -= skip;
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ animations[ i ].numFrames = atoi( token );
+ animations[ i ].reversed = qfalse;
+ animations[ i ].flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( animations[ i ].numFrames < 0 )
+ {
+ animations[ i ].numFrames = -animations[ i ].numFrames;
+ animations[ i ].reversed = qtrue;
+ }
+
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ animations[ i ].loopFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ fps = atof( token );
+ if( fps == 0 )
+ fps = 1;
+
+ animations[ i ].frameLerp = 1000 / fps;
+ animations[ i ].initialLerp = 1000 / fps;
+ }
+
+ if( i != MAX_PLAYER_ANIMATIONS )
+ {
+ CG_Printf( "Error parsing animation file: %s", filename );
+ return qfalse;
+ }
+ // crouch backward animation
+ memcpy( &animations[ LEGS_BACKCR ], &animations[ LEGS_WALKCR ], sizeof( animation_t ) );
+ animations[ LEGS_BACKCR ].reversed = qtrue;
+ // walk backward animation
+ memcpy( &animations[ LEGS_BACKWALK ], &animations[ LEGS_WALK ], sizeof( animation_t ) );
+ animations[ LEGS_BACKWALK ].reversed = qtrue;
+ // flag moving fast
+ animations[ FLAG_RUN ].firstFrame = 0;
+ animations[ FLAG_RUN ].numFrames = 16;
+ animations[ FLAG_RUN ].loopFrames = 16;
+ animations[ FLAG_RUN ].frameLerp = 1000 / 15;
+ animations[ FLAG_RUN ].initialLerp = 1000 / 15;
+ animations[ FLAG_RUN ].reversed = qfalse;
+ // flag not moving or moving slowly
+ animations[ FLAG_STAND ].firstFrame = 16;
+ animations[ FLAG_STAND ].numFrames = 5;
+ animations[ FLAG_STAND ].loopFrames = 0;
+ animations[ FLAG_STAND ].frameLerp = 1000 / 20;
+ animations[ FLAG_STAND ].initialLerp = 1000 / 20;
+ animations[ FLAG_STAND ].reversed = qfalse;
+ // flag speeding up
+ animations[ FLAG_STAND2RUN ].firstFrame = 16;
+ animations[ FLAG_STAND2RUN ].numFrames = 5;
+ animations[ FLAG_STAND2RUN ].loopFrames = 1;
+ animations[ FLAG_STAND2RUN ].frameLerp = 1000 / 15;
+ animations[ FLAG_STAND2RUN ].initialLerp = 1000 / 15;
+ animations[ FLAG_STAND2RUN ].reversed = qtrue;
+ }
+ else
+ {
+ // read information for each frame
+ for( i = 0; i < MAX_NONSEG_PLAYER_ANIMATIONS; i++ )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ animations[ i ].firstFrame = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if( !*token )
+ break;
+
+ animations[ i ].numFrames = atoi( token );
+ animations[ i ].reversed = qfalse;
+ animations[ i ].flipflop = qfalse;
+
+ // if numFrames is negative the animation is reversed
+ if( animations[ i ].numFrames < 0 )
+ {
+ animations[ i ].numFrames = -animations[ i ].numFrames;
+ animations[ i ].reversed = qtrue;
+ }
+
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ animations[ i ].loopFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+
+ if( !*token )
+ break;
+
+ fps = atof( token );
+ if( fps == 0 )
+ fps = 1;
+
+ animations[ i ].frameLerp = 1000 / fps;
+ animations[ i ].initialLerp = 1000 / fps;
+ }
+
+ if( i != MAX_NONSEG_PLAYER_ANIMATIONS )
+ {
+ CG_Printf( "Error parsing animation file: %s", filename );
+ return qfalse;
+ }
+
+ // walk backward animation
+ memcpy( &animations[ NSPA_WALKBACK ], &animations[ NSPA_WALK ], sizeof( animation_t ) );
+ animations[ NSPA_WALKBACK ].reversed = qtrue;
+ }
+
+ return qtrue;
+}
+
+/*
+==========================
+CG_RegisterClientSkin
+==========================
+*/
+static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *modelName, const char *skinName )
+{
+ char filename[ MAX_QPATH ];
+
+ if( !ci->nonsegmented )
+ {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName );
+ ci->legsSkin = trap_R_RegisterSkin( filename );
+ if( !ci->legsSkin )
+ Com_Printf( "Leg skin load failure: %s\n", filename );
+
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName );
+ ci->torsoSkin = trap_R_RegisterSkin( filename );
+ if( !ci->torsoSkin )
+ Com_Printf( "Torso skin load failure: %s\n", filename );
+
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/head_%s.skin", modelName, skinName );
+ ci->headSkin = trap_R_RegisterSkin( filename );
+ if( !ci->headSkin )
+ Com_Printf( "Head skin load failure: %s\n", filename );
+
+ if( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin )
+ return qfalse;
+ }
+ else
+ {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/nonseg_%s.skin", modelName, skinName );
+ ci->nonSegSkin = trap_R_RegisterSkin( filename );
+ if( !ci->nonSegSkin )
+ Com_Printf( "Non-segmented skin load failure: %s\n", filename );
+
+ if( !ci->nonSegSkin )
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+==========================
+CG_RegisterClientModelname
+==========================
+*/
+static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName )
+{
+ char filename[ MAX_QPATH * 2 ];
+
+ // do this first so the nonsegmented property is set
+ // load the animations
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName );
+ if( !CG_ParseAnimationFile( filename, ci ) )
+ {
+ Com_Printf( "Failed to load animation file %s\n", filename );
+ return qfalse;
+ }
+
+ // load cmodels before models so filecache works
+
+ if( !ci->nonsegmented )
+ {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName );
+ ci->legsModel = trap_R_RegisterModel( filename );
+ if( !ci->legsModel )
+ {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName );
+ ci->torsoModel = trap_R_RegisterModel( filename );
+ if( !ci->torsoModel )
+ {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", modelName );
+ ci->headModel = trap_R_RegisterModel( filename );
+ if( !ci->headModel )
+ {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+ }
+ else
+ {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/nonseg.md3", modelName );
+ ci->nonSegModel = trap_R_RegisterModel( filename );
+ if( !ci->nonSegModel )
+ {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+ }
+
+ // if any skins failed to load, return failure
+ if( !CG_RegisterClientSkin( ci, modelName, skinName ) )
+ {
+ Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName );
+ return qfalse;
+ }
+
+ //FIXME: skins do not load without icon present. do we want icons anyway?
+/* Com_sprintf( filename, sizeof( filename ), "models/players/%s/icon_%s.tga", modelName, skinName );
+ ci->modelIcon = trap_R_RegisterShaderNoMip( filename );
+ if( !ci->modelIcon )
+ {
+ Com_Printf( "Failed to load icon file: %s\n", filename );
+ return qfalse;
+ }*/
+
+ return qtrue;
+}
+
+/*
+====================
+CG_ColorFromString
+====================
+*/
+static void CG_ColorFromString( const char *v, vec3_t color )
+{
+ int val;
+
+ VectorClear( color );
+
+ val = atoi( v );
+
+ if( val < 1 || val > 7 )
+ {
+ VectorSet( color, 1, 1, 1 );
+ return;
+ }
+
+ if( val & 1 )
+ color[ 2 ] = 1.0f;
+
+ if( val & 2 )
+ color[ 1 ] = 1.0f;
+
+ if( val & 4 )
+ color[ 0 ] = 1.0f;
+}
+
+
+/*
+===================
+CG_LoadClientInfo
+
+Load it now, taking the disk hits
+===================
+*/
+static void CG_LoadClientInfo( clientInfo_t *ci )
+{
+ const char *dir, *fallback;
+ int i;
+ const char *s;
+ int clientNum;
+
+ if( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName ) )
+ {
+ if( cg_buildScript.integer )
+ CG_Error( "CG_RegisterClientModelname( %s, %s ) failed", ci->modelName, ci->skinName );
+
+ // fall back
+ if( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default" ) )
+ CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL );
+ }
+
+ // sounds
+ dir = ci->modelName;
+ fallback = DEFAULT_MODEL;
+
+ for( i = 0; i < MAX_CUSTOM_SOUNDS; i++ )
+ {
+ s = cg_customSoundNames[ i ];
+
+ if( !s )
+ break;
+
+ // fanny about a bit with sounds that are missing
+ if( !CG_FileExists( va( "sound/player/%s/%s", dir, s + 1 ) ) )
+ {
+ //file doesn't exist
+
+ if( i == 11 || i == 8 ) //fall or falling
+ {
+ ci->sounds[ i ] = trap_S_RegisterSound( "sound/null.wav", qfalse );
+ }
+ else
+ {
+ if( i == 9 ) //gasp
+ s = cg_customSoundNames[ 7 ]; //pain100_1
+ else if( i == 10 ) //drown
+ s = cg_customSoundNames[ 0 ]; //death1
+
+ ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse );
+ if( !ci->sounds[ i ] )
+ ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", fallback, s + 1 ), qfalse );
+ }
+ }
+ else
+ {
+ ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", dir, s + 1 ), qfalse );
+ if( !ci->sounds[ i ] )
+ ci->sounds[ i ] = trap_S_RegisterSound( va( "sound/player/%s/%s", fallback, s + 1 ), qfalse );
+ }
+ }
+
+ if( ci->footsteps == FOOTSTEP_CUSTOM )
+ {
+ for( i = 0; i < 4; i++ )
+ {
+ ci->customFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/%s/step%d.wav", dir, i + 1 ), qfalse );
+ if( !ci->customFootsteps[ i ] )
+ ci->customFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/footsteps/step%d.wav", i + 1 ), qfalse );
+
+ ci->customMetalFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/%s/clank%d.wav", dir, i + 1 ), qfalse );
+ if( !ci->customMetalFootsteps[ i ] )
+ ci->customMetalFootsteps[ i ] = trap_S_RegisterSound( va( "sound/player/footsteps/clank%d.wav", i + 1 ), qfalse );
+ }
+ }
+
+ // reset any existing players and bodies, because they might be in bad
+ // frames for this new model
+ clientNum = ci - cgs.clientinfo;
+ for( i = 0; i < MAX_GENTITIES; i++ )
+ {
+ if( cg_entities[ i ].currentState.clientNum == clientNum &&
+ cg_entities[ i ].currentState.eType == ET_PLAYER )
+ CG_ResetPlayerEntity( &cg_entities[ i ] );
+ }
+}
+
+/*
+======================
+CG_CopyClientInfoModel
+======================
+*/
+static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to )
+{
+ VectorCopy( from->headOffset, to->headOffset );
+ to->footsteps = from->footsteps;
+ to->gender = from->gender;
+
+ to->legsModel = from->legsModel;
+ to->legsSkin = from->legsSkin;
+ to->torsoModel = from->torsoModel;
+ to->torsoSkin = from->torsoSkin;
+ to->headModel = from->headModel;
+ to->headSkin = from->headSkin;
+ to->nonSegModel = from->nonSegModel;
+ to->nonSegSkin = from->nonSegSkin;
+ to->nonsegmented = from->nonsegmented;
+ to->modelIcon = from->modelIcon;
+
+ memcpy( to->animations, from->animations, sizeof( to->animations ) );
+ memcpy( to->sounds, from->sounds, sizeof( to->sounds ) );
+ memcpy( to->customFootsteps, from->customFootsteps, sizeof( to->customFootsteps ) );
+ memcpy( to->customMetalFootsteps, from->customMetalFootsteps, sizeof( to->customMetalFootsteps ) );
+}
+
+
+/*
+======================
+CG_GetCorpseNum
+======================
+*/
+static int CG_GetCorpseNum( pClass_t class )
+{
+ int i;
+ clientInfo_t *match;
+ char *modelName;
+ char *skinName;
+
+ modelName = BG_FindModelNameForClass( class );
+ skinName = BG_FindSkinNameForClass( class );
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ match = &cgs.corpseinfo[ i ];
+
+ if( !match->infoValid )
+ continue;
+
+ if( !Q_stricmp( modelName, match->modelName )
+ && !Q_stricmp( skinName, match->skinName ) )
+ {
+ // this clientinfo is identical, so use it's handles
+ return i;
+ }
+ }
+
+ //something has gone horribly wrong
+ return -1;
+}
+
+
+/*
+======================
+CG_ScanForExistingClientInfo
+======================
+*/
+static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci )
+{
+ int i;
+ clientInfo_t *match;
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ match = &cgs.corpseinfo[ i ];
+
+ if( !match->infoValid )
+ continue;
+
+ if( !Q_stricmp( ci->modelName, match->modelName ) &&
+ !Q_stricmp( ci->skinName, match->skinName ) )
+ {
+ // this clientinfo is identical, so use it's handles
+ CG_CopyClientInfoModel( match, ci );
+
+ return qtrue;
+ }
+ }
+
+ // shouldn't happen
+ return qfalse;
+}
+
+
+/*
+======================
+CG_PrecacheClientInfo
+======================
+*/
+void CG_PrecacheClientInfo( pClass_t class, char *model, char *skin )
+{
+ clientInfo_t *ci;
+ clientInfo_t newInfo;
+
+ ci = &cgs.corpseinfo[ class ];
+
+ // the old value
+ memset( &newInfo, 0, sizeof( newInfo ) );
+
+ // model
+ Q_strncpyz( newInfo.modelName, model, sizeof( newInfo.modelName ) );
+ Q_strncpyz( newInfo.headModelName, model, sizeof( newInfo.headModelName ) );
+
+ // modelName didn not include a skin name
+ if( !skin )
+ {
+ Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
+ Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) );
+ }
+ else
+ {
+ Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) );
+ Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) );
+ }
+
+ newInfo.infoValid = qtrue;
+
+ // actually register the models
+ *ci = newInfo;
+ CG_LoadClientInfo( ci );
+}
+
+
+/*
+======================
+CG_NewClientInfo
+======================
+*/
+void CG_NewClientInfo( int clientNum )
+{
+ clientInfo_t *ci;
+ clientInfo_t newInfo;
+ const char *configstring;
+ const char *v;
+ char *slash;
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ configstring = CG_ConfigString( clientNum + CS_PLAYERS );
+ if( !configstring[ 0 ] )
+ {
+ memset( ci, 0, sizeof( *ci ) );
+ return; // player just left
+ }
+
+ // the old value
+ memset( &newInfo, 0, sizeof( newInfo ) );
+
+ // isolate the player's name
+ v = Info_ValueForKey( configstring, "n" );
+ Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) );
+
+ // colors
+ v = Info_ValueForKey( configstring, "c1" );
+ CG_ColorFromString( v, newInfo.color1 );
+
+ v = Info_ValueForKey( configstring, "c2" );
+ CG_ColorFromString( v, newInfo.color2 );
+
+ // bot skill
+ v = Info_ValueForKey( configstring, "skill" );
+ newInfo.botSkill = atoi( v );
+
+ // handicap
+ v = Info_ValueForKey( configstring, "hc" );
+ newInfo.handicap = atoi( v );
+
+ // wins
+ v = Info_ValueForKey( configstring, "w" );
+ newInfo.wins = atoi( v );
+
+ // losses
+ v = Info_ValueForKey( configstring, "l" );
+ newInfo.losses = atoi( v );
+
+ // team
+ v = Info_ValueForKey( configstring, "t" );
+ newInfo.team = atoi( v );
+
+ // team task
+ v = Info_ValueForKey( configstring, "tt" );
+ newInfo.teamTask = atoi( v );
+
+ // team leader
+ v = Info_ValueForKey( configstring, "tl" );
+ newInfo.teamLeader = atoi( v );
+
+ // model
+ v = Info_ValueForKey( configstring, "model" );
+ Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) );
+
+ slash = strchr( newInfo.modelName, '/' );
+
+ if( !slash )
+ {
+ // modelName didn not include a skin name
+ Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) );
+ }
+ else
+ {
+ Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) );
+ // truncate modelName
+ *slash = 0;
+ }
+
+ //CG_Printf( "NCI: %s\n", v );
+
+ // head model
+ v = Info_ValueForKey( configstring, "hmodel" );
+ Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) );
+
+ slash = strchr( newInfo.headModelName, '/' );
+
+ if( !slash )
+ {
+ // modelName didn not include a skin name
+ Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) );
+ }
+ else
+ {
+ Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) );
+ // truncate modelName
+ *slash = 0;
+ }
+
+ // replace whatever was there with the new one
+ newInfo.infoValid = qtrue;
+ *ci = newInfo;
+
+ // scan for an existing clientinfo that matches this modelname
+ // so we can avoid loading checks if possible
+ if( !CG_ScanForExistingClientInfo( ci ) )
+ CG_LoadClientInfo( ci );
+}
+
+
+
+/*
+=============================================================================
+
+PLAYER ANIMATION
+
+=============================================================================
+*/
+
+
+/*
+===============
+CG_SetLerpFrameAnimation
+
+may include ANIM_TOGGLEBIT
+===============
+*/
+static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation )
+{
+ animation_t *anim;
+
+ lf->animationNumber = newAnimation;
+ newAnimation &= ~ANIM_TOGGLEBIT;
+
+ if( newAnimation < 0 || newAnimation >= MAX_PLAYER_TOTALANIMATIONS )
+ CG_Error( "Bad animation number: %i", newAnimation );
+
+ anim = &ci->animations[ newAnimation ];
+
+ lf->animation = anim;
+ lf->animationTime = lf->frameTime + anim->initialLerp;
+
+ if( cg_debugAnim.integer )
+ CG_Printf( "Anim: %i\n", newAnimation );
+}
+
+/*
+===============
+CG_RunPlayerLerpFrame
+
+Sets cg.snap, cg.oldFrame, and cg.backlerp
+cg.time should be between oldFrameTime and frameTime after exit
+===============
+*/
+static void CG_RunPlayerLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale )
+{
+ int f, numFrames;
+ animation_t *anim;
+
+ // debugging tool to get no animations
+ if( cg_animSpeed.integer == 0 )
+ {
+ lf->oldFrame = lf->frame = lf->backlerp = 0;
+ return;
+ }
+
+ // see if the animation sequence is switching
+ if( newAnimation != lf->animationNumber || !lf->animation )
+ {
+ CG_SetLerpFrameAnimation( ci, lf, newAnimation );
+ }
+
+ // if we have passed the current frame, move it to
+ // oldFrame and calculate a new frame
+ if( cg.time >= lf->frameTime )
+ {
+ lf->oldFrame = lf->frame;
+ lf->oldFrameTime = lf->frameTime;
+
+ // get the next frame based on the animation
+ anim = lf->animation;
+ if( !anim->frameLerp )
+ return; // shouldn't happen
+
+ if( cg.time < lf->animationTime )
+ lf->frameTime = lf->animationTime; // initial lerp
+ else
+ lf->frameTime = lf->oldFrameTime + anim->frameLerp;
+
+ f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
+ f *= speedScale; // adjust for haste, etc
+ numFrames = anim->numFrames;
+
+ if( anim->flipflop )
+ numFrames *= 2;
+
+ if( f >= numFrames )
+ {
+ f -= numFrames;
+ if( anim->loopFrames )
+ {
+ f %= anim->loopFrames;
+ f += anim->numFrames - anim->loopFrames;
+ }
+ else
+ {
+ f = numFrames - 1;
+ // the animation is stuck at the end, so it
+ // can immediately transition to another sequence
+ lf->frameTime = cg.time;
+ }
+ }
+
+ if( anim->reversed )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
+ else if( anim->flipflop && f>=anim->numFrames )
+ lf->frame = anim->firstFrame + anim->numFrames - 1 - ( f % anim->numFrames );
+ else
+ lf->frame = anim->firstFrame + f;
+
+ if( cg.time > lf->frameTime )
+ {
+ lf->frameTime = cg.time;
+
+ if( cg_debugAnim.integer )
+ CG_Printf( "Clamp lf->frameTime\n" );
+ }
+ }
+
+ if( lf->frameTime > cg.time + 200 )
+ lf->frameTime = cg.time;
+
+ if( lf->oldFrameTime > cg.time )
+ lf->oldFrameTime = cg.time;
+
+ // calculate current lerp value
+ if( lf->frameTime == lf->oldFrameTime )
+ lf->backlerp = 0;
+ else
+ lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
+}
+
+
+/*
+===============
+CG_ClearLerpFrame
+===============
+*/
+static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber )
+{
+ lf->frameTime = lf->oldFrameTime = cg.time;
+ CG_SetLerpFrameAnimation( ci, lf, animationNumber );
+ lf->oldFrame = lf->frame = lf->animation->firstFrame;
+}
+
+
+/*
+===============
+CG_PlayerAnimation
+===============
+*/
+static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp,
+ int *torsoOld, int *torso, float *torsoBackLerp )
+{
+ clientInfo_t *ci;
+ int clientNum;
+ float speedScale = 1.0f;
+
+ clientNum = cent->currentState.clientNum;
+
+ if( cg_noPlayerAnims.integer )
+ {
+ *legsOld = *legs = *torsoOld = *torso = 0;
+ return;
+ }
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ // do the shuffle turn frames locally
+ if( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE )
+ CG_RunPlayerLerpFrame( ci, &cent->pe.legs, LEGS_TURN, speedScale );
+ else
+ CG_RunPlayerLerpFrame( ci, &cent->pe.legs, cent->currentState.legsAnim, speedScale );
+
+ *legsOld = cent->pe.legs.oldFrame;
+ *legs = cent->pe.legs.frame;
+ *legsBackLerp = cent->pe.legs.backlerp;
+
+ CG_RunPlayerLerpFrame( ci, &cent->pe.torso, cent->currentState.torsoAnim, speedScale );
+
+ *torsoOld = cent->pe.torso.oldFrame;
+ *torso = cent->pe.torso.frame;
+ *torsoBackLerp = cent->pe.torso.backlerp;
+}
+
+
+/*
+===============
+CG_PlayerNonSegAnimation
+===============
+*/
+static void CG_PlayerNonSegAnimation( centity_t *cent, int *nonSegOld,
+ int *nonSeg, float *nonSegBackLerp )
+{
+ clientInfo_t *ci;
+ int clientNum;
+ float speedScale = 1.0f;
+
+ clientNum = cent->currentState.clientNum;
+
+ if( cg_noPlayerAnims.integer )
+ {
+ *nonSegOld = *nonSeg = 0;
+ return;
+ }
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ // do the shuffle turn frames locally
+ if( cent->pe.nonseg.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == NSPA_STAND )
+ CG_RunPlayerLerpFrame( ci, &cent->pe.nonseg, NSPA_TURN, speedScale );
+ else
+ CG_RunPlayerLerpFrame( ci, &cent->pe.nonseg, cent->currentState.legsAnim, speedScale );
+
+ *nonSegOld = cent->pe.nonseg.oldFrame;
+ *nonSeg = cent->pe.nonseg.frame;
+ *nonSegBackLerp = cent->pe.nonseg.backlerp;
+}
+
+/*
+=============================================================================
+
+PLAYER ANGLES
+
+=============================================================================
+*/
+
+/*
+==================
+CG_SwingAngles
+==================
+*/
+static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance,
+ float speed, float *angle, qboolean *swinging )
+{
+ float swing;
+ float move;
+ float scale;
+
+ if( !*swinging )
+ {
+ // see if a swing should be started
+ swing = AngleSubtract( *angle, destination );
+
+ if( swing > swingTolerance || swing < -swingTolerance )
+ *swinging = qtrue;
+ }
+
+ if( !*swinging )
+ return;
+
+ // modify the speed depending on the delta
+ // so it doesn't seem so linear
+ swing = AngleSubtract( destination, *angle );
+ scale = fabs( swing );
+
+ if( scale < swingTolerance * 0.5 )
+ scale = 0.5;
+ else if( scale < swingTolerance )
+ scale = 1.0;
+ else
+ scale = 2.0;
+
+ // swing towards the destination angle
+ if( swing >= 0 )
+ {
+ move = cg.frametime * scale * speed;
+
+ if( move >= swing )
+ {
+ move = swing;
+ *swinging = qfalse;
+ }
+ *angle = AngleMod( *angle + move );
+ }
+ else if( swing < 0 )
+ {
+ move = cg.frametime * scale * -speed;
+
+ if( move <= swing )
+ {
+ move = swing;
+ *swinging = qfalse;
+ }
+ *angle = AngleMod( *angle + move );
+ }
+
+ // clamp to no more than tolerance
+ swing = AngleSubtract( destination, *angle );
+ if( swing > clampTolerance )
+ *angle = AngleMod( destination - ( clampTolerance - 1 ) );
+ else if( swing < -clampTolerance )
+ *angle = AngleMod( destination + ( clampTolerance - 1 ) );
+}
+
+/*
+=================
+CG_AddPainTwitch
+=================
+*/
+static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles )
+{
+ int t;
+ float f;
+
+ t = cg.time - cent->pe.painTime;
+
+ if( t >= PAIN_TWITCH_TIME )
+ return;
+
+ f = 1.0 - (float)t / PAIN_TWITCH_TIME;
+
+ if( cent->pe.painDirection )
+ torsoAngles[ ROLL ] += 20 * f;
+ else
+ torsoAngles[ ROLL ] -= 20 * f;
+}
+
+
+/*
+===============
+CG_PlayerAngles
+
+Handles seperate torso motion
+
+ legs pivot based on direction of movement
+
+ head always looks exactly at cent->lerpAngles
+
+ if motion < 20 degrees, show in head only
+ if < 45 degrees, also show in torso
+===============
+*/
+static void CG_PlayerAngles( centity_t *cent, vec3_t srcAngles,
+ vec3_t legs[ 3 ], vec3_t torso[ 3 ], vec3_t head[ 3 ] )
+{
+ vec3_t legsAngles, torsoAngles, headAngles;
+ float dest;
+ static int movementOffsets[ 8 ] = { 0, 22, 45, -22, 0, 22, -45, -22 };
+ vec3_t velocity;
+ float speed;
+ int dir, clientNum;
+ clientInfo_t *ci;
+
+ VectorCopy( srcAngles, headAngles );
+ headAngles[ YAW ] = AngleMod( headAngles[ YAW ] );
+ VectorClear( legsAngles );
+ VectorClear( torsoAngles );
+
+ // --------- yaw -------------
+
+ // allow yaw to drift a bit
+ if( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE ||
+ ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND )
+ {
+ // if not standing still, always point all in the same direction
+ cent->pe.torso.yawing = qtrue; // always center
+ cent->pe.torso.pitching = qtrue; // always center
+ cent->pe.legs.yawing = qtrue; // always center
+ }
+
+ // adjust legs for movement dir
+ if( cent->currentState.eFlags & EF_DEAD )
+ {
+ // don't let dead bodies twitch
+ dir = 0;
+ }
+ else
+ {
+ // did use angles2.. now uses time2.. looks a bit funny but time2 isn't used othwise
+ dir = cent->currentState.time2;
+ if( dir < 0 || dir > 7 )
+ CG_Error( "Bad player movement angle" );
+ }
+
+ legsAngles[ YAW ] = headAngles[ YAW ] + movementOffsets[ dir ];
+ torsoAngles[ YAW ] = headAngles[ YAW ] + 0.25 * movementOffsets[ dir ];
+
+ // torso
+ if( cent->currentState.eFlags & EF_DEAD )
+ {
+ CG_SwingAngles( torsoAngles[ YAW ], 0, 0, cg_swingSpeed.value,
+ &cent->pe.torso.yawAngle, &cent->pe.torso.yawing );
+ CG_SwingAngles( legsAngles[ YAW ], 0, 0, cg_swingSpeed.value,
+ &cent->pe.legs.yawAngle, &cent->pe.legs.yawing );
+ }
+ else
+ {
+ CG_SwingAngles( torsoAngles[ YAW ], 25, 90, cg_swingSpeed.value,
+ &cent->pe.torso.yawAngle, &cent->pe.torso.yawing );
+ CG_SwingAngles( legsAngles[ YAW ], 40, 90, cg_swingSpeed.value,
+ &cent->pe.legs.yawAngle, &cent->pe.legs.yawing );
+ }
+
+ torsoAngles[ YAW ] = cent->pe.torso.yawAngle;
+ legsAngles[ YAW ] = cent->pe.legs.yawAngle;
+
+ // --------- pitch -------------
+
+ // only show a fraction of the pitch angle in the torso
+ if( headAngles[ PITCH ] > 180 )
+ dest = ( -360 + headAngles[ PITCH ] ) * 0.75f;
+ else
+ dest = headAngles[ PITCH ] * 0.75f;
+
+ CG_SwingAngles( dest, 15, 30, 0.1f, &cent->pe.torso.pitchAngle, &cent->pe.torso.pitching );
+ torsoAngles[ PITCH ] = cent->pe.torso.pitchAngle;
+
+ //
+ clientNum = cent->currentState.clientNum;
+
+ if( clientNum >= 0 && clientNum < MAX_CLIENTS )
+ {
+ ci = &cgs.clientinfo[ clientNum ];
+ if( ci->fixedtorso )
+ torsoAngles[ PITCH ] = 0.0f;
+ }
+
+ // --------- roll -------------
+
+
+ // lean towards the direction of travel
+ VectorCopy( cent->currentState.pos.trDelta, velocity );
+ speed = VectorNormalize( velocity );
+
+ if( speed )
+ {
+ vec3_t axis[ 3 ];
+ float side;
+
+ speed *= 0.05f;
+
+ AnglesToAxis( legsAngles, axis );
+ side = speed * DotProduct( velocity, axis[ 1 ] );
+ legsAngles[ ROLL ] -= side;
+
+ side = speed * DotProduct( velocity, axis[ 0 ] );
+ legsAngles[ PITCH ] += side;
+ }
+
+ //
+ clientNum = cent->currentState.clientNum;
+
+ if( clientNum >= 0 && clientNum < MAX_CLIENTS )
+ {
+ ci = &cgs.clientinfo[ clientNum ];
+
+ if( ci->fixedlegs )
+ {
+ legsAngles[ YAW ] = torsoAngles[ YAW ];
+ legsAngles[ PITCH ] = 0.0f;
+ legsAngles[ ROLL ] = 0.0f;
+ }
+ }
+
+ // pain twitch
+ CG_AddPainTwitch( cent, torsoAngles );
+
+ // pull the angles back out of the hierarchial chain
+ AnglesSubtract( headAngles, torsoAngles, headAngles );
+ AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
+ AnglesToAxis( legsAngles, legs );
+ AnglesToAxis( torsoAngles, torso );
+ AnglesToAxis( headAngles, head );
+}
+
+#define MODEL_WWSMOOTHTIME 200
+
+/*
+===============
+CG_PlayerWWSmoothing
+
+Smooth the angles of transitioning wall walkers
+===============
+*/
+static void CG_PlayerWWSmoothing( centity_t *cent, vec3_t in[ 3 ], vec3_t out[ 3 ] )
+{
+ entityState_t *es = &cent->currentState;
+ int i;
+ vec3_t surfNormal, rotAxis, temp;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f };
+ float stLocal, sFraction, rotAngle;
+ vec3_t inAxis[ 3 ], lastAxis[ 3 ], outAxis[ 3 ];
+
+ //set surfNormal
+ if( !(es->eFlags & EF_WALLCLIMB ) )
+ VectorCopy( refNormal, surfNormal );
+ else if( !( es->eFlags & EF_WALLCLIMBCEILING ) )
+ VectorCopy( es->angles2, surfNormal );
+ else
+ VectorCopy( ceilingNormal, surfNormal );
+
+ AxisCopy( in, inAxis );
+
+ if( !VectorCompare( surfNormal, cent->pe.lastNormal ) )
+ {
+ //if we moving from the ceiling to the floor special case
+ //( x product of colinear vectors is undefined)
+ if( VectorCompare( ceilingNormal, cent->pe.lastNormal ) &&
+ VectorCompare( refNormal, surfNormal ) )
+ {
+ VectorCopy( in[ 1 ], rotAxis );
+ rotAngle = 180.0f;
+ }
+ else
+ {
+ AxisCopy( cent->pe.lastAxis, lastAxis );
+ rotAngle = DotProduct( inAxis[ 0 ], lastAxis[ 0 ] ) +
+ DotProduct( inAxis[ 1 ], lastAxis[ 1 ] ) +
+ DotProduct( inAxis[ 2 ], lastAxis[ 2 ] );
+
+ rotAngle = RAD2DEG( acos( ( rotAngle - 1.0f ) / 2.0f ) );
+
+ CrossProduct( lastAxis[ 0 ], inAxis[ 0 ], temp );
+ VectorCopy( temp, rotAxis );
+ CrossProduct( lastAxis[ 1 ], inAxis[ 1 ], temp );
+ VectorAdd( rotAxis, temp, rotAxis );
+ CrossProduct( lastAxis[ 2 ], inAxis[ 2 ], temp );
+ VectorAdd( rotAxis, temp, rotAxis );
+
+ VectorNormalize( rotAxis );
+ }
+
+ //iterate through smooth array
+ for( i = 0; i < MAXSMOOTHS; i++ )
+ {
+ //found an unused index in the smooth array
+ if( cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME < cg.time )
+ {
+ //copy to array and stop
+ VectorCopy( rotAxis, cent->pe.sList[ i ].rotAxis );
+ cent->pe.sList[ i ].rotAngle = rotAngle;
+ cent->pe.sList[ i ].time = cg.time;
+ break;
+ }
+ }
+ }
+
+ //iterate through ops
+ for( i = MAXSMOOTHS - 1; i >= 0; i-- )
+ {
+ //if this op has time remaining, perform it
+ if( cg.time < cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME )
+ {
+ stLocal = 1.0f - ( ( ( cent->pe.sList[ i ].time + MODEL_WWSMOOTHTIME ) - cg.time ) / MODEL_WWSMOOTHTIME );
+ sFraction = -( cos( stLocal * M_PI ) + 1.0f ) / 2.0f;
+
+ RotatePointAroundVector( outAxis[ 0 ], cent->pe.sList[ i ].rotAxis,
+ inAxis[ 0 ], sFraction * cent->pe.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 1 ], cent->pe.sList[ i ].rotAxis,
+ inAxis[ 1 ], sFraction * cent->pe.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 2 ], cent->pe.sList[ i ].rotAxis,
+ inAxis[ 2 ], sFraction * cent->pe.sList[ i ].rotAngle );
+
+ AxisCopy( outAxis, inAxis );
+ }
+ }
+
+ //outAxis has been copied to inAxis
+ AxisCopy( inAxis, out );
+}
+
+/*
+===============
+CG_PlayerNonSegAngles
+
+Resolve angles for non-segmented models
+===============
+*/
+static void CG_PlayerNonSegAngles( centity_t *cent, vec3_t srcAngles, vec3_t nonSegAxis[ 3 ] )
+{
+ vec3_t localAngles;
+ vec3_t velocity;
+ float speed;
+ int dir;
+ entityState_t *es = &cent->currentState;
+ vec3_t surfNormal;
+ vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f };
+
+ VectorCopy( srcAngles, localAngles );
+ localAngles[ YAW ] = AngleMod( localAngles[ YAW ] );
+ localAngles[ PITCH ] = 0.0f;
+ localAngles[ ROLL ] = 0.0f;
+
+ //set surfNormal
+ if( !( es->eFlags & EF_WALLCLIMBCEILING ) )
+ VectorCopy( es->angles2, surfNormal );
+ else
+ VectorCopy( ceilingNormal, surfNormal );
+
+ //make sure that WW transitions don't cause the swing stuff to go nuts
+ if( !VectorCompare( surfNormal, cent->pe.lastNormal ) )
+ {
+ //stop CG_SwingAngles having an eppy
+ cent->pe.nonseg.yawAngle = localAngles[ YAW ];
+ cent->pe.nonseg.yawing = qfalse;
+ }
+
+ // --------- yaw -------------
+
+ // allow yaw to drift a bit
+ if( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != NSPA_STAND )
+ {
+ // if not standing still, always point all in the same direction
+ cent->pe.nonseg.yawing = qtrue; // always center
+ }
+
+ // adjust legs for movement dir
+ if( cent->currentState.eFlags & EF_DEAD )
+ {
+ // don't let dead bodies twitch
+ dir = 0;
+ }
+ else
+ {
+ // did use angles2.. now uses time2.. looks a bit funny but time2 isn't used othwise
+ dir = cent->currentState.time2;
+ if( dir < 0 || dir > 7 )
+ CG_Error( "Bad player movement angle" );
+ }
+
+ // torso
+ if( cent->currentState.eFlags & EF_DEAD )
+ {
+ CG_SwingAngles( localAngles[ YAW ], 0, 0, cg_swingSpeed.value,
+ &cent->pe.nonseg.yawAngle, &cent->pe.nonseg.yawing );
+ }
+ else
+ {
+ CG_SwingAngles( localAngles[ YAW ], 40, 90, cg_swingSpeed.value,
+ &cent->pe.nonseg.yawAngle, &cent->pe.nonseg.yawing );
+ }
+
+ localAngles[ YAW ] = cent->pe.nonseg.yawAngle;
+
+ // --------- pitch -------------
+
+ //NO PITCH!
+
+
+ // --------- roll -------------
+
+
+ // lean towards the direction of travel
+ VectorCopy( cent->currentState.pos.trDelta, velocity );
+ speed = VectorNormalize( velocity );
+
+ if( speed )
+ {
+ vec3_t axis[ 3 ];
+ float side;
+
+ //much less than with the regular model system
+ speed *= 0.01f;
+
+ AnglesToAxis( localAngles, axis );
+ side = speed * DotProduct( velocity, axis[ 1 ] );
+ localAngles[ ROLL ] -= side;
+
+ side = speed * DotProduct( velocity, axis[ 0 ] );
+ localAngles[ PITCH ] += side;
+ }
+
+ //FIXME: PAIN[123] animations?
+ // pain twitch
+ //CG_AddPainTwitch( cent, torsoAngles );
+
+ AnglesToAxis( localAngles, nonSegAxis );
+}
+
+
+//==========================================================================
+
+/*
+===============
+CG_PlayerUpgrade
+===============
+*/
+static void CG_PlayerUpgrades( centity_t *cent, refEntity_t *torso )
+{
+ int held, active;
+ refEntity_t jetpack;
+ refEntity_t battpack;
+ refEntity_t flash;
+ entityState_t *es = &cent->currentState;
+
+ held = es->modelindex;
+ active = es->modelindex2;
+
+ if( held & ( 1 << UP_JETPACK ) )
+ {
+ memset( &jetpack, 0, sizeof( jetpack ) );
+ VectorCopy( torso->lightingOrigin, jetpack.lightingOrigin );
+ jetpack.shadowPlane = torso->shadowPlane;
+ jetpack.renderfx = torso->renderfx;
+
+ jetpack.hModel = cgs.media.jetpackModel;
+
+ //identity matrix
+ AxisCopy( axisDefault, jetpack.axis );
+
+ //FIXME: change to tag_back when it exists
+ CG_PositionRotatedEntityOnTag( &jetpack, torso, torso->hModel, "tag_head" );
+
+ trap_R_AddRefEntityToScene( &jetpack );
+
+ if( active & ( 1 << UP_JETPACK ) )
+ {
+ if( es->pos.trDelta[ 2 ] > 10.0f )
+ {
+ if( cent->jetPackState != JPS_ASCENDING )
+ {
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+
+ cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackAscendPS );
+ cent->jetPackState = JPS_ASCENDING;
+ }
+
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin,
+ vec3_origin, cgs.media.jetpackAscendSound );
+ }
+ else if( es->pos.trDelta[ 2 ] < -10.0f )
+ {
+ if( cent->jetPackState != JPS_DESCENDING )
+ {
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+
+ cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackDescendPS );
+ cent->jetPackState = JPS_DESCENDING;
+ }
+
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin,
+ vec3_origin, cgs.media.jetpackDescendSound );
+ }
+ else
+ {
+ if( cent->jetPackState != JPS_HOVERING )
+ {
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+
+ cent->jetPackPS = CG_SpawnNewParticleSystem( cgs.media.jetPackHoverPS );
+ cent->jetPackState = JPS_HOVERING;
+ }
+
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin,
+ vec3_origin, cgs.media.jetpackIdleSound );
+ }
+
+ memset( &flash, 0, sizeof( flash ) );
+ VectorCopy( torso->lightingOrigin, flash.lightingOrigin );
+ flash.shadowPlane = torso->shadowPlane;
+ flash.renderfx = torso->renderfx;
+
+ flash.hModel = cgs.media.jetpackFlashModel;
+ if( !flash.hModel )
+ return;
+
+ AxisCopy( axisDefault, flash.axis );
+
+ CG_PositionRotatedEntityOnTag( &flash, &jetpack, jetpack.hModel, "tag_flash" );
+ trap_R_AddRefEntityToScene( &flash );
+
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ {
+ CG_SetAttachmentTag( &cent->jetPackPS->attachment,
+ jetpack, jetpack.hModel, "tag_flash" );
+ CG_SetAttachmentCent( &cent->jetPackPS->attachment, cent );
+ CG_AttachToTag( &cent->jetPackPS->attachment );
+ }
+ }
+ else if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ {
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+ cent->jetPackState = JPS_OFF;
+ }
+ }
+ else if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ {
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+ cent->jetPackState = JPS_OFF;
+ }
+
+ if( held & ( 1 << UP_BATTPACK ) )
+ {
+ memset( &battpack, 0, sizeof( battpack ) );
+ VectorCopy( torso->lightingOrigin, battpack.lightingOrigin );
+ battpack.shadowPlane = torso->shadowPlane;
+ battpack.renderfx = torso->renderfx;
+
+ battpack.hModel = cgs.media.battpackModel;
+
+ //identity matrix
+ AxisCopy( axisDefault, battpack.axis );
+
+ //FIXME: change to tag_back when it exists
+ CG_PositionRotatedEntityOnTag( &battpack, torso, torso->hModel, "tag_head" );
+
+ trap_R_AddRefEntityToScene( &battpack );
+ }
+
+ if( es->eFlags & EF_BLOBLOCKED )
+ {
+ vec3_t temp, origin, up = { 0.0f, 0.0f, 1.0f };
+ trace_t tr;
+ float size;
+
+ VectorCopy( es->pos.trBase, temp );
+ temp[ 2 ] -= 4096.0f;
+
+ CG_Trace( &tr, es->pos.trBase, NULL, NULL, temp, es->number, MASK_SOLID );
+ VectorCopy( tr.endpos, origin );
+
+ size = 32.0f;
+
+ if( size > 0.0f )
+ CG_ImpactMark( cgs.media.creepShader, origin, up,
+ 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, size, qtrue );
+ }
+}
+
+
+/*
+===============
+CG_PlayerFloatSprite
+
+Float a sprite over the player's head
+===============
+*/
+static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader )
+{
+ int rf;
+ refEntity_t ent;
+
+ if( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson )
+ rf = RF_THIRD_PERSON; // only show in mirrors
+ else
+ rf = 0;
+
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( cent->lerpOrigin, ent.origin );
+ ent.origin[ 2 ] += 48;
+ ent.reType = RT_SPRITE;
+ ent.customShader = shader;
+ ent.radius = 10;
+ ent.renderfx = rf;
+ ent.shaderRGBA[ 0 ] = 255;
+ ent.shaderRGBA[ 1 ] = 255;
+ ent.shaderRGBA[ 2 ] = 255;
+ ent.shaderRGBA[ 3 ] = 255;
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+
+
+/*
+===============
+CG_PlayerSprites
+
+Float sprites over the player's head
+===============
+*/
+static void CG_PlayerSprites( centity_t *cent )
+{
+ if( cent->currentState.eFlags & EF_CONNECTION )
+ {
+ CG_PlayerFloatSprite( cent, cgs.media.connectionShader );
+ return;
+ }
+
+ if( cent->currentState.eFlags & EF_TALK )
+ {
+ // the masses have decreed this to be wrong
+/* CG_PlayerFloatSprite( cent, cgs.media.balloonShader );
+ return;*/
+ }
+}
+
+/*
+===============
+CG_PlayerShadow
+
+Returns the Z component of the surface being shadowed
+
+ should it return a full plane instead of a Z?
+===============
+*/
+#define SHADOW_DISTANCE 128
+static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane, pClass_t class )
+{
+ vec3_t end, mins, maxs;
+ trace_t trace;
+ float alpha;
+ entityState_t *es = &cent->currentState;
+ vec3_t surfNormal = { 0.0f, 0.0f, 1.0f };
+
+ BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL );
+ mins[ 2 ] = 0.0f;
+ maxs[ 2 ] = 2.0f;
+
+ if( es->eFlags & EF_WALLCLIMB )
+ {
+ if( es->eFlags & EF_WALLCLIMBCEILING )
+ VectorSet( surfNormal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( es->angles2, surfNormal );
+ }
+
+ *shadowPlane = 0;
+
+ if( cg_shadows.integer == 0 )
+ return qfalse;
+
+ // send a trace down from the player to the ground
+ VectorCopy( cent->lerpOrigin, end );
+ VectorMA( cent->lerpOrigin, -SHADOW_DISTANCE, surfNormal, end );
+
+ trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID );
+
+ // no shadow if too high
+ if( trace.fraction == 1.0 || trace.startsolid || trace.allsolid )
+ return qfalse;
+
+ // FIXME: stencil shadows will be broken for walls.
+ // Unfortunately there isn't much that can be
+ // done since Q3 references only the Z coord
+ // of the shadowPlane
+ if( surfNormal[ 2 ] < 0.0f )
+ *shadowPlane = trace.endpos[ 2 ] - 1.0f;
+ else
+ *shadowPlane = trace.endpos[ 2 ] + 1.0f;
+
+ if( cg_shadows.integer != 1 ) // no mark for stencil or projection shadows
+ return qtrue;
+
+ // fade the shadow out with height
+ alpha = 1.0 - trace.fraction;
+
+ // add the mark as a temporary, so it goes directly to the renderer
+ // without taking a spot in the cg_marks array
+ CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal,
+ cent->pe.legs.yawAngle, 0.0f, 0.0f, 0.0f, alpha, qfalse,
+ 24.0f * BG_FindShadowScaleForClass( class ), qtrue );
+
+ return qtrue;
+}
+
+
+/*
+===============
+CG_PlayerSplash
+
+Draw a mark at the water surface
+===============
+*/
+static void CG_PlayerSplash( centity_t *cent, pClass_t class )
+{
+ vec3_t start, end;
+ vec3_t mins, maxs;
+ trace_t trace;
+ int contents;
+
+ if( !cg_shadows.integer )
+ return;
+
+ BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL );
+
+ VectorCopy( cent->lerpOrigin, end );
+ end[ 2 ] += mins[ 2 ];
+
+ // if the feet aren't in liquid, don't make a mark
+ // this won't handle moving water brushes, but they wouldn't draw right anyway...
+ contents = trap_CM_PointContents( end, 0 );
+
+ if( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) )
+ return;
+
+ VectorCopy( cent->lerpOrigin, start );
+ start[ 2 ] += 32;
+
+ // if the head isn't out of liquid, don't make a mark
+ contents = trap_CM_PointContents( start, 0 );
+
+ if( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) )
+ return;
+
+ // trace down to find the surface
+ trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0,
+ ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) );
+
+ if( trace.fraction == 1.0f )
+ return;
+
+ CG_ImpactMark( cgs.media.wakeMarkShader, trace.endpos, trace.plane.normal,
+ cent->pe.legs.yawAngle, 1.0f, 1.0f, 1.0f, 1.0f, qfalse,
+ 32.0f * BG_FindShadowScaleForClass( class ), qtrue );
+}
+
+
+/*
+=================
+CG_LightVerts
+=================
+*/
+int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts )
+{
+ int i, j;
+ float incoming;
+ vec3_t ambientLight;
+ vec3_t lightDir;
+ vec3_t directedLight;
+
+ trap_R_LightForPoint( verts[ 0 ].xyz, ambientLight, directedLight, lightDir );
+
+ for( i = 0; i < numVerts; i++ )
+ {
+ incoming = DotProduct( normal, lightDir );
+
+ if( incoming <= 0 )
+ {
+ verts[ i ].modulate[ 0 ] = ambientLight[ 0 ];
+ verts[ i ].modulate[ 1 ] = ambientLight[ 1 ];
+ verts[ i ].modulate[ 2 ] = ambientLight[ 2 ];
+ verts[ i ].modulate[ 3 ] = 255;
+ continue;
+ }
+
+ j = ( ambientLight[ 0 ] + incoming * directedLight[ 0 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ verts[ i ].modulate[ 0 ] = j;
+
+ j = ( ambientLight[ 1 ] + incoming * directedLight[ 1 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ verts[ i ].modulate[ 1 ] = j;
+
+ j = ( ambientLight[ 2 ] + incoming * directedLight[ 2 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ verts[ i ].modulate[ 2 ] = j;
+
+ verts[ i ].modulate[ 3 ] = 255;
+ }
+ return qtrue;
+}
+
+
+/*
+=================
+CG_LightFromDirection
+=================
+*/
+int CG_LightFromDirection( vec3_t point, vec3_t direction )
+{
+ int j;
+ float incoming;
+ vec3_t ambientLight;
+ vec3_t lightDir;
+ vec3_t directedLight;
+ vec3_t result;
+
+ trap_R_LightForPoint( point, ambientLight, directedLight, lightDir );
+
+ incoming = DotProduct( direction, lightDir );
+
+ if( incoming <= 0 )
+ {
+ result[ 0 ] = ambientLight[ 0 ];
+ result[ 1 ] = ambientLight[ 1 ];
+ result[ 2 ] = ambientLight[ 2 ];
+ return (int)( (float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f );
+ }
+
+ j = ( ambientLight[ 0 ] + incoming * directedLight[ 0 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ result[ 0 ] = j;
+
+ j = ( ambientLight[ 1 ] + incoming * directedLight[ 1 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ result[ 1 ] = j;
+
+ j = ( ambientLight[ 2 ] + incoming * directedLight[ 2 ] );
+
+ if( j > 255 )
+ j = 255;
+
+ result[ 2 ] = j;
+
+ return (int)((float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f );
+}
+
+
+/*
+=================
+CG_AmbientLight
+=================
+*/
+int CG_AmbientLight( vec3_t point )
+{
+ vec3_t ambientLight;
+ vec3_t lightDir;
+ vec3_t directedLight;
+ vec3_t result;
+
+ trap_R_LightForPoint( point, ambientLight, directedLight, lightDir );
+
+ result[ 0 ] = ambientLight[ 0 ];
+ result[ 1 ] = ambientLight[ 1 ];
+ result[ 2 ] = ambientLight[ 2 ];
+ return (int)((float)( result[ 0 ] + result[ 1 ] + result[ 2 ] ) / 3.0f );
+}
+
+#define TRACE_DEPTH 32.0f
+
+/*
+===============
+CG_Player
+===============
+*/
+void CG_Player( centity_t *cent )
+{
+ clientInfo_t *ci;
+
+ // NOTE: legs is used for nonsegmented models
+ // this helps reduce code to be changed
+ refEntity_t legs;
+ refEntity_t torso;
+ refEntity_t head;
+ int clientNum;
+ int renderfx;
+ qboolean shadow = qfalse;
+ float shadowPlane;
+ entityState_t *es = &cent->currentState;
+ pClass_t class = ( es->powerups >> 8 ) & 0xFF;
+ float scale;
+ vec3_t tempAxis[ 3 ], tempAxis2[ 3 ];
+ vec3_t angles;
+ int held = es->modelindex;
+ vec3_t surfNormal = { 0.0f, 0.0f, 1.0f };
+
+ // the client number is stored in clientNum. It can't be derived
+ // from the entity number, because a single client may have
+ // multiple corpses on the level using the same clientinfo
+ clientNum = es->clientNum;
+ if( clientNum < 0 || clientNum >= MAX_CLIENTS )
+ CG_Error( "Bad clientNum on player entity" );
+
+ ci = &cgs.clientinfo[ clientNum ];
+
+ // it is possible to see corpses from disconnected players that may
+ // not have valid clientinfo
+ if( !ci->infoValid )
+ return;
+
+ //don't draw
+ if( es->eFlags & EF_NODRAW )
+ return;
+
+ // get the player model information
+ renderfx = 0;
+ if( es->number == cg.snap->ps.clientNum )
+ {
+ if( !cg.renderingThirdPerson )
+ renderfx = RF_THIRD_PERSON; // only draw in mirrors
+ else if( cg_cameraMode.integer )
+ return;
+ }
+
+ if( cg_drawBBOX.integer )
+ {
+ vec3_t mins, maxs;
+
+ BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL );
+ CG_DrawBoundingBox( cent->lerpOrigin, mins, maxs );
+ }
+
+ memset( &legs, 0, sizeof( legs ) );
+ memset( &torso, 0, sizeof( torso ) );
+ memset( &head, 0, sizeof( head ) );
+
+ VectorCopy( cent->lerpAngles, angles );
+ AnglesToAxis( cent->lerpAngles, tempAxis );
+
+ //rotate lerpAngles to floor
+ if( es->eFlags & EF_WALLCLIMB &&
+ BG_RotateAxis( es->angles2, tempAxis, tempAxis2, qtrue, es->eFlags & EF_WALLCLIMBCEILING ) )
+ AxisToAngles( tempAxis2, angles );
+ else
+ VectorCopy( cent->lerpAngles, angles );
+
+ //normalise the pitch
+ if( angles[ PITCH ] < -180.0f )
+ angles[ PITCH ] += 360.0f;
+
+ // get the rotation information
+ if( !ci->nonsegmented )
+ CG_PlayerAngles( cent, angles, legs.axis, torso.axis, head.axis );
+ else
+ CG_PlayerNonSegAngles( cent, angles, legs.axis );
+
+ AxisCopy( legs.axis, tempAxis );
+
+ //rotate the legs axis to back to the wall
+ if( es->eFlags & EF_WALLCLIMB &&
+ BG_RotateAxis( es->angles2, legs.axis, tempAxis, qfalse, es->eFlags & EF_WALLCLIMBCEILING ) )
+ AxisCopy( tempAxis, legs.axis );
+
+ //smooth out WW transitions so the model doesn't hop around
+ CG_PlayerWWSmoothing( cent, legs.axis, legs.axis );
+
+ AxisCopy( tempAxis, cent->pe.lastAxis );
+
+ // get the animation state (after rotation, to allow feet shuffle)
+ if( !ci->nonsegmented )
+ CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp,
+ &torso.oldframe, &torso.frame, &torso.backlerp );
+ else
+ CG_PlayerNonSegAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp );
+
+ // add the talk baloon or disconnect icon
+ CG_PlayerSprites( cent );
+
+ // add the shadow
+ if( ( es->number == cg.snap->ps.clientNum && cg.renderingThirdPerson ) ||
+ es->number != cg.snap->ps.clientNum )
+ shadow = CG_PlayerShadow( cent, &shadowPlane, class );
+
+ // add a water splash if partially in and out of water
+ CG_PlayerSplash( cent, class );
+
+ if( cg_shadows.integer == 3 && shadow )
+ renderfx |= RF_SHADOW_PLANE;
+
+ renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
+
+ //
+ // add the legs
+ //
+ if( !ci->nonsegmented )
+ {
+ legs.hModel = ci->legsModel;
+
+ if( held & ( 1 << UP_LIGHTARMOUR ) )
+ legs.customSkin = cgs.media.larmourLegsSkin;
+ else
+ legs.customSkin = ci->legsSkin;
+ }
+ else
+ {
+ legs.hModel = ci->nonSegModel;
+ legs.customSkin = ci->nonSegSkin;
+ }
+
+ VectorCopy( cent->lerpOrigin, legs.origin );
+
+ VectorCopy( cent->lerpOrigin, legs.lightingOrigin );
+ legs.shadowPlane = shadowPlane;
+ legs.renderfx = renderfx;
+ VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all
+
+ //move the origin closer into the wall with a CapTrace
+ if( es->eFlags & EF_WALLCLIMB && !( es->eFlags & EF_DEAD ) && !( cg.intermissionStarted ) )
+ {
+ vec3_t start, end, mins, maxs;
+ trace_t tr;
+
+ if( es->eFlags & EF_WALLCLIMBCEILING )
+ VectorSet( surfNormal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( es->angles2, surfNormal );
+
+ BG_FindBBoxForClass( class, mins, maxs, NULL, NULL, NULL );
+
+ VectorMA( legs.origin, -TRACE_DEPTH, surfNormal, end );
+ VectorMA( legs.origin, 1.0f, surfNormal, start );
+ CG_CapTrace( &tr, start, mins, maxs, end, es->number, MASK_PLAYERSOLID );
+
+ //if the trace misses completely then just use legs.origin
+ //apparently capsule traces are "smaller" than box traces
+ if( tr.fraction != 1.0f )
+ VectorMA( legs.origin, tr.fraction * -TRACE_DEPTH, surfNormal, legs.origin );
+
+ VectorCopy( legs.origin, legs.lightingOrigin );
+ VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all
+ }
+
+ //rescale the model
+ scale = BG_FindModelScaleForClass( class );
+
+ if( scale != 1.0f )
+ {
+ VectorScale( legs.axis[ 0 ], scale, legs.axis[ 0 ] );
+ VectorScale( legs.axis[ 1 ], scale, legs.axis[ 1 ] );
+ VectorScale( legs.axis[ 2 ], scale, legs.axis[ 2 ] );
+
+ legs.nonNormalizedAxes = qtrue;
+ }
+
+ //offset on the Z axis if required
+ VectorMA( legs.origin, BG_FindZOffsetForClass( class ), surfNormal, legs.origin );
+ VectorCopy( legs.origin, legs.lightingOrigin );
+ VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all
+
+ trap_R_AddRefEntityToScene( &legs );
+
+ // if the model failed, allow the default nullmodel to be displayed
+ if( !legs.hModel )
+ return;
+
+ if( !ci->nonsegmented )
+ {
+ //
+ // add the torso
+ //
+ torso.hModel = ci->torsoModel;
+
+ if( held & ( 1 << UP_LIGHTARMOUR ) )
+ torso.customSkin = cgs.media.larmourTorsoSkin;
+ else
+ torso.customSkin = ci->torsoSkin;
+
+ if( !torso.hModel )
+ return;
+
+ VectorCopy( cent->lerpOrigin, torso.lightingOrigin );
+
+ CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" );
+
+ torso.shadowPlane = shadowPlane;
+ torso.renderfx = renderfx;
+
+ trap_R_AddRefEntityToScene( &torso );
+
+ //
+ // add the head
+ //
+ head.hModel = ci->headModel;
+
+ if( held & ( 1 << UP_HELMET ) )
+ head.customSkin = cgs.media.larmourHeadSkin;
+ else
+ head.customSkin = ci->headSkin;
+
+ if( !head.hModel )
+ return;
+
+ VectorCopy( cent->lerpOrigin, head.lightingOrigin );
+
+ CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head" );
+
+ head.shadowPlane = shadowPlane;
+ head.renderfx = renderfx;
+
+ trap_R_AddRefEntityToScene( &head );
+ }
+
+ //
+ // add the gun / barrel / flash
+ //
+ if( es->weapon != WP_NONE )
+ {
+ if( !ci->nonsegmented )
+ CG_AddPlayerWeapon( &torso, NULL, cent );
+ else
+ CG_AddPlayerWeapon( &legs, NULL, cent );
+ }
+
+ CG_PlayerUpgrades( cent, &torso );
+
+ //sanity check that particle systems are stopped when dead
+ if( es->eFlags & EF_DEAD )
+ {
+ if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
+ CG_DestroyParticleSystem( &cent->muzzlePS );
+
+ if( CG_IsParticleSystemValid( &cent->jetPackPS ) )
+ CG_DestroyParticleSystem( &cent->jetPackPS );
+ }
+
+ VectorCopy( surfNormal, cent->pe.lastNormal );
+}
+
+/*
+===============
+CG_Corpse
+===============
+*/
+void CG_Corpse( centity_t *cent )
+{
+ clientInfo_t *ci;
+ refEntity_t legs;
+ refEntity_t torso;
+ refEntity_t head;
+ entityState_t *es = &cent->currentState;
+ int corpseNum;
+ int renderfx;
+ qboolean shadow = qfalse;
+ float shadowPlane;
+ vec3_t origin, liveZ, deadZ;
+ float scale;
+
+ corpseNum = CG_GetCorpseNum( es->clientNum );
+
+ if( corpseNum < 0 || corpseNum >= MAX_CLIENTS )
+ CG_Error( "Bad corpseNum on corpse entity: %d", corpseNum );
+
+ ci = &cgs.corpseinfo[ corpseNum ];
+
+ // it is possible to see corpses from disconnected players that may
+ // not have valid clientinfo
+ if( !ci->infoValid )
+ return;
+
+ memset( &legs, 0, sizeof( legs ) );
+ memset( &torso, 0, sizeof( torso ) );
+ memset( &head, 0, sizeof( head ) );
+
+ VectorCopy( cent->lerpOrigin, origin );
+ BG_FindBBoxForClass( es->clientNum, liveZ, NULL, NULL, deadZ, NULL );
+ origin[ 2 ] -= ( liveZ[ 2 ] - deadZ[ 2 ] );
+
+ VectorCopy( es->angles, cent->lerpAngles );
+
+ // get the rotation information
+ if( !ci->nonsegmented )
+ CG_PlayerAngles( cent, cent->lerpAngles, legs.axis, torso.axis, head.axis );
+ else
+ CG_PlayerNonSegAngles( cent, cent->lerpAngles, legs.axis );
+
+ //set the correct frame (should always be dead)
+ if( cg_noPlayerAnims.integer )
+ legs.oldframe = legs.frame = torso.oldframe = torso.frame = 0;
+ else if( !ci->nonsegmented )
+ {
+ memset( &cent->pe.legs, 0, sizeof( lerpFrame_t ) );
+ CG_RunPlayerLerpFrame( ci, &cent->pe.legs, es->legsAnim, 1 );
+ legs.oldframe = cent->pe.legs.oldFrame;
+ legs.frame = cent->pe.legs.frame;
+ legs.backlerp = cent->pe.legs.backlerp;
+
+ memset( &cent->pe.torso, 0, sizeof( lerpFrame_t ) );
+ CG_RunPlayerLerpFrame( ci, &cent->pe.torso, es->torsoAnim, 1 );
+ torso.oldframe = cent->pe.torso.oldFrame;
+ torso.frame = cent->pe.torso.frame;
+ torso.backlerp = cent->pe.torso.backlerp;
+ }
+ else
+ {
+ memset( &cent->pe.nonseg, 0, sizeof( lerpFrame_t ) );
+ CG_RunPlayerLerpFrame( ci, &cent->pe.nonseg, es->legsAnim, 1 );
+ legs.oldframe = cent->pe.nonseg.oldFrame;
+ legs.frame = cent->pe.nonseg.frame;
+ legs.backlerp = cent->pe.nonseg.backlerp;
+ }
+
+ // add the shadow
+ shadow = CG_PlayerShadow( cent, &shadowPlane, es->clientNum );
+
+ // get the player model information
+ renderfx = 0;
+
+ if( cg_shadows.integer == 3 && shadow )
+ renderfx |= RF_SHADOW_PLANE;
+
+ renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
+
+ //
+ // add the legs
+ //
+ if( !ci->nonsegmented )
+ {
+ legs.hModel = ci->legsModel;
+ legs.customSkin = ci->legsSkin;
+ }
+ else
+ {
+ legs.hModel = ci->nonSegModel;
+ legs.customSkin = ci->nonSegSkin;
+ }
+
+ VectorCopy( origin, legs.origin );
+
+ VectorCopy( origin, legs.lightingOrigin );
+ legs.shadowPlane = shadowPlane;
+ legs.renderfx = renderfx;
+ legs.origin[ 2 ] += BG_FindZOffsetForClass( es->clientNum );
+ VectorCopy( legs.origin, legs.oldorigin ); // don't positionally lerp at all
+
+ //rescale the model
+ scale = BG_FindModelScaleForClass( es->clientNum );
+
+ if( scale != 1.0f )
+ {
+ VectorScale( legs.axis[ 0 ], scale, legs.axis[ 0 ] );
+ VectorScale( legs.axis[ 1 ], scale, legs.axis[ 1 ] );
+ VectorScale( legs.axis[ 2 ], scale, legs.axis[ 2 ] );
+
+ legs.nonNormalizedAxes = qtrue;
+ }
+
+ //CG_AddRefEntityWithPowerups( &legs, es->powerups, ci->team );
+ trap_R_AddRefEntityToScene( &legs );
+
+ // if the model failed, allow the default nullmodel to be displayed
+ if( !legs.hModel )
+ return;
+
+ if( !ci->nonsegmented )
+ {
+ //
+ // add the torso
+ //
+ torso.hModel = ci->torsoModel;
+ if( !torso.hModel )
+ return;
+
+ torso.customSkin = ci->torsoSkin;
+
+ VectorCopy( origin, torso.lightingOrigin );
+
+ CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso" );
+
+ torso.shadowPlane = shadowPlane;
+ torso.renderfx = renderfx;
+
+ //CG_AddRefEntityWithPowerups( &torso, es->powerups, ci->team );
+ trap_R_AddRefEntityToScene( &torso );
+
+ //
+ // add the head
+ //
+ head.hModel = ci->headModel;
+ if( !head.hModel )
+ return;
+
+ head.customSkin = ci->headSkin;
+
+ VectorCopy( origin, head.lightingOrigin );
+
+ CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head");
+
+ head.shadowPlane = shadowPlane;
+ head.renderfx = renderfx;
+
+ //CG_AddRefEntityWithPowerups( &head, es->powerups, ci->team );
+ trap_R_AddRefEntityToScene( &head );
+ }
+}
+
+
+//=====================================================================
+
+/*
+===============
+CG_ResetPlayerEntity
+
+A player just came into view or teleported, so reset all animation info
+===============
+*/
+void CG_ResetPlayerEntity( centity_t *cent )
+{
+ cent->errorTime = -99999; // guarantee no error decay added
+ cent->extrapolated = qfalse;
+
+ CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ],
+ &cent->pe.legs, cent->currentState.legsAnim );
+ CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ],
+ &cent->pe.torso, cent->currentState.torsoAnim );
+ CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ],
+ &cent->pe.nonseg, cent->currentState.legsAnim );
+
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.time, cent->lerpOrigin );
+ BG_EvaluateTrajectory( &cent->currentState.apos, cg.time, cent->lerpAngles );
+
+ VectorCopy( cent->lerpOrigin, cent->rawOrigin );
+ VectorCopy( cent->lerpAngles, cent->rawAngles );
+
+ memset( &cent->pe.legs, 0, sizeof( cent->pe.legs ) );
+ cent->pe.legs.yawAngle = cent->rawAngles[ YAW ];
+ cent->pe.legs.yawing = qfalse;
+ cent->pe.legs.pitchAngle = 0;
+ cent->pe.legs.pitching = qfalse;
+
+ memset( &cent->pe.torso, 0, sizeof( cent->pe.legs ) );
+ cent->pe.torso.yawAngle = cent->rawAngles[ YAW ];
+ cent->pe.torso.yawing = qfalse;
+ cent->pe.torso.pitchAngle = cent->rawAngles[ PITCH ];
+ cent->pe.torso.pitching = qfalse;
+
+ memset( &cent->pe.nonseg, 0, sizeof( cent->pe.nonseg ) );
+ cent->pe.nonseg.yawAngle = cent->rawAngles[ YAW ];
+ cent->pe.nonseg.yawing = qfalse;
+ cent->pe.nonseg.pitchAngle = cent->rawAngles[ PITCH ];
+ cent->pe.nonseg.pitching = qfalse;
+
+ if( cg_debugPosition.integer )
+ CG_Printf( "%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle );
+}
+
+/*
+==================
+CG_PlayerDisconnect
+
+Player disconnecting
+==================
+*/
+void CG_PlayerDisconnect( vec3_t org )
+{
+ particleSystem_t *ps;
+
+ trap_S_StartSound( org, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.disconnectSound );
+
+ ps = CG_SpawnNewParticleSystem( cgs.media.disconnectPS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentPoint( &ps->attachment, org );
+ CG_AttachToPoint( &ps->attachment );
+ }
+}
+
+/*
+=================
+CG_Bleed
+
+This is the spurt of blood when a character gets hit
+=================
+*/
+void CG_Bleed( vec3_t origin, vec3_t normal, int entityNum )
+{
+ pTeam_t team = cgs.clientinfo[ entityNum ].team;
+ qhandle_t bleedPS;
+ particleSystem_t *ps;
+
+ if( !cg_blood.integer )
+ return;
+
+ if( team == PTE_ALIENS )
+ bleedPS = cgs.media.alienBleedPS;
+ else if( team == PTE_HUMANS )
+ bleedPS = cgs.media.humanBleedPS;
+ else
+ return;
+
+ ps = CG_SpawnNewParticleSystem( bleedPS );
+
+ if( CG_IsParticleSystemValid( &ps ) )
+ {
+ CG_SetAttachmentPoint( &ps->attachment, origin );
+ CG_SetAttachmentCent( &ps->attachment, &cg_entities[ entityNum ] );
+ CG_AttachToPoint( &ps->attachment );
+
+ CG_SetParticleSystemNormal( ps, normal );
+ }
+}
+
+/*
+===============
+CG_AtHighestClass
+
+Is the local client at the highest class possible?
+===============
+*/
+qboolean CG_AtHighestClass( void )
+{
+ int i;
+ qboolean superiorClasses = qfalse;
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ if( BG_ClassCanEvolveFromTo(
+ cg.predictedPlayerState.stats[ STAT_PCLASS ], i,
+ ALIEN_MAX_KILLS, 0 ) >= 0 &&
+ BG_FindStagesForClass( i, cgs.alienStage ) &&
+ BG_ClassIsAllowed( i ) )
+ {
+ superiorClasses = qtrue;
+ break;
+ }
+ }
+
+ return !superiorClasses;
+}
+
diff --git a/src/cgame/cg_playerstate.c b/src/cgame/cg_playerstate.c
new file mode 100644
index 0000000..e1bcb09
--- /dev/null
+++ b/src/cgame/cg_playerstate.c
@@ -0,0 +1,317 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_playerstate.c -- this file acts on changes in a new playerState_t
+// With normal play, this will be done after local prediction, but when
+// following another player or playing back a demo, it will be checked
+// when the snapshot transitions like all the other entities
+
+
+#include "cg_local.h"
+
+/*
+==============
+CG_DamageFeedback
+==============
+*/
+void CG_DamageFeedback( int yawByte, int pitchByte, int damage )
+{
+ float left, front, up;
+ float kick;
+ int health;
+ float scale;
+ vec3_t dir;
+ vec3_t angles;
+ float dist;
+ float yaw, pitch;
+
+ // show the attacking player's head and name in corner
+ cg.attackerTime = cg.time;
+
+ // the lower on health you are, the greater the view kick will be
+ health = cg.snap->ps.stats[STAT_HEALTH];
+
+ if( health < 40 )
+ scale = 1;
+ else
+ scale = 40.0 / health;
+
+ kick = damage * scale;
+
+ if( kick < 5 )
+ kick = 5;
+
+ if( kick > 10 )
+ kick = 10;
+
+ // if yaw and pitch are both 255, make the damage always centered (falling, etc)
+ if( yawByte == 255 && pitchByte == 255 )
+ {
+ cg.damageX = 0;
+ cg.damageY = 0;
+ cg.v_dmg_roll = 0;
+ cg.v_dmg_pitch = -kick;
+ }
+ else
+ {
+ // positional
+ pitch = pitchByte / 255.0 * 360;
+ yaw = yawByte / 255.0 * 360;
+
+ angles[ PITCH ] = pitch;
+ angles[ YAW ] = yaw;
+ angles[ ROLL ] = 0;
+
+ AngleVectors( angles, dir, NULL, NULL );
+ VectorSubtract( vec3_origin, dir, dir );
+
+ front = DotProduct( dir, cg.refdef.viewaxis[ 0 ] );
+ left = DotProduct( dir, cg.refdef.viewaxis[ 1 ] );
+ up = DotProduct( dir, cg.refdef.viewaxis[ 2 ] );
+
+ dir[ 0 ] = front;
+ dir[ 1 ] = left;
+ dir[ 2 ] = 0;
+ dist = VectorLength( dir );
+
+ if( dist < 0.1f )
+ dist = 0.1f;
+
+ cg.v_dmg_roll = kick * left;
+
+ cg.v_dmg_pitch = -kick * front;
+
+ if( front <= 0.1 )
+ front = 0.1f;
+
+ cg.damageX = -left / front;
+ cg.damageY = up / dist;
+ }
+
+ // clamp the position
+ if( cg.damageX > 1.0 )
+ cg.damageX = 1.0;
+
+ if( cg.damageX < - 1.0 )
+ cg.damageX = -1.0;
+
+ if( cg.damageY > 1.0 )
+ cg.damageY = 1.0;
+
+ if( cg.damageY < - 1.0 )
+ cg.damageY = -1.0;
+
+ // don't let the screen flashes vary as much
+ if( kick > 10 )
+ kick = 10;
+
+ cg.damageValue = kick;
+ cg.v_dmg_time = cg.time + DAMAGE_TIME;
+ cg.damageTime = cg.snap->serverTime;
+}
+
+
+
+
+/*
+================
+CG_Respawn
+
+A respawn happened this snapshot
+================
+*/
+void CG_Respawn( void )
+{
+ // no error decay on player movement
+ cg.thisFrameTeleport = qtrue;
+
+ // display weapons available
+ cg.weaponSelectTime = cg.time;
+
+ // select the weapon the server says we are using
+ cg.weaponSelect = cg.snap->ps.weapon;
+
+ CG_ResetPainBlend( );
+}
+
+/*
+==============
+CG_CheckPlayerstateEvents
+
+==============
+*/
+void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops )
+{
+ int i;
+ int event;
+ centity_t *cent;
+
+ if( ps->externalEvent && ps->externalEvent != ops->externalEvent )
+ {
+ cent = &cg_entities[ ps->clientNum ];
+ cent->currentState.event = ps->externalEvent;
+ cent->currentState.eventParm = ps->externalEventParm;
+ CG_EntityEvent( cent, cent->lerpOrigin );
+ }
+
+ cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ];
+
+ // go through the predictable events buffer
+ for( i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++ )
+ {
+ // if we have a new predictable event
+ if( i >= ops->eventSequence ||
+ // or the server told us to play another event instead of a predicted event we already issued
+ // or something the server told us changed our prediction causing a different event
+ ( i > ops->eventSequence - MAX_PS_EVENTS && ps->events[ i & ( MAX_PS_EVENTS - 1 ) ] !=
+ ops->events[ i & ( MAX_PS_EVENTS - 1 ) ] ) )
+ {
+ event = ps->events[ i & ( MAX_PS_EVENTS - 1 ) ];
+
+ cent->currentState.event = event;
+ cent->currentState.eventParm = ps->eventParms[ i & ( MAX_PS_EVENTS - 1 ) ];
+ CG_EntityEvent( cent, cent->lerpOrigin );
+ cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event;
+
+ cg.eventSequence++;
+ }
+ }
+}
+
+
+/*
+==================
+CG_CheckChangedPredictableEvents
+==================
+*/
+void CG_CheckChangedPredictableEvents( playerState_t *ps )
+{
+ int i;
+ int event;
+ centity_t *cent;
+
+ cent = &cg.predictedPlayerEntity;
+
+ for( i = ps->eventSequence - MAX_PS_EVENTS; i < ps->eventSequence; i++ )
+ {
+ //
+ if( i >= cg.eventSequence )
+ continue;
+
+ // if this event is not further back in than the maximum predictable events we remember
+ if( i > cg.eventSequence - MAX_PREDICTED_EVENTS )
+ {
+ // if the new playerstate event is different from a previously predicted one
+ if( ps->events[ i & ( MAX_PS_EVENTS - 1 ) ] != cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] )
+ {
+ event = ps->events[ i & ( MAX_PS_EVENTS - 1 ) ];
+ cent->currentState.event = event;
+ cent->currentState.eventParm = ps->eventParms[ i & ( MAX_PS_EVENTS - 1 ) ];
+ CG_EntityEvent( cent, cent->lerpOrigin );
+
+ cg.predictableEvents[ i & ( MAX_PREDICTED_EVENTS - 1 ) ] = event;
+
+ if( cg_showmiss.integer )
+ CG_Printf( "WARNING: changed predicted event\n" );
+ }
+ }
+ }
+}
+
+/*
+==================
+CG_CheckLocalSounds
+==================
+*/
+void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops )
+{
+ int reward;
+
+ // don't play the sounds if the player just changed teams
+ if( ps->persistant[ PERS_TEAM ] != ops->persistant[ PERS_TEAM ] )
+ return;
+
+ // health changes of more than -1 should make pain sounds
+ if( ps->stats[ STAT_HEALTH ] < ops->stats[ STAT_HEALTH ] - 1 )
+ {
+ if( ps->stats[ STAT_HEALTH ] > 0 )
+ CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[ STAT_HEALTH ] );
+ }
+
+
+ // if we are going into the intermission, don't start any voices
+ if( cg.intermissionStarted )
+ return;
+
+ // reward sounds
+ reward = qfalse;
+}
+
+
+/*
+===============
+CG_TransitionPlayerState
+
+===============
+*/
+void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops )
+{
+ // check for changing follow mode
+ if( ps->clientNum != ops->clientNum )
+ {
+ cg.thisFrameTeleport = qtrue;
+ // make sure we don't get any unwanted transition effects
+ *ops = *ps;
+
+ CG_ResetPainBlend( );
+ }
+
+ // damage events (player is getting wounded)
+ if( ps->damageEvent != ops->damageEvent && ps->damageCount )
+ CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount );
+
+ // respawning
+ if( ps->persistant[ PERS_SPAWN_COUNT ] != ops->persistant[ PERS_SPAWN_COUNT ] )
+ CG_Respawn( );
+
+ if( cg.mapRestart )
+ {
+ CG_Respawn( );
+ cg.mapRestart = qfalse;
+ }
+
+ if( cg.snap->ps.pm_type != PM_INTERMISSION &&
+ ps->persistant[ PERS_TEAM ] != TEAM_SPECTATOR )
+ CG_CheckLocalSounds( ps, ops );
+
+ // run events
+ CG_CheckPlayerstateEvents( ps, ops );
+
+ // smooth the ducking viewheight change
+ if( ps->viewheight != ops->viewheight )
+ {
+ cg.duckChange = ps->viewheight - ops->viewheight;
+ cg.duckTime = cg.time;
+ }
+}
+
diff --git a/src/cgame/cg_predict.c b/src/cgame/cg_predict.c
new file mode 100644
index 0000000..34f00c4
--- /dev/null
+++ b/src/cgame/cg_predict.c
@@ -0,0 +1,878 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_predict.c -- this file generates cg.predictedPlayerState by either
+// interpolating between snapshots from the server or locally predicting
+// ahead the client's movement.
+// It also handles local physics interaction, like fragments bouncing off walls
+
+
+#include "cg_local.h"
+
+static pmove_t cg_pmove;
+
+static int cg_numSolidEntities;
+static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT];
+static int cg_numTriggerEntities;
+static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT];
+
+/*
+====================
+CG_BuildSolidList
+
+When a new cg.snap has been set, this function builds a sublist
+of the entities that are actually solid, to make for more
+efficient collision detection
+====================
+*/
+void CG_BuildSolidList( void )
+{
+ int i;
+ centity_t *cent;
+ snapshot_t *snap;
+ entityState_t *ent;
+
+ cg_numSolidEntities = 0;
+ cg_numTriggerEntities = 0;
+
+ if( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport )
+ snap = cg.nextSnap;
+ else
+ snap = cg.snap;
+
+ for( i = 0; i < snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ snap->entities[ i ].number ];
+ ent = &cent->currentState;
+
+ if( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER )
+ {
+ cg_triggerEntities[ cg_numTriggerEntities ] = cent;
+ cg_numTriggerEntities++;
+ continue;
+ }
+
+ if( cent->nextState.solid && ent->eType != ET_MISSILE )
+ {
+ cg_solidEntities[ cg_numSolidEntities ] = cent;
+ cg_numSolidEntities++;
+ continue;
+ }
+ }
+}
+
+/*
+====================
+CG_ClipMoveToEntities
+
+====================
+*/
+static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins,
+ const vec3_t maxs, const vec3_t end, int skipNumber,
+ int mask, trace_t *tr, traceType_t collisionType )
+{
+ int i, j, x, zd, zu;
+ trace_t trace;
+ entityState_t *ent;
+ clipHandle_t cmodel;
+ vec3_t bmins, bmaxs;
+ vec3_t origin, angles;
+ centity_t *cent;
+
+ //SUPAR HACK
+ //this causes a trace to collide with the local player
+ if( skipNumber == MAGIC_TRACE_HACK )
+ j = cg_numSolidEntities + 1;
+ else
+ j = cg_numSolidEntities;
+
+ for( i = 0; i < j; i++ )
+ {
+ if( i < cg_numSolidEntities )
+ cent = cg_solidEntities[ i ];
+ else
+ cent = &cg.predictedPlayerEntity;
+
+ ent = &cent->currentState;
+
+ if( ent->number == skipNumber )
+ continue;
+
+ if( ent->solid == SOLID_BMODEL )
+ {
+ // special value for bmodel
+ cmodel = trap_CM_InlineModel( ent->modelindex );
+ VectorCopy( cent->lerpAngles, angles );
+ BG_EvaluateTrajectory( &cent->currentState.pos, cg.physicsTime, origin );
+ }
+ else
+ {
+ // encoded bbox
+ x = ( ent->solid & 255 );
+ zd = ( ( ent->solid >> 8 ) & 255 );
+ zu = ( ( ent->solid >> 16 ) & 255 ) - 32;
+
+ bmins[ 0 ] = bmins[ 1 ] = -x;
+ bmaxs[ 0 ] = bmaxs[ 1 ] = x;
+ bmins[ 2 ] = -zd;
+ bmaxs[ 2 ] = zu;
+
+ if( i == cg_numSolidEntities )
+ BG_FindBBoxForClass( ( ent->powerups >> 8 ) & 0xFF, bmins, bmaxs, NULL, NULL, NULL );
+
+ cmodel = trap_CM_TempBoxModel( bmins, bmaxs );
+ VectorCopy( vec3_origin, angles );
+ VectorCopy( cent->lerpOrigin, origin );
+ }
+
+
+ if( collisionType == TT_CAPSULE )
+ {
+ trap_CM_TransformedCapsuleTrace ( &trace, start, end,
+ mins, maxs, cmodel, mask, origin, angles );
+ }
+ else if( collisionType == TT_AABB )
+ {
+ trap_CM_TransformedBoxTrace ( &trace, start, end,
+ mins, maxs, cmodel, mask, origin, angles );
+ }
+ else if( collisionType == TT_BISPHERE )
+ {
+ trap_CM_TransformedBiSphereTrace( &trace, start, end,
+ mins[ 0 ], maxs[ 0 ], cmodel, mask, origin );
+ }
+
+ if( trace.allsolid || trace.fraction < tr->fraction )
+ {
+ trace.entityNum = ent->number;
+
+ if( tr->lateralFraction < trace.lateralFraction )
+ {
+ float oldLateralFraction = tr->lateralFraction;
+ *tr = trace;
+ tr->lateralFraction = oldLateralFraction;
+ }
+ else
+ *tr = trace;
+ }
+ else if( trace.startsolid )
+ tr->startsolid = qtrue;
+
+ if( tr->allsolid )
+ return;
+ }
+}
+
+/*
+================
+CG_Trace
+================
+*/
+void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end,
+ int skipNumber, int mask )
+{
+ trace_t t;
+
+ trap_CM_BoxTrace( &t, start, end, mins, maxs, 0, mask );
+ t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
+ // check all other solid models
+ CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_AABB );
+
+ *result = t;
+}
+
+/*
+================
+CG_CapTrace
+================
+*/
+void CG_CapTrace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end,
+ int skipNumber, int mask )
+{
+ trace_t t;
+
+ trap_CM_CapsuleTrace( &t, start, end, mins, maxs, 0, mask );
+ t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
+ // check all other solid models
+ CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_CAPSULE );
+
+ *result = t;
+}
+
+/*
+================
+CG_BiSphereTrace
+================
+*/
+void CG_BiSphereTrace( trace_t *result, const vec3_t start, const vec3_t end,
+ const float startRadius, const float endRadius, int skipNumber, int mask )
+{
+ trace_t t;
+ vec3_t mins, maxs;
+
+ mins[ 0 ] = startRadius;
+ maxs[ 0 ] = endRadius;
+
+ trap_CM_BiSphereTrace( &t, start, end, startRadius, endRadius, 0, mask );
+ t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
+ // check all other solid models
+ CG_ClipMoveToEntities( start, mins, maxs, end, skipNumber, mask, &t, TT_BISPHERE );
+
+ *result = t;
+}
+
+/*
+================
+CG_PointContents
+================
+*/
+int CG_PointContents( const vec3_t point, int passEntityNum )
+{
+ int i;
+ entityState_t *ent;
+ centity_t *cent;
+ clipHandle_t cmodel;
+ int contents;
+
+ contents = trap_CM_PointContents (point, 0);
+
+ for( i = 0; i < cg_numSolidEntities; i++ )
+ {
+ cent = cg_solidEntities[ i ];
+
+ ent = &cent->currentState;
+
+ if( ent->number == passEntityNum )
+ continue;
+
+ if( ent->solid != SOLID_BMODEL ) // special value for bmodel
+ continue;
+
+ cmodel = trap_CM_InlineModel( ent->modelindex );
+
+ if( !cmodel )
+ continue;
+
+ contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles );
+ }
+
+ return contents;
+}
+
+
+/*
+========================
+CG_InterpolatePlayerState
+
+Generates cg.predictedPlayerState by interpolating between
+cg.snap->player_state and cg.nextFrame->player_state
+========================
+*/
+static void CG_InterpolatePlayerState( qboolean grabAngles )
+{
+ float f;
+ int i;
+ playerState_t *out;
+ snapshot_t *prev, *next;
+
+ out = &cg.predictedPlayerState;
+ prev = cg.snap;
+ next = cg.nextSnap;
+
+ *out = cg.snap->ps;
+
+ // if we are still allowing local input, short circuit the view angles
+ if( grabAngles )
+ {
+ usercmd_t cmd;
+ int cmdNum;
+
+ cmdNum = trap_GetCurrentCmdNumber( );
+ trap_GetUserCmd( cmdNum, &cmd );
+
+ PM_UpdateViewAngles( out, &cmd );
+ }
+
+ // if the next frame is a teleport, we can't lerp to it
+ if( cg.nextFrameTeleport )
+ return;
+
+ if( !next || next->serverTime <= prev->serverTime )
+ return;
+
+ f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime );
+
+ i = next->ps.bobCycle;
+ if( i < prev->ps.bobCycle )
+ i += 256; // handle wraparound
+
+ out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle );
+
+ for( i = 0; i < 3; i++ )
+ {
+ out->origin[ i ] = prev->ps.origin[ i ] + f * ( next->ps.origin[ i ] - prev->ps.origin[ i ] );
+
+ if( !grabAngles )
+ out->viewangles[ i ] = LerpAngle( prev->ps.viewangles[ i ], next->ps.viewangles[ i ], f );
+
+ out->velocity[ i ] = prev->ps.velocity[ i ] +
+ f * (next->ps.velocity[ i ] - prev->ps.velocity[ i ] );
+ }
+}
+
+
+/*
+=========================
+CG_TouchTriggerPrediction
+
+Predict push triggers and items
+=========================
+*/
+static void CG_TouchTriggerPrediction( void )
+{
+ int i;
+ trace_t trace;
+ entityState_t *ent;
+ clipHandle_t cmodel;
+ centity_t *cent;
+ qboolean spectator;
+
+ // dead clients don't activate triggers
+ if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR );
+
+ if( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator )
+ return;
+
+ for( i = 0; i < cg_numTriggerEntities; i++ )
+ {
+ cent = cg_triggerEntities[ i ];
+ ent = &cent->currentState;
+
+ if( ent->solid != SOLID_BMODEL )
+ continue;
+
+ cmodel = trap_CM_InlineModel( ent->modelindex );
+ if( !cmodel )
+ continue;
+
+ trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin,
+ cg_pmove.mins, cg_pmove.maxs, cmodel, -1 );
+
+ if( !trace.startsolid )
+ continue;
+
+ if( ent->eType == ET_TELEPORT_TRIGGER )
+ cg.hyperspace = qtrue;
+ }
+}
+
+static int CG_IsUnacceptableError( playerState_t *ps, playerState_t *pps )
+{
+ vec3_t delta;
+ int i;
+
+ if( pps->pm_type != ps->pm_type ||
+ pps->pm_flags != ps->pm_flags ||
+ pps->pm_time != ps->pm_time )
+ {
+ return 1;
+ }
+
+ VectorSubtract( pps->origin, ps->origin, delta );
+ if( VectorLengthSquared( delta ) > 0.1f * 0.1f )
+ {
+ if( cg_showmiss.integer )
+ {
+ CG_Printf( "origin delta: %.2f ", VectorLength( delta ) );
+ }
+ return 2;
+ }
+
+ VectorSubtract( pps->velocity, ps->velocity, delta );
+ if( VectorLengthSquared( delta ) > 0.1f * 0.1f )
+ {
+ if( cg_showmiss.integer )
+ {
+ CG_Printf( "velocity delta: %.2f ", VectorLength( delta ) );
+ }
+ return 3;
+ }
+
+ if( pps->weaponTime != ps->weaponTime ||
+ pps->gravity != ps->gravity ||
+ pps->speed != ps->speed ||
+ pps->delta_angles[ 0 ] != ps->delta_angles[ 0 ] ||
+ pps->delta_angles[ 1 ] != ps->delta_angles[ 1 ] ||
+ pps->delta_angles[ 2 ] != ps->delta_angles[ 2 ] ||
+ pps->groundEntityNum != ps->groundEntityNum )
+ {
+ return 4;
+ }
+
+ if( pps->legsTimer != ps->legsTimer ||
+ pps->legsAnim != ps->legsAnim ||
+ pps->torsoTimer != ps->torsoTimer ||
+ pps->torsoAnim != ps->torsoAnim ||
+ pps->movementDir != ps->movementDir )
+ {
+ return 5;
+ }
+
+ VectorSubtract( pps->grapplePoint, ps->grapplePoint, delta );
+ if( VectorLengthSquared( delta ) > 0.1f * 0.1f )
+ return 6;
+
+ if( pps->eFlags != ps->eFlags )
+ return 7;
+
+ if( pps->eventSequence != ps->eventSequence )
+ return 8;
+
+ for( i = 0; i < MAX_PS_EVENTS; i++ )
+ {
+ if ( pps->events[ i ] != ps->events[ i ] ||
+ pps->eventParms[ i ] != ps->eventParms[ i ] )
+ {
+ return 9;
+ }
+ }
+
+ if( pps->externalEvent != ps->externalEvent ||
+ pps->externalEventParm != ps->externalEventParm ||
+ pps->externalEventTime != ps->externalEventTime )
+ {
+ return 10;
+ }
+
+ if( pps->clientNum != ps->clientNum ||
+ pps->weapon != ps->weapon ||
+ pps->weaponstate != ps->weaponstate )
+ {
+ return 11;
+ }
+
+ if( fabs( AngleDelta( ps->viewangles[ 0 ], pps->viewangles[ 0 ] ) ) > 1.0f ||
+ fabs( AngleDelta( ps->viewangles[ 1 ], pps->viewangles[ 1 ] ) ) > 1.0f ||
+ fabs( AngleDelta( ps->viewangles[ 2 ], pps->viewangles[ 2 ] ) ) > 1.0f )
+ {
+ return 12;
+ }
+
+ if( pps->viewheight != ps->viewheight )
+ return 13;
+
+ if( pps->damageEvent != ps->damageEvent ||
+ pps->damageYaw != ps->damageYaw ||
+ pps->damagePitch != ps->damagePitch ||
+ pps->damageCount != ps->damageCount )
+ {
+ return 14;
+ }
+
+ for( i = 0; i < MAX_STATS; i++ )
+ {
+ if( pps->stats[ i ] != ps->stats[ i ] )
+ return 15;
+ }
+
+ for( i = 0; i < MAX_PERSISTANT; i++ )
+ {
+ if( pps->persistant[ i ] != ps->persistant[ i ] )
+ return 16;
+ }
+
+ for( i = 0; i < MAX_WEAPONS; i++ )
+ {
+ if( pps->ammo[ i ] != ps->ammo[ i ] )
+ return 18;
+ }
+
+ if( pps->generic1 != ps->generic1 ||
+ pps->loopSound != ps->loopSound )
+ {
+ return 19;
+ }
+
+ return 0;
+}
+
+
+/*
+=================
+CG_PredictPlayerState
+
+Generates cg.predictedPlayerState for the current cg.time
+cg.predictedPlayerState is guaranteed to be valid after exiting.
+
+For demo playback, this will be an interpolation between two valid
+playerState_t.
+
+For normal gameplay, it will be the result of predicted usercmd_t on
+top of the most recent playerState_t received from the server.
+
+Each new snapshot will usually have one or more new usercmd over the last,
+but we simulate all unacknowledged commands each time, not just the new ones.
+This means that on an internet connection, quite a few pmoves may be issued
+each frame.
+
+OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t
+differs from the predicted one. Would require saving all intermediate
+playerState_t during prediction.
+
+We detect prediction errors and allow them to be decayed off over several frames
+to ease the jerk.
+=================
+*/
+void CG_PredictPlayerState( void )
+{
+ int cmdNum, current, i;
+ playerState_t oldPlayerState;
+ qboolean moved;
+ usercmd_t oldestCmd;
+ usercmd_t latestCmd;
+ int stateIndex = 0, predictCmd = 0;
+
+ cg.hyperspace = qfalse; // will be set if touching a trigger_teleport
+
+ // if this is the first frame we must guarantee
+ // predictedPlayerState is valid even if there is some
+ // other error condition
+ if( !cg.validPPS )
+ {
+ cg.validPPS = qtrue;
+ cg.predictedPlayerState = cg.snap->ps;
+ }
+
+
+ // demo playback just copies the moves
+ if( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) )
+ {
+ CG_InterpolatePlayerState( qfalse );
+ return;
+ }
+
+ // non-predicting local movement will grab the latest angles
+ if( cg_nopredict.integer || cg_synchronousClients.integer )
+ {
+ CG_InterpolatePlayerState( qtrue );
+ return;
+ }
+
+ // prepare for pmove
+ cg_pmove.ps = &cg.predictedPlayerState;
+ cg_pmove.pmext = &cg.pmext;
+ cg_pmove.trace = CG_Trace;
+ cg_pmove.pointcontents = CG_PointContents;
+ cg_pmove.debugLevel = cg_debugMove.integer;
+
+ if( cg_pmove.ps->pm_type == PM_DEAD )
+ cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
+ else
+ cg_pmove.tracemask = MASK_PLAYERSOLID;
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies
+
+ cg_pmove.noFootsteps = 0;
+
+ // save the state before the pmove so we can detect transitions
+ oldPlayerState = cg.predictedPlayerState;
+
+ current = trap_GetCurrentCmdNumber( );
+
+ // if we don't have the commands right after the snapshot, we
+ // can't accurately predict a current position, so just freeze at
+ // the last good position we had
+ cmdNum = current - CMD_BACKUP + 1;
+ trap_GetUserCmd( cmdNum, &oldestCmd );
+
+ if( oldestCmd.serverTime > cg.snap->ps.commandTime &&
+ oldestCmd.serverTime < cg.time )
+ { // special check for map_restart
+ if( cg_showmiss.integer )
+ CG_Printf( "exceeded PACKET_BACKUP on commands\n" );
+
+ return;
+ }
+
+ // get the latest command so we can know which commands are from previous map_restarts
+ trap_GetUserCmd( current, &latestCmd );
+
+ // get the most recent information we have, even if
+ // the server time is beyond our current cg.time,
+ // because predicted player positions are going to
+ // be ahead of everything else anyway
+ if( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport )
+ {
+ cg.predictedPlayerState = cg.nextSnap->ps;
+ cg.physicsTime = cg.nextSnap->serverTime;
+ }
+ else
+ {
+ cg.predictedPlayerState = cg.snap->ps;
+ cg.physicsTime = cg.snap->serverTime;
+ }
+
+ if( pmove_msec.integer < 8 )
+ trap_Cvar_Set( "pmove_msec", "8" );
+ else if( pmove_msec.integer > 33 )
+ trap_Cvar_Set( "pmove_msec", "33" );
+
+ cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer;
+ cg_pmove.pmove_msec = pmove_msec.integer;
+
+ // Like the comments described above, a player's state is entirely
+ // re-predicted from the last valid snapshot every client frame, which
+ // can be really, really, really slow. Every old command has to be
+ // run again. For every client frame that is *not* directly after a
+ // snapshot, this is unnecessary, since we have no new information.
+ // For those, we'll play back the predictions from the last frame and
+ // predict only the newest commands. Essentially, we'll be doing
+ // an incremental predict instead of a full predict.
+ //
+ // If we have a new snapshot, we can compare its player state's command
+ // time to the command times in the queue to find a match. If we find
+ // a matching state, and the predicted version has not deviated, we can
+ // use the predicted state as a base - and also do an incremental predict.
+ //
+ // With this method, we get incremental predicts on every client frame
+ // except a frame following a new snapshot in which there was a prediction
+ // error. This yeilds anywhere from a 15% to 40% performance increase,
+ // depending on how much of a bottleneck the CPU is.
+ if( cg_optimizePrediction.integer )
+ {
+ if( cg.nextFrameTeleport || cg.thisFrameTeleport )
+ {
+ // do a full predict
+ cg.lastPredictedCommand = 0;
+ cg.stateTail = cg.stateHead;
+ predictCmd = current - CMD_BACKUP + 1;
+ }
+ // cg.physicsTime is the current snapshot's serverTime if it's the same
+ // as the last one
+ else if( cg.physicsTime == cg.lastServerTime )
+ {
+ // we have no new information, so do an incremental predict
+ predictCmd = cg.lastPredictedCommand + 1;
+ }
+ else
+ {
+ // we have a new snapshot
+ int i;
+ int errorcode;
+ qboolean error = qtrue;
+
+ // loop through the saved states queue
+ for( i = cg.stateHead; i != cg.stateTail;
+ i = ( i + 1 ) % NUM_SAVED_STATES )
+ {
+ // if we find a predicted state whose commandTime matches the snapshot
+ // player state's commandTime
+ if( cg.savedPmoveStates[ i ].commandTime !=
+ cg.predictedPlayerState.commandTime )
+ {
+ continue;
+ }
+ // make sure the state differences are acceptable
+ errorcode = CG_IsUnacceptableError( &cg.predictedPlayerState,
+ &cg.savedPmoveStates[ i ] );
+
+ if( errorcode )
+ {
+ if( cg_showmiss.integer )
+ CG_Printf("errorcode %d at %d\n", errorcode, cg.time);
+ break;
+ }
+
+ // this one is almost exact, so we'll copy it in as the starting point
+ *cg_pmove.ps = cg.savedPmoveStates[ i ];
+ // advance the head
+ cg.stateHead = ( i + 1 ) % NUM_SAVED_STATES;
+
+ // set the next command to predict
+ predictCmd = cg.lastPredictedCommand + 1;
+
+ // a saved state matched, so flag it
+ error = qfalse;
+ break;
+ }
+
+ // if no saved states matched
+ if( error )
+ {
+ // do a full predict
+ cg.lastPredictedCommand = 0;
+ cg.stateTail = cg.stateHead;
+ predictCmd = current - CMD_BACKUP + 1;
+ }
+ }
+
+ // keep track of the server time of the last snapshot so we
+ // know when we're starting from a new one in future calls
+ cg.lastServerTime = cg.physicsTime;
+ stateIndex = cg.stateHead;
+ }
+
+ // run cmds
+ moved = qfalse;
+
+ for( cmdNum = current - CMD_BACKUP + 1; cmdNum <= current; cmdNum++ )
+ {
+ // get the command
+ trap_GetUserCmd( cmdNum, &cg_pmove.cmd );
+
+ if( cg_pmove.pmove_fixed )
+ PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd );
+
+ // don't do anything if the time is before the snapshot player time
+ if( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime )
+ continue;
+
+ // don't do anything if the command was from a previous map_restart
+ if( cg_pmove.cmd.serverTime > latestCmd.serverTime )
+ continue;
+
+ // check for a prediction error from last frame
+ // on a lan, this will often be the exact value
+ // from the snapshot, but on a wan we will have
+ // to predict several commands to get to the point
+ // we want to compare
+ if( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime )
+ {
+ vec3_t delta;
+ float len;
+
+ if( cg.thisFrameTeleport )
+ {
+ // a teleport will not cause an error decay
+ VectorClear( cg.predictedError );
+
+ if( cg_showmiss.integer )
+ CG_Printf( "PredictionTeleport\n" );
+
+ cg.thisFrameTeleport = qfalse;
+ }
+ else
+ {
+ vec3_t adjusted;
+ CG_AdjustPositionForMover( cg.predictedPlayerState.origin,
+ cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted );
+
+ if( cg_showmiss.integer )
+ {
+ if( !VectorCompare( oldPlayerState.origin, adjusted ) )
+ CG_Printf("prediction error\n");
+ }
+
+ VectorSubtract( oldPlayerState.origin, adjusted, delta );
+ len = VectorLength( delta );
+
+ if( len > 0.1 )
+ {
+ if( cg_showmiss.integer )
+ CG_Printf( "Prediction miss: %f\n", len );
+
+ if( cg_errorDecay.integer )
+ {
+ int t;
+ float f;
+
+ t = cg.time - cg.predictedErrorTime;
+ f = ( cg_errorDecay.value - t ) / cg_errorDecay.value;
+
+ if( f < 0 )
+ f = 0;
+
+ if( f > 0 && cg_showmiss.integer )
+ CG_Printf( "Double prediction decay: %f\n", f );
+
+ VectorScale( cg.predictedError, f, cg.predictedError );
+ }
+ else
+ VectorClear( cg.predictedError );
+
+ VectorAdd( delta, cg.predictedError, cg.predictedError );
+ cg.predictedErrorTime = cg.oldTime;
+ }
+ }
+ }
+
+ // don't predict gauntlet firing, which is only supposed to happen
+ // when it actually inflicts damage
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ cg_pmove.autoWeaponHit[ i ] = qfalse;
+
+ if( cg_pmove.pmove_fixed )
+ cg_pmove.cmd.serverTime = ( ( cg_pmove.cmd.serverTime + pmove_msec.integer - 1 ) /
+ pmove_msec.integer ) * pmove_msec.integer;
+
+ if( !cg_optimizePrediction.integer )
+ {
+ Pmove( &cg_pmove );
+ }
+ else if( cg_optimizePrediction.integer && ( cmdNum >= predictCmd ||
+ ( stateIndex + 1 ) % NUM_SAVED_STATES == cg.stateHead ) )
+ {
+ Pmove( &cg_pmove );
+ // record the last predicted command
+ cg.lastPredictedCommand = cmdNum;
+
+ // if we haven't run out of space in the saved states queue
+ if( ( stateIndex + 1 ) % NUM_SAVED_STATES != cg.stateHead )
+ {
+ // save the state for the false case ( of cmdNum >= predictCmd )
+ // in later calls to this function
+ cg.savedPmoveStates[ stateIndex ] = *cg_pmove.ps;
+ stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES;
+ cg.stateTail = stateIndex;
+ }
+ }
+ else
+ {
+ *cg_pmove.ps = cg.savedPmoveStates[ stateIndex ];
+ stateIndex = ( stateIndex + 1 ) % NUM_SAVED_STATES;
+ }
+
+ moved = qtrue;
+
+ // add push trigger movement effects
+ CG_TouchTriggerPrediction( );
+
+ // check for predictable events that changed from previous predictions
+ //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState);
+ }
+
+ // adjust for the movement of the groundentity
+ CG_AdjustPositionForMover( cg.predictedPlayerState.origin,
+ cg.predictedPlayerState.groundEntityNum,
+ cg.physicsTime, cg.time, cg.predictedPlayerState.origin );
+
+
+ // fire events and other transition triggered things
+ CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState );
+
+
+}
diff --git a/src/cgame/cg_ptr.c b/src/cgame/cg_ptr.c
new file mode 100644
index 0000000..1881087
--- /dev/null
+++ b/src/cgame/cg_ptr.c
@@ -0,0 +1,81 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_ptr.c -- post timeout restoration handling
+
+
+#include "cg_local.h"
+
+#define PTRC_FILE "ptrc.cfg"
+
+/*
+===============
+CG_ReadPTRCode
+
+Read a PTR code from disk
+===============
+*/
+int CG_ReadPTRCode( void )
+{
+ int len;
+ char text[ 16 ];
+ fileHandle_t f;
+
+ // load the file
+ len = trap_FS_FOpenFile( PTRC_FILE, &f, FS_READ );
+ if( len <= 0 )
+ return 0;
+
+ // should never happen - malformed write
+ if( len >= sizeof( text ) - 1 )
+ return 0;
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ return atoi( text );
+}
+
+/*
+===============
+CG_WritePTRCode
+
+Write a PTR code to disk
+===============
+*/
+void CG_WritePTRCode( int code )
+{
+ char text[ 16 ];
+ fileHandle_t f;
+
+ Com_sprintf( text, 16, "%d", code );
+
+ // open file
+ if( trap_FS_FOpenFile( PTRC_FILE, &f, FS_WRITE ) < 0 )
+ return;
+
+ // write the code
+ trap_FS_Write( text, strlen( text ), f );
+
+ trap_FS_FCloseFile( f );
+}
diff --git a/src/cgame/cg_scanner.c b/src/cgame/cg_scanner.c
new file mode 100644
index 0000000..ab98b23
--- /dev/null
+++ b/src/cgame/cg_scanner.c
@@ -0,0 +1,365 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+
+#include "cg_local.h"
+
+static entityPos_t entityPositions;
+
+#define HUMAN_SCANNER_UPDATE_PERIOD 700
+
+/*
+=============
+CG_UpdateEntityPositions
+
+Update this client's perception of entity positions
+=============
+*/
+void CG_UpdateEntityPositions( void )
+{
+ centity_t *cent = NULL;
+ int i;
+
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ if( entityPositions.lastUpdateTime + HUMAN_SCANNER_UPDATE_PERIOD > cg.time )
+ return;
+ }
+
+ VectorCopy( cg.refdef.vieworg, entityPositions.origin );
+ VectorCopy( cg.refdefViewAngles, entityPositions.vangles );
+ entityPositions.lastUpdateTime = cg.time;
+
+ entityPositions.numAlienBuildables = 0;
+ entityPositions.numHumanBuildables = 0;
+ entityPositions.numAlienClients = 0;
+ entityPositions.numHumanClients = 0;
+
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ i ].number ];
+
+ if( cent->currentState.eType == ET_BUILDABLE )
+ {
+ //TA: add to list of item positions (for creep)
+ if( cent->currentState.modelindex2 == BIT_ALIENS )
+ {
+ VectorCopy( cent->lerpOrigin, entityPositions.alienBuildablePos[
+ entityPositions.numAlienBuildables ] );
+ entityPositions.alienBuildableTimes[
+ entityPositions.numAlienBuildables ] = cent->miscTime;
+
+ if( entityPositions.numAlienBuildables < MAX_GENTITIES )
+ entityPositions.numAlienBuildables++;
+ }
+ else if( cent->currentState.modelindex2 == BIT_HUMANS )
+ {
+ VectorCopy( cent->lerpOrigin, entityPositions.humanBuildablePos[
+ entityPositions.numHumanBuildables ] );
+
+ if( entityPositions.numHumanBuildables < MAX_GENTITIES )
+ entityPositions.numHumanBuildables++;
+ }
+ }
+ else if( cent->currentState.eType == ET_PLAYER )
+ {
+ int team = cent->currentState.powerups & 0x00FF;
+
+ if( team == PTE_ALIENS )
+ {
+ VectorCopy( cent->lerpOrigin, entityPositions.alienClientPos[
+ entityPositions.numAlienClients ] );
+
+ if( entityPositions.numAlienClients < MAX_CLIENTS )
+ entityPositions.numAlienClients++;
+ }
+ else if( team == PTE_HUMANS )
+ {
+ VectorCopy( cent->lerpOrigin, entityPositions.humanClientPos[
+ entityPositions.numHumanClients ] );
+
+ if( entityPositions.numHumanClients < MAX_CLIENTS )
+ entityPositions.numHumanClients++;
+ }
+ }
+ }
+}
+
+#define STALKWIDTH 2.0f
+#define BLIPX 16.0f
+#define BLIPY 8.0f
+#define FAR_ALPHA 0.8f
+#define NEAR_ALPHA 1.2f
+
+/*
+=============
+CG_DrawBlips
+
+Draw blips and stalks for the human scanner
+=============
+*/
+static void CG_DrawBlips( rectDef_t *rect, vec3_t origin, vec4_t colour )
+{
+ vec3_t drawOrigin;
+ vec3_t up = { 0, 0, 1 };
+ float alphaMod = 1.0f;
+ float timeFractionSinceRefresh = 1.0f -
+ ( (float)( cg.time - entityPositions.lastUpdateTime ) /
+ (float)HUMAN_SCANNER_UPDATE_PERIOD );
+ vec4_t localColour;
+
+ Vector4Copy( colour, localColour );
+
+ RotatePointAroundVector( drawOrigin, up, origin, -entityPositions.vangles[ 1 ] - 90 );
+ drawOrigin[ 0 ] /= ( 2 * HELMET_RANGE / rect->w );
+ drawOrigin[ 1 ] /= ( 2 * HELMET_RANGE / rect->h );
+ drawOrigin[ 2 ] /= ( 2 * HELMET_RANGE / rect->w );
+
+ alphaMod = FAR_ALPHA +
+ ( ( drawOrigin[ 1 ] + ( rect->h / 2.0f ) ) / rect->h ) * ( NEAR_ALPHA - FAR_ALPHA );
+
+ localColour[ 3 ] *= alphaMod;
+ localColour[ 3 ] *= ( 0.5f + ( timeFractionSinceRefresh * 0.5f ) );
+
+ if( localColour[ 3 ] > 1.0f )
+ localColour[ 3 ] = 1.0f;
+ else if( localColour[ 3 ] < 0.0f )
+ localColour[ 3 ] = 0.0f;
+
+ trap_R_SetColor( localColour );
+
+ if( drawOrigin[ 2 ] > 0 )
+ CG_DrawPic( rect->x + ( rect->w / 2 ) - ( STALKWIDTH / 2 ) - drawOrigin[ 0 ],
+ rect->y + ( rect->h / 2 ) + drawOrigin[ 1 ] - drawOrigin[ 2 ],
+ STALKWIDTH, drawOrigin[ 2 ], cgs.media.scannerLineShader );
+ else
+ CG_DrawPic( rect->x + ( rect->w / 2 ) - ( STALKWIDTH / 2 ) - drawOrigin[ 0 ],
+ rect->y + ( rect->h / 2 ) + drawOrigin[ 1 ],
+ STALKWIDTH, -drawOrigin[ 2 ], cgs.media.scannerLineShader );
+
+ CG_DrawPic( rect->x + ( rect->w / 2 ) - ( BLIPX / 2 ) - drawOrigin[ 0 ],
+ rect->y + ( rect->h / 2 ) - ( BLIPY / 2 ) + drawOrigin[ 1 ] - drawOrigin[ 2 ],
+ BLIPX, BLIPY, cgs.media.scannerBlipShader );
+ trap_R_SetColor( NULL );
+}
+
+#define BLIPX2 24.0f
+#define BLIPY2 24.0f
+
+/*
+=============
+CG_DrawDir
+
+Draw dot marking the direction to an enemy
+=============
+*/
+static void CG_DrawDir( rectDef_t *rect, vec3_t origin, vec4_t colour )
+{
+ vec3_t drawOrigin;
+ vec3_t noZOrigin;
+ vec3_t normal, antinormal, normalDiff;
+ vec3_t view, noZview;
+ vec3_t up = { 0.0f, 0.0f, 1.0f };
+ vec3_t top = { 0.0f, -1.0f, 0.0f };
+ float angle;
+ playerState_t *ps = &cg.snap->ps;
+
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( ps->grapplePoint, normal );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+
+ AngleVectors( entityPositions.vangles, view, NULL, NULL );
+
+ ProjectPointOnPlane( noZOrigin, origin, normal );
+ ProjectPointOnPlane( noZview, view, normal );
+ VectorNormalize( noZOrigin );
+ VectorNormalize( noZview );
+
+ //calculate the angle between the images of the blip and the view
+ angle = RAD2DEG( acos( DotProduct( noZOrigin, noZview ) ) );
+ CrossProduct( noZOrigin, noZview, antinormal );
+ VectorNormalize( antinormal );
+
+ //decide which way to rotate
+ VectorSubtract( normal, antinormal, normalDiff );
+ if( VectorLength( normalDiff ) < 1.0f )
+ angle = 360.0f - angle;
+
+ RotatePointAroundVector( drawOrigin, up, top, angle );
+
+ trap_R_SetColor( colour );
+ CG_DrawPic( rect->x + ( rect->w / 2 ) - ( BLIPX2 / 2 ) - drawOrigin[ 0 ] * ( rect->w / 2 ),
+ rect->y + ( rect->h / 2 ) - ( BLIPY2 / 2 ) + drawOrigin[ 1 ] * ( rect->h / 2 ),
+ BLIPX2, BLIPY2, cgs.media.scannerBlipShader );
+ trap_R_SetColor( NULL );
+}
+
+/*
+=============
+CG_AlienSense
+=============
+*/
+void CG_AlienSense( rectDef_t *rect )
+{
+ int i;
+ vec3_t origin;
+ vec3_t relOrigin;
+ vec4_t buildable = { 1.0f, 0.0f, 0.0f, 0.7f };
+ vec4_t client = { 0.0f, 0.0f, 1.0f, 0.7f };
+
+ VectorCopy( entityPositions.origin, origin );
+
+ //draw human buildables
+ for( i = 0; i < entityPositions.numHumanBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < ALIENSENSE_RANGE )
+ CG_DrawDir( rect, relOrigin, buildable );
+ }
+
+ //draw human clients
+ for( i = 0; i < entityPositions.numHumanClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < ALIENSENSE_RANGE )
+ CG_DrawDir( rect, relOrigin, client );
+ }
+}
+
+/*
+=============
+CG_Scanner
+=============
+*/
+void CG_Scanner( rectDef_t *rect, qhandle_t shader, vec4_t color )
+{
+ int i;
+ vec3_t origin;
+ vec3_t relOrigin;
+ vec4_t hIabove;
+ vec4_t hIbelow;
+ vec4_t aIabove = { 1.0f, 0.0f, 0.0f, 0.75f };
+ vec4_t aIbelow = { 1.0f, 0.0f, 0.0f, 0.5f };
+
+ Vector4Copy( color, hIabove );
+ hIabove[ 3 ] *= 1.5f;
+ Vector4Copy( color, hIbelow );
+
+ VectorCopy( entityPositions.origin, origin );
+
+ //draw human buildables below scanner plane
+ for( i = 0; i < entityPositions.numHumanBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) )
+ CG_DrawBlips( rect, relOrigin, hIbelow );
+ }
+
+ //draw alien buildables below scanner plane
+ for( i = 0; i < entityPositions.numAlienBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) )
+ CG_DrawBlips( rect, relOrigin, aIbelow );
+ }
+
+ //draw human clients below scanner plane
+ for( i = 0; i < entityPositions.numHumanClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) )
+ CG_DrawBlips( rect, relOrigin, hIbelow );
+ }
+
+ //draw alien buildables below scanner plane
+ for( i = 0; i < entityPositions.numAlienClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] < 0 ) )
+ CG_DrawBlips( rect, relOrigin, aIbelow );
+ }
+
+ if( !cg_disableScannerPlane.integer )
+ {
+ trap_R_SetColor( color );
+ CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader );
+ trap_R_SetColor( NULL );
+ }
+
+ //draw human buildables above scanner plane
+ for( i = 0; i < entityPositions.numHumanBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) )
+ CG_DrawBlips( rect, relOrigin, hIabove );
+ }
+
+ //draw alien buildables above scanner plane
+ for( i = 0; i < entityPositions.numAlienBuildables; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.alienBuildablePos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) )
+ CG_DrawBlips( rect, relOrigin, aIabove );
+ }
+
+ //draw human clients above scanner plane
+ for( i = 0; i < entityPositions.numHumanClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.humanClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) )
+ CG_DrawBlips( rect, relOrigin, hIabove );
+ }
+
+ //draw alien clients above scanner plane
+ for( i = 0; i < entityPositions.numAlienClients; i++ )
+ {
+ VectorClear( relOrigin );
+ VectorSubtract( entityPositions.alienClientPos[ i ], origin, relOrigin );
+
+ if( VectorLength( relOrigin ) < HELMET_RANGE && ( relOrigin[ 2 ] > 0 ) )
+ CG_DrawBlips( rect, relOrigin, aIabove );
+ }
+}
diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c
new file mode 100644
index 0000000..7fb3e06
--- /dev/null
+++ b/src/cgame/cg_servercmds.c
@@ -0,0 +1,1016 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_servercmds.c -- reliably sequenced text commands sent by the server
+// these are processed at snapshot transition time, so there will definately
+// be a valid snapshot this frame
+
+
+#include "cg_local.h"
+
+/*
+=================
+CG_ParseScores
+
+=================
+*/
+static void CG_ParseScores( void )
+{
+ int i;
+
+ cg.numScores = atoi( CG_Argv( 1 ) );
+
+ if( cg.numScores > MAX_CLIENTS )
+ cg.numScores = MAX_CLIENTS;
+
+ cg.teamScores[ 0 ] = atoi( CG_Argv( 2 ) );
+ cg.teamScores[ 1 ] = atoi( CG_Argv( 3 ) );
+
+ memset( cg.scores, 0, sizeof( cg.scores ) );
+
+ if( cg_debugRandom.integer )
+ CG_Printf( "cg.numScores: %d\n", cg.numScores );
+
+ for( i = 0; i < cg.numScores; i++ )
+ {
+ //
+ cg.scores[ i ].client = atoi( CG_Argv( i * 6 + 4 ) );
+ cg.scores[ i ].score = atoi( CG_Argv( i * 6 + 5 ) );
+ cg.scores[ i ].ping = atoi( CG_Argv( i * 6 + 6 ) );
+ cg.scores[ i ].time = atoi( CG_Argv( i * 6 + 7 ) );
+ cg.scores[ i ].weapon = atoi( CG_Argv( i * 6 + 8 ) );
+ cg.scores[ i ].upgrade = atoi( CG_Argv( i * 6 + 9 ) );
+
+ if( cg.scores[ i ].client < 0 || cg.scores[ i ].client >= MAX_CLIENTS )
+ cg.scores[ i ].client = 0;
+
+ cgs.clientinfo[ cg.scores[ i ].client ].score = cg.scores[ i ].score;
+ cgs.clientinfo[ cg.scores[ i ].client ].powerups = 0;
+
+ cg.scores[ i ].team = cgs.clientinfo[ cg.scores[ i ].client ].team;
+ }
+}
+
+/*
+=================
+CG_ParseTeamInfo
+
+=================
+*/
+static void CG_ParseTeamInfo( void )
+{
+ int i;
+ int client;
+
+ numSortedTeamPlayers = atoi( CG_Argv( 1 ) );
+
+ for( i = 0; i < numSortedTeamPlayers; i++ )
+ {
+ client = atoi( CG_Argv( i * 6 + 2 ) );
+
+ sortedTeamPlayers[ i ] = client;
+
+ cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) );
+ cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) );
+ cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) );
+ cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) );
+ cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) );
+ }
+}
+
+
+/*
+================
+CG_ParseServerinfo
+
+This is called explicitly when the gamestate is first received,
+and whenever the server updates any serverinfo flagged cvars
+================
+*/
+void CG_ParseServerinfo( void )
+{
+ const char *info;
+ char *mapname;
+
+ info = CG_ConfigString( CS_SERVERINFO );
+ cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) );
+ cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
+ cgs.markDeconstruct = atoi( Info_ValueForKey( info, "g_markDeconstruct" ) );
+ mapname = Info_ValueForKey( info, "mapname" );
+ Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname );
+}
+
+/*
+==================
+CG_ParseWarmup
+==================
+*/
+static void CG_ParseWarmup( void )
+{
+ const char *info;
+ int warmup;
+
+ info = CG_ConfigString( CS_WARMUP );
+
+ warmup = atoi( info );
+ cg.warmupCount = -1;
+
+ if( warmup == 0 && cg.warmup )
+ {
+ }
+
+ cg.warmup = warmup;
+}
+
+/*
+================
+CG_SetConfigValues
+
+Called on load to set the initial values from configure strings
+================
+*/
+void CG_SetConfigValues( void )
+{
+ sscanf( CG_ConfigString( CS_BUILDPOINTS ),
+ "%d %d %d %d %d", &cgs.alienBuildPoints,
+ &cgs.alienBuildPointsTotal,
+ &cgs.humanBuildPoints,
+ &cgs.humanBuildPointsTotal,
+ &cgs.humanBuildPointsPowered );
+
+ sscanf( CG_ConfigString( CS_STAGES ), "%d %d %d %d %d %d", &cgs.alienStage, &cgs.humanStage,
+ &cgs.alienKills, &cgs.humanKills, &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold );
+ sscanf( CG_ConfigString( CS_SPAWNS ), "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
+
+ cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
+ cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
+}
+
+
+/*
+=====================
+CG_ShaderStateChanged
+=====================
+*/
+void CG_ShaderStateChanged( void )
+{
+ char originalShader[ MAX_QPATH ];
+ char newShader[ MAX_QPATH ];
+ char timeOffset[ 16 ];
+ const char *o;
+ char *n, *t;
+
+ o = CG_ConfigString( CS_SHADERSTATE );
+
+ while( o && *o )
+ {
+ n = strstr( o, "=" );
+
+ if( n && *n )
+ {
+ strncpy( originalShader, o, n - o );
+ originalShader[ n - o ] = 0;
+ n++;
+ t = strstr( n, ":" );
+
+ if( t && *t )
+ {
+ strncpy( newShader, n, t - n );
+ newShader[ t - n ] = 0;
+ }
+ else
+ break;
+
+ t++;
+ o = strstr( t, "@" );
+
+ if( o )
+ {
+ strncpy( timeOffset, t, o - t );
+ timeOffset[ o - t ] = 0;
+ o++;
+ trap_R_RemapShader( originalShader, newShader, timeOffset );
+ }
+ }
+ else
+ break;
+ }
+}
+
+/*
+================
+CG_AnnounceAlienStageTransistion
+================
+*/
+static void CG_AnnounceAlienStageTransistion( stage_t from, stage_t to )
+{
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_ALIENS )
+ return;
+
+ trap_S_StartLocalSound( cgs.media.alienStageTransition, CHAN_ANNOUNCER );
+ CG_CenterPrint( "We have evolved!", 200, GIANTCHAR_WIDTH * 4 );
+}
+
+/*
+================
+CG_AnnounceHumanStageTransistion
+================
+*/
+static void CG_AnnounceHumanStageTransistion( stage_t from, stage_t to )
+{
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] != PTE_HUMANS )
+ return;
+
+ trap_S_StartLocalSound( cgs.media.humanStageTransition, CHAN_ANNOUNCER );
+ CG_CenterPrint( "Reinforcements have arrived!", 200, GIANTCHAR_WIDTH * 4 );
+}
+
+/*
+================
+CG_ConfigStringModified
+
+================
+*/
+static void CG_ConfigStringModified( void )
+{
+ const char *str;
+ int num;
+
+ num = atoi( CG_Argv( 1 ) );
+
+ // get the gamestate from the client system, which will have the
+ // new configstring already integrated
+ trap_GetGameState( &cgs.gameState );
+
+ // look up the individual string that was modified
+ str = CG_ConfigString( num );
+
+ // do something with it if necessary
+ if( num == CS_MUSIC )
+ CG_StartMusic( );
+ else if( num == CS_SERVERINFO )
+ CG_ParseServerinfo( );
+ else if( num == CS_WARMUP )
+ CG_ParseWarmup( );
+ else if( num == CS_BUILDPOINTS )
+ sscanf( str, "%d %d %d %d %d", &cgs.alienBuildPoints,
+ &cgs.alienBuildPointsTotal,
+ &cgs.humanBuildPoints,
+ &cgs.humanBuildPointsTotal,
+ &cgs.humanBuildPointsPowered );
+ else if( num == CS_STAGES )
+ {
+ stage_t oldAlienStage = cgs.alienStage;
+ stage_t oldHumanStage = cgs.humanStage;
+
+ sscanf( str, "%d %d %d %d %d %d",
+ &cgs.alienStage, &cgs.humanStage,
+ &cgs.alienKills, &cgs.humanKills,
+ &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold );
+
+ if( cgs.alienStage != oldAlienStage )
+ CG_AnnounceAlienStageTransistion( oldAlienStage, cgs.alienStage );
+
+ if( cgs.humanStage != oldHumanStage )
+ CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage );
+ }
+ else if( num == CS_SPAWNS )
+ sscanf( str, "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
+ else if( num == CS_LEVEL_START_TIME )
+ cgs.levelStartTime = atoi( str );
+ else if( num == CS_VOTE_TIME )
+ {
+ cgs.voteTime = atoi( str );
+ cgs.voteModified = qtrue;
+
+ if( cgs.voteTime )
+ trap_Cvar_Set( "ui_voteActive", "1" );
+ else
+ trap_Cvar_Set( "ui_voteActive", "0" );
+ }
+ else if( num == CS_VOTE_YES )
+ {
+ cgs.voteYes = atoi( str );
+ cgs.voteModified = qtrue;
+ }
+ else if( num == CS_VOTE_NO )
+ {
+ cgs.voteNo = atoi( str );
+ cgs.voteModified = qtrue;
+ }
+ else if( num == CS_VOTE_STRING )
+ Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) );
+ else if( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1 )
+ {
+ int cs_offset = num - CS_TEAMVOTE_TIME;
+
+ cgs.teamVoteTime[ cs_offset ] = atoi( str );
+ cgs.teamVoteModified[ cs_offset ] = qtrue;
+
+ if( cs_offset == 0 )
+ {
+ if( cgs.teamVoteTime[ cs_offset ] )
+ trap_Cvar_Set( "ui_humanTeamVoteActive", "1" );
+ else
+ trap_Cvar_Set( "ui_humanTeamVoteActive", "0" );
+ }
+ else if( cs_offset == 1 )
+ {
+ if( cgs.teamVoteTime[ cs_offset ] )
+ trap_Cvar_Set( "ui_alienTeamVoteActive", "1" );
+ else
+ trap_Cvar_Set( "ui_alienTeamVoteActive", "0" );
+ }
+ }
+ else if( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1 )
+ {
+ cgs.teamVoteYes[ num - CS_TEAMVOTE_YES ] = atoi( str );
+ cgs.teamVoteModified[ num - CS_TEAMVOTE_YES ] = qtrue;
+ }
+ else if( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1 )
+ {
+ cgs.teamVoteNo[ num - CS_TEAMVOTE_NO ] = atoi( str );
+ cgs.teamVoteModified[ num - CS_TEAMVOTE_NO ] = qtrue;
+ }
+ else if( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1 )
+ {
+ Q_strncpyz( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ], str,
+ sizeof( cgs.teamVoteString[ num - CS_TEAMVOTE_STRING ] ) );
+ }
+ else if( num == CS_INTERMISSION )
+ cg.intermissionStarted = atoi( str );
+ else if( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS )
+ cgs.gameModels[ num - CS_MODELS ] = trap_R_RegisterModel( str );
+ else if( num >= CS_SHADERS && num < CS_SHADERS+MAX_GAME_SHADERS )
+ cgs.gameShaders[ num - CS_SHADERS ] = trap_R_RegisterShader( str );
+ else if( num >= CS_PARTICLE_SYSTEMS && num < CS_PARTICLE_SYSTEMS+MAX_GAME_PARTICLE_SYSTEMS )
+ cgs.gameParticleSystems[ num - CS_PARTICLE_SYSTEMS ] = CG_RegisterParticleSystem( (char *)str );
+ else if( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_SOUNDS )
+ {
+ if( str[ 0 ] != '*' )
+ { // player specific sounds don't register here
+ cgs.gameSounds[ num - CS_SOUNDS ] = trap_S_RegisterSound( str, qfalse );
+ }
+ }
+ else if( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS )
+ {
+ CG_NewClientInfo( num - CS_PLAYERS );
+ CG_BuildSpectatorString( );
+ }
+ else if( num == CS_WINNER )
+ {
+ trap_Cvar_Set( "ui_winner", str );
+ }
+ else if( num == CS_SHADERSTATE )
+ {
+ CG_ShaderStateChanged( );
+ }
+}
+
+
+/*
+===============
+CG_MapRestart
+
+The server has issued a map_restart, so the next snapshot
+is completely new and should not be interpolated to.
+
+A tournement restart will clear everything, but doesn't
+require a reload of all the media
+===============
+*/
+static void CG_MapRestart( void )
+{
+ if( cg_showmiss.integer )
+ CG_Printf( "CG_MapRestart\n" );
+
+ CG_InitMarkPolys( );
+
+ // make sure the "3 frags left" warnings play again
+ cg.fraglimitWarnings = 0;
+
+ cg.timelimitWarnings = 0;
+
+ cg.intermissionStarted = qfalse;
+
+ cgs.voteTime = 0;
+
+ cg.mapRestart = qtrue;
+
+ CG_StartMusic( );
+
+ trap_S_ClearLoopingSounds( qtrue );
+
+ // we really should clear more parts of cg here and stop sounds
+
+ // play the "fight" sound if this is a restart without warmup
+ if( cg.warmup == 0 )
+ CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH * 2 );
+
+ trap_Cvar_Set( "cg_thirdPerson", "0" );
+}
+
+/*
+=================
+CG_RemoveChatEscapeChar
+=================
+*/
+static void CG_RemoveChatEscapeChar( char *text )
+{
+ int i, l;
+
+ l = 0;
+ for( i = 0; text[ i ]; i++ )
+ {
+ if( text[ i ] == '\x19' )
+ continue;
+
+ text[ l++ ] = text[ i ];
+ }
+
+ text[ l ] = '\0';
+}
+
+/*
+===============
+CG_SetUIVars
+
+Set some cvars used by the UI
+===============
+*/
+static void CG_SetUIVars( void )
+{
+ int i;
+ char carriageCvar[ MAX_TOKEN_CHARS ];
+
+ *carriageCvar = 0;
+
+ //determine what the player is carrying
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) &&
+ BG_FindPurchasableForWeapon( i ) )
+ strcat( carriageCvar, va( "W%d ", i ) );
+ }
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) &&
+ BG_FindPurchasableForUpgrade( i ) )
+ strcat( carriageCvar, va( "U%d ", i ) );
+ }
+ strcat( carriageCvar, "$" );
+
+ trap_Cvar_Set( "ui_carriage", carriageCvar );
+
+ trap_Cvar_Set( "ui_stages", va( "%d %d", cgs.alienStage, cgs.humanStage ) );
+}
+
+
+/*
+==============
+CG_Menu
+==============
+*/
+void CG_Menu( int menu )
+{
+ const char *cmd = NULL; // command to send
+ const char *longMsg = NULL; // command parameter
+ const char *shortMsg = NULL; // non-modal version of message
+ CG_SetUIVars( );
+
+ // string literals have static storage duration, this is safe,
+ // cleaner and much more readable.
+ switch( menu )
+ {
+ case MN_TEAM:
+ cmd = "menu tremulous_teamselect\n";
+ break;
+
+ case MN_A_CLASS:
+ cmd = "menu tremulous_alienclass\n";
+ break;
+
+ case MN_H_SPAWN:
+ cmd = "menu tremulous_humanitem\n";
+ break;
+
+ case MN_A_BUILD:
+ cmd = "menu tremulous_alienbuild\n";
+ break;
+
+ case MN_H_BUILD:
+ cmd = "menu tremulous_humanbuild\n";
+ break;
+
+ case MN_H_ARMOURY:
+ cmd = "menu tremulous_humanarmoury\n";
+ break;
+
+ case MN_A_TEAMFULL:
+ longMsg = "The alien team has too many players. Please wait until slots "
+ "become available or join the human team.";
+ shortMsg = "The alien team has too many players\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_H_TEAMFULL:
+ longMsg = "The human team has too many players. Please wait until slots "
+ "become available or join the alien team.";
+ shortMsg = "The human team has too many players\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_A_TEAMCHANGEBUILDTIMER:
+ longMsg = "You cannot leave the Alien team until your build timer "
+ "has expired.";
+ shortMsg = "You cannot change teams until your build timer expires.\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_H_TEAMCHANGEBUILDTIMER:
+ longMsg = "You cannot leave the Human team until your build timer "
+ "has expired.";
+ shortMsg = "You cannot change teams until your build timer expires.\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ //===============================
+
+ case MN_H_NOROOM:
+ longMsg = "There is no room to build here. Move until the buildable turns "
+ "translucent green indicating a valid build location.";
+ shortMsg = "There is no room to build here\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOPOWER:
+ longMsg = "There is no power remaining. Free up power by destroying "
+ "existing buildable objects.";
+ shortMsg = "There is no power remaining\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOTPOWERED:
+ longMsg = "This buildable is not powered. Build a Reactor and/or Repeater "
+ "in order to power it.";
+ shortMsg = "This buildable is not powered\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NORMAL:
+ longMsg = "Cannot build on this surface. The surface is too steep or "
+ "unsuitable to build on. Please choose another site for this "
+ "structure.";
+ shortMsg = "Cannot build on this surface\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_REACTOR:
+ longMsg = "There can only be one Reactor. Destroy the existing one if you "
+ "wish to move it.";
+ shortMsg = "There can only be one Reactor\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_REPEATER:
+ longMsg = "There is no power here. If available, a Repeater may be used to "
+ "transmit power to this location.";
+ shortMsg = "There is no power here\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NODCC:
+ longMsg = "There is no Defense Computer. A Defense Computer is needed to "
+ "build this.";
+ shortMsg = "There is no Defense Computer\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_TNODEWARN:
+ longMsg = "WARNING: This Telenode will not be powered. Build near a power "
+ "structure to prevent seeing this message again.";
+ shortMsg = "This Telenode will not be powered\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_RPTWARN:
+ longMsg = "WARNING: This Repeater will not be powered as there is no parent "
+ "Reactor providing power. Build a Reactor.";
+ shortMsg = "This Repeater will not be powered\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_RPTWARN2:
+ longMsg = "This area already has power. A Repeater is not required here.";
+ shortMsg = "This area already has power\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOSLOTS:
+ longMsg = "You have no room to carry this. Please sell any conflicting "
+ "upgrades before purchasing this item.";
+ shortMsg = "You have no room to carry this\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOFUNDS:
+ longMsg = "Insufficient funds. You do not have enough credits to perform "
+ "this action.";
+ shortMsg = "Insufficient funds\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_ITEMHELD:
+ longMsg = "You already hold this item. It is not possible to carry multiple "
+ "items of the same type.";
+ shortMsg = "You already hold this item\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOARMOURYHERE:
+ longMsg = "You must be near a powered Armoury in order to purchase "
+ "weapons, upgrades or non-energy ammunition.";
+ shortMsg = "You must be near a powered Armoury\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOENERGYAMMOHERE:
+ longMsg = "You must be near an Armoury, Reactor or Repeater in order "
+ "to purchase energy ammunition.";
+ shortMsg = "You must be near an Armoury, Reactor or Repeater\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOROOMBSUITON:
+ longMsg = "There is not enough room here to put on a Battle Suit. "
+ "Make sure you have enough head room to climb in.";
+ shortMsg = "Not enough room here to put on a Battle Suit\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_NOROOMBSUITOFF:
+ longMsg = "There is not enough room here to take off your Battle Suit. "
+ "Make sure you have enough head room to climb out.";
+ shortMsg = "Not enough room here to take off your Battle Suit\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+ case MN_H_ARMOURYBUILDTIMER:
+ longMsg = "You are not allowed to buy or sell weapons until your "
+ "build timer has expired.";
+ shortMsg = "You can not buy or sell weapos until your build timer "
+ "expires\n";
+ cmd = "menu tremulous_human_dialog\n";
+ break;
+
+
+ //===============================
+
+ case MN_A_NOROOM:
+ longMsg = "There is no room to build here. Move until the structure turns "
+ "translucent green indicating a valid build location.";
+ shortMsg = "There is no room to build here\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOCREEP:
+ longMsg = "There is no creep here. You must build near existing Eggs or "
+ "the Overmind. Alien structures will not support themselves.";
+ shortMsg = "There is no creep here\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOOVMND:
+ longMsg = "There is no Overmind. An Overmind must be built to control "
+ "the structure you tried to place";
+ shortMsg = "There is no Overmind\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_OVERMIND:
+ longMsg = "There can only be one Overmind. Destroy the existing one if you "
+ "wish to move it.";
+ shortMsg = "There can only be one Overmind\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_HOVEL:
+ longMsg = "There can only be one Hovel. Destroy the existing one if you "
+ "wish to move it.";
+ shortMsg = "There can only be one Hovel\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOASSERT:
+ longMsg = "The Overmind cannot control any more structures. Destroy existing "
+ "structures to build more.";
+ shortMsg = "The Overmind cannot control any more structures\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_SPWNWARN:
+ longMsg = "WARNING: This spawn will not be controlled by an Overmind. "
+ "Build an Overmind to prevent seeing this message again.";
+ shortMsg = "This spawn will not be controlled by an Overmind\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NORMAL:
+ longMsg = "Cannot build on this surface. This surface is too steep or "
+ "unsuitable to build on. Please choose another site for this "
+ "structure.";
+ shortMsg = "Cannot build on this surface\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOEROOM:
+ longMsg = "There is no room to evolve here. Move away from walls or other "
+ "nearby objects and try again.";
+ cmd = "menu tremulous_alien_dialog\n";
+ shortMsg = "There is no room to evolve here\n";
+ break;
+
+ case MN_A_TOOCLOSE:
+ longMsg = "This location is too close to the enemy to evolve. Move away "
+ "until you are no longer aware of the enemy's presence and try "
+ "again.";
+ shortMsg = "This location is too close to the enemy to evolve\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_NOOVMND_EVOLVE:
+ longMsg = "There is no Overmind. An Overmind must be built to allow "
+ "you to upgrade.";
+ shortMsg = "There is no Overmind\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_EVOLVEBUILDTIMER:
+ longMsg = "You cannot Evolve until your build timer has expired.";
+ shortMsg = "You cannot Evolve until your build timer expires\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_HOVEL_OCCUPIED:
+ longMsg = "This Hovel is already occupied by another builder.";
+ shortMsg = "This Hovel is already occupied by another builder\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_HOVEL_BLOCKED:
+ longMsg = "The exit to this Hovel is currently blocked. Please wait until it "
+ "becomes clear then try again.";
+ shortMsg = "The exit to this Hovel is currently blocked\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_HOVEL_EXIT:
+ longMsg = "The exit to this Hovel would always be blocked. Please choose "
+ "a more suitable location.";
+ shortMsg = "The exit to this Hovel would always be blocked\n";
+ cmd = "menu tremulous_alien_dialog\n";
+ break;
+
+ case MN_A_INFEST:
+ trap_Cvar_Set( "ui_currentClass", va( "%d %d", cg.snap->ps.stats[ STAT_PCLASS ],
+ cg.snap->ps.persistant[ PERS_CREDIT ] ) );
+ cmd = "menu tremulous_alienupgrade\n";
+ break;
+
+ default:
+ Com_Printf( "cgame: debug: no such menu %d\n", menu );
+ }
+
+ if( !cg_disableWarningDialogs.integer || !shortMsg )
+ {
+ // Player either wants dialog window or there's no short message
+ if( cmd )
+ {
+ if( longMsg )
+ trap_Cvar_Set( "ui_dialog", longMsg );
+
+ trap_SendConsoleCommand( cmd );
+ }
+ }
+ else
+ {
+ // There is short message and player wants it
+ CG_Printf( shortMsg );
+ }
+}
+
+/*
+=================
+CG_ServerCommand
+
+The string has been tokenized and can be retrieved with
+Cmd_Argc() / Cmd_Argv()
+=================
+*/
+static void CG_ServerCommand( void )
+{
+ const char *cmd;
+ char text[ MAX_SAY_TEXT ];
+
+ cmd = CG_Argv( 0 );
+
+ if( !cmd[ 0 ] )
+ {
+ // server claimed the command
+ return;
+ }
+
+ if( !strcmp( cmd, "cp" ) )
+ {
+ CG_CenterPrint( CG_Argv( 1 ), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH );
+ return;
+ }
+
+ if( !strcmp( cmd, "cs" ) )
+ {
+ CG_ConfigStringModified( );
+ return;
+ }
+
+ if( !strcmp( cmd, "print" ) )
+ {
+ CG_Printf( "%s", CG_Argv( 1 ) );
+ return;
+ }
+
+ if( !strcmp( cmd, "chat" ) )
+ {
+ if( !cg_teamChatsOnly.integer )
+ {
+ Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT );
+ if( Q_stricmpn( text, "[skipnotify]", 12 ) )
+ trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
+ CG_RemoveChatEscapeChar( text );
+ CG_Printf( "%s\n", text );
+ }
+
+ return;
+ }
+
+ if( !strcmp( cmd, "tchat" ) )
+ {
+ Q_strncpyz( text, CG_Argv( 1 ), MAX_SAY_TEXT );
+ if( Q_stricmpn( text, "[skipnotify]", 12 ) )
+ {
+ if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ trap_S_StartLocalSound( cgs.media.alienTalkSound, CHAN_LOCAL_SOUND );
+ else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ trap_S_StartLocalSound( cgs.media.humanTalkSound, CHAN_LOCAL_SOUND );
+ else
+ trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
+ }
+ CG_RemoveChatEscapeChar( text );
+ CG_Printf( "%s\n", text );
+ return;
+ }
+
+ if( !strcmp( cmd, "scores" ) )
+ {
+ CG_ParseScores( );
+ return;
+ }
+
+ if( !strcmp( cmd, "tinfo" ) )
+ {
+ CG_ParseTeamInfo( );
+ return;
+ }
+
+ if( !strcmp( cmd, "map_restart" ) )
+ {
+ CG_MapRestart( );
+ return;
+ }
+
+ if( Q_stricmp( cmd, "remapShader" ) == 0 )
+ {
+ if( trap_Argc( ) == 4 )
+ trap_R_RemapShader( CG_Argv( 1 ), CG_Argv( 2 ), CG_Argv( 3 ) );
+ }
+
+ // clientLevelShot is sent before taking a special screenshot for
+ // the menu system during development
+ if( !strcmp( cmd, "clientLevelShot" ) )
+ {
+ cg.levelShot = qtrue;
+ return;
+ }
+
+ //the server has triggered a menu
+ if( !strcmp( cmd, "servermenu" ) )
+ {
+ if( trap_Argc( ) == 2 && !cg.demoPlayback )
+ CG_Menu( atoi( CG_Argv( 1 ) ) );
+
+ return;
+ }
+
+ //the server thinks this client should close all menus
+ if( !strcmp( cmd, "serverclosemenus" ) )
+ {
+ trap_SendConsoleCommand( "closemenus\n" );
+ return;
+ }
+
+ //poison cloud effect needs to be reliable
+ if( !strcmp( cmd, "poisoncloud" ) )
+ {
+ cg.poisonedTime = cg.time;
+
+ if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) )
+ {
+ cg.poisonCloudPS = CG_SpawnNewParticleSystem( cgs.media.poisonCloudPS );
+ CG_SetAttachmentCent( &cg.poisonCloudPS->attachment, &cg.predictedPlayerEntity );
+ CG_AttachToCent( &cg.poisonCloudPS->attachment );
+ }
+
+ return;
+ }
+
+ if( !strcmp( cmd, "weaponswitch" ) )
+ {
+ CG_Printf( "client weaponswitch\n" );
+ if( trap_Argc( ) == 2 )
+ {
+ cg.weaponSelect = atoi( CG_Argv( 1 ) );
+ cg.weaponSelectTime = cg.time;
+ }
+
+ return;
+ }
+
+ // server requests a ptrc
+ if( !strcmp( cmd, "ptrcrequest" ) )
+ {
+ int code = CG_ReadPTRCode( );
+
+ trap_SendClientCommand( va( "ptrcverify %d", code ) );
+ return;
+ }
+
+ // server issues a ptrc
+ if( !strcmp( cmd, "ptrcissue" ) )
+ {
+ if( trap_Argc( ) == 2 )
+ {
+ int code = atoi( CG_Argv( 1 ) );
+
+ CG_WritePTRCode( code );
+ }
+
+ return;
+ }
+
+ // reply to ptrcverify
+ if( !strcmp( cmd, "ptrcconfirm" ) )
+ {
+ trap_SendConsoleCommand( "menu ptrc_popmenu\n" );
+
+ return;
+ }
+
+ CG_Printf( "Unknown client game command: %s\n", cmd );
+}
+
+
+/*
+====================
+CG_ExecuteNewServerCommands
+
+Execute all of the server commands that were received along
+with this this snapshot.
+====================
+*/
+void CG_ExecuteNewServerCommands( int latestSequence )
+{
+ while( cgs.serverCommandSequence < latestSequence )
+ {
+ if( trap_GetServerCommand( ++cgs.serverCommandSequence ) )
+ CG_ServerCommand( );
+ }
+}
diff --git a/src/cgame/cg_snapshot.c b/src/cgame/cg_snapshot.c
new file mode 100644
index 0000000..f439a69
--- /dev/null
+++ b/src/cgame/cg_snapshot.c
@@ -0,0 +1,411 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_snapshot.c -- things that happen on snapshot transition,
+// not necessarily every single rendered frame
+
+
+#include "cg_local.h"
+
+/*
+==================
+CG_ResetEntity
+==================
+*/
+static void CG_ResetEntity( centity_t *cent )
+{
+ // if the previous snapshot this entity was updated in is at least
+ // an event window back in time then we can reset the previous event
+ if( cent->snapShotTime < cg.time - EVENT_VALID_MSEC )
+ cent->previousEvent = 0;
+
+ cent->trailTime = cg.snap->serverTime;
+
+ VectorCopy( cent->currentState.origin, cent->lerpOrigin );
+ VectorCopy( cent->currentState.angles, cent->lerpAngles );
+
+ if( cent->currentState.eType == ET_PLAYER )
+ CG_ResetPlayerEntity( cent );
+}
+
+/*
+===============
+CG_TransitionEntity
+
+cent->nextState is moved to cent->currentState and events are fired
+===============
+*/
+static void CG_TransitionEntity( centity_t *cent )
+{
+ cent->currentState = cent->nextState;
+ cent->currentValid = qtrue;
+
+ // reset if the entity wasn't in the last frame or was teleported
+ if( !cent->interpolate )
+ CG_ResetEntity( cent );
+
+ // clear the next state. if will be set by the next CG_SetNextSnap
+ cent->interpolate = qfalse;
+
+ // check for events
+ CG_CheckEvents( cent );
+}
+
+
+/*
+==================
+CG_SetInitialSnapshot
+
+This will only happen on the very first snapshot, or
+on tourney restarts. All other times will use
+CG_TransitionSnapshot instead.
+
+FIXME: Also called by map_restart?
+==================
+*/
+void CG_SetInitialSnapshot( snapshot_t *snap )
+{
+ int i;
+ centity_t *cent;
+ entityState_t *state;
+
+ cg.snap = snap;
+
+ BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse );
+
+ // sort out solid entities
+ CG_BuildSolidList( );
+
+ CG_ExecuteNewServerCommands( snap->serverCommandSequence );
+
+ // set our local weapon selection pointer to
+ // what the server has indicated the current weapon is
+ CG_Respawn( );
+
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ state = &cg.snap->entities[ i ];
+ cent = &cg_entities[ state->number ];
+
+ memcpy( &cent->currentState, state, sizeof( entityState_t ) );
+ //cent->currentState = *state;
+ cent->interpolate = qfalse;
+ cent->currentValid = qtrue;
+
+ CG_ResetEntity( cent );
+
+ // check for events
+ CG_CheckEvents( cent );
+ }
+}
+
+
+/*
+===================
+CG_TransitionSnapshot
+
+The transition point from snap to nextSnap has passed
+===================
+*/
+static void CG_TransitionSnapshot( void )
+{
+ centity_t *cent;
+ snapshot_t *oldFrame;
+ int i;
+
+ if( !cg.snap )
+ CG_Error( "CG_TransitionSnapshot: NULL cg.snap" );
+
+ if( !cg.nextSnap )
+ CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" );
+
+ // execute any server string commands before transitioning entities
+ CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence );
+
+ // if we had a map_restart, set everthing with initial
+ if( !cg.snap ) { } //TA: ?
+
+ // clear the currentValid flag for all entities in the existing snapshot
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ i ].number ];
+ cent->currentValid = qfalse;
+ }
+
+ // move nextSnap to snap and do the transitions
+ oldFrame = cg.snap;
+ cg.snap = cg.nextSnap;
+
+ BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse );
+ cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse;
+
+ for( i = 0; i < cg.snap->numEntities; i++ )
+ {
+ cent = &cg_entities[ cg.snap->entities[ i ].number ];
+ CG_TransitionEntity( cent );
+
+ // remember time of snapshot this entity was last updated in
+ cent->snapShotTime = cg.snap->serverTime;
+ }
+
+ cg.nextSnap = NULL;
+
+ // check for playerstate transition events
+ if( oldFrame )
+ {
+ playerState_t *ops, *ps;
+
+ ops = &oldFrame->ps;
+ ps = &cg.snap->ps;
+ // teleporting checks are irrespective of prediction
+ if( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT )
+ cg.thisFrameTeleport = qtrue; // will be cleared by prediction code
+
+ // if we are not doing client side movement prediction for any
+ // reason, then the client events and view changes will be issued now
+ if( cg.demoPlayback || ( cg.snap->ps.pm_flags & PMF_FOLLOW ) ||
+ cg_nopredict.integer || cg_synchronousClients.integer )
+ CG_TransitionPlayerState( ps, ops );
+ }
+}
+
+
+/*
+===================
+CG_SetNextSnap
+
+A new snapshot has just been read in from the client system.
+===================
+*/
+static void CG_SetNextSnap( snapshot_t *snap )
+{
+ int num;
+ entityState_t *es;
+ centity_t *cent;
+
+ cg.nextSnap = snap;
+
+ BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse );
+ cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue;
+
+ // check for extrapolation errors
+ for( num = 0 ; num < snap->numEntities ; num++ )
+ {
+ es = &snap->entities[ num ];
+ cent = &cg_entities[ es->number ];
+
+ memcpy( &cent->nextState, es, sizeof( entityState_t ) );
+ //cent->nextState = *es;
+
+ // if this frame is a teleport, or the entity wasn't in the
+ // previous frame, don't interpolate
+ if( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) )
+ cent->interpolate = qfalse;
+ else
+ cent->interpolate = qtrue;
+ }
+
+ // if the next frame is a teleport for the playerstate, we
+ // can't interpolate during demos
+ if( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) )
+ cg.nextFrameTeleport = qtrue;
+ else
+ cg.nextFrameTeleport = qfalse;
+
+ // if changing follow mode, don't interpolate
+ if( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum )
+ cg.nextFrameTeleport = qtrue;
+
+ // if changing server restarts, don't interpolate
+ if( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT )
+ cg.nextFrameTeleport = qtrue;
+
+ // sort out solid entities
+ CG_BuildSolidList( );
+}
+
+
+/*
+========================
+CG_ReadNextSnapshot
+
+This is the only place new snapshots are requested
+This may increment cgs.processedSnapshotNum multiple
+times if the client system fails to return a
+valid snapshot.
+========================
+*/
+static snapshot_t *CG_ReadNextSnapshot( void )
+{
+ qboolean r;
+ snapshot_t *dest;
+
+ if( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 )
+ {
+ CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i",
+ cg.latestSnapshotNum, cgs.processedSnapshotNum );
+ }
+
+ while( cgs.processedSnapshotNum < cg.latestSnapshotNum )
+ {
+ // decide which of the two slots to load it into
+ if( cg.snap == &cg.activeSnapshots[ 0 ] )
+ dest = &cg.activeSnapshots[ 1 ];
+ else
+ dest = &cg.activeSnapshots[ 0 ];
+
+ // try to read the snapshot from the client system
+ cgs.processedSnapshotNum++;
+ r = trap_GetSnapshot( cgs.processedSnapshotNum, dest );
+
+ // FIXME: why would trap_GetSnapshot return a snapshot with the same server time
+ if( cg.snap && r && dest->serverTime == cg.snap->serverTime )
+ {
+ //continue;
+ }
+
+ // if it succeeded, return
+ if( r )
+ {
+ CG_AddLagometerSnapshotInfo( dest );
+ return dest;
+ }
+
+ // a GetSnapshot will return failure if the snapshot
+ // never arrived, or is so old that its entities
+ // have been shoved off the end of the circular
+ // buffer in the client system.
+
+ // record as a dropped packet
+ CG_AddLagometerSnapshotInfo( NULL );
+
+ // If there are additional snapshots, continue trying to
+ // read them.
+ }
+
+ // nothing left to read
+ return NULL;
+}
+
+
+/*
+============
+CG_ProcessSnapshots
+
+We are trying to set up a renderable view, so determine
+what the simulated time is, and try to get snapshots
+both before and after that time if available.
+
+If we don't have a valid cg.snap after exiting this function,
+then a 3D game view cannot be rendered. This should only happen
+right after the initial connection. After cg.snap has been valid
+once, it will never turn invalid.
+
+Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot
+hasn't arrived yet (it becomes an extrapolating situation instead
+of an interpolating one)
+
+============
+*/
+void CG_ProcessSnapshots( void )
+{
+ snapshot_t *snap;
+ int n;
+
+ // see what the latest snapshot the client system has is
+ trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime );
+
+ if( n != cg.latestSnapshotNum )
+ {
+ if( n < cg.latestSnapshotNum )
+ {
+ // this should never happen
+ CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" );
+ }
+
+ cg.latestSnapshotNum = n;
+ }
+
+ // If we have yet to receive a snapshot, check for it.
+ // Once we have gotten the first snapshot, cg.snap will
+ // always have valid data for the rest of the game
+ while( !cg.snap )
+ {
+ snap = CG_ReadNextSnapshot( );
+
+ if( !snap )
+ {
+ // we can't continue until we get a snapshot
+ return;
+ }
+
+ // set our weapon selection to what
+ // the playerstate is currently using
+ if( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) )
+ CG_SetInitialSnapshot( snap );
+ }
+
+ // loop until we either have a valid nextSnap with a serverTime
+ // greater than cg.time to interpolate towards, or we run
+ // out of available snapshots
+ do
+ {
+ // if we don't have a nextframe, try and read a new one in
+ if( !cg.nextSnap )
+ {
+ snap = CG_ReadNextSnapshot( );
+
+ // if we still don't have a nextframe, we will just have to
+ // extrapolate
+ if( !snap )
+ break;
+
+ CG_SetNextSnap( snap );
+
+ // if time went backwards, we have a level restart
+ if( cg.nextSnap->serverTime < cg.snap->serverTime )
+ CG_Error( "CG_ProcessSnapshots: Server time went backwards" );
+ }
+
+ // if our time is < nextFrame's, we have a nice interpolating state
+ if( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime )
+ break;
+
+ // we have passed the transition from nextFrame to frame
+ CG_TransitionSnapshot( );
+ } while( 1 );
+
+ // assert our valid conditions upon exiting
+ if( cg.snap == NULL )
+ CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" );
+
+ if( cg.time < cg.snap->serverTime )
+ {
+ // this can happen right after a vid_restart
+ cg.time = cg.snap->serverTime;
+ }
+
+ if( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time )
+ CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" );
+}
+
diff --git a/src/cgame/cg_syscalls.asm b/src/cgame/cg_syscalls.asm
new file mode 100644
index 0000000..0893ebc
--- /dev/null
+++ b/src/cgame/cg_syscalls.asm
@@ -0,0 +1,115 @@
+code
+
+equ trap_Print -1
+equ trap_Error -2
+equ trap_Milliseconds -3
+equ trap_Cvar_Register -4
+equ trap_Cvar_Update -5
+equ trap_Cvar_Set -6
+equ trap_Cvar_VariableStringBuffer -7
+equ trap_Argc -8
+equ trap_Argv -9
+equ trap_Args -10
+equ trap_FS_FOpenFile -11
+equ trap_FS_Read -12
+equ trap_FS_Write -13
+equ trap_FS_FCloseFile -14
+equ trap_SendConsoleCommand -15
+equ trap_AddCommand -16
+equ trap_SendClientCommand -17
+equ trap_UpdateScreen -18
+equ trap_CM_LoadMap -19
+equ trap_CM_NumInlineModels -20
+equ trap_CM_InlineModel -21
+equ trap_CM_LoadModel -22
+equ trap_CM_TempBoxModel -23
+equ trap_CM_PointContents -24
+equ trap_CM_TransformedPointContents -25
+equ trap_CM_BoxTrace -26
+equ trap_CM_TransformedBoxTrace -27
+equ trap_CM_MarkFragments -28
+equ trap_S_StartSound -29
+equ trap_S_StartLocalSound -30
+equ trap_S_ClearLoopingSounds -31
+equ trap_S_AddLoopingSound -32
+equ trap_S_UpdateEntityPosition -33
+equ trap_S_Respatialize -34
+equ trap_S_RegisterSound -35
+equ trap_S_StartBackgroundTrack -36
+equ trap_R_LoadWorldMap -37
+equ trap_R_RegisterModel -38
+equ trap_R_RegisterSkin -39
+equ trap_R_RegisterShader -40
+equ trap_R_ClearScene -41
+equ trap_R_AddRefEntityToScene -42
+equ trap_R_AddPolyToScene -43
+equ trap_R_AddLightToScene -44
+equ trap_R_RenderScene -45
+equ trap_R_SetColor -46
+equ trap_R_DrawStretchPic -47
+equ trap_R_ModelBounds -48
+equ trap_R_LerpTag -49
+equ trap_GetGlconfig -50
+equ trap_GetGameState -51
+equ trap_GetCurrentSnapshotNumber -52
+equ trap_GetSnapshot -53
+equ trap_GetServerCommand -54
+equ trap_GetCurrentCmdNumber -55
+equ trap_GetUserCmd -56
+equ trap_SetUserCmdValue -57
+equ trap_R_RegisterShaderNoMip -58
+equ trap_MemoryRemaining -59
+equ trap_R_RegisterFont -60
+equ trap_Key_IsDown -61
+equ trap_Key_GetCatcher -62
+equ trap_Key_SetCatcher -63
+equ trap_Key_GetKey -64
+equ trap_Parse_AddGlobalDefine -65
+equ trap_Parse_LoadSource -66
+equ trap_Parse_FreeSource -67
+equ trap_Parse_ReadToken -68
+equ trap_Parse_SourceFileAndLine -69
+equ trap_S_StopBackgroundTrack -70
+equ trap_RealTime -71
+equ trap_SnapVector -72
+equ trap_RemoveCommand -73
+equ trap_R_LightForPoint -74
+equ trap_CIN_PlayCinematic -75
+equ trap_CIN_StopCinematic -76
+equ trap_CIN_RunCinematic -77
+equ trap_CIN_DrawCinematic -78
+equ trap_CIN_SetExtents -79
+equ trap_R_RemapShader -80
+equ trap_S_AddRealLoopingSound -81
+equ trap_S_StopLoopingSound -82
+equ trap_CM_TempCapsuleModel -83
+equ trap_CM_CapsuleTrace -84
+equ trap_CM_TransformedCapsuleTrace -85
+equ trap_R_AddAdditiveLightToScene -86
+equ trap_GetEntityToken -87
+equ trap_R_AddPolysToScene -88
+equ trap_R_inPVS -89
+equ trap_FS_Seek -90
+equ trap_FS_GetFileList -91
+equ trap_LiteralArgs -92
+equ trap_CM_BiSphereTrace -93
+equ trap_CM_TransformedBiSphereTrace -94
+equ trap_GetDemoState -95
+equ trap_GetDemoPos -96
+equ trap_GetDemoName -97
+equ trap_Key_KeynumToStringBuf -98
+equ trap_Key_GetBindingBuf -99
+equ trap_Key_SetBinding -100
+
+equ memset -201
+equ memcpy -202
+equ strncpy -203
+equ sin -204
+equ cos -205
+equ atan2 -206
+equ sqrt -207
+equ floor -208
+equ ceil -209
+equ testPrintInt -210
+equ testPrintFloat -211
+
diff --git a/src/cgame/cg_syscalls.c b/src/cgame/cg_syscalls.c
new file mode 100644
index 0000000..43afc2c
--- /dev/null
+++ b/src/cgame/cg_syscalls.c
@@ -0,0 +1,570 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_syscalls.c -- this file is only included when building a dll
+// cg_syscalls.asm is included instead when building a qvm
+
+
+#include "cg_local.h"
+
+static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1;
+
+
+void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) )
+{
+ syscall = syscallptr;
+}
+
+
+int PASSFLOAT( float x )
+{
+ float floatTemp;
+ floatTemp = x;
+ return *(int *)&floatTemp;
+}
+
+void trap_Print( const char *fmt )
+{
+ syscall( CG_PRINT, fmt );
+}
+
+void trap_Error( const char *fmt )
+{
+ syscall( CG_ERROR, fmt );
+}
+
+int trap_Milliseconds( void )
+{
+ return syscall( CG_MILLISECONDS );
+}
+
+void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags )
+{
+ syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags );
+}
+
+void trap_Cvar_Update( vmCvar_t *vmCvar )
+{
+ syscall( CG_CVAR_UPDATE, vmCvar );
+}
+
+void trap_Cvar_Set( const char *var_name, const char *value )
+{
+ syscall( CG_CVAR_SET, var_name, value );
+}
+
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize )
+{
+ syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize );
+}
+
+int trap_Argc( void )
+{
+ return syscall( CG_ARGC );
+}
+
+void trap_Argv( int n, char *buffer, int bufferLength )
+{
+ syscall( CG_ARGV, n, buffer, bufferLength );
+}
+
+void trap_Args( char *buffer, int bufferLength )
+{
+ syscall( CG_ARGS, buffer, bufferLength );
+}
+
+void trap_LiteralArgs( char *buffer, int bufferLength )
+{
+ syscall( CG_LITERAL_ARGS, buffer, bufferLength );
+}
+
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode )
+{
+ return syscall( CG_FS_FOPENFILE, qpath, f, mode );
+}
+
+void trap_FS_Read( void *buffer, int len, fileHandle_t f )
+{
+ syscall( CG_FS_READ, buffer, len, f );
+}
+
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f )
+{
+ syscall( CG_FS_WRITE, buffer, len, f );
+}
+
+void trap_FS_FCloseFile( fileHandle_t f )
+{
+ syscall( CG_FS_FCLOSEFILE, f );
+}
+
+void trap_FS_Seek( fileHandle_t f, long offset, fsOrigin_t origin )
+{
+ syscall( CG_FS_SEEK, f, offset, origin );
+}
+
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize )
+{
+ return syscall( CG_FS_GETFILELIST, path, extension, listbuf, bufsize );
+}
+
+void trap_SendConsoleCommand( const char *text )
+{
+ syscall( CG_SENDCONSOLECOMMAND, text );
+}
+
+void trap_AddCommand( const char *cmdName )
+{
+ syscall( CG_ADDCOMMAND, cmdName );
+}
+
+void trap_RemoveCommand( const char *cmdName )
+{
+ syscall( CG_REMOVECOMMAND, cmdName );
+}
+
+void trap_SendClientCommand( const char *s )
+{
+ syscall( CG_SENDCLIENTCOMMAND, s );
+}
+
+void trap_UpdateScreen( void )
+{
+ syscall( CG_UPDATESCREEN );
+}
+
+void trap_CM_LoadMap( const char *mapname )
+{
+ syscall( CG_CM_LOADMAP, mapname );
+}
+
+int trap_CM_NumInlineModels( void )
+{
+ return syscall( CG_CM_NUMINLINEMODELS );
+}
+
+clipHandle_t trap_CM_InlineModel( int index )
+{
+ return syscall( CG_CM_INLINEMODEL, index );
+}
+
+clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs )
+{
+ return syscall( CG_CM_TEMPBOXMODEL, mins, maxs );
+}
+
+clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs )
+{
+ return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs );
+}
+
+int trap_CM_PointContents( const vec3_t p, clipHandle_t model )
+{
+ return syscall( CG_CM_POINTCONTENTS, p, model );
+}
+
+int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin,
+ const vec3_t angles )
+{
+ return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles );
+}
+
+void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask )
+{
+ syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask );
+}
+
+void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask )
+{
+ syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask );
+}
+
+void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles )
+{
+ syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles );
+}
+
+void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end,
+ const vec3_t mins, const vec3_t maxs,
+ clipHandle_t model, int brushmask,
+ const vec3_t origin, const vec3_t angles )
+{
+ syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles );
+}
+
+void trap_CM_BiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask )
+{
+ syscall( CG_CM_BISPHERETRACE, results, start, end,
+ PASSFLOAT( startRad ), PASSFLOAT( endRad ), model, mask );
+}
+
+void trap_CM_TransformedBiSphereTrace( trace_t *results, const vec3_t start,
+ const vec3_t end, float startRad, float endRad,
+ clipHandle_t model, int mask,
+ const vec3_t origin )
+{
+ syscall( CG_CM_TRANSFORMEDBISPHERETRACE, results, start, end, PASSFLOAT( startRad ),
+ PASSFLOAT( endRad ), model, mask, origin );
+}
+
+int trap_CM_MarkFragments( int numPoints, const vec3_t *points,
+ const vec3_t projection,
+ int maxPoints, vec3_t pointBuffer,
+ int maxFragments, markFragment_t *fragmentBuffer )
+{
+ return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints,
+ pointBuffer, maxFragments, fragmentBuffer );
+}
+
+void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx )
+{
+ syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx );
+}
+
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum )
+{
+ syscall( CG_S_STARTLOCALSOUND, sfx, channelNum );
+}
+
+void trap_S_ClearLoopingSounds( qboolean killall )
+{
+ syscall( CG_S_CLEARLOOPINGSOUNDS, killall );
+}
+
+void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx )
+{
+ syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx );
+}
+
+void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx )
+{
+ syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx );
+}
+
+void trap_S_StopLoopingSound( int entityNum )
+{
+ syscall( CG_S_STOPLOOPINGSOUND, entityNum );
+}
+
+void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin )
+{
+ syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin );
+}
+
+void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater )
+{
+ syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater );
+}
+
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed )
+{
+ return syscall( CG_S_REGISTERSOUND, sample, compressed );
+}
+
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop )
+{
+ syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop );
+}
+
+void trap_R_LoadWorldMap( const char *mapname )
+{
+ syscall( CG_R_LOADWORLDMAP, mapname );
+}
+
+qhandle_t trap_R_RegisterModel( const char *name )
+{
+ return syscall( CG_R_REGISTERMODEL, name );
+}
+
+qhandle_t trap_R_RegisterSkin( const char *name )
+{
+ return syscall( CG_R_REGISTERSKIN, name );
+}
+
+qhandle_t trap_R_RegisterShader( const char *name )
+{
+ return syscall( CG_R_REGISTERSHADER, name );
+}
+
+qhandle_t trap_R_RegisterShaderNoMip( const char *name )
+{
+ return syscall( CG_R_REGISTERSHADERNOMIP, name );
+}
+
+void trap_R_RegisterFont( const char *fontName, int pointSize, fontInfo_t *font )
+{
+ syscall(CG_R_REGISTERFONT, fontName, pointSize, font );
+}
+
+void trap_R_ClearScene( void )
+{
+ syscall( CG_R_CLEARSCENE );
+}
+
+void trap_R_AddRefEntityToScene( const refEntity_t *re )
+{
+ syscall( CG_R_ADDREFENTITYTOSCENE, re );
+}
+
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts )
+{
+ syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts );
+}
+
+void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num )
+{
+ syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num );
+}
+
+int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir )
+{
+ return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir );
+}
+
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b )
+{
+ syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) );
+}
+
+void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b )
+{
+ syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) );
+}
+
+void trap_R_RenderScene( const refdef_t *fd )
+{
+ syscall( CG_R_RENDERSCENE, fd );
+}
+
+void trap_R_SetColor( const float *rgba )
+{
+ syscall( CG_R_SETCOLOR, rgba );
+}
+
+void trap_R_DrawStretchPic( float x, float y, float w, float h,
+ float s1, float t1, float s2, float t2, qhandle_t hShader )
+{
+ syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h),
+ PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader );
+}
+
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) {
+ syscall( CG_R_MODELBOUNDS, model, mins, maxs );
+}
+
+int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame,
+ float frac, const char *tagName )
+{
+ return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName );
+}
+
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset )
+{
+ syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset );
+}
+
+void trap_GetGlconfig( glconfig_t *glconfig )
+{
+ syscall( CG_GETGLCONFIG, glconfig );
+}
+
+void trap_GetGameState( gameState_t *gamestate )
+{
+ syscall( CG_GETGAMESTATE, gamestate );
+}
+
+void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime )
+{
+ syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime );
+}
+
+qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot )
+{
+ return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot );
+}
+
+qboolean trap_GetServerCommand( int serverCommandNumber )
+{
+ return syscall( CG_GETSERVERCOMMAND, serverCommandNumber );
+}
+
+int trap_GetCurrentCmdNumber( void )
+{
+ return syscall( CG_GETCURRENTCMDNUMBER );
+}
+
+qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd )
+{
+ return syscall( CG_GETUSERCMD, cmdNumber, ucmd );
+}
+
+void trap_SetUserCmdValue( int stateValue, float sensitivityScale )
+{
+ syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT( sensitivityScale ) );
+}
+
+void testPrintInt( char *string, int i )
+{
+ syscall( CG_TESTPRINTINT, string, i );
+}
+
+void testPrintFloat( char *string, float f )
+{
+ syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) );
+}
+
+int trap_MemoryRemaining( void )
+{
+ return syscall( CG_MEMORY_REMAINING );
+}
+
+qboolean trap_Key_IsDown( int keynum )
+{
+ return syscall( CG_KEY_ISDOWN, keynum );
+}
+
+int trap_Key_GetCatcher( void )
+{
+ return syscall( CG_KEY_GETCATCHER );
+}
+
+void trap_Key_SetCatcher( int catcher )
+{
+ syscall( CG_KEY_SETCATCHER, catcher );
+}
+
+int trap_Key_GetKey( const char *binding )
+{
+ return syscall( CG_KEY_GETKEY, binding );
+}
+
+int trap_Parse_AddGlobalDefine( char *define )
+{
+ return syscall( CG_PARSE_ADD_GLOBAL_DEFINE, define );
+}
+
+int trap_Parse_LoadSource( const char *filename )
+{
+ return syscall( CG_PARSE_LOAD_SOURCE, filename );
+}
+
+int trap_Parse_FreeSource( int handle )
+{
+ return syscall( CG_PARSE_FREE_SOURCE, handle );
+}
+
+int trap_Parse_ReadToken( int handle, pc_token_t *pc_token )
+{
+ return syscall( CG_PARSE_READ_TOKEN, handle, pc_token );
+}
+
+int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line )
+{
+ return syscall( CG_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line );
+}
+
+void trap_S_StopBackgroundTrack( void )
+{
+ syscall( CG_S_STOPBACKGROUNDTRACK );
+}
+
+int trap_RealTime(qtime_t *qtime)
+{
+ return syscall( CG_REAL_TIME, qtime );
+}
+
+void trap_SnapVector( float *v )
+{
+ syscall( CG_SNAPVECTOR, v );
+}
+
+// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate)
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits )
+{
+ return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits);
+}
+
+// stops playing the cinematic and ends it. should always return FMV_EOF
+// cinematics must be stopped in reverse order of when they are started
+e_status trap_CIN_StopCinematic( int handle )
+{
+ return syscall(CG_CIN_STOPCINEMATIC, handle);
+}
+
+
+// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached.
+e_status trap_CIN_RunCinematic( int handle )
+{
+ return syscall(CG_CIN_RUNCINEMATIC, handle);
+}
+
+
+// draws the current frame
+void trap_CIN_DrawCinematic( int handle )
+{
+ syscall(CG_CIN_DRAWCINEMATIC, handle);
+}
+
+
+// allows you to resize the animation dynamically
+void trap_CIN_SetExtents( int handle, int x, int y, int w, int h )
+{
+ syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h);
+}
+
+int trap_GetDemoState( void )
+{
+ return syscall( CG_GETDEMOSTATE );
+}
+
+int trap_GetDemoPos( void )
+{
+ return syscall( CG_GETDEMOPOS );
+}
+
+void trap_GetDemoName( char *buffer, int size )
+{
+ syscall( CG_GETDEMONAME, buffer, size );
+}
+
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) {
+ syscall( CG_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) {
+ syscall( CG_KEY_GETBINDINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_SetBinding( int keynum, const char *binding ) {
+ syscall( CG_KEY_SETBINDING, keynum, binding );
+}
+
diff --git a/src/cgame/cg_trails.c b/src/cgame/cg_trails.c
new file mode 100644
index 0000000..c8943ed
--- /dev/null
+++ b/src/cgame/cg_trails.c
@@ -0,0 +1,1500 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_trails.c -- the trail system
+
+
+#include "cg_local.h"
+
+static baseTrailSystem_t baseTrailSystems[ MAX_BASETRAIL_SYSTEMS ];
+static baseTrailBeam_t baseTrailBeams[ MAX_BASETRAIL_BEAMS ];
+static int numBaseTrailSystems = 0;
+static int numBaseTrailBeams = 0;
+
+static trailSystem_t trailSystems[ MAX_TRAIL_SYSTEMS ];
+static trailBeam_t trailBeams[ MAX_TRAIL_BEAMS ];
+
+/*
+===============
+CG_CalculateBeamNodeProperties
+
+Fills in trailBeamNode_t.textureCoord
+===============
+*/
+static void CG_CalculateBeamNodeProperties( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = NULL;
+ trailSystem_t *ts;
+ baseTrailBeam_t *btb;
+ float nodeDistances[ MAX_TRAIL_BEAM_NODES ];
+ float totalDistance = 0.0f, position = 0.0f;
+ int j, numNodes = 0;
+ float TCRange, widthRange, alphaRange;
+ vec3_t colorRange;
+ float fadeAlpha = 1.0f;
+
+ if( !tb || !tb->nodes )
+ return;
+
+ ts = tb->parent;
+ btb = tb->class;
+
+ if( ts->destroyTime > 0 && btb->fadeOutTime )
+ {
+ fadeAlpha -= ( cg.time - ts->destroyTime ) / btb->fadeOutTime;
+
+ if( fadeAlpha < 0.0f )
+ fadeAlpha = 0.0f;
+ }
+
+ TCRange = tb->class->backTextureCoord -
+ tb->class->frontTextureCoord;
+ widthRange = tb->class->backWidth -
+ tb->class->frontWidth;
+ alphaRange = tb->class->backAlpha -
+ tb->class->frontAlpha;
+ VectorSubtract( tb->class->backColor,
+ tb->class->frontColor, colorRange );
+
+ for( i = tb->nodes; i && i->next; i = i->next )
+ {
+ nodeDistances[ numNodes++ ] =
+ Distance( i->position, i->next->position );
+ }
+
+ for( j = 0; j < numNodes; j++ )
+ totalDistance += nodeDistances[ j ];
+
+ for( j = 0, i = tb->nodes; i; i = i->next, j++ )
+ {
+ if( tb->class->textureType == TBTT_STRETCH )
+ {
+ i->textureCoord = tb->class->frontTextureCoord +
+ ( ( position / totalDistance ) * TCRange );
+ }
+ else if( tb->class->textureType == TBTT_REPEAT )
+ {
+ if( tb->class->clampToBack )
+ i->textureCoord = ( totalDistance - position ) /
+ tb->class->repeatLength;
+ else
+ i->textureCoord = position / tb->class->repeatLength;
+ }
+
+ i->halfWidth = ( tb->class->frontWidth +
+ ( ( position / totalDistance ) * widthRange ) ) / 2.0f;
+ i->alpha = (byte)( (float)0xFF * ( tb->class->frontAlpha +
+ ( ( position / totalDistance ) * alphaRange ) ) * fadeAlpha );
+ VectorMA( tb->class->frontColor, ( position / totalDistance ),
+ colorRange, i->color );
+
+ position += nodeDistances[ j ];
+ }
+}
+
+/*
+===============
+CG_LightVertex
+
+Lights a particular vertex
+===============
+*/
+static void CG_LightVertex( vec3_t point, byte alpha, byte *rgba )
+{
+ int i;
+ vec3_t alight, dlight, lightdir;
+
+ trap_R_LightForPoint( point, alight, dlight, lightdir );
+ for( i = 0; i <= 2; i++ )
+ rgba[ i ] = (int)alight[ i ];
+
+ rgba[ 3 ] = alpha;
+}
+
+/*
+===============
+CG_RenderBeam
+
+Renders a beam
+===============
+*/
+static void CG_RenderBeam( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = NULL;
+ trailBeamNode_t *prev = NULL;
+ trailBeamNode_t *next = NULL;
+ vec3_t up;
+ polyVert_t verts[ ( MAX_TRAIL_BEAM_NODES - 1 ) * 4 ];
+ int numVerts = 0;
+ baseTrailBeam_t *btb;
+ trailSystem_t *ts;
+ baseTrailSystem_t *bts;
+
+ if( !tb || !tb->nodes )
+ return;
+
+ btb = tb->class;
+ ts = tb->parent;
+ bts = ts->class;
+
+ if( bts->thirdPersonOnly &&
+ ( CG_AttachmentCentNum( &ts->frontAttachment ) == cg.snap->ps.clientNum ||
+ CG_AttachmentCentNum( &ts->backAttachment ) == cg.snap->ps.clientNum ) &&
+ !cg.renderingThirdPerson )
+ return;
+
+ CG_CalculateBeamNodeProperties( tb );
+
+ i = tb->nodes;
+
+ do
+ {
+ prev = i->prev;
+ next = i->next;
+
+ if( prev && next )
+ {
+ //this node has two neighbours
+ GetPerpendicularViewVector( cg.refdef.vieworg, next->position, prev->position, up );
+ }
+ else if( !prev && next )
+ {
+ //this is the front
+ GetPerpendicularViewVector( cg.refdef.vieworg, next->position, i->position, up );
+ }
+ else if( prev && !next )
+ {
+ //this is the back
+ GetPerpendicularViewVector( cg.refdef.vieworg, i->position, prev->position, up );
+ }
+ else
+ break;
+
+ if( prev )
+ {
+ VectorMA( i->position, i->halfWidth, up, verts[ numVerts ].xyz );
+ verts[ numVerts ].st[ 0 ] = i->textureCoord;
+ verts[ numVerts ].st[ 1 ] = 1.0f;
+
+ if( btb->realLight )
+ CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate );
+ else
+ {
+ VectorCopy( i->color, verts[ numVerts ].modulate );
+ verts[ numVerts ].modulate[ 3 ] = i->alpha;
+ }
+
+ numVerts++;
+
+ VectorMA( i->position, -i->halfWidth, up, verts[ numVerts ].xyz );
+ verts[ numVerts ].st[ 0 ] = i->textureCoord;
+ verts[ numVerts ].st[ 1 ] = 0.0f;
+
+ if( btb->realLight )
+ CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate );
+ else
+ {
+ VectorCopy( i->color, verts[ numVerts ].modulate );
+ verts[ numVerts ].modulate[ 3 ] = i->alpha;
+ }
+
+ numVerts++;
+ }
+
+ if( next )
+ {
+ VectorMA( i->position, -i->halfWidth, up, verts[ numVerts ].xyz );
+ verts[ numVerts ].st[ 0 ] = i->textureCoord;
+ verts[ numVerts ].st[ 1 ] = 0.0f;
+
+ if( btb->realLight )
+ CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate );
+ else
+ {
+ VectorCopy( i->color, verts[ numVerts ].modulate );
+ verts[ numVerts ].modulate[ 3 ] = i->alpha;
+ }
+
+ numVerts++;
+
+ VectorMA( i->position, i->halfWidth, up, verts[ numVerts ].xyz );
+ verts[ numVerts ].st[ 0 ] = i->textureCoord;
+ verts[ numVerts ].st[ 1 ] = 1.0f;
+
+ if( btb->realLight )
+ CG_LightVertex( verts[ numVerts ].xyz, i->alpha, verts[ numVerts ].modulate );
+ else
+ {
+ VectorCopy( i->color, verts[ numVerts ].modulate );
+ verts[ numVerts ].modulate[ 3 ] = i->alpha;
+ }
+
+ numVerts++;
+ }
+
+ i = i->next;
+ } while( i );
+
+ trap_R_AddPolysToScene( tb->class->shader, 4, &verts[ 0 ], numVerts / 4 );
+}
+
+/*
+===============
+CG_AllocateBeamNode
+
+Allocates a trailBeamNode_t from a trailBeam_t's nodePool
+===============
+*/
+static trailBeamNode_t *CG_AllocateBeamNode( trailBeam_t *tb )
+{
+ baseTrailBeam_t *btb = tb->class;
+ int i;
+ trailBeamNode_t *tbn;
+
+ for( i = 0; i < MAX_TRAIL_BEAM_NODES; i++ )
+ {
+ tbn = &tb->nodePool[ i ];
+ if( !tbn->used )
+ {
+ tbn->timeLeft = btb->segmentTime;
+ tbn->prev = NULL;
+ tbn->next = NULL;
+ tbn->used = qtrue;
+ return tbn;
+ }
+ }
+
+ // no space left
+ return NULL;
+}
+
+/*
+===============
+CG_DestroyBeamNode
+
+Removes a node from a beam
+Returns the new head
+===============
+*/
+static trailBeamNode_t *CG_DestroyBeamNode( trailBeamNode_t *tbn )
+{
+ trailBeamNode_t *newHead = NULL;
+
+ if( tbn->prev )
+ {
+ if( tbn->next )
+ {
+ // node is in the middle
+ tbn->prev->next = tbn->next;
+ tbn->next->prev = tbn->prev;
+ }
+ else // node is at the back
+ tbn->prev->next = NULL;
+
+ // find the new head (shouldn't have changed)
+ newHead = tbn->prev;
+
+ while( newHead->prev )
+ newHead = newHead->prev;
+ }
+ else if( tbn->next )
+ {
+ //node is at the front
+ tbn->next->prev = NULL;
+ newHead = tbn->next;
+ }
+
+ tbn->prev = NULL;
+ tbn->next = NULL;
+ tbn->used = qfalse;
+
+ return newHead;
+}
+
+/*
+===============
+CG_FindLastBeamNode
+
+Returns the last beam node in a beam
+===============
+*/
+static trailBeamNode_t *CG_FindLastBeamNode( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = tb->nodes;
+
+ while( i && i->next )
+ i = i->next;
+
+ return i;
+}
+
+/*
+===============
+CG_CountBeamNodes
+
+Returns the number of nodes in a beam
+===============
+*/
+static int CG_CountBeamNodes( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = tb->nodes;
+ int numNodes = 0;
+
+ while( i )
+ {
+ numNodes++;
+ i = i->next;
+ }
+
+ return numNodes;
+}
+
+/*
+===============
+CG_PrependBeamNode
+
+Prepend a new beam node to the front of a beam
+Returns the new node
+===============
+*/
+static trailBeamNode_t *CG_PrependBeamNode( trailBeam_t *tb )
+{
+ trailBeamNode_t *i;
+
+ if( tb->nodes )
+ {
+ // prepend another node
+ i = CG_AllocateBeamNode( tb );
+
+ if( i )
+ {
+ i->next = tb->nodes;
+ tb->nodes->prev = i;
+ tb->nodes = i;
+ }
+ }
+ else //add first node
+ {
+ i = CG_AllocateBeamNode( tb );
+
+ if( i )
+ tb->nodes = i;
+ }
+
+ return i;
+}
+
+/*
+===============
+CG_AppendBeamNode
+
+Append a new beam node to the back of a beam
+Returns the new node
+===============
+*/
+static trailBeamNode_t *CG_AppendBeamNode( trailBeam_t *tb )
+{
+ trailBeamNode_t *last, *i;
+
+ if( tb->nodes )
+ {
+ // append another node
+ last = CG_FindLastBeamNode( tb );
+ i = CG_AllocateBeamNode( tb );
+
+ if( i )
+ {
+ last->next = i;
+ i->prev = last;
+ i->next = NULL;
+ }
+ }
+ else //add first node
+ {
+ i = CG_AllocateBeamNode( tb );
+
+ if( i )
+ tb->nodes = i;
+ }
+
+ return i;
+}
+
+/*
+===============
+CG_ApplyJitters
+===============
+*/
+static void CG_ApplyJitters( trailBeam_t *tb )
+{
+ trailBeamNode_t *i = NULL;
+ int j;
+ baseTrailBeam_t *btb;
+ trailSystem_t *ts;
+ trailBeamNode_t *start;
+ trailBeamNode_t *end;
+
+ if( !tb || !tb->nodes )
+ return;
+
+ btb = tb->class;
+ ts = tb->parent;
+
+ for( j = 0; j < btb->numJitters; j++ )
+ {
+ if( tb->nextJitterTimes[ j ] <= cg.time )
+ {
+ for( i = tb->nodes; i; i = i->next )
+ {
+ i->jitters[ j ][ 0 ] = ( crandom( ) * btb->jitters[ j ].magnitude );
+ i->jitters[ j ][ 1 ] = ( crandom( ) * btb->jitters[ j ].magnitude );
+ }
+
+ tb->nextJitterTimes[ j ] = cg.time + btb->jitters[ j ].period;
+ }
+ }
+
+ start = tb->nodes;
+ end = CG_FindLastBeamNode( tb );
+
+ if( !btb->jitterAttachments )
+ {
+ if( CG_Attached( &ts->frontAttachment ) && start->next )
+ start = start->next;
+
+ if( CG_Attached( &ts->backAttachment ) && end->prev )
+ end = end->prev;
+ }
+
+ for( i = start; i; i = i->next )
+ {
+ vec3_t forward, right, up;
+ trailBeamNode_t *prev;
+ trailBeamNode_t *next;
+ float upJitter = 0.0f, rightJitter = 0.0f;
+
+ prev = i->prev;
+ next = i->next;
+
+ if( prev && next )
+ {
+ //this node has two neighbours
+ GetPerpendicularViewVector( cg.refdef.vieworg, next->position, prev->position, up );
+ VectorSubtract( next->position, prev->position, forward );
+ }
+ else if( !prev && next )
+ {
+ //this is the front
+ GetPerpendicularViewVector( cg.refdef.vieworg, next->position, i->position, up );
+ VectorSubtract( next->position, i->position, forward );
+ }
+ else if( prev && !next )
+ {
+ //this is the back
+ GetPerpendicularViewVector( cg.refdef.vieworg, i->position, prev->position, up );
+ VectorSubtract( i->position, prev->position, forward );
+ }
+
+ VectorNormalize( forward );
+ CrossProduct( forward, up, right );
+ VectorNormalize( right );
+
+ for( j = 0; j < btb->numJitters; j++ )
+ {
+ upJitter += i->jitters[ j ][ 0 ];
+ rightJitter += i->jitters[ j ][ 1 ];
+ }
+
+ VectorMA( i->position, upJitter, up, i->position );
+ VectorMA( i->position, rightJitter, right, i->position );
+
+ if( i == end )
+ break;
+ }
+}
+
+/*
+===============
+CG_UpdateBeam
+
+Updates a beam
+===============
+*/
+static void CG_UpdateBeam( trailBeam_t *tb )
+{
+ baseTrailBeam_t *btb;
+ trailSystem_t *ts;
+ trailBeamNode_t *i;
+ int deltaTime;
+ int nodesToAdd;
+ int j;
+ int numNodes;
+
+ if( !tb )
+ return;
+
+ btb = tb->class;
+ ts = tb->parent;
+
+ deltaTime = cg.time - tb->lastEvalTime;
+ tb->lastEvalTime = cg.time;
+
+ // first make sure this beam has enough nodes
+ if( ts->destroyTime <= 0 )
+ {
+ nodesToAdd = btb->numSegments - CG_CountBeamNodes( tb ) + 1;
+
+ while( nodesToAdd-- )
+ {
+ i = CG_AppendBeamNode( tb );
+
+ if( !tb->nodes->next && CG_Attached( &ts->frontAttachment ) )
+ {
+ // this is the first node to be added
+ if( !CG_AttachmentPoint( &ts->frontAttachment, i->refPosition ) )
+ CG_DestroyTrailSystem( &ts );
+ }
+ else
+ VectorCopy( i->prev->refPosition, i->refPosition );
+ }
+ }
+
+ numNodes = CG_CountBeamNodes( tb );
+
+ for( i = tb->nodes; i; i = i->next )
+ VectorCopy( i->refPosition, i->position );
+
+ if( CG_Attached( &ts->frontAttachment ) && CG_Attached( &ts->backAttachment ) )
+ {
+ // beam between two attachments
+ vec3_t dir, front, back;
+
+ if( ts->destroyTime > 0 && ( cg.time - ts->destroyTime ) >= btb->fadeOutTime )
+ {
+ tb->valid = qfalse;
+ return;
+ }
+
+ if( !CG_AttachmentPoint( &ts->frontAttachment, front ) )
+ CG_DestroyTrailSystem( &ts );
+
+ if( !CG_AttachmentPoint( &ts->backAttachment, back ) )
+ CG_DestroyTrailSystem( &ts );
+
+ VectorSubtract( back, front, dir );
+
+ for( j = 0, i = tb->nodes; i; i = i->next, j++ )
+ {
+ float scale = (float)j / (float)( numNodes - 1 );
+
+ VectorMA( front, scale, dir, i->position );
+ }
+ }
+ else if( CG_Attached( &ts->frontAttachment ) )
+ {
+ // beam from one attachment
+
+ // cull the trail tail
+ i = CG_FindLastBeamNode( tb );
+
+ if( i && i->timeLeft >= 0 )
+ {
+ i->timeLeft -= deltaTime;
+
+ if( i->timeLeft < 0 )
+ {
+ tb->nodes = CG_DestroyBeamNode( i );
+
+ if( !tb->nodes )
+ {
+ tb->valid = qfalse;
+ return;
+ }
+
+ // if the ts has been destroyed, stop creating new nodes
+ if( ts->destroyTime <= 0 )
+ CG_PrependBeamNode( tb );
+ }
+ else if( i->timeLeft >= 0 && i->prev )
+ {
+ vec3_t dir;
+ float length;
+
+ VectorSubtract( i->refPosition, i->prev->refPosition, dir );
+ length = VectorNormalize( dir ) *
+ ( (float)i->timeLeft / (float)tb->class->segmentTime );
+
+ VectorMA( i->prev->refPosition, length, dir, i->position );
+ }
+ }
+
+ if( tb->nodes )
+ {
+ if( !CG_AttachmentPoint( &ts->frontAttachment, tb->nodes->refPosition ) )
+ CG_DestroyTrailSystem( &ts );
+
+ VectorCopy( tb->nodes->refPosition, tb->nodes->position );
+ }
+ }
+
+ CG_ApplyJitters( tb );
+}
+
+/*
+===============
+CG_ParseTrailBeamColor
+===============
+*/
+static qboolean CG_ParseTrailBeamColor( byte *c, char **text_p )
+{
+ char *token;
+ int i;
+
+ for( i = 0; i <= 2; i++ )
+ {
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ c[ i ] = (int)( (float)0xFF * atof_neg( token, qfalse ) );
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_ParseTrailBeam
+
+Parse a trail beam
+===============
+*/
+static qboolean CG_ParseTrailBeam( baseTrailBeam_t *btb, char **text_p )
+{
+ char *token;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "segments" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->numSegments = atoi_neg( token, qfalse );
+
+ if( btb->numSegments >= MAX_TRAIL_BEAM_NODES )
+ {
+ btb->numSegments = MAX_TRAIL_BEAM_NODES - 1;
+ CG_Printf( S_COLOR_YELLOW "WARNING: too many segments in trail beam\n" );
+ }
+ continue;
+ }
+ else if( !Q_stricmp( token, "width" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->frontWidth = atof_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ btb->backWidth = btb->frontWidth;
+ else
+ btb->backWidth = atof_neg( token, qfalse );
+ continue;
+ }
+ else if( !Q_stricmp( token, "alpha" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->frontAlpha = atof_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ btb->backAlpha = btb->frontAlpha;
+ else
+ btb->backAlpha = atof_neg( token, qfalse );
+ continue;
+ }
+ else if( !Q_stricmp( token, "color" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseTrailBeamColor( btb->frontColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "-" ) )
+ {
+ btb->backColor[ 0 ] = btb->frontColor[ 0 ];
+ btb->backColor[ 1 ] = btb->frontColor[ 1 ];
+ btb->backColor[ 2 ] = btb->frontColor[ 2 ];
+ }
+ else if( !Q_stricmp( token, "{" ) )
+ {
+ if( !CG_ParseTrailBeamColor( btb->backColor, text_p ) )
+ break;
+
+ token = COM_Parse( text_p );
+ if( Q_stricmp( token, "}" ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '}'\n" );
+ break;
+ }
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" );
+ break;
+ }
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: missing '{'\n" );
+ break;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "segmentTime" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->segmentTime = atoi_neg( token, qfalse );
+ continue;
+ }
+ else if( !Q_stricmp( token, "fadeOutTime" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->fadeOutTime = atoi_neg( token, qfalse );
+ continue;
+ }
+ else if( !Q_stricmp( token, "shader" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ Q_strncpyz( btb->shaderName, token, MAX_QPATH );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "textureType" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "stretch" ) )
+ {
+ btb->textureType = TBTT_STRETCH;
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->frontTextureCoord = atof_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->backTextureCoord = atof_neg( token, qfalse );
+ }
+ else if( !Q_stricmp( token, "repeat" ) )
+ {
+ btb->textureType = TBTT_REPEAT;
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "front" ) )
+ btb->clampToBack = qfalse;
+ else if( !Q_stricmp( token, "back" ) )
+ btb->clampToBack = qtrue;
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown textureType clamp \"%s\"\n", token );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->repeatLength = atof_neg( token, qfalse );
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown textureType \"%s\"\n", token );
+ break;
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "realLight" ) )
+ {
+ btb->realLight = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "jitter" ) )
+ {
+ if( btb->numJitters == MAX_TRAIL_BEAM_JITTERS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: too many jitters\n", token );
+ break;
+ }
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->jitters[ btb->numJitters ].magnitude = atof_neg( token, qfalse );
+
+ token = COM_Parse( text_p );
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ btb->jitters[ btb->numJitters ].period = atoi_neg( token, qfalse );
+
+ btb->numJitters++;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "jitterAttachments" ) )
+ {
+ btb->jitterAttachments = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this trail beam
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail beam\n", token );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_InitialiseBaseTrailBeam
+===============
+*/
+static void CG_InitialiseBaseTrailBeam( baseTrailBeam_t *btb )
+{
+ memset( btb, 0, sizeof( baseTrailBeam_t ) );
+
+ btb->numSegments = 1;
+ btb->frontWidth = btb->backWidth = 1.0f;
+ btb->frontAlpha = btb->backAlpha = 1.0f;
+ memset( btb->frontColor, 0xFF, sizeof( btb->frontColor ) );
+ memset( btb->backColor, 0xFF, sizeof( btb->backColor ) );
+
+ btb->segmentTime = 100;
+
+ btb->textureType = TBTT_STRETCH;
+ btb->frontTextureCoord = 0.0f;
+ btb->backTextureCoord = 1.0f;
+}
+
+/*
+===============
+CG_ParseTrailSystem
+
+Parse a trail system section
+===============
+*/
+static qboolean CG_ParseTrailSystem( baseTrailSystem_t *bts, char **text_p, const char *name )
+{
+ char *token;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ CG_InitialiseBaseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ] );
+
+ if( !CG_ParseTrailBeam( &baseTrailBeams[ numBaseTrailBeams ], text_p ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: failed to parse trail beam\n" );
+ return qfalse;
+ }
+
+ if( bts->numBeams == MAX_BEAMS_PER_SYSTEM )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: trail system has > %d beams\n", MAX_BEAMS_PER_SYSTEM );
+ return qfalse;
+ }
+ else if( numBaseTrailBeams == MAX_BASETRAIL_BEAMS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of trail beams (%d) reached\n",
+ MAX_BASETRAIL_BEAMS );
+ return qfalse;
+ }
+ else
+ {
+ //start parsing beams again
+ bts->beams[ bts->numBeams ] = &baseTrailBeams[ numBaseTrailBeams ];
+ bts->numBeams++;
+ numBaseTrailBeams++;
+ }
+ continue;
+ }
+ else if( !Q_stricmp( token, "thirdPersonOnly" ) )
+ bts->thirdPersonOnly = qtrue;
+ else if( !Q_stricmp( token, "beam" ) ) //acceptable text
+ continue;
+ else if( !Q_stricmp( token, "}" ) )
+ {
+ if( cg_debugTrails.integer >= 1 )
+ CG_Printf( "Parsed trail system %s\n", name );
+
+ return qtrue; //reached the end of this trail system
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in trail system %s\n", token, bts->name );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+===============
+CG_ParseTrailFile
+
+Load the trail systems from a trail file
+===============
+*/
+static qboolean CG_ParseTrailFile( const char *fileName )
+{
+ char *text_p;
+ int i;
+ int len;
+ char *token;
+ char text[ 32000 ];
+ char tsName[ MAX_QPATH ];
+ qboolean tsNameSet = qfalse;
+ fileHandle_t f;
+
+ // load the file
+ len = trap_FS_FOpenFile( fileName, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: trail file %s too long\n", fileName );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( tsNameSet )
+ {
+ //check for name space clashes
+ for( i = 0; i < numBaseTrailSystems; i++ )
+ {
+ if( !Q_stricmp( baseTrailSystems[ i ].name, tsName ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a trail system is already named %s\n", tsName );
+ return qfalse;
+ }
+ }
+
+ Q_strncpyz( baseTrailSystems[ numBaseTrailSystems ].name, tsName, MAX_QPATH );
+
+ if( !CG_ParseTrailSystem( &baseTrailSystems[ numBaseTrailSystems ], &text_p, tsName ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse trail system %s\n", fileName, tsName );
+ return qfalse;
+ }
+
+ //start parsing trail systems again
+ tsNameSet = qfalse;
+
+ if( numBaseTrailSystems == MAX_BASETRAIL_SYSTEMS )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: maximum number of trail systems (%d) reached\n",
+ MAX_BASETRAIL_SYSTEMS );
+ return qfalse;
+ }
+ else
+ numBaseTrailSystems++;
+
+ continue;
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unamed trail system\n" );
+ return qfalse;
+ }
+ }
+
+ if( !tsNameSet )
+ {
+ Q_strncpyz( tsName, token, sizeof( tsName ) );
+ tsNameSet = qtrue;
+ }
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: trail system already named\n" );
+ return qfalse;
+ }
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_LoadTrailSystems
+
+Load trail system templates
+===============
+*/
+void CG_LoadTrailSystems( void )
+{
+ int i, numFiles, fileLen;
+ char fileList[ MAX_TRAIL_FILES * MAX_QPATH ];
+ char fileName[ MAX_QPATH ];
+ char *filePtr;
+
+ //clear out the old
+ numBaseTrailSystems = 0;
+ numBaseTrailBeams = 0;
+
+ for( i = 0; i < MAX_BASETRAIL_SYSTEMS; i++ )
+ {
+ baseTrailSystem_t *bts = &baseTrailSystems[ i ];
+ memset( bts, 0, sizeof( baseTrailSystem_t ) );
+ }
+
+ for( i = 0; i < MAX_BASETRAIL_BEAMS; i++ )
+ {
+ baseTrailBeam_t *btb = &baseTrailBeams[ i ];
+ memset( btb, 0, sizeof( baseTrailBeam_t ) );
+ }
+
+ //and bring in the new
+ numFiles = trap_FS_GetFileList( "scripts", ".trail",
+ fileList, MAX_TRAIL_FILES * MAX_QPATH );
+ filePtr = fileList;
+
+ for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 )
+ {
+ fileLen = strlen( filePtr );
+ strcpy( fileName, "scripts/" );
+ strcat( fileName, filePtr );
+ CG_Printf( "...loading '%s'\n", fileName );
+ CG_ParseTrailFile( fileName );
+ }
+}
+
+/*
+===============
+CG_RegisterTrailSystem
+
+Load the media that a trail system needs
+===============
+*/
+qhandle_t CG_RegisterTrailSystem( char *name )
+{
+ int i, j;
+ baseTrailSystem_t *bts;
+ baseTrailBeam_t *btb;
+
+ for( i = 0; i < MAX_BASETRAIL_SYSTEMS; i++ )
+ {
+ bts = &baseTrailSystems[ i ];
+
+ if( !Q_stricmp( bts->name, name ) )
+ {
+ //already registered
+ if( bts->registered )
+ return i + 1;
+
+ for( j = 0; j < bts->numBeams; j++ )
+ {
+ btb = bts->beams[ j ];
+
+ btb->shader = trap_R_RegisterShader( btb->shaderName );
+ }
+
+ if( cg_debugTrails.integer >= 1 )
+ CG_Printf( "Registered trail system %s\n", name );
+
+ bts->registered = qtrue;
+
+ //avoid returning 0
+ return i + 1;
+ }
+ }
+
+ CG_Printf( S_COLOR_RED "ERROR: failed to register trail system %s\n", name );
+ return 0;
+}
+
+
+/*
+===============
+CG_SpawnNewTrailBeam
+
+Allocate a new trail beam
+===============
+*/
+static trailBeam_t *CG_SpawnNewTrailBeam( baseTrailBeam_t *btb,
+ trailSystem_t *parent )
+{
+ int i;
+ trailBeam_t *tb = NULL;
+ trailSystem_t *ts = parent;
+
+ for( i = 0; i < MAX_TRAIL_BEAMS; i++ )
+ {
+ tb = &trailBeams[ i ];
+
+ if( !tb->valid )
+ {
+ memset( tb, 0, sizeof( trailBeam_t ) );
+
+ //found a free slot
+ tb->class = btb;
+ tb->parent = ts;
+
+ tb->valid = qtrue;
+
+ if( cg_debugTrails.integer >= 1 )
+ CG_Printf( "TB %s created\n", ts->class->name );
+
+ break;
+ }
+ }
+
+ return tb;
+}
+
+
+/*
+===============
+CG_SpawnNewTrailSystem
+
+Spawns a new trail system
+===============
+*/
+trailSystem_t *CG_SpawnNewTrailSystem( qhandle_t psHandle )
+{
+ int i, j;
+ trailSystem_t *ts = NULL;
+ baseTrailSystem_t *bts = &baseTrailSystems[ psHandle - 1 ];
+
+ if( !bts->registered )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: a trail system has not been registered yet\n" );
+ return NULL;
+ }
+
+ for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ )
+ {
+ ts = &trailSystems[ i ];
+
+ if( !ts->valid )
+ {
+ memset( ts, 0, sizeof( trailSystem_t ) );
+
+ //found a free slot
+ ts->class = bts;
+
+ ts->valid = qtrue;
+ ts->destroyTime = -1;
+
+ for( j = 0; j < bts->numBeams; j++ )
+ CG_SpawnNewTrailBeam( bts->beams[ j ], ts );
+
+ if( cg_debugTrails.integer >= 1 )
+ CG_Printf( "TS %s created\n", bts->name );
+
+ break;
+ }
+ }
+
+ return ts;
+}
+
+/*
+===============
+CG_DestroyTrailSystem
+
+Destroy a trail system
+===============
+*/
+void CG_DestroyTrailSystem( trailSystem_t **ts )
+{
+ (*ts)->destroyTime = cg.time;
+
+ if( CG_Attached( &(*ts)->frontAttachment ) &&
+ !CG_Attached( &(*ts)->backAttachment ) )
+ {
+ vec3_t v;
+
+ // attach the trail head to a static point
+ CG_AttachmentPoint( &(*ts)->frontAttachment, v );
+ CG_SetAttachmentPoint( &(*ts)->frontAttachment, v );
+ CG_AttachToPoint( &(*ts)->frontAttachment );
+
+ (*ts)->frontAttachment.centValid = qfalse; // a bit naughty
+ }
+
+ ts = NULL;
+}
+
+/*
+===============
+CG_IsTrailSystemValid
+
+Test a trail system for validity
+===============
+*/
+qboolean CG_IsTrailSystemValid( trailSystem_t **ts )
+{
+ if( *ts == NULL || ( *ts && !(*ts)->valid ) )
+ {
+ if( *ts && !(*ts)->valid )
+ *ts = NULL;
+
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+===============
+CG_GarbageCollectTrailSystems
+
+Destroy inactive trail systems
+===============
+*/
+static void CG_GarbageCollectTrailSystems( void )
+{
+ int i, j, count;
+ trailSystem_t *ts;
+ trailBeam_t *tb;
+ int centNum;
+
+ for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ )
+ {
+ ts = &trailSystems[ i ];
+ count = 0;
+
+ //don't bother checking already invalid systems
+ if( !ts->valid )
+ continue;
+
+ for( j = 0; j < MAX_TRAIL_BEAMS; j++ )
+ {
+ tb = &trailBeams[ j ];
+
+ if( tb->valid && tb->parent == ts )
+ count++;
+ }
+
+ if( !count )
+ ts->valid = qfalse;
+
+ //check systems where the parent cent has left the PVS
+ //( local player entity is always valid )
+ if( ( centNum = CG_AttachmentCentNum( &ts->frontAttachment ) ) >= 0 &&
+ centNum != cg.snap->ps.clientNum )
+ {
+ trailSystem_t *tempTS = ts;
+
+ if( !cg_entities[ centNum ].valid )
+ CG_DestroyTrailSystem( &tempTS );
+ }
+
+ if( ( centNum = CG_AttachmentCentNum( &ts->backAttachment ) ) >= 0 &&
+ centNum != cg.snap->ps.clientNum )
+ {
+ trailSystem_t *tempTS = ts;
+
+ if( !cg_entities[ centNum ].valid )
+ CG_DestroyTrailSystem( &tempTS );
+ }
+
+ if( cg_debugTrails.integer >= 1 && !ts->valid )
+ CG_Printf( "TS %s garbage collected\n", ts->class->name );
+ }
+}
+
+/*
+===============
+CG_AddTrails
+
+Add trails to the scene
+===============
+*/
+void CG_AddTrails( void )
+{
+ int i;
+ trailBeam_t *tb;
+ int numTS = 0, numTB = 0;
+
+ //remove expired trail systems
+ CG_GarbageCollectTrailSystems( );
+
+ for( i = 0; i < MAX_TRAIL_BEAMS; i++ )
+ {
+ tb = &trailBeams[ i ];
+
+ if( tb->valid )
+ {
+ CG_UpdateBeam( tb );
+ CG_RenderBeam( tb );
+ }
+ }
+
+ if( cg_debugTrails.integer >= 2 )
+ {
+ for( i = 0; i < MAX_TRAIL_SYSTEMS; i++ )
+ if( trailSystems[ i ].valid )
+ numTS++;
+
+ for( i = 0; i < MAX_TRAIL_BEAMS; i++ )
+ if( trailBeams[ i ].valid )
+ numTB++;
+
+ CG_Printf( "TS: %d TB: %d\n", numTS, numTB );
+ }
+}
+
+static trailSystem_t *testTS;
+static qhandle_t testTSHandle;
+
+/*
+===============
+CG_DestroyTestTS_f
+
+Destroy the test a trail system
+===============
+*/
+void CG_DestroyTestTS_f( void )
+{
+ if( CG_IsTrailSystemValid( &testTS ) )
+ CG_DestroyTrailSystem( &testTS );
+}
+
+/*
+===============
+CG_TestTS_f
+
+Test a trail system
+===============
+*/
+void CG_TestTS_f( void )
+{
+ char tsName[ MAX_QPATH ];
+
+ if( trap_Argc( ) < 2 )
+ return;
+
+ Q_strncpyz( tsName, CG_Argv( 1 ), MAX_QPATH );
+ testTSHandle = CG_RegisterTrailSystem( tsName );
+
+ if( testTSHandle )
+ {
+ CG_DestroyTestTS_f( );
+
+ testTS = CG_SpawnNewTrailSystem( testTSHandle );
+
+ if( CG_IsTrailSystemValid( &testTS ) )
+ {
+ CG_SetAttachmentCent( &testTS->frontAttachment, &cg_entities[ 0 ] );
+ CG_AttachToCent( &testTS->frontAttachment );
+ }
+ }
+}
diff --git a/src/cgame/cg_tutorial.c b/src/cgame/cg_tutorial.c
new file mode 100644
index 0000000..5ad29b7
--- /dev/null
+++ b/src/cgame/cg_tutorial.c
@@ -0,0 +1,663 @@
+/*
+===========================================================================
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_tutorial.c -- the tutorial system
+
+#include "cg_local.h"
+
+typedef struct
+{
+ char *command;
+ char *humanName;
+ keyNum_t keys[ 2 ];
+} bind_t;
+
+static bind_t bindings[ ] =
+{
+ { "+button2", "Activate Upgrade", { -1, -1 } },
+ { "+speed", "Run/Walk", { -1, -1 } },
+ { "boost", "Sprint", { -1, -1 } },
+ { "+moveup", "Jump", { -1, -1 } },
+ { "+movedown", "Crouch", { -1, -1 } },
+ { "+attack", "Primary Attack", { -1, -1 } },
+ { "+button5", "Secondary Attack", { -1, -1 } },
+ { "reload", "Reload", { -1, -1 } },
+ { "buy ammo", "Buy Ammo", { -1, -1 } },
+ { "itemact medkit", "Use Medkit", { -1, -1 } },
+ { "+button7", "Use Structure/Evolve", { -1, -1 } },
+ { "deconstruct", "Deconstruct Structure", { -1, -1 } },
+ { "weapprev", "Previous Upgrade", { -1, -1 } },
+ { "weapnext", "Next Upgrade", { -1, -1 } }
+};
+
+static const int numBindings = sizeof( bindings ) / sizeof( bind_t );
+
+/*
+=================
+CG_GetBindings
+=================
+*/
+static void CG_GetBindings( void )
+{
+ int i, j, numKeys;
+ char buffer[ MAX_STRING_CHARS ];
+
+ for( i = 0; i < numBindings; i++ )
+ {
+ bindings[ i ].keys[ 0 ] = bindings[ i ].keys[ 1 ] = K_NONE;
+ numKeys = 0;
+
+ for( j = 0; j < K_LAST_KEY; j++ )
+ {
+ trap_Key_GetBindingBuf( j, buffer, MAX_STRING_CHARS );
+
+ if( buffer[ 0 ] == 0 )
+ continue;
+
+ if( !Q_stricmp( buffer, bindings[ i ].command ) )
+ {
+ bindings[ i ].keys[ numKeys++ ] = j;
+
+ if( numKeys > 1 )
+ break;
+ }
+ }
+ }
+}
+
+/*
+===============
+CG_KeyNameForCommand
+===============
+*/
+static const char *CG_KeyNameForCommand( const char *command )
+{
+ int i, j;
+ static char buffer[ MAX_STRING_CHARS ];
+ int firstKeyLength;
+
+ buffer[ 0 ] = '\0';
+
+ for( i = 0; i < numBindings; i++ )
+ {
+ if( !Q_stricmp( command, bindings[ i ].command ) )
+ {
+ if( bindings[ i ].keys[ 0 ] != K_NONE )
+ {
+ trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 0 ],
+ buffer, MAX_STRING_CHARS );
+ firstKeyLength = strlen( buffer );
+
+ for( j = 0; j < firstKeyLength; j++ )
+ buffer[ j ] = toupper( buffer[ j ] );
+
+ if( bindings[ i ].keys[ 1 ] != K_NONE )
+ {
+ Q_strcat( buffer, MAX_STRING_CHARS, " or " );
+ trap_Key_KeynumToStringBuf( bindings[ i ].keys[ 1 ],
+ buffer + strlen( buffer ), MAX_STRING_CHARS - strlen( buffer ) );
+
+ for( j = firstKeyLength + 4; j < strlen( buffer ); j++ )
+ buffer[ j ] = toupper( buffer[ j ] );
+ }
+ }
+ else
+ {
+ Q_strncpyz( buffer, va( "\"%s\"", bindings[ i ].humanName ),
+ MAX_STRING_CHARS );
+ Q_strcat( buffer, MAX_STRING_CHARS, " (unbound)" );
+ }
+
+ return buffer;
+ }
+ }
+
+ return "";
+}
+
+#define MAX_TUTORIAL_TEXT 4096
+
+/*
+===============
+CG_BuildableInRange
+===============
+*/
+static entityState_t *CG_BuildableInRange( playerState_t *ps, float *healthFraction )
+{
+ vec3_t view, point;
+ trace_t trace;
+ entityState_t *es;
+ int health;
+
+ AngleVectors( cg.refdefViewAngles, view, NULL, NULL );
+ VectorMA( cg.refdef.vieworg, 64, view, point );
+ CG_Trace( &trace, cg.refdef.vieworg, NULL, NULL,
+ point, ps->clientNum, MASK_SHOT );
+
+ es = &cg_entities[ trace.entityNum ].currentState;
+
+ if( healthFraction )
+ {
+ health = es->generic1 & B_HEALTH_MASK;
+ *healthFraction = (float)health / B_HEALTH_MASK;
+ }
+
+ if( es->eType == ET_BUILDABLE &&
+ ps->stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( es->modelindex ) )
+ return es;
+ else
+ return NULL;
+}
+
+/*
+===============
+CG_AlienBuilderText
+===============
+*/
+static void CG_AlienBuilderText( char *text, playerState_t *ps )
+{
+ buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT;
+ entityState_t *es;
+
+ if( buildable > BA_NONE )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to place the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForBuildable( buildable ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to cancel placing the %s\n",
+ CG_KeyNameForCommand( "+button5" ),
+ BG_FindHumanNameForBuildable( buildable ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to build a structure\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( ( es = CG_BuildableInRange( ps, NULL ) ) )
+ {
+ if( cgs.markDeconstruct )
+ {
+ if( es->generic1 & B_MARKED_TOGGLEBIT )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to unmark this structure\n",
+ CG_KeyNameForCommand( "deconstruct" ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to mark this structure\n",
+ CG_KeyNameForCommand( "deconstruct" ) ) );
+ }
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to destroy this structure\n",
+ CG_KeyNameForCommand( "deconstruct" ) ) );
+ }
+ }
+ }
+
+ if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG )
+ {
+ if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) == BA_NONE )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to swipe\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to launch a projectile\n",
+ CG_KeyNameForCommand( "+button2" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to walk on walls\n",
+ CG_KeyNameForCommand( "+movedown" ) ) );
+ }
+}
+
+/*
+===============
+CG_AlienLevel0Text
+===============
+*/
+static void CG_AlienLevel0Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ "Touch a human to damage it\n" );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to walk on walls\n",
+ CG_KeyNameForCommand( "+movedown" ) ) );
+}
+
+/*
+===============
+CG_AlienLevel1Text
+===============
+*/
+static void CG_AlienLevel1Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ "Touch a human to grab it\n" );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to swipe\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL1_UPG )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to spray poisonous gas\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to walk on walls\n",
+ CG_KeyNameForCommand( "+movedown" ) ) );
+}
+
+/*
+===============
+CG_AlienLevel2Text
+===============
+*/
+static void CG_AlienLevel2Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to bite\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL2_UPG )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to invoke an electrical attack\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold down %s then touch a wall to wall jump\n",
+ CG_KeyNameForCommand( "+moveup" ) ) );
+}
+
+/*
+===============
+CG_AlienLevel3Text
+===============
+*/
+static void CG_AlienLevel3Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to bite\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL3_UPG )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to launch a projectile\n",
+ CG_KeyNameForCommand( "+button2" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold down and release %s to pounce\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+}
+
+/*
+===============
+CG_AlienLevel4Text
+===============
+*/
+static void CG_AlienLevel4Text( char *text, playerState_t *ps )
+{
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to swipe\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold down and release %s to charge\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+}
+
+/*
+===============
+CG_HumanCkitText
+===============
+*/
+static void CG_HumanCkitText( char *text, playerState_t *ps )
+{
+ buildable_t buildable = ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT;
+ float health;
+
+ if( buildable > BA_NONE )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to place the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForBuildable( buildable ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to cancel placing the %s\n",
+ CG_KeyNameForCommand( "+button5" ),
+ BG_FindHumanNameForBuildable( buildable ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to build a structure\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ if( CG_BuildableInRange( ps, &health ) )
+ {
+ if( health < 1.0f )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold %s to repair this structure\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to destroy this structure\n",
+ CG_KeyNameForCommand( "deconstruct" ) ) );
+ }
+ }
+}
+
+/*
+===============
+CG_HumanText
+===============
+*/
+static void CG_HumanText( char *text, playerState_t *ps )
+{
+ char *name;
+ int ammo, clips;
+ upgrade_t upgrade = UP_NONE;
+
+ if( cg.weaponSelect <= 32 )
+ name = cg_weapons[ cg.weaponSelect ].humanName;
+ else if( cg.weaponSelect > 32 )
+ {
+ name = cg_upgrades[ cg.weaponSelect - 32 ].humanName;
+ upgrade = cg.weaponSelect - 32;
+ }
+
+ BG_UnpackAmmoArray( ps->weapon, ps->ammo, ps->powerups, &ammo, &clips );
+
+ if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( ps->weapon ) )
+ {
+ //no ammo
+ switch( ps->weapon )
+ {
+ case WP_MACHINEGUN:
+ case WP_CHAINGUN:
+ case WP_SHOTGUN:
+ case WP_FLAMER:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Find an Armoury and press %s for more ammo\n",
+ CG_KeyNameForCommand( "buy ammo" ) ) );
+ break;
+
+ case WP_LAS_GUN:
+ case WP_PULSE_RIFLE:
+ case WP_MASS_DRIVER:
+ case WP_LUCIFER_CANNON:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Find a Reactor or Repeater and press %s for more ammo\n",
+ CG_KeyNameForCommand( "buy ammo" ) ) );
+ break;
+
+ default:
+ break;
+ }
+ }
+ else
+ {
+ switch( ps->weapon )
+ {
+ case WP_BLASTER:
+ case WP_MACHINEGUN:
+ case WP_SHOTGUN:
+ case WP_LAS_GUN:
+ case WP_CHAINGUN:
+ case WP_PULSE_RIFLE:
+ case WP_FLAMER:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to fire the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForWeapon( ps->weapon ) ) );
+ break;
+
+ case WP_MASS_DRIVER:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to fire the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForWeapon( ps->weapon ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold %s to zoom\n",
+ CG_KeyNameForCommand( "+button5" ) ) );
+ break;
+
+ case WP_PAIN_SAW:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold %s to activate the %s\n",
+ CG_KeyNameForCommand( "+attack" ),
+ BG_FindHumanNameForWeapon( ps->weapon ) ) );
+ break;
+
+ case WP_LUCIFER_CANNON:
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Hold and release %s to fire a charged shot\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to fire the %s\n",
+ CG_KeyNameForCommand( "+button5" ),
+ BG_FindHumanNameForWeapon( ps->weapon ) ) );
+ break;
+
+ case WP_HBUILD:
+ case WP_HBUILD2:
+ CG_HumanCkitText( text, ps );
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s and ",
+ CG_KeyNameForCommand( "weapprev" ) ) );
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "%s to select an upgrade\n",
+ CG_KeyNameForCommand( "weapnext" ) ) );
+
+ if( upgrade == UP_NONE ||
+ ( upgrade > UP_NONE && BG_FindUsableForUpgrade( upgrade ) ) )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to use the %s\n",
+ CG_KeyNameForCommand( "+button2" ),
+ name ) );
+ }
+
+ if( ps->stats[ STAT_HEALTH ] <= 35 &&
+ BG_InventoryContainsUpgrade( UP_MEDKIT, ps->stats ) )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to use your %s\n",
+ CG_KeyNameForCommand( "itemact medkit" ),
+ BG_FindHumanNameForUpgrade( UP_MEDKIT ) ) );
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to use a structure\n",
+ CG_KeyNameForCommand( "+button7" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to sprint\n",
+ CG_KeyNameForCommand( "boost" ) ) );
+}
+
+/*
+===============
+CG_SpectatorText
+===============
+*/
+static void CG_SpectatorText( char *text, playerState_t *ps )
+{
+ if( cgs.clientinfo[ cg.clientNum ].team != PTE_NONE )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to spawn\n", CG_KeyNameForCommand( "+attack" ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to join a team\n", CG_KeyNameForCommand( "+attack" ) ) );
+ }
+
+ if( ps->pm_flags & PMF_FOLLOW )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to stop following\n",
+ CG_KeyNameForCommand( "+button2" ) ) );
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s or ", CG_KeyNameForCommand( "weapprev" ) ) );
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "%s to change player\n", CG_KeyNameForCommand( "weapnext" ) ) );
+ }
+ else
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to follow a %s\n", CG_KeyNameForCommand( "+button2" ),
+ ( cgs.clientinfo[ cg.clientNum ].team == PTE_NONE )
+ ? "player" : "teammate" ) );
+ }
+}
+
+/*
+===============
+CG_TutorialText
+
+Returns context help for the current class/weapon
+===============
+*/
+const char *CG_TutorialText( void )
+{
+ playerState_t *ps;
+ static char text[ MAX_TUTORIAL_TEXT ];
+
+ CG_GetBindings( );
+
+ text[ 0 ] = '\0';
+ ps = &cg.snap->ps;
+
+ if( !cg.intermissionStarted && !cg.demoPlayback )
+ {
+ if( ps->persistant[ PERS_TEAM ] == TEAM_SPECTATOR ||
+ ps->pm_flags & PMF_FOLLOW )
+ {
+ CG_SpectatorText( text, ps );
+ }
+ else if( ps->stats[ STAT_HEALTH ] > 0 )
+ {
+ switch( ps->stats[ STAT_PCLASS ] )
+ {
+ case PCL_ALIEN_BUILDER0:
+ case PCL_ALIEN_BUILDER0_UPG:
+ CG_AlienBuilderText( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL0:
+ CG_AlienLevel0Text( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL1:
+ case PCL_ALIEN_LEVEL1_UPG:
+ CG_AlienLevel1Text( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL2:
+ case PCL_ALIEN_LEVEL2_UPG:
+ CG_AlienLevel2Text( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL3:
+ case PCL_ALIEN_LEVEL3_UPG:
+ CG_AlienLevel3Text( text, ps );
+ break;
+
+ case PCL_ALIEN_LEVEL4:
+ CG_AlienLevel4Text( text, ps );
+ break;
+
+ case PCL_HUMAN:
+ CG_HumanText( text, ps );
+ break;
+
+ default:
+ break;
+ }
+
+ if( ps->stats[ STAT_PTEAM ] == PTE_ALIENS )
+ {
+ entityState_t *es = CG_BuildableInRange( ps, NULL );
+
+ if( ps->stats[ STAT_STATE ] & SS_HOVELING )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to exit the hovel\n",
+ CG_KeyNameForCommand( "+button7" ) ) );
+ }
+ else if( es && es->modelindex == BA_A_HOVEL &&
+ es->generic1 & B_SPAWNED_TOGGLEBIT &&
+ ( ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0 ||
+ ps->stats[ STAT_PCLASS ] == PCL_ALIEN_BUILDER0_UPG ) )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to enter the hovel\n",
+ CG_KeyNameForCommand( "+button7" ) ) );
+ }
+ else if( BG_UpgradeClassAvailable( ps ) )
+ {
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to evolve\n",
+ CG_KeyNameForCommand( "+button7" ) ) );
+ }
+ }
+ }
+
+ Q_strcat( text, MAX_TUTORIAL_TEXT, "Press ESC for the menu" );
+ }
+
+ return text;
+}
diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c
new file mode 100644
index 0000000..477196c
--- /dev/null
+++ b/src/cgame/cg_view.c
@@ -0,0 +1,1340 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_view.c -- setup all the parameters (position, angle, etc)
+// for a 3D rendering
+
+
+#include "cg_local.h"
+
+
+/*
+=============================================================================
+
+ MODEL TESTING
+
+The viewthing and gun positioning tools from Q2 have been integrated and
+enhanced into a single model testing facility.
+
+Model viewing can begin with either "testmodel <modelname>" or "testgun <modelname>".
+
+The names must be the full pathname after the basedir, like
+"models/weapons/v_launch/tris.md3" or "players/male/tris.md3"
+
+Testmodel will create a fake entity 100 units in front of the current view
+position, directly facing the viewer. It will remain immobile, so you can
+move around it to view it from different angles.
+
+Testgun will cause the model to follow the player around and supress the real
+view weapon model. The default frame 0 of most guns is completely off screen,
+so you will probably have to cycle a couple frames to see it.
+
+"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the
+frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in
+q3default.cfg.
+
+If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let
+you adjust the positioning.
+
+Note that none of the model testing features update while the game is paused, so
+it may be convenient to test with deathmatch set to 1 so that bringing down the
+console doesn't pause the game.
+
+=============================================================================
+*/
+
+/*
+=================
+CG_TestModel_f
+
+Creates an entity in front of the current position, which
+can then be moved around
+=================
+*/
+void CG_TestModel_f( void )
+{
+ vec3_t angles;
+
+ memset( &cg.testModelEntity, 0, sizeof( cg.testModelEntity ) );
+ memset( &cg.testModelBarrelEntity, 0, sizeof( cg.testModelBarrelEntity ) );
+
+ if( trap_Argc( ) < 2 )
+ return;
+
+ Q_strncpyz( cg.testModelName, CG_Argv( 1 ), MAX_QPATH );
+ cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName );
+
+ Q_strncpyz( cg.testModelBarrelName, CG_Argv( 1 ), MAX_QPATH );
+ cg.testModelBarrelName[ strlen( cg.testModelBarrelName ) - 4 ] = '\0';
+ Q_strcat( cg.testModelBarrelName, MAX_QPATH, "_barrel.md3" );
+ cg.testModelBarrelEntity.hModel = trap_R_RegisterModel( cg.testModelBarrelName );
+
+ if( trap_Argc( ) == 3 )
+ {
+ cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) );
+ cg.testModelEntity.frame = 1;
+ cg.testModelEntity.oldframe = 0;
+ }
+
+ if( !cg.testModelEntity.hModel )
+ {
+ CG_Printf( "Can't register model\n" );
+ return;
+ }
+
+ VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[ 0 ], cg.testModelEntity.origin );
+
+ angles[ PITCH ] = 0;
+ angles[ YAW ] = 180 + cg.refdefViewAngles[ 1 ];
+ angles[ ROLL ] = 0;
+
+ AnglesToAxis( angles, cg.testModelEntity.axis );
+ cg.testGun = qfalse;
+
+ if( cg.testModelBarrelEntity.hModel )
+ {
+ angles[ YAW ] = 0;
+ angles[ PITCH ] = 0;
+ angles[ ROLL ] = 0;
+ AnglesToAxis( angles, cg.testModelBarrelEntity.axis );
+ }
+}
+
+/*
+=================
+CG_TestGun_f
+
+Replaces the current view weapon with the given model
+=================
+*/
+void CG_TestGun_f( void )
+{
+ CG_TestModel_f( );
+ cg.testGun = qtrue;
+ cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON;
+}
+
+
+void CG_TestModelNextFrame_f( void )
+{
+ cg.testModelEntity.frame++;
+ CG_Printf( "frame %i\n", cg.testModelEntity.frame );
+}
+
+void CG_TestModelPrevFrame_f( void )
+{
+ cg.testModelEntity.frame--;
+
+ if( cg.testModelEntity.frame < 0 )
+ cg.testModelEntity.frame = 0;
+
+ CG_Printf( "frame %i\n", cg.testModelEntity.frame );
+}
+
+void CG_TestModelNextSkin_f( void )
+{
+ cg.testModelEntity.skinNum++;
+ CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
+}
+
+void CG_TestModelPrevSkin_f( void )
+{
+ cg.testModelEntity.skinNum--;
+
+ if( cg.testModelEntity.skinNum < 0 )
+ cg.testModelEntity.skinNum = 0;
+
+ CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
+}
+
+static void CG_AddTestModel( void )
+{
+ int i;
+
+ // re-register the model, because the level may have changed
+ cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName );
+ cg.testModelBarrelEntity.hModel = trap_R_RegisterModel( cg.testModelBarrelName );
+
+ if( !cg.testModelEntity.hModel )
+ {
+ CG_Printf( "Can't register model\n" );
+ return;
+ }
+
+ // if testing a gun, set the origin reletive to the view origin
+ if( cg.testGun )
+ {
+ VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin );
+ VectorCopy( cg.refdef.viewaxis[ 0 ], cg.testModelEntity.axis[ 0 ] );
+ VectorCopy( cg.refdef.viewaxis[ 1 ], cg.testModelEntity.axis[ 1 ] );
+ VectorCopy( cg.refdef.viewaxis[ 2 ], cg.testModelEntity.axis[ 2 ] );
+
+ // allow the position to be adjusted
+ for( i = 0; i < 3; i++ )
+ {
+ cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 0 ][ i ] * cg_gun_x.value;
+ cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 1 ][ i ] * cg_gun_y.value;
+ cg.testModelEntity.origin[ i ] += cg.refdef.viewaxis[ 2 ][ i ] * cg_gun_z.value;
+ }
+ }
+
+ trap_R_AddRefEntityToScene( &cg.testModelEntity );
+
+ if( cg.testModelBarrelEntity.hModel )
+ {
+ CG_PositionEntityOnTag( &cg.testModelBarrelEntity, &cg.testModelEntity,
+ cg.testModelEntity.hModel, "tag_barrel" );
+
+ trap_R_AddRefEntityToScene( &cg.testModelBarrelEntity );
+ }
+}
+
+
+
+//============================================================================
+
+
+/*
+=================
+CG_CalcVrect
+
+Sets the coordinates of the rendered window
+=================
+*/
+static void CG_CalcVrect( void )
+{
+ int size;
+
+ // the intermission should allways be full screen
+ if( cg.snap->ps.pm_type == PM_INTERMISSION )
+ size = 100;
+ else
+ {
+ // bound normal viewsize
+ if( cg_viewsize.integer < 30 )
+ {
+ trap_Cvar_Set( "cg_viewsize", "30" );
+ size = 30;
+ }
+ else if( cg_viewsize.integer > 100 )
+ {
+ trap_Cvar_Set( "cg_viewsize","100" );
+ size = 100;
+ }
+ else
+ size = cg_viewsize.integer;
+ }
+
+ cg.refdef.width = cgs.glconfig.vidWidth * size / 100;
+ cg.refdef.width &= ~1;
+
+ cg.refdef.height = cgs.glconfig.vidHeight * size / 100;
+ cg.refdef.height &= ~1;
+
+ cg.refdef.x = ( cgs.glconfig.vidWidth - cg.refdef.width ) / 2;
+ cg.refdef.y = ( cgs.glconfig.vidHeight - cg.refdef.height ) / 2;
+}
+
+//==============================================================================
+
+
+/*
+===============
+CG_OffsetThirdPersonView
+
+===============
+*/
+#define FOCUS_DISTANCE 512
+static void CG_OffsetThirdPersonView( void )
+{
+ vec3_t forward, right, up;
+ vec3_t view;
+ vec3_t focusAngles;
+ trace_t trace;
+ static vec3_t mins = { -8, -8, -8 };
+ static vec3_t maxs = { 8, 8, 8 };
+ vec3_t focusPoint;
+ float focusDist;
+ float forwardScale, sideScale;
+ vec3_t surfNormal;
+
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( surfNormal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( cg.predictedPlayerState.grapplePoint, surfNormal );
+ }
+ else
+ VectorSet( surfNormal, 0.0f, 0.0f, 1.0f );
+
+ VectorMA( cg.refdef.vieworg, cg.predictedPlayerState.viewheight, surfNormal, cg.refdef.vieworg );
+
+ VectorCopy( cg.refdefViewAngles, focusAngles );
+
+ // if dead, look at killer
+ if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 )
+ {
+ focusAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ];
+ cg.refdefViewAngles[ YAW ] = cg.predictedPlayerState.stats[ STAT_VIEWLOCK ];
+ }
+
+ //if ( focusAngles[PITCH] > 45 ) {
+ // focusAngles[PITCH] = 45; // don't go too far overhead
+ //}
+ AngleVectors( focusAngles, forward, NULL, NULL );
+
+ VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint );
+
+ VectorCopy( cg.refdef.vieworg, view );
+
+ VectorMA( view, 12, surfNormal, view );
+
+ //cg.refdefViewAngles[PITCH] *= 0.5;
+
+ AngleVectors( cg.refdefViewAngles, forward, right, up );
+
+ forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI );
+ sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI );
+ VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view );
+ VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view );
+
+ // trace a ray from the origin to the viewpoint to make sure the view isn't
+ // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything
+
+ if( !cg_cameraMode.integer )
+ {
+ CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID );
+
+ if( trace.fraction != 1.0 )
+ {
+ VectorCopy( trace.endpos, view );
+ view[ 2 ] += ( 1.0 - trace.fraction ) * 32;
+ // try another trace to this position, because a tunnel may have the ceiling
+ // close enogh that this is poking out
+
+ CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID );
+ VectorCopy( trace.endpos, view );
+ }
+ }
+
+ VectorCopy( view, cg.refdef.vieworg );
+
+ // select pitch to look at focus point from vieword
+ VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint );
+ focusDist = sqrt( focusPoint[ 0 ] * focusPoint[ 0 ] + focusPoint[ 1 ] * focusPoint[ 1 ] );
+ if ( focusDist < 1 ) {
+ focusDist = 1; // should never happen
+ }
+ cg.refdefViewAngles[ PITCH ] = -180 / M_PI * atan2( focusPoint[ 2 ], focusDist );
+ cg.refdefViewAngles[ YAW ] -= cg_thirdPersonAngle.value;
+}
+
+
+// this causes a compiler bug on mac MrC compiler
+static void CG_StepOffset( void )
+{
+ float steptime;
+ int timeDelta;
+ vec3_t normal;
+ playerState_t *ps = &cg.predictedPlayerState;
+
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( ps->grapplePoint, normal );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+
+ steptime = BG_FindSteptimeForClass( ps->stats[ STAT_PCLASS ] );
+
+ // smooth out stair climbing
+ timeDelta = cg.time - cg.stepTime;
+ if( timeDelta < steptime )
+ {
+ float stepChange = cg.stepChange
+ * (steptime - timeDelta) / steptime;
+
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ VectorMA( cg.refdef.vieworg, -stepChange, normal, cg.refdef.vieworg );
+ else
+ cg.refdef.vieworg[ 2 ] -= stepChange;
+ }
+}
+
+#define PCLOUD_ROLL_AMPLITUDE 25.0f
+#define PCLOUD_ROLL_FREQUENCY 0.4f
+#define PCLOUD_ZOOM_AMPLITUDE 15
+#define PCLOUD_ZOOM_FREQUENCY 0.7f
+
+
+/*
+===============
+CG_OffsetFirstPersonView
+
+===============
+*/
+static void CG_OffsetFirstPersonView( void )
+{
+ float *origin;
+ float *angles;
+ float bob;
+ float ratio;
+ float delta;
+ float speed;
+ float f;
+ vec3_t predictedVelocity;
+ int timeDelta;
+ float bob2;
+ vec3_t normal, baseOrigin;
+ playerState_t *ps = &cg.predictedPlayerState;
+
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ {
+ if( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING )
+ VectorSet( normal, 0.0f, 0.0f, -1.0f );
+ else
+ VectorCopy( ps->grapplePoint, normal );
+ }
+ else
+ VectorSet( normal, 0.0f, 0.0f, 1.0f );
+
+
+ if( cg.snap->ps.pm_type == PM_INTERMISSION )
+ return;
+
+ origin = cg.refdef.vieworg;
+ angles = cg.refdefViewAngles;
+
+ VectorCopy( origin, baseOrigin );
+
+ // if dead, fix the angle and don't add any kick
+ if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 )
+ {
+ angles[ ROLL ] = 40;
+ angles[ PITCH ] = -15;
+ angles[ YAW ] = cg.snap->ps.stats[ STAT_VIEWLOCK ];
+ origin[ 2 ] += cg.predictedPlayerState.viewheight;
+ return;
+ }
+
+ // add angles based on weapon kick
+ VectorAdd( angles, cg.kick_angles, angles );
+
+ // add angles based on damage kick
+ if( cg.damageTime )
+ {
+ ratio = cg.time - cg.damageTime;
+ if( ratio < DAMAGE_DEFLECT_TIME )
+ {
+ ratio /= DAMAGE_DEFLECT_TIME;
+ angles[ PITCH ] += ratio * cg.v_dmg_pitch;
+ angles[ ROLL ] += ratio * cg.v_dmg_roll;
+ }
+ else
+ {
+ ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME;
+ if( ratio > 0 )
+ {
+ angles[ PITCH ] += ratio * cg.v_dmg_pitch;
+ angles[ ROLL ] += ratio * cg.v_dmg_roll;
+ }
+ }
+ }
+
+ // add pitch based on fall kick
+#if 0
+ ratio = ( cg.time - cg.landTime) / FALL_TIME;
+ if (ratio < 0)
+ ratio = 0;
+ angles[PITCH] += ratio * cg.fall_value;
+#endif
+
+ // add angles based on velocity
+ VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity );
+
+ delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 0 ] );
+ angles[ PITCH ] += delta * cg_runpitch.value;
+
+ delta = DotProduct( predictedVelocity, cg.refdef.viewaxis[ 1 ] );
+ angles[ ROLL ] -= delta * cg_runroll.value;
+
+ // add angles based on bob
+ // bob amount is class dependant
+
+ if( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR )
+ bob2 = 0.0f;
+ else
+ bob2 = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] );
+
+
+#define LEVEL4_FEEDBACK 10.0f
+
+ //give a charging player some feedback
+ if( ps->weapon == WP_ALEVEL4 )
+ {
+ if( ps->stats[ STAT_MISC ] > 0 )
+ {
+ float fraction = (float)ps->stats[ STAT_MISC ] / (float)LEVEL4_CHARGE_TIME;
+
+ if( fraction > 1.0f )
+ fraction = 1.0f;
+
+ bob2 *= ( 1.0f + fraction * LEVEL4_FEEDBACK );
+ }
+ }
+
+ if( bob2 != 0.0f )
+ {
+ // make sure the bob is visible even at low speeds
+ speed = cg.xyspeed > 200 ? cg.xyspeed : 200;
+
+ delta = cg.bobfracsin * ( bob2 ) * speed;
+ if( cg.predictedPlayerState.pm_flags & PMF_DUCKED )
+ delta *= 3; // crouching
+
+ angles[ PITCH ] += delta;
+ delta = cg.bobfracsin * ( bob2 ) * speed;
+ if( cg.predictedPlayerState.pm_flags & PMF_DUCKED )
+ delta *= 3; // crouching accentuates roll
+
+ if( cg.bobcycle & 1 )
+ delta = -delta;
+
+ angles[ ROLL ] += delta;
+ }
+
+#define LEVEL3_FEEDBACK 20.0f
+
+ //provide some feedback for pouncing
+ if( cg.predictedPlayerState.weapon == WP_ALEVEL3 ||
+ cg.predictedPlayerState.weapon == WP_ALEVEL3_UPG )
+ {
+ if( cg.predictedPlayerState.stats[ STAT_MISC ] > 0 )
+ {
+ float fraction1, fraction2;
+ vec3_t forward;
+
+ AngleVectors( angles, forward, NULL, NULL );
+ VectorNormalize( forward );
+
+ fraction1 = (float)( cg.time - cg.weapon2Time ) / (float)LEVEL3_POUNCE_CHARGE_TIME;
+
+ if( fraction1 > 1.0f )
+ fraction1 = 1.0f;
+
+ fraction2 = -sin( fraction1 * M_PI / 2 );
+
+ VectorMA( origin, LEVEL3_FEEDBACK * fraction2, forward, origin );
+ }
+ }
+
+#define STRUGGLE_DIST 5.0f
+#define STRUGGLE_TIME 250
+
+ //allow the player to struggle a little whilst grabbed
+ if( cg.predictedPlayerState.pm_type == PM_GRABBED )
+ {
+ vec3_t forward, right, up;
+ usercmd_t cmd;
+ int cmdNum;
+ float fFraction, rFraction, uFraction;
+ float fFraction2, rFraction2, uFraction2;
+
+ cmdNum = trap_GetCurrentCmdNumber();
+ trap_GetUserCmd( cmdNum, &cmd );
+
+ AngleVectors( angles, forward, right, up );
+
+ fFraction = (float)( cg.time - cg.forwardMoveTime ) / STRUGGLE_TIME;
+ rFraction = (float)( cg.time - cg.rightMoveTime ) / STRUGGLE_TIME;
+ uFraction = (float)( cg.time - cg.upMoveTime ) / STRUGGLE_TIME;
+
+ if( fFraction > 1.0f )
+ fFraction = 1.0f;
+ if( rFraction > 1.0f )
+ rFraction = 1.0f;
+ if( uFraction > 1.0f )
+ uFraction = 1.0f;
+
+ fFraction2 = -sin( fFraction * M_PI / 2 );
+ rFraction2 = -sin( rFraction * M_PI / 2 );
+ uFraction2 = -sin( uFraction * M_PI / 2 );
+
+ if( cmd.forwardmove > 0 )
+ VectorMA( origin, STRUGGLE_DIST * fFraction, forward, origin );
+ else if( cmd.forwardmove < 0 )
+ VectorMA( origin, -STRUGGLE_DIST * fFraction, forward, origin );
+ else
+ cg.forwardMoveTime = cg.time;
+
+ if( cmd.rightmove > 0 )
+ VectorMA( origin, STRUGGLE_DIST * rFraction, right, origin );
+ else if( cmd.rightmove < 0 )
+ VectorMA( origin, -STRUGGLE_DIST * rFraction, right, origin );
+ else
+ cg.rightMoveTime = cg.time;
+
+ if( cmd.upmove > 0 )
+ VectorMA( origin, STRUGGLE_DIST * uFraction, up, origin );
+ else if( cmd.upmove < 0 )
+ VectorMA( origin, -STRUGGLE_DIST * uFraction, up, origin );
+ else
+ cg.upMoveTime = cg.time;
+ }
+
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED &&
+ !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ {
+ float fraction = sin( ( (float)cg.time / 1000.0f ) * M_PI * 2 * PCLOUD_ROLL_FREQUENCY );
+ float pitchFraction = sin( ( (float)cg.time / 1000.0f ) * M_PI * 5 * PCLOUD_ROLL_FREQUENCY );
+
+ fraction *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME );
+ pitchFraction *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME );
+
+ angles[ ROLL ] += fraction * PCLOUD_ROLL_AMPLITUDE;
+ angles[ YAW ] += fraction * PCLOUD_ROLL_AMPLITUDE;
+ angles[ PITCH ] += pitchFraction * PCLOUD_ROLL_AMPLITUDE / 2.0f;
+ }
+
+ // this *feels* more realisitic for humans
+ if( cg.predictedPlayerState.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ {
+ angles[PITCH] += cg.bobfracsin * bob2 * 0.5;
+
+ // heavy breathing effects //FIXME: sound
+ if( cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 )
+ {
+ float deltaBreath = (float)(
+ cg.predictedPlayerState.stats[ STAT_STAMINA ] < 0 ?
+ -cg.predictedPlayerState.stats[ STAT_STAMINA ] :
+ cg.predictedPlayerState.stats[ STAT_STAMINA ] ) / 200.0;
+ float deltaAngle = cos( (float)cg.time/150.0 ) * deltaBreath;
+
+ deltaAngle += ( deltaAngle < 0 ? -deltaAngle : deltaAngle ) * 0.5;
+
+ angles[ PITCH ] -= deltaAngle;
+ }
+ }
+
+//===================================
+
+ // add view height
+ // when wall climbing the viewheight is not straight up
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ VectorMA( origin, ps->viewheight, normal, origin );
+ else
+ origin[ 2 ] += cg.predictedPlayerState.viewheight;
+
+ // smooth out duck height changes
+ timeDelta = cg.time - cg.duckTime;
+ if( timeDelta < DUCK_TIME)
+ {
+ cg.refdef.vieworg[ 2 ] -= cg.duckChange
+ * ( DUCK_TIME - timeDelta ) / DUCK_TIME;
+ }
+
+ // add bob height
+ bob = cg.bobfracsin * cg.xyspeed * bob2;
+
+ if( bob > 6 )
+ bob = 6;
+
+ // likewise for bob
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_WALLCLIMBING )
+ VectorMA( origin, bob, normal, origin );
+ else
+ origin[ 2 ] += bob;
+
+
+ // add fall height
+ delta = cg.time - cg.landTime;
+
+ if( delta < LAND_DEFLECT_TIME )
+ {
+ f = delta / LAND_DEFLECT_TIME;
+ cg.refdef.vieworg[ 2 ] += cg.landChange * f;
+ }
+ else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME )
+ {
+ delta -= LAND_DEFLECT_TIME;
+ f = 1.0 - ( delta / LAND_RETURN_TIME );
+ cg.refdef.vieworg[ 2 ] += cg.landChange * f;
+ }
+
+ // add step offset
+ CG_StepOffset( );
+
+ // add kick offset
+
+ VectorAdd (origin, cg.kick_origin, origin);
+}
+
+//======================================================================
+
+/*
+====================
+CG_CalcFov
+
+Fixed fov at intermissions, otherwise account for fov variable and zooms.
+====================
+*/
+#define WAVE_AMPLITUDE 1
+#define WAVE_FREQUENCY 0.4
+
+#define FOVWARPTIME 400.0
+
+static int CG_CalcFov( void )
+{
+ float x;
+ float phase;
+ float v;
+ int contents;
+ float fov_x, fov_y;
+ float zoomFov;
+ float f;
+ int inwater;
+ int attribFov;
+ usercmd_t cmd;
+ int cmdNum;
+
+ cmdNum = trap_GetCurrentCmdNumber( );
+ trap_GetUserCmd( cmdNum, &cmd );
+
+ if( cg.predictedPlayerState.pm_type == PM_INTERMISSION ||
+ ( cg.snap->ps.persistant[ PERS_TEAM ] == TEAM_SPECTATOR ) )
+ {
+ // if in intermission, use a fixed value
+ fov_x = 90;
+ }
+ else
+ {
+ // don't lock the fov globally - we need to be able to change it
+ attribFov = BG_FindFovForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] );
+ fov_x = attribFov;
+
+ if ( fov_x < 1 )
+ fov_x = 1;
+ else if ( fov_x > 160 )
+ fov_x = 160;
+
+ if( cg.spawnTime > ( cg.time - FOVWARPTIME ) &&
+ BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_FOVWARPS ) )
+ {
+ float temp, temp2;
+
+ temp = (float)( cg.time - cg.spawnTime ) / FOVWARPTIME;
+ temp2 = ( 170 - fov_x ) * temp;
+
+ //Com_Printf( "%f %f\n", temp*100, temp2*100 );
+
+ fov_x = 170 - temp2;
+ }
+
+ // account for zooms
+ zoomFov = BG_FindZoomFovForWeapon( cg.predictedPlayerState.weapon );
+ if ( zoomFov < 1 )
+ zoomFov = 1;
+ else if ( zoomFov > attribFov )
+ zoomFov = attribFov;
+
+ // only do all the zoom stuff if the client CAN zoom
+ // FIXME: zoom control is currently hard coded to BUTTON_ATTACK2
+ if( BG_WeaponCanZoom( cg.predictedPlayerState.weapon ) )
+ {
+ if ( cg.zoomed )
+ {
+ f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME;
+
+ if ( f > 1.0 )
+ fov_x = zoomFov;
+ else
+ fov_x = fov_x + f * ( zoomFov - fov_x );
+
+ // BUTTON_ATTACK2 isn't held so unzoom next time
+ if( !( cmd.buttons & BUTTON_ATTACK2 ) )
+ {
+ cg.zoomed = qfalse;
+ cg.zoomTime = cg.time;
+ }
+ }
+ else
+ {
+ f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME;
+
+ if ( f > 1.0 )
+ fov_x = fov_x;
+ else
+ fov_x = zoomFov + f * ( fov_x - zoomFov );
+
+ // BUTTON_ATTACK2 is held so zoom next time
+ if( cmd.buttons & BUTTON_ATTACK2 )
+ {
+ cg.zoomed = qtrue;
+ cg.zoomTime = cg.time;
+ }
+ }
+ }
+ }
+
+ x = cg.refdef.width / tan( fov_x / 360 * M_PI );
+ fov_y = atan2( cg.refdef.height, x );
+ fov_y = fov_y * 360 / M_PI;
+
+ // warp if underwater
+ contents = CG_PointContents( cg.refdef.vieworg, -1 );
+
+ if( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) )
+ {
+ phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
+ v = WAVE_AMPLITUDE * sin( phase );
+ fov_x += v;
+ fov_y -= v;
+ inwater = qtrue;
+ }
+ else
+ inwater = qfalse;
+
+ if( cg.predictedPlayerState.stats[ STAT_STATE ] & SS_POISONCLOUDED &&
+ cg.predictedPlayerState.stats[ STAT_HEALTH ] > 0 &&
+ !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ {
+ phase = cg.time / 1000.0 * PCLOUD_ZOOM_FREQUENCY * M_PI * 2;
+ v = PCLOUD_ZOOM_AMPLITUDE * sin( phase );
+ v *= 1.0f - ( ( cg.time - cg.poisonedTime ) / (float)LEVEL1_PCLOUD_TIME );
+ fov_x += v;
+ fov_y += v;
+ }
+
+
+ // set it
+ cg.refdef.fov_x = fov_x;
+ cg.refdef.fov_y = fov_y;
+
+ if( !cg.zoomed )
+ cg.zoomSensitivity = 1;
+ else
+ cg.zoomSensitivity = cg.refdef.fov_y / 75.0;
+
+ return inwater;
+}
+
+
+
+#define NORMAL_HEIGHT 64.0f
+#define NORMAL_WIDTH 6.0f
+
+/*
+===============
+CG_DrawSurfNormal
+
+Draws a vector against
+the surface player is looking at
+===============
+*/
+static void CG_DrawSurfNormal( void )
+{
+ trace_t tr;
+ vec3_t end, temp;
+ polyVert_t normal[ 4 ];
+ vec4_t color = { 0.0f, 255.0f, 0.0f, 128.0f };
+
+ VectorMA( cg.refdef.vieworg, 8192, cg.refdef.viewaxis[ 0 ], end );
+
+ CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, end, cg.predictedPlayerState.clientNum, MASK_SOLID );
+
+ VectorCopy( tr.endpos, normal[ 0 ].xyz );
+ normal[ 0 ].st[ 0 ] = 0;
+ normal[ 0 ].st[ 1 ] = 0;
+ Vector4Copy( color, normal[ 0 ].modulate );
+
+ VectorMA( tr.endpos, NORMAL_WIDTH, cg.refdef.viewaxis[ 1 ], temp );
+ VectorCopy( temp, normal[ 1 ].xyz);
+ normal[ 1 ].st[ 0 ] = 0;
+ normal[ 1 ].st[ 1 ] = 1;
+ Vector4Copy( color, normal[ 1 ].modulate );
+
+ VectorMA( tr.endpos, NORMAL_HEIGHT, tr.plane.normal, temp );
+ VectorMA( temp, NORMAL_WIDTH, cg.refdef.viewaxis[ 1 ], temp );
+ VectorCopy( temp, normal[ 2 ].xyz );
+ normal[ 2 ].st[ 0 ] = 1;
+ normal[ 2 ].st[ 1 ] = 1;
+ Vector4Copy( color, normal[ 2 ].modulate );
+
+ VectorMA( tr.endpos, NORMAL_HEIGHT, tr.plane.normal, temp );
+ VectorCopy( temp, normal[ 3 ].xyz );
+ normal[ 3 ].st[ 0 ] = 1;
+ normal[ 3 ].st[ 1 ] = 0;
+ Vector4Copy( color, normal[ 3 ].modulate );
+
+ trap_R_AddPolyToScene( cgs.media.outlineShader, 4, normal );
+}
+
+/*
+===============
+CG_addSmoothOp
+===============
+*/
+void CG_addSmoothOp( vec3_t rotAxis, float rotAngle, float timeMod )
+{
+ int i;
+
+ //iterate through smooth array
+ for( i = 0; i < MAXSMOOTHS; i++ )
+ {
+ //found an unused index in the smooth array
+ if( cg.sList[ i ].time + cg_wwSmoothTime.integer < cg.time )
+ {
+ //copy to array and stop
+ VectorCopy( rotAxis, cg.sList[ i ].rotAxis );
+ cg.sList[ i ].rotAngle = rotAngle;
+ cg.sList[ i ].time = cg.time;
+ cg.sList[ i ].timeMod = timeMod;
+ return;
+ }
+ }
+
+ //no free indices in the smooth array
+}
+
+/*
+===============
+CG_smoothWWTransitions
+===============
+*/
+static void CG_smoothWWTransitions( playerState_t *ps, const vec3_t in, vec3_t out )
+{
+ vec3_t surfNormal, rotAxis, temp;
+ vec3_t refNormal = { 0.0f, 0.0f, 1.0f };
+ vec3_t ceilingNormal = { 0.0f, 0.0f, -1.0f };
+ int i;
+ float stLocal, sFraction, rotAngle;
+ float smoothTime, timeMod;
+ qboolean performed = qfalse;
+ vec3_t inAxis[ 3 ], lastAxis[ 3 ], outAxis[ 3 ];
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ {
+ VectorCopy( in, out );
+ return;
+ }
+
+ //set surfNormal
+ if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) )
+ VectorCopy( ps->grapplePoint, surfNormal );
+ else
+ VectorCopy( ceilingNormal, surfNormal );
+
+ AnglesToAxis( in, inAxis );
+
+ //if we are moving from one surface to another smooth the transition
+ if( !VectorCompare( surfNormal, cg.lastNormal ) )
+ {
+ //if we moving from the ceiling to the floor special case
+ //( x product of colinear vectors is undefined)
+ if( VectorCompare( ceilingNormal, cg.lastNormal ) &&
+ VectorCompare( refNormal, surfNormal ) )
+ {
+ AngleVectors( in, temp, NULL, NULL );
+ ProjectPointOnPlane( rotAxis, temp, refNormal );
+ VectorNormalize( rotAxis );
+ rotAngle = 180.0f;
+ timeMod = 1.5f;
+ }
+ else
+ {
+ AnglesToAxis( cg.lastVangles, lastAxis );
+ rotAngle = DotProduct( inAxis[ 0 ], lastAxis[ 0 ] ) +
+ DotProduct( inAxis[ 1 ], lastAxis[ 1 ] ) +
+ DotProduct( inAxis[ 2 ], lastAxis[ 2 ] );
+
+ rotAngle = RAD2DEG( acos( ( rotAngle - 1.0f ) / 2.0f ) );
+
+ CrossProduct( lastAxis[ 0 ], inAxis[ 0 ], temp );
+ VectorCopy( temp, rotAxis );
+ CrossProduct( lastAxis[ 1 ], inAxis[ 1 ], temp );
+ VectorAdd( rotAxis, temp, rotAxis );
+ CrossProduct( lastAxis[ 2 ], inAxis[ 2 ], temp );
+ VectorAdd( rotAxis, temp, rotAxis );
+
+ VectorNormalize( rotAxis );
+
+ timeMod = 1.0f;
+ }
+
+ //add the op
+ CG_addSmoothOp( rotAxis, rotAngle, timeMod );
+ }
+
+ //iterate through ops
+ for( i = MAXSMOOTHS - 1; i >= 0; i-- )
+ {
+ smoothTime = (int)( cg_wwSmoothTime.integer * cg.sList[ i ].timeMod );
+
+ //if this op has time remaining, perform it
+ if( cg.time < cg.sList[ i ].time + smoothTime )
+ {
+ stLocal = 1.0f - ( ( ( cg.sList[ i ].time + smoothTime ) - cg.time ) / smoothTime );
+ sFraction = -( cos( stLocal * M_PI ) + 1.0f ) / 2.0f;
+
+ RotatePointAroundVector( outAxis[ 0 ], cg.sList[ i ].rotAxis,
+ inAxis[ 0 ], sFraction * cg.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 1 ], cg.sList[ i ].rotAxis,
+ inAxis[ 1 ], sFraction * cg.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 2 ], cg.sList[ i ].rotAxis,
+ inAxis[ 2 ], sFraction * cg.sList[ i ].rotAngle );
+
+ AxisCopy( outAxis, inAxis );
+ performed = qtrue;
+ }
+ }
+
+ //if we performed any ops then return the smoothed angles
+ //otherwise simply return the in angles
+ if( performed )
+ AxisToAngles( outAxis, out );
+ else
+ VectorCopy( in, out );
+
+ //copy the current normal to the lastNormal
+ VectorCopy( in, cg.lastVangles );
+ VectorCopy( surfNormal, cg.lastNormal );
+}
+
+/*
+===============
+CG_smoothWJTransitions
+===============
+*/
+static void CG_smoothWJTransitions( playerState_t *ps, const vec3_t in, vec3_t out )
+{
+ int i;
+ float stLocal, sFraction;
+ qboolean performed = qfalse;
+ vec3_t inAxis[ 3 ], outAxis[ 3 ];
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ {
+ VectorCopy( in, out );
+ return;
+ }
+
+ AnglesToAxis( in, inAxis );
+
+ //iterate through ops
+ for( i = MAXSMOOTHS - 1; i >= 0; i-- )
+ {
+ //if this op has time remaining, perform it
+ if( cg.time < cg.sList[ i ].time + cg_wwSmoothTime.integer )
+ {
+ stLocal = ( ( cg.sList[ i ].time + cg_wwSmoothTime.integer ) - cg.time ) / cg_wwSmoothTime.integer;
+ sFraction = 1.0f - ( ( cos( stLocal * M_PI * 2.0f ) + 1.0f ) / 2.0f );
+
+ RotatePointAroundVector( outAxis[ 0 ], cg.sList[ i ].rotAxis,
+ inAxis[ 0 ], sFraction * cg.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 1 ], cg.sList[ i ].rotAxis,
+ inAxis[ 1 ], sFraction * cg.sList[ i ].rotAngle );
+ RotatePointAroundVector( outAxis[ 2 ], cg.sList[ i ].rotAxis,
+ inAxis[ 2 ], sFraction * cg.sList[ i ].rotAngle );
+
+ AxisCopy( outAxis, inAxis );
+ performed = qtrue;
+ }
+ }
+
+ //if we performed any ops then return the smoothed angles
+ //otherwise simply return the in angles
+ if( performed )
+ AxisToAngles( outAxis, out );
+ else
+ VectorCopy( in, out );
+}
+
+
+/*
+===============
+CG_CalcViewValues
+
+Sets cg.refdef view values
+===============
+*/
+static int CG_CalcViewValues( void )
+{
+ playerState_t *ps;
+
+ memset( &cg.refdef, 0, sizeof( cg.refdef ) );
+
+ // calculate size of 3D view
+ CG_CalcVrect( );
+
+ ps = &cg.predictedPlayerState;
+
+ // intermission view
+ if( ps->pm_type == PM_INTERMISSION )
+ {
+ VectorCopy( ps->origin, cg.refdef.vieworg );
+ VectorCopy( ps->viewangles, cg.refdefViewAngles );
+ AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
+
+ return CG_CalcFov( );
+ }
+
+ cg.bobcycle = ( ps->bobCycle & 128 ) >> 7;
+ cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) );
+ cg.xyspeed = sqrt( ps->velocity[ 0 ] * ps->velocity[ 0 ] +
+ ps->velocity[ 1 ] * ps->velocity[ 1 ] );
+
+ VectorCopy( ps->origin, cg.refdef.vieworg );
+
+ if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLCLIMBER ) )
+ CG_smoothWWTransitions( ps, ps->viewangles, cg.refdefViewAngles );
+ else if( BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) )
+ CG_smoothWJTransitions( ps, ps->viewangles, cg.refdefViewAngles );
+ else
+ VectorCopy( ps->viewangles, cg.refdefViewAngles );
+
+ //clumsy logic, but it needs to be this way round because the CS propogation
+ //delay screws things up otherwise
+ if( !BG_ClassHasAbility( ps->stats[ STAT_PCLASS ], SCA_WALLJUMPER ) )
+ {
+ if( !( ps->stats[ STAT_STATE ] & SS_WALLCLIMBING ) )
+ VectorSet( cg.lastNormal, 0.0f, 0.0f, 1.0f );
+ }
+
+ // add error decay
+ if( cg_errorDecay.value > 0 )
+ {
+ int t;
+ float f;
+
+ t = cg.time - cg.predictedErrorTime;
+ f = ( cg_errorDecay.value - t ) / cg_errorDecay.value;
+
+ if( f > 0 && f < 1 )
+ VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg );
+ else
+ cg.predictedErrorTime = 0;
+ }
+
+ //shut off the poison cloud effect if it's still on the go
+ if( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 )
+ {
+ if( CG_IsParticleSystemValid( &cg.poisonCloudPS ) )
+ CG_DestroyParticleSystem( &cg.poisonCloudPS );
+ }
+
+ if( cg.renderingThirdPerson )
+ {
+ // back away from character
+ CG_OffsetThirdPersonView( );
+ }
+ else
+ {
+ // offset for local bobbing and kicks
+ CG_OffsetFirstPersonView( );
+ }
+
+ // position eye reletive to origin
+ AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
+
+ if( cg.hyperspace )
+ cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE;
+
+ //draw the surface normal looking at
+ if( cg_drawSurfNormal.integer )
+ CG_DrawSurfNormal( );
+
+ // field of view
+ return CG_CalcFov( );
+}
+
+/*
+=====================
+CG_AddBufferedSound
+=====================
+*/
+void CG_AddBufferedSound( sfxHandle_t sfx )
+{
+ if( !sfx )
+ return;
+
+ cg.soundBuffer[ cg.soundBufferIn ] = sfx;
+ cg.soundBufferIn = ( cg.soundBufferIn + 1 ) % MAX_SOUNDBUFFER;
+
+ if( cg.soundBufferIn == cg.soundBufferOut )
+ cg.soundBufferOut++;
+}
+
+/*
+=====================
+CG_PlayBufferedSounds
+=====================
+*/
+static void CG_PlayBufferedSounds( void )
+{
+ if( cg.soundTime < cg.time )
+ {
+ if( cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[ cg.soundBufferOut ] )
+ {
+ trap_S_StartLocalSound( cg.soundBuffer[ cg.soundBufferOut ], CHAN_ANNOUNCER );
+ cg.soundBuffer[ cg.soundBufferOut ] = 0;
+ cg.soundBufferOut = ( cg.soundBufferOut + 1 ) % MAX_SOUNDBUFFER;
+ cg.soundTime = cg.time + 750;
+ }
+ }
+}
+
+//=========================================================================
+
+/*
+=================
+CG_DrawActiveFrame
+
+Generates and draws a game scene and status information at the given time.
+=================
+*/
+void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback )
+{
+ int inwater;
+
+ cg.time = serverTime;
+ cg.demoPlayback = demoPlayback;
+
+ // update cvars
+ CG_UpdateCvars( );
+
+ // if we are only updating the screen as a loading
+ // pacifier, don't even try to read snapshots
+ if( cg.infoScreenText[ 0 ] != 0 )
+ {
+ CG_DrawLoadingScreen( );
+ return;
+ }
+
+ // any looped sounds will be respecified as entities
+ // are added to the render list
+ trap_S_ClearLoopingSounds( qfalse );
+
+ // clear all the render lists
+ trap_R_ClearScene( );
+
+ // set up cg.snap and possibly cg.nextSnap
+ CG_ProcessSnapshots( );
+
+ // if we haven't received any snapshots yet, all
+ // we can draw is the information screen
+ if( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) )
+ {
+ CG_DrawLoadingScreen( );
+ return;
+ }
+
+ // let the client system know what our weapon and zoom settings are
+ trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity );
+
+ // this counter will be bumped for every valid scene we generate
+ cg.clientFrame++;
+
+ // update cg.predictedPlayerState
+ CG_PredictPlayerState( );
+
+ // decide on third person view
+ cg.renderingThirdPerson = cg_thirdPerson.integer || ( cg.snap->ps.stats[ STAT_HEALTH ] <= 0 );
+
+ // build cg.refdef
+ inwater = CG_CalcViewValues( );
+
+ // build the render lists
+ if( !cg.hyperspace )
+ {
+ CG_AddPacketEntities( ); // after calcViewValues, so predicted player state is correct
+ CG_AddMarks( );
+ }
+
+ CG_AddViewWeapon( &cg.predictedPlayerState );
+
+ //after CG_AddViewWeapon
+ if( !cg.hyperspace )
+ {
+ CG_AddParticles( );
+ CG_AddTrails( );
+ }
+
+ // add buffered sounds
+ CG_PlayBufferedSounds( );
+
+ // finish up the rest of the refdef
+ if( cg.testModelEntity.hModel )
+ CG_AddTestModel( );
+
+ cg.refdef.time = cg.time;
+ memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) );
+
+ //remove expired console lines
+ if( cg.consoleLines[ 0 ].time + cg_consoleLatency.integer < cg.time && cg_consoleLatency.integer > 0 )
+ CG_RemoveNotifyLine( );
+
+ // update audio positions
+ trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater );
+
+ // make sure the lagometerSample and frame timing isn't done twice when in stereo
+ if( stereoView != STEREO_RIGHT )
+ {
+ cg.frametime = cg.time - cg.oldTime;
+
+ if( cg.frametime < 0 )
+ cg.frametime = 0;
+
+ cg.oldTime = cg.time;
+ CG_AddLagometerFrameInfo( );
+ }
+
+ if( cg_timescale.value != cg_timescaleFadeEnd.value )
+ {
+ if( cg_timescale.value < cg_timescaleFadeEnd.value )
+ {
+ cg_timescale.value += cg_timescaleFadeSpeed.value * ( (float)cg.frametime ) / 1000;
+ if( cg_timescale.value > cg_timescaleFadeEnd.value )
+ cg_timescale.value = cg_timescaleFadeEnd.value;
+ }
+ else
+ {
+ cg_timescale.value -= cg_timescaleFadeSpeed.value * ( (float)cg.frametime ) / 1000;
+ if( cg_timescale.value < cg_timescaleFadeEnd.value )
+ cg_timescale.value = cg_timescaleFadeEnd.value;
+ }
+
+ if( cg_timescaleFadeSpeed.value )
+ trap_Cvar_Set( "timescale", va( "%f", cg_timescale.value ) );
+ }
+
+ // actually issue the rendering calls
+ CG_DrawActive( stereoView );
+
+ if( cg_stats.integer )
+ CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame );
+}
+
diff --git a/src/cgame/cg_weapons.c b/src/cgame/cg_weapons.c
new file mode 100644
index 0000000..deae4e0
--- /dev/null
+++ b/src/cgame/cg_weapons.c
@@ -0,0 +1,1845 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// cg_weapons.c -- events and effects dealing with weapons
+
+
+#include "cg_local.h"
+
+/*
+=================
+CG_RegisterUpgrade
+
+The server says this item is used on this level
+=================
+*/
+void CG_RegisterUpgrade( int upgradeNum )
+{
+ upgradeInfo_t *upgradeInfo;
+ char *icon;
+
+ upgradeInfo = &cg_upgrades[ upgradeNum ];
+
+ if( upgradeNum == 0 )
+ return;
+
+ if( upgradeInfo->registered )
+ return;
+
+ memset( upgradeInfo, 0, sizeof( *upgradeInfo ) );
+ upgradeInfo->registered = qtrue;
+
+ if( !BG_FindNameForUpgrade( upgradeNum ) )
+ CG_Error( "Couldn't find upgrade %i", upgradeNum );
+
+ upgradeInfo->humanName = BG_FindHumanNameForUpgrade( upgradeNum );
+
+ //la la la la la, i'm not listening!
+ if( upgradeNum == UP_GRENADE )
+ upgradeInfo->upgradeIcon = cg_weapons[ WP_GRENADE ].weaponIcon;
+ else if( ( icon = BG_FindIconForUpgrade( upgradeNum ) ) )
+ upgradeInfo->upgradeIcon = trap_R_RegisterShader( icon );
+}
+
+/*
+===============
+CG_InitUpgrades
+
+Precaches upgrades
+===============
+*/
+void CG_InitUpgrades( void )
+{
+ int i;
+
+ memset( cg_upgrades, 0, sizeof( cg_upgrades ) );
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ CG_RegisterUpgrade( i );
+}
+
+
+/*
+===============
+CG_ParseWeaponModeSection
+
+Parse a weapon mode section
+===============
+*/
+static qboolean CG_ParseWeaponModeSection( weaponInfoMode_t *wim, char **text_p )
+{
+ char *token;
+ int i;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ return qfalse;
+
+ if( !Q_stricmp( token, "missileModel" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileModel = trap_R_RegisterModel( token );
+
+ if( !wim->missileModel )
+ CG_Printf( S_COLOR_RED "ERROR: missile model not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileSprite" ) )
+ {
+ int size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ size = atoi( token );
+
+ if( size < 0 )
+ size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileSprite = trap_R_RegisterShader( token );
+ wim->missileSpriteSize = size;
+ wim->usesSpriteMissle = qtrue;
+
+ if( !wim->missileSprite )
+ CG_Printf( S_COLOR_RED "ERROR: missile sprite not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileRotates" ) )
+ {
+ wim->missileRotates = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileAnimates" ) )
+ {
+ wim->missileAnimates = qtrue;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileAnimStartFrame = atoi( token );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileAnimNumFrames = atoi( token );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileAnimFrameRate = atoi( token );
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileAnimLooping = atoi( token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileParticleSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileParticleSystem = CG_RegisterParticleSystem( token );
+
+ if( !wim->missileParticleSystem )
+ CG_Printf( S_COLOR_RED "ERROR: missile particle system not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileTrailSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileTrailSystem = CG_RegisterTrailSystem( token );
+
+ if( !wim->missileTrailSystem )
+ CG_Printf( S_COLOR_RED "ERROR: missile trail system not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "muzzleParticleSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->muzzleParticleSystem = CG_RegisterParticleSystem( token );
+
+ if( !wim->muzzleParticleSystem )
+ CG_Printf( S_COLOR_RED "ERROR: muzzle particle system not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "impactParticleSystem" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->impactParticleSystem = CG_RegisterParticleSystem( token );
+
+ if( !wim->impactParticleSystem )
+ CG_Printf( S_COLOR_RED "ERROR: impact particle system not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "impactMark" ) )
+ {
+ int size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ size = atoi( token );
+
+ if( size < 0 )
+ size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->impactMark = trap_R_RegisterShader( token );
+ wim->impactMarkSize = size;
+
+ if( !wim->impactMark )
+ CG_Printf( S_COLOR_RED "ERROR: impact mark shader not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "impactSound" ) )
+ {
+ int index = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ index = atoi( token );
+
+ if( index < 0 )
+ index = 0;
+ else if( index > 3 )
+ index = 3;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->impactSound[ index ] = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "impactFleshSound" ) )
+ {
+ int index = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ index = atoi( token );
+
+ if( index < 0 )
+ index = 0;
+ else if( index > 3 )
+ index = 3;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->impactFleshSound[ index ] = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "alwaysImpact" ) )
+ {
+ wim->alwaysImpact = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "flashDLightColor" ) )
+ {
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->flashDlightColor[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "continuousFlash" ) )
+ {
+ wim->continuousFlash = qtrue;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileDlightColor" ) )
+ {
+ for( i = 0 ; i < 3 ; i++ )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileDlightColor[ i ] = atof( token );
+ }
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileDlight" ) )
+ {
+ int size = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ size = atoi( token );
+
+ if( size < 0 )
+ size = 0;
+
+ wim->missileDlight = size;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "firingSound" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->firingSound = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "missileSound" ) )
+ {
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->missileSound = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "flashSound" ) )
+ {
+ int index = 0;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ index = atoi( token );
+
+ if( index < 0 )
+ index = 0;
+ else if( index > 3 )
+ index = 3;
+
+ token = COM_Parse( text_p );
+ if( !token )
+ break;
+
+ wim->flashSound[ index ] = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "}" ) )
+ return qtrue; //reached the end of this weapon section
+ else
+ {
+ CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in weapon section\n", token );
+ return qfalse;
+ }
+ }
+
+ return qfalse;
+}
+
+
+/*
+======================
+CG_ParseWeaponFile
+
+Parses a configuration file describing a weapon
+======================
+*/
+static qboolean CG_ParseWeaponFile( const char *filename, weaponInfo_t *wi )
+{
+ char *text_p;
+ int len;
+ char *token;
+ char text[ 20000 ];
+ fileHandle_t f;
+ weaponMode_t weaponMode = WPM_NONE;
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if( len <= 0 )
+ return qfalse;
+
+ if( len >= sizeof( text ) - 1 )
+ {
+ CG_Printf( "File %s too long\n", filename );
+ return qfalse;
+ }
+
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ // parse the text
+ text_p = text;
+
+ // read optional parameters
+ while( 1 )
+ {
+ token = COM_Parse( &text_p );
+
+ if( !token )
+ break;
+
+ if( !Q_stricmp( token, "" ) )
+ break;
+
+ if( !Q_stricmp( token, "{" ) )
+ {
+ if( weaponMode == WPM_NONE )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: weapon mode section started without a declaration\n" );
+ return qfalse;
+ }
+ else if( !CG_ParseWeaponModeSection( &wi->wim[ weaponMode ], &text_p ) )
+ {
+ CG_Printf( S_COLOR_RED "ERROR: failed to parse weapon mode section\n" );
+ return qfalse;
+ }
+
+ //start parsing ejectors again
+ weaponMode = WPM_NONE;
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "primary" ) )
+ {
+ weaponMode = WPM_PRIMARY;
+ continue;
+ }
+ else if( !Q_stricmp( token, "secondary" ) )
+ {
+ weaponMode = WPM_SECONDARY;
+ continue;
+ }
+ else if( !Q_stricmp( token, "tertiary" ) )
+ {
+ weaponMode = WPM_TERTIARY;
+ continue;
+ }
+ else if( !Q_stricmp( token, "weaponModel" ) )
+ {
+ char path[ MAX_QPATH ];
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ wi->weaponModel = trap_R_RegisterModel( token );
+
+ if( !wi->weaponModel )
+ CG_Printf( S_COLOR_RED "ERROR: weapon model not found %s\n", token );
+
+ strcpy( path, token );
+ COM_StripExtension( path, path, MAX_QPATH );
+ strcat( path, "_flash.md3" );
+ wi->flashModel = trap_R_RegisterModel( path );
+
+ strcpy( path, token );
+ COM_StripExtension( path, path, MAX_QPATH );
+ strcat( path, "_barrel.md3" );
+ wi->barrelModel = trap_R_RegisterModel( path );
+
+ strcpy( path, token );
+ COM_StripExtension( path, path, MAX_QPATH );
+ strcat( path, "_hand.md3" );
+ wi->handsModel = trap_R_RegisterModel( path );
+
+ if( !wi->handsModel )
+ wi->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "idleSound" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ wi->readySound = trap_S_RegisterSound( token, qfalse );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "icon" ) )
+ {
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ wi->weaponIcon = wi->ammoIcon = trap_R_RegisterShader( token );
+
+ if( !wi->weaponIcon )
+ CG_Printf( S_COLOR_RED "ERROR: weapon icon not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "crosshair" ) )
+ {
+ int size = 0;
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ size = atoi( token );
+
+ if( size < 0 )
+ size = 0;
+
+ token = COM_Parse( &text_p );
+ if( !token )
+ break;
+
+ wi->crossHair = trap_R_RegisterShader( token );
+ wi->crossHairSize = size;
+
+ if( !wi->crossHair )
+ CG_Printf( S_COLOR_RED "ERROR: weapon crosshair not found %s\n", token );
+
+ continue;
+ }
+ else if( !Q_stricmp( token, "disableIn3rdPerson" ) )
+ {
+ wi->disableIn3rdPerson = qtrue;
+
+ continue;
+ }
+
+ Com_Printf( S_COLOR_RED "ERROR: unknown token '%s'\n", token );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+=================
+CG_RegisterWeapon
+=================
+*/
+void CG_RegisterWeapon( int weaponNum )
+{
+ weaponInfo_t *weaponInfo;
+ char path[ MAX_QPATH ];
+ vec3_t mins, maxs;
+ int i;
+
+ weaponInfo = &cg_weapons[ weaponNum ];
+
+ if( weaponNum == 0 )
+ return;
+
+ if( weaponInfo->registered )
+ return;
+
+ memset( weaponInfo, 0, sizeof( *weaponInfo ) );
+ weaponInfo->registered = qtrue;
+
+ if( !BG_FindNameForWeapon( weaponNum ) )
+ CG_Error( "Couldn't find weapon %i", weaponNum );
+
+ Com_sprintf( path, MAX_QPATH, "models/weapons/%s/weapon.cfg", BG_FindNameForWeapon( weaponNum ) );
+
+ weaponInfo->humanName = BG_FindHumanNameForWeapon( weaponNum );
+
+ if( !CG_ParseWeaponFile( path, weaponInfo ) )
+ Com_Printf( S_COLOR_RED "ERROR: failed to parse %s\n", path );
+
+ // calc midpoint for rotation
+ trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs );
+ for( i = 0 ; i < 3 ; i++ )
+ weaponInfo->weaponMidpoint[ i ] = mins[ i ] + 0.5 * ( maxs[ i ] - mins[ i ] );
+
+ //FIXME:
+ for( i = WPM_NONE + 1; i < WPM_NUM_WEAPONMODES; i++ )
+ weaponInfo->wim[ i ].loopFireSound = qfalse;
+}
+
+/*
+===============
+CG_InitWeapons
+
+Precaches weapons
+===============
+*/
+void CG_InitWeapons( void )
+{
+ int i;
+
+ memset( cg_weapons, 0, sizeof( cg_weapons ) );
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ CG_RegisterWeapon( i );
+
+ cgs.media.level2ZapTS = CG_RegisterTrailSystem( "models/weapons/lev2zap/lightning" );
+}
+
+
+/*
+========================================================================================
+
+VIEW WEAPON
+
+========================================================================================
+*/
+
+/*
+=================
+CG_MapTorsoToWeaponFrame
+
+=================
+*/
+static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame )
+{
+
+ // change weapon
+ if( frame >= ci->animations[ TORSO_DROP ].firstFrame &&
+ frame < ci->animations[ TORSO_DROP ].firstFrame + 9 )
+ return frame - ci->animations[ TORSO_DROP ].firstFrame + 6;
+
+ // stand attack
+ if( frame >= ci->animations[ TORSO_ATTACK ].firstFrame &&
+ frame < ci->animations[ TORSO_ATTACK ].firstFrame + 6 )
+ return 1 + frame - ci->animations[ TORSO_ATTACK ].firstFrame;
+
+ // stand attack 2
+ if( frame >= ci->animations[ TORSO_ATTACK2 ].firstFrame &&
+ frame < ci->animations[ TORSO_ATTACK2 ].firstFrame + 6 )
+ return 1 + frame - ci->animations[ TORSO_ATTACK2 ].firstFrame;
+
+ return 0;
+}
+
+
+/*
+==============
+CG_CalculateWeaponPosition
+==============
+*/
+static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles )
+{
+ float scale;
+ int delta;
+ float fracsin;
+ float bob;
+
+ VectorCopy( cg.refdef.vieworg, origin );
+ VectorCopy( cg.refdefViewAngles, angles );
+
+ // on odd legs, invert some angles
+ if( cg.bobcycle & 1 )
+ scale = -cg.xyspeed;
+ else
+ scale = cg.xyspeed;
+
+ // gun angles from bobbing
+ // bob amount is class dependant
+ bob = BG_FindBobForClass( cg.predictedPlayerState.stats[ STAT_PCLASS ] );
+
+ if( bob != 0 )
+ {
+ angles[ ROLL ] += scale * cg.bobfracsin * 0.005;
+ angles[ YAW ] += scale * cg.bobfracsin * 0.01;
+ angles[ PITCH ] += cg.xyspeed * cg.bobfracsin * 0.005;
+ }
+
+ // drop the weapon when landing
+ if( !BG_ClassHasAbility( cg.predictedPlayerState.stats[ STAT_PCLASS ], SCA_NOWEAPONDRIFT ) )
+ {
+ delta = cg.time - cg.landTime;
+ if( delta < LAND_DEFLECT_TIME )
+ origin[ 2 ] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME;
+ else if( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME )
+ origin[ 2 ] += cg.landChange*0.25 *
+ ( LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta ) / LAND_RETURN_TIME;
+
+ // idle drift
+ scale = cg.xyspeed + 40;
+ fracsin = sin( cg.time * 0.001 );
+ angles[ ROLL ] += scale * fracsin * 0.01;
+ angles[ YAW ] += scale * fracsin * 0.01;
+ angles[ PITCH ] += scale * fracsin * 0.01;
+ }
+}
+
+
+/*
+======================
+CG_MachinegunSpinAngle
+======================
+*/
+#define SPIN_SPEED 0.9
+#define COAST_TIME 1000
+static float CG_MachinegunSpinAngle( centity_t *cent, qboolean firing )
+{
+ int delta;
+ float angle;
+ float speed;
+
+ delta = cg.time - cent->pe.barrelTime;
+ if( cent->pe.barrelSpinning )
+ angle = cent->pe.barrelAngle + delta * SPIN_SPEED;
+ else
+ {
+ if( delta > COAST_TIME )
+ delta = COAST_TIME;
+
+ speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
+ angle = cent->pe.barrelAngle + delta * speed;
+ }
+
+ if( cent->pe.barrelSpinning == !firing )
+ {
+ cent->pe.barrelTime = cg.time;
+ cent->pe.barrelAngle = AngleMod( angle );
+ cent->pe.barrelSpinning = firing;
+ }
+
+ return angle;
+}
+
+
+/*
+=============
+CG_AddPlayerWeapon
+
+Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL)
+The main player will have this called for BOTH cases, so effects like light and
+sound should only be done on the world model case.
+=============
+*/
+void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent )
+{
+ refEntity_t gun;
+ refEntity_t barrel;
+ refEntity_t flash;
+ vec3_t angles;
+ weapon_t weaponNum;
+ weaponMode_t weaponMode;
+ weaponInfo_t *weapon;
+ qboolean noGunModel;
+ qboolean firing;
+
+ weaponNum = cent->currentState.weapon;
+ weaponMode = cent->currentState.generic1;
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ if( ( ( cent->currentState.eFlags & EF_FIRING ) && weaponMode == WPM_PRIMARY ) ||
+ ( ( cent->currentState.eFlags & EF_FIRING2 ) && weaponMode == WPM_SECONDARY ) ||
+ ( ( cent->currentState.eFlags & EF_FIRING3 ) && weaponMode == WPM_TERTIARY ) )
+ firing = qtrue;
+ else
+ firing = qfalse;
+
+ CG_RegisterWeapon( weaponNum );
+ weapon = &cg_weapons[ weaponNum ];
+
+ // add the weapon
+ memset( &gun, 0, sizeof( gun ) );
+ VectorCopy( parent->lightingOrigin, gun.lightingOrigin );
+ gun.shadowPlane = parent->shadowPlane;
+ gun.renderfx = parent->renderfx;
+
+ // set custom shading for railgun refire rate
+ if( ps )
+ {
+ gun.shaderRGBA[ 0 ] = 255;
+ gun.shaderRGBA[ 1 ] = 255;
+ gun.shaderRGBA[ 2 ] = 255;
+ gun.shaderRGBA[ 3 ] = 255;
+
+ //set weapon[1/2]Time when respective buttons change state
+ if( cg.weapon1Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING ) )
+ {
+ cg.weapon1Time = cg.time;
+ cg.weapon1Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING );
+ }
+
+ if( cg.weapon2Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING2 ) )
+ {
+ cg.weapon2Time = cg.time;
+ cg.weapon2Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING2 );
+ }
+
+ if( cg.weapon3Firing != ( cg.predictedPlayerState.eFlags & EF_FIRING3 ) )
+ {
+ cg.weapon3Time = cg.time;
+ cg.weapon3Firing = ( cg.predictedPlayerState.eFlags & EF_FIRING3 );
+ }
+ }
+
+ gun.hModel = weapon->weaponModel;
+
+ noGunModel = ( ( !ps || cg.renderingThirdPerson ) && weapon->disableIn3rdPerson ) || !gun.hModel;
+
+ if( !ps )
+ {
+ // add weapon ready sound
+ if( firing && weapon->wim[ weaponMode ].firingSound )
+ {
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin,
+ weapon->wim[ weaponMode ].firingSound );
+ }
+ else if( weapon->readySound )
+ trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound );
+ }
+
+ if( !noGunModel )
+ {
+ CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon" );
+
+ trap_R_AddRefEntityToScene( &gun );
+
+ // add the spinning barrel
+ if( weapon->barrelModel )
+ {
+ memset( &barrel, 0, sizeof( barrel ) );
+ VectorCopy( parent->lightingOrigin, barrel.lightingOrigin );
+ barrel.shadowPlane = parent->shadowPlane;
+ barrel.renderfx = parent->renderfx;
+
+ barrel.hModel = weapon->barrelModel;
+ angles[ YAW ] = 0;
+ angles[ PITCH ] = 0;
+ angles[ ROLL ] = CG_MachinegunSpinAngle( cent, firing );
+ AnglesToAxis( angles, barrel.axis );
+
+ CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" );
+
+ trap_R_AddRefEntityToScene( &barrel );
+ }
+ }
+
+ if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
+ {
+ if( ps || cg.renderingThirdPerson ||
+ cent->currentState.number != cg.predictedPlayerState.clientNum )
+ {
+ if( noGunModel )
+ CG_SetAttachmentTag( &cent->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" );
+ else
+ CG_SetAttachmentTag( &cent->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" );
+ }
+
+ //if the PS is infinite disable it when not firing
+ if( !firing && CG_IsParticleSystemInfinite( cent->muzzlePS ) )
+ CG_DestroyParticleSystem( &cent->muzzlePS );
+ }
+
+ // add the flash
+ if( !weapon->wim[ weaponMode ].continuousFlash || !firing )
+ {
+ // impulse flash
+ if( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME )
+ return;
+ }
+
+ memset( &flash, 0, sizeof( flash ) );
+ VectorCopy( parent->lightingOrigin, flash.lightingOrigin );
+ flash.shadowPlane = parent->shadowPlane;
+ flash.renderfx = parent->renderfx;
+
+ flash.hModel = weapon->flashModel;
+ if( flash.hModel )
+ {
+ angles[ YAW ] = 0;
+ angles[ PITCH ] = 0;
+ angles[ ROLL ] = crandom( ) * 10;
+ AnglesToAxis( angles, flash.axis );
+
+ if( noGunModel )
+ CG_PositionRotatedEntityOnTag( &flash, parent, parent->hModel, "tag_weapon" );
+ else
+ CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash" );
+
+ trap_R_AddRefEntityToScene( &flash );
+ }
+
+ if( ps || cg.renderingThirdPerson ||
+ cent->currentState.number != cg.predictedPlayerState.clientNum )
+ {
+ if( weapon->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger )
+ {
+ cent->muzzlePS = CG_SpawnNewParticleSystem( weapon->wim[ weaponMode ].muzzleParticleSystem );
+
+ if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
+ {
+ if( noGunModel )
+ CG_SetAttachmentTag( &cent->muzzlePS->attachment, *parent, parent->hModel, "tag_weapon" );
+ else
+ CG_SetAttachmentTag( &cent->muzzlePS->attachment, gun, weapon->weaponModel, "tag_flash" );
+
+ CG_SetAttachmentCent( &cent->muzzlePS->attachment, cent );
+ CG_AttachToTag( &cent->muzzlePS->attachment );
+ }
+
+ cent->muzzlePsTrigger = qfalse;
+ }
+
+ // make a dlight for the flash
+ if( weapon->wim[ weaponMode ].flashDlightColor[ 0 ] ||
+ weapon->wim[ weaponMode ].flashDlightColor[ 1 ] ||
+ weapon->wim[ weaponMode ].flashDlightColor[ 2 ] )
+ {
+ trap_R_AddLightToScene( flash.origin, 300 + ( rand( ) & 31 ),
+ weapon->wim[ weaponMode ].flashDlightColor[ 0 ],
+ weapon->wim[ weaponMode ].flashDlightColor[ 1 ],
+ weapon->wim[ weaponMode ].flashDlightColor[ 2 ] );
+ }
+ }
+}
+
+/*
+==============
+CG_AddViewWeapon
+
+Add the weapon, and flash for the player's view
+==============
+*/
+void CG_AddViewWeapon( playerState_t *ps )
+{
+ refEntity_t hand;
+ centity_t *cent;
+ clientInfo_t *ci;
+ float fovOffset;
+ vec3_t angles;
+ weaponInfo_t *wi;
+ weapon_t weapon = ps->weapon;
+ weaponMode_t weaponMode = ps->generic1;
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ CG_RegisterWeapon( weapon );
+ wi = &cg_weapons[ weapon ];
+ cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum];
+
+ if( ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) ||
+ ( ps->stats[ STAT_STATE ] & SS_INFESTING ) ||
+ ( ps->stats[ STAT_STATE ] & SS_HOVELING ) )
+ return;
+
+ // no weapon carried - can't draw it
+ if( weapon == WP_NONE )
+ return;
+
+ if( ps->pm_type == PM_INTERMISSION )
+ return;
+
+ // draw a prospective buildable infront of the player
+ if( ( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE )
+ CG_GhostBuildable( ps->stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT );
+
+ if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 )
+ {
+ if( ps->stats[ STAT_MISC ] > ( LCANNON_TOTAL_CHARGE - ( LCANNON_TOTAL_CHARGE / 3 ) ) )
+ trap_S_AddLoopingSound( ps->clientNum, ps->origin, vec3_origin, cgs.media.lCannonWarningSound );
+ }
+
+ // no gun if in third person view
+ if( cg.renderingThirdPerson )
+ return;
+
+ // allow the gun to be completely removed
+ if( !cg_drawGun.integer )
+ {
+ vec3_t origin;
+
+ VectorCopy( cg.refdef.vieworg, origin );
+ VectorMA( origin, -8, cg.refdef.viewaxis[ 2 ], origin );
+
+ if( cent->muzzlePS )
+ CG_SetAttachmentPoint( &cent->muzzlePS->attachment, origin );
+
+ //check for particle systems
+ if( wi->wim[ weaponMode ].muzzleParticleSystem && cent->muzzlePsTrigger )
+ {
+ cent->muzzlePS = CG_SpawnNewParticleSystem( wi->wim[ weaponMode ].muzzleParticleSystem );
+
+ if( CG_IsParticleSystemValid( &cent->muzzlePS ) )
+ {
+ CG_SetAttachmentPoint( &cent->muzzlePS->attachment, origin );
+ CG_SetAttachmentCent( &cent->muzzlePS->attachment, cent );
+ CG_AttachToPoint( &cent->muzzlePS->attachment );
+ }
+ cent->muzzlePsTrigger = qfalse;
+ }
+
+ return;
+ }
+
+ // don't draw if testing a gun model
+ if( cg.testGun )
+ return;
+
+ // drop gun lower at higher fov
+ if( cg.refdef.fov_y > 90 )
+ fovOffset = -0.4 * ( cg.refdef.fov_y - 90 );
+ else
+ fovOffset = 0;
+
+ memset( &hand, 0, sizeof( hand ) );
+
+ // set up gun position
+ CG_CalculateWeaponPosition( hand.origin, angles );
+
+ VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[ 0 ], hand.origin );
+ VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[ 1 ], hand.origin );
+ VectorMA( hand.origin, ( cg_gun_z.value + fovOffset ), cg.refdef.viewaxis[ 2 ], hand.origin );
+
+ if( weapon == WP_LUCIFER_CANNON && ps->stats[ STAT_MISC ] > 0 )
+ {
+ float fraction = (float)ps->stats[ STAT_MISC ] / (float)LCANNON_TOTAL_CHARGE;
+
+ VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 0 ], hand.origin );
+ VectorMA( hand.origin, random( ) * fraction, cg.refdef.viewaxis[ 1 ], hand.origin );
+ }
+
+ AnglesToAxis( angles, hand.axis );
+
+ // map torso animations to weapon animations
+ if( cg_gun_frame.integer )
+ {
+ // development tool
+ hand.frame = hand.oldframe = cg_gun_frame.integer;
+ hand.backlerp = 0;
+ }
+ else
+ {
+ // get clientinfo for animation map
+ ci = &cgs.clientinfo[ cent->currentState.clientNum ];
+ hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame );
+ hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame );
+ hand.backlerp = cent->pe.torso.backlerp;
+ }
+
+ hand.hModel = wi->handsModel;
+ hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT;
+
+ // add everything onto the hand
+ CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity );
+}
+
+/*
+==============================================================================
+
+WEAPON SELECTION
+
+==============================================================================
+*/
+
+/*
+===============
+CG_WeaponSelectable
+===============
+*/
+static qboolean CG_WeaponSelectable( weapon_t weapon )
+{
+ //int ammo, clips;
+ //
+ //BG_UnpackAmmoArray( i, cg.snap->ps.ammo, cg.snap->ps.powerups, &ammo, &clips );
+ //
+ // this is a pain in the ass
+ //if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( i ) )
+ // return qfalse;
+
+ if( !BG_InventoryContainsWeapon( weapon, cg.snap->ps.stats ) )
+ return qfalse;
+
+ return qtrue;
+}
+
+
+/*
+===============
+CG_UpgradeSelectable
+===============
+*/
+static qboolean CG_UpgradeSelectable( upgrade_t upgrade )
+{
+ if( !BG_InventoryContainsUpgrade( upgrade, cg.snap->ps.stats ) )
+ return qfalse;
+
+ return BG_FindUsableForUpgrade( upgrade );
+}
+
+
+#define ICON_BORDER 4
+
+/*
+===================
+CG_DrawItemSelect
+===================
+*/
+void CG_DrawItemSelect( rectDef_t *rect, vec4_t color )
+{
+ int i;
+ int x = rect->x;
+ int y = rect->y;
+ int width = rect->w;
+ int height = rect->h;
+ int iconsize;
+ int items[ 64 ];
+ int numItems = 0, selectedItem = 0;
+ int length;
+ int selectWindow;
+ qboolean vertical;
+ centity_t *cent;
+ playerState_t *ps;
+
+ int colinfo[ 64 ];
+
+ cent = &cg_entities[ cg.snap->ps.clientNum ];
+ ps = &cg.snap->ps;
+
+ // don't display if dead
+ if( cg.predictedPlayerState.stats[ STAT_HEALTH ] <= 0 )
+ return;
+
+ if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ {
+ // first make sure that whatever it selected is actually selectable
+ if( cg.weaponSelect <= 32 && !CG_WeaponSelectable( cg.weaponSelect ) )
+ CG_NextWeapon_f( );
+ else if( cg.weaponSelect > 32 && !CG_UpgradeSelectable( cg.weaponSelect - 32 ) )
+ CG_NextWeapon_f( );
+ }
+
+ // showing weapon select clears pickup item display, but not the blend blob
+ cg.itemPickupTime = 0;
+
+ if( height > width )
+ {
+ vertical = qtrue;
+ iconsize = width;
+ length = height / width;
+ }
+ else if( height <= width )
+ {
+ vertical = qfalse;
+ iconsize = height;
+ length = width / height;
+ }
+
+ selectWindow = length / 2;
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( !BG_InventoryContainsWeapon( i, cg.snap->ps.stats ) )
+ continue;
+
+ {
+ int ammo, clips;
+
+ BG_UnpackAmmoArray( i, cg.snap->ps.ammo, cg.snap->ps.powerups, &ammo, &clips );
+
+ if( !ammo && !clips && !BG_FindInfinteAmmoForWeapon( i ) )
+ colinfo[ numItems ] = 1;
+ else
+ colinfo[ numItems ] = 0;
+
+ }
+
+ if( i == cg.weaponSelect )
+ selectedItem = numItems;
+
+ CG_RegisterWeapon( i );
+ items[ numItems ] = i;
+ numItems++;
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( !BG_InventoryContainsUpgrade( i, cg.snap->ps.stats ) )
+ continue;
+ colinfo[ numItems ] = 0;
+ if( !BG_FindUsableForUpgrade ( i ) )
+ colinfo[ numItems ] = 2;
+
+
+ if( i == cg.weaponSelect - 32 )
+ selectedItem = numItems;
+
+ CG_RegisterUpgrade( i );
+ items[ numItems ] = i + 32;
+ numItems++;
+ }
+
+ for( i = 0; i < length; i++ )
+ {
+ int displacement = i - selectWindow;
+ int item = displacement + selectedItem;
+
+ if( ( item >= 0 ) && ( item < numItems ) )
+ {
+ switch( colinfo[ item ] )
+ {
+ case 0:
+ color = colorCyan;
+ break;
+ case 1:
+ color = colorRed;
+ break;
+ case 2:
+ color = colorMdGrey;
+ break;
+ }
+ color[3] = 0.5;
+
+ trap_R_SetColor( color );
+
+ if( items[ item ] <= 32 )
+ CG_DrawPic( x, y, iconsize, iconsize, cg_weapons[ items[ item ] ].weaponIcon );
+ else if( items[ item ] > 32 )
+ CG_DrawPic( x, y, iconsize, iconsize, cg_upgrades[ items[ item ] - 32 ].upgradeIcon );
+
+ trap_R_SetColor( NULL );
+ }
+
+ if( vertical )
+ y += iconsize;
+ else
+ x += iconsize;
+ }
+}
+
+
+/*
+===================
+CG_DrawItemSelectText
+===================
+*/
+void CG_DrawItemSelectText( rectDef_t *rect, float scale, int textStyle )
+{
+ int x, w;
+ char *name;
+ float *color;
+
+ color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME );
+ if( !color )
+ return;
+
+ trap_R_SetColor( color );
+
+ // draw the selected name
+ if( cg.weaponSelect <= 32 )
+ {
+ if( cg_weapons[ cg.weaponSelect ].registered &&
+ BG_InventoryContainsWeapon( cg.weaponSelect, cg.snap->ps.stats ) )
+ {
+ if( ( name = cg_weapons[ cg.weaponSelect ].humanName ) )
+ {
+ w = CG_Text_Width( name, scale, 0 );
+ x = rect->x + rect->w / 2;
+ CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle );
+ }
+ }
+ }
+ else if( cg.weaponSelect > 32 )
+ {
+ if( cg_upgrades[ cg.weaponSelect - 32 ].registered &&
+ BG_InventoryContainsUpgrade( cg.weaponSelect - 32, cg.snap->ps.stats ) )
+ {
+ if( ( name = cg_upgrades[ cg.weaponSelect - 32 ].humanName ) )
+ {
+ w = CG_Text_Width( name, scale, 0 );
+ x = rect->x + rect->w / 2;
+ CG_Text_Paint( x - w / 2, rect->y + rect->h, scale, color, name, 0, 0, textStyle );
+ }
+ }
+ }
+
+ trap_R_SetColor( NULL );
+}
+
+
+/*
+===============
+CG_NextWeapon_f
+===============
+*/
+void CG_NextWeapon_f( void )
+{
+ int i;
+ int original;
+
+ if( !cg.snap )
+ return;
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ {
+ trap_SendClientCommand( "followprev\n" );
+ return;
+ }
+
+ cg.weaponSelectTime = cg.time;
+ original = cg.weaponSelect;
+
+ for( i = 0; i < 64; i++ )
+ {
+ cg.weaponSelect++;
+ if( cg.weaponSelect == 64 )
+ cg.weaponSelect = 0;
+
+ if( cg.weaponSelect <= 32 )
+ {
+ if( CG_WeaponSelectable( cg.weaponSelect ) )
+ break;
+ }
+ else if( cg.weaponSelect > 32 )
+ {
+ if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) )
+ break;
+ }
+ }
+
+ if( i == 64 )
+ cg.weaponSelect = original;
+}
+
+/*
+===============
+CG_PrevWeapon_f
+===============
+*/
+void CG_PrevWeapon_f( void )
+{
+ int i;
+ int original;
+
+ if( !cg.snap )
+ return;
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ {
+ trap_SendClientCommand( "follownext\n" );
+ return;
+ }
+
+ cg.weaponSelectTime = cg.time;
+ original = cg.weaponSelect;
+
+ for( i = 0; i < 64; i++ )
+ {
+ cg.weaponSelect--;
+ if( cg.weaponSelect == -1 )
+ cg.weaponSelect = 63;
+
+ if( cg.weaponSelect <= 32 )
+ {
+ if( CG_WeaponSelectable( cg.weaponSelect ) )
+ break;
+ }
+ else if( cg.weaponSelect > 32 )
+ {
+ if( CG_UpgradeSelectable( cg.weaponSelect - 32 ) )
+ break;
+ }
+ }
+
+ if( i == 64 )
+ cg.weaponSelect = original;
+}
+
+/*
+===============
+CG_Weapon_f
+===============
+*/
+void CG_Weapon_f( void )
+{
+ int num;
+
+ if( !cg.snap )
+ return;
+
+ if( cg.snap->ps.pm_flags & PMF_FOLLOW )
+ return;
+
+ num = atoi( CG_Argv( 1 ) );
+
+ if( num < 1 || num > 31 )
+ return;
+
+ cg.weaponSelectTime = cg.time;
+
+ if( !BG_InventoryContainsWeapon( num, cg.snap->ps.stats ) )
+ return; // don't have the weapon
+
+ cg.weaponSelect = num;
+}
+
+
+/*
+===================================================================================================
+
+WEAPON EVENTS
+
+===================================================================================================
+*/
+
+/*
+================
+CG_FireWeapon
+
+Caused by an EV_FIRE_WEAPON event
+================
+*/
+void CG_FireWeapon( centity_t *cent, weaponMode_t weaponMode )
+{
+ entityState_t *es;
+ int c;
+ weaponInfo_t *wi;
+ weapon_t weaponNum;
+
+ es = &cent->currentState;
+
+ weaponNum = es->weapon;
+
+ if( weaponNum == WP_NONE )
+ return;
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ if( weaponNum >= WP_NUM_WEAPONS )
+ {
+ CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" );
+ return;
+ }
+
+ wi = &cg_weapons[ weaponNum ];
+
+ // mark the entity as muzzle flashing, so when it is added it will
+ // append the flash to the weapon model
+ cent->muzzleFlashTime = cg.time;
+
+ if( wi->wim[ weaponMode ].muzzleParticleSystem )
+ {
+ if( !CG_IsParticleSystemValid( &cent->muzzlePS ) ||
+ !CG_IsParticleSystemInfinite( cent->muzzlePS ) )
+ cent->muzzlePsTrigger = qtrue;
+ }
+
+ // play a sound
+ for( c = 0; c < 4; c++ )
+ {
+ if( !wi->wim[ weaponMode ].flashSound[ c ] )
+ break;
+ }
+
+ if( c > 0 )
+ {
+ c = rand( ) % c;
+ if( wi->wim[ weaponMode ].flashSound[ c ] )
+ trap_S_StartSound( NULL, es->number, CHAN_WEAPON, wi->wim[ weaponMode ].flashSound[ c ] );
+ }
+}
+
+
+/*
+=================
+CG_MissileHitWall
+
+Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing
+=================
+*/
+void CG_MissileHitWall( weapon_t weaponNum, weaponMode_t weaponMode, int clientNum,
+ vec3_t origin, vec3_t dir, impactSound_t soundType )
+{
+ qhandle_t mark = 0;
+ qhandle_t ps = 0;
+ int c;
+ float radius = 1.0f;
+ weaponInfo_t *weapon = &cg_weapons[ weaponNum ];
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ mark = weapon->wim[ weaponMode ].impactMark;
+ radius = weapon->wim[ weaponMode ].impactMarkSize;
+ ps = weapon->wim[ weaponMode ].impactParticleSystem;
+
+ if( soundType == IMPACTSOUND_FLESH )
+ {
+ //flesh sound
+ for( c = 0; c < 4; c++ )
+ {
+ if( !weapon->wim[ weaponMode ].impactFleshSound[ c ] )
+ break;
+ }
+
+ if( c > 0 )
+ {
+ c = rand( ) % c;
+ if( weapon->wim[ weaponMode ].impactFleshSound[ c ] )
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactFleshSound[ c ] );
+ }
+ }
+ else
+ {
+ //normal sound
+ for( c = 0; c < 4; c++ )
+ {
+ if( !weapon->wim[ weaponMode ].impactSound[ c ] )
+ break;
+ }
+
+ if( c > 0 )
+ {
+ c = rand( ) % c;
+ if( weapon->wim[ weaponMode ].impactSound[ c ] )
+ trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, weapon->wim[ weaponMode ].impactSound[ c ] );
+ }
+ }
+
+ //create impact particle system
+ if( ps )
+ {
+ particleSystem_t *partSystem = CG_SpawnNewParticleSystem( ps );
+
+ if( CG_IsParticleSystemValid( &partSystem ) )
+ {
+ CG_SetAttachmentPoint( &partSystem->attachment, origin );
+ CG_SetParticleSystemNormal( partSystem, dir );
+ CG_AttachToPoint( &partSystem->attachment );
+ }
+ }
+
+ //
+ // impact mark
+ //
+ if( radius > 0.0f )
+ CG_ImpactMark( mark, origin, dir, random( ) * 360, 1, 1, 1, 1, qfalse, radius, qfalse );
+}
+
+
+/*
+=================
+CG_MissileHitPlayer
+=================
+*/
+void CG_MissileHitPlayer( weapon_t weaponNum, weaponMode_t weaponMode,
+ vec3_t origin, vec3_t dir, int entityNum )
+{
+ vec3_t normal;
+ weaponInfo_t *weapon = &cg_weapons[ weaponNum ];
+
+ VectorCopy( dir, normal );
+ VectorInverse( normal );
+
+ CG_Bleed( origin, normal, entityNum );
+
+ if( weaponMode <= WPM_NONE || weaponMode >= WPM_NUM_WEAPONMODES )
+ weaponMode = WPM_PRIMARY;
+
+ if( weapon->wim[ weaponMode ].alwaysImpact )
+ CG_MissileHitWall( weaponNum, weaponMode, 0, origin, dir, IMPACTSOUND_FLESH );
+}
+
+
+/*
+============================================================================
+
+BULLETS
+
+============================================================================
+*/
+
+
+/*
+===============
+CG_Tracer
+===============
+*/
+void CG_Tracer( vec3_t source, vec3_t dest )
+{
+ vec3_t forward, right;
+ polyVert_t verts[ 4 ];
+ vec3_t line;
+ float len, begin, end;
+ vec3_t start, finish;
+ vec3_t midpoint;
+
+ // tracer
+ VectorSubtract( dest, source, forward );
+ len = VectorNormalize( forward );
+
+ // start at least a little ways from the muzzle
+ if( len < 100 )
+ return;
+
+ begin = 50 + random( ) * ( len - 60 );
+ end = begin + cg_tracerLength.value;
+ if( end > len )
+ end = len;
+
+ VectorMA( source, begin, forward, start );
+ VectorMA( source, end, forward, finish );
+
+ line[ 0 ] = DotProduct( forward, cg.refdef.viewaxis[ 1 ] );
+ line[ 1 ] = DotProduct( forward, cg.refdef.viewaxis[ 2 ] );
+
+ VectorScale( cg.refdef.viewaxis[ 1 ], line[ 1 ], right );
+ VectorMA( right, -line[ 0 ], cg.refdef.viewaxis[ 2 ], right );
+ VectorNormalize( right );
+
+ VectorMA( finish, cg_tracerWidth.value, right, verts[ 0 ].xyz );
+ verts[ 0 ].st[ 0 ] = 0;
+ verts[ 0 ].st[ 1 ] = 1;
+ verts[ 0 ].modulate[ 0 ] = 255;
+ verts[ 0 ].modulate[ 1 ] = 255;
+ verts[ 0 ].modulate[ 2 ] = 255;
+ verts[ 0 ].modulate[ 3 ] = 255;
+
+ VectorMA( finish, -cg_tracerWidth.value, right, verts[ 1 ].xyz );
+ verts[ 1 ].st[ 0 ] = 1;
+ verts[ 1 ].st[ 1 ] = 0;
+ verts[ 1 ].modulate[ 0 ] = 255;
+ verts[ 1 ].modulate[ 1 ] = 255;
+ verts[ 1 ].modulate[ 2 ] = 255;
+ verts[ 1 ].modulate[ 3 ] = 255;
+
+ VectorMA( start, -cg_tracerWidth.value, right, verts[ 2 ].xyz );
+ verts[ 2 ].st[ 0 ] = 1;
+ verts[ 2 ].st[ 1 ] = 1;
+ verts[ 2 ].modulate[ 0 ] = 255;
+ verts[ 2 ].modulate[ 1 ] = 255;
+ verts[ 2 ].modulate[ 2 ] = 255;
+ verts[ 2 ].modulate[ 3 ] = 255;
+
+ VectorMA( start, cg_tracerWidth.value, right, verts[ 3 ].xyz );
+ verts[ 3 ].st[ 0 ] = 0;
+ verts[ 3 ].st[ 1 ] = 0;
+ verts[ 3 ].modulate[ 0 ] = 255;
+ verts[ 3 ].modulate[ 1 ] = 255;
+ verts[ 3 ].modulate[ 2 ] = 255;
+ verts[ 3 ].modulate[ 3 ] = 255;
+
+ trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts );
+
+ midpoint[ 0 ] = ( start[ 0 ] + finish[ 0 ] ) * 0.5;
+ midpoint[ 1 ] = ( start[ 1 ] + finish[ 1 ] ) * 0.5;
+ midpoint[ 2 ] = ( start[ 2 ] + finish[ 2 ] ) * 0.5;
+
+ // add the tracer sound
+ trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound );
+}
+
+
+/*
+======================
+CG_CalcMuzzlePoint
+======================
+*/
+static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle )
+{
+ vec3_t forward;
+ centity_t *cent;
+ int anim;
+
+ if( entityNum == cg.snap->ps.clientNum )
+ {
+ VectorCopy( cg.snap->ps.origin, muzzle );
+ muzzle[ 2 ] += cg.snap->ps.viewheight;
+ AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL );
+ VectorMA( muzzle, 14, forward, muzzle );
+ return qtrue;
+ }
+
+ cent = &cg_entities[entityNum];
+
+ if( !cent->currentValid )
+ return qfalse;
+
+ VectorCopy( cent->currentState.pos.trBase, muzzle );
+
+ AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL );
+ anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT;
+
+ if( anim == LEGS_WALKCR || anim == LEGS_IDLECR )
+ muzzle[ 2 ] += CROUCH_VIEWHEIGHT;
+ else
+ muzzle[ 2 ] += DEFAULT_VIEWHEIGHT;
+
+ VectorMA( muzzle, 14, forward, muzzle );
+
+ return qtrue;
+
+}
+
+
+/*
+======================
+CG_Bullet
+
+Renders bullet effects.
+======================
+*/
+void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum )
+{
+ vec3_t start;
+
+ // if the shooter is currently valid, calc a source point and possibly
+ // do trail effects
+ if( sourceEntityNum >= 0 && cg_tracerChance.value > 0 )
+ {
+ if( CG_CalcMuzzlePoint( sourceEntityNum, start ) )
+ {
+ // draw a tracer
+ if( random( ) < cg_tracerChance.value )
+ CG_Tracer( start, end );
+ }
+ }
+
+ // impact splash and mark
+ if( flesh )
+ CG_Bleed( end, normal, fleshEntityNum );
+ else
+ CG_MissileHitWall( WP_MACHINEGUN, WPM_PRIMARY, 0, end, normal, IMPACTSOUND_DEFAULT );
+}
+
+/*
+============================================================================
+
+SHOTGUN TRACING
+
+============================================================================
+*/
+
+/*
+================
+CG_ShotgunPattern
+
+Perform the same traces the server did to locate the
+hit splashes
+================
+*/
+static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum )
+{
+ int i;
+ float r, u;
+ vec3_t end;
+ vec3_t forward, right, up;
+ trace_t tr;
+
+ // derive the right and up vectors from the forward vector, because
+ // the client won't have any other information
+ VectorNormalize2( origin2, forward );
+ PerpendicularVector( right, forward );
+ CrossProduct( forward, right, up );
+
+ // generate the "random" spread pattern
+ for( i = 0; i < SHOTGUN_PELLETS; i++ )
+ {
+ r = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
+ u = Q_crandom( &seed ) * SHOTGUN_SPREAD * 16;
+ VectorMA( origin, 8192 * 16, forward, end );
+ VectorMA( end, r, right, end );
+ VectorMA( end, u, up, end );
+
+ CG_Trace( &tr, origin, NULL, NULL, end, otherEntNum, MASK_SHOT );
+
+ if( !( tr.surfaceFlags & SURF_NOIMPACT ) )
+ {
+ if( cg_entities[ tr.entityNum ].currentState.eType == ET_PLAYER )
+ CG_MissileHitPlayer( WP_SHOTGUN, WPM_PRIMARY, tr.endpos, tr.plane.normal, tr.entityNum );
+ else if( tr.surfaceFlags & SURF_METALSTEPS )
+ CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL );
+ else
+ CG_MissileHitWall( WP_SHOTGUN, WPM_PRIMARY, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT );
+ }
+ }
+}
+
+/*
+==============
+CG_ShotgunFire
+==============
+*/
+void CG_ShotgunFire( entityState_t *es )
+{
+ vec3_t v;
+
+ VectorSubtract( es->origin2, es->pos.trBase, v );
+ VectorNormalize( v );
+ VectorScale( v, 32, v );
+ VectorAdd( es->pos.trBase, v, v );
+
+ CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum );
+}
+
diff --git a/src/client/keycodes.h b/src/client/keycodes.h
new file mode 100644
index 0000000..ae6f189
--- /dev/null
+++ b/src/client/keycodes.h
@@ -0,0 +1,278 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __KEYCODES_H__
+#define __KEYCODES_H__
+
+//
+// these are the key numbers that should be passed to KeyEvent
+//
+
+// normal keys should be passed as lowercased ascii
+
+typedef enum {
+ K_NONE = -1,
+ K_TAB = 9,
+ K_ENTER = 13,
+ K_ESCAPE = 27,
+ K_SPACE = 32,
+
+ K_BACKSPACE = 127,
+
+ K_COMMAND = 128,
+ K_CAPSLOCK,
+ K_POWER,
+ K_PAUSE,
+
+ K_UPARROW,
+ K_DOWNARROW,
+ K_LEFTARROW,
+ K_RIGHTARROW,
+
+ K_ALT,
+ K_CTRL,
+ K_SHIFT,
+ K_INS,
+ K_DEL,
+ K_PGDN,
+ K_PGUP,
+ K_HOME,
+ K_END,
+
+ K_F1,
+ K_F2,
+ K_F3,
+ K_F4,
+ K_F5,
+ K_F6,
+ K_F7,
+ K_F8,
+ K_F9,
+ K_F10,
+ K_F11,
+ K_F12,
+ K_F13,
+ K_F14,
+ K_F15,
+
+ K_KP_HOME,
+ K_KP_UPARROW,
+ K_KP_PGUP,
+ K_KP_LEFTARROW,
+ K_KP_5,
+ K_KP_RIGHTARROW,
+ K_KP_END,
+ K_KP_DOWNARROW,
+ K_KP_PGDN,
+ K_KP_ENTER,
+ K_KP_INS,
+ K_KP_DEL,
+ K_KP_SLASH,
+ K_KP_MINUS,
+ K_KP_PLUS,
+ K_KP_NUMLOCK,
+ K_KP_STAR,
+ K_KP_EQUALS,
+
+ K_MOUSE1,
+ K_MOUSE2,
+ K_MOUSE3,
+ K_MOUSE4,
+ K_MOUSE5,
+
+ K_MWHEELDOWN,
+ K_MWHEELUP,
+
+ K_JOY1,
+ K_JOY2,
+ K_JOY3,
+ K_JOY4,
+ K_JOY5,
+ K_JOY6,
+ K_JOY7,
+ K_JOY8,
+ K_JOY9,
+ K_JOY10,
+ K_JOY11,
+ K_JOY12,
+ K_JOY13,
+ K_JOY14,
+ K_JOY15,
+ K_JOY16,
+ K_JOY17,
+ K_JOY18,
+ K_JOY19,
+ K_JOY20,
+ K_JOY21,
+ K_JOY22,
+ K_JOY23,
+ K_JOY24,
+ K_JOY25,
+ K_JOY26,
+ K_JOY27,
+ K_JOY28,
+ K_JOY29,
+ K_JOY30,
+ K_JOY31,
+ K_JOY32,
+
+ K_AUX1,
+ K_AUX2,
+ K_AUX3,
+ K_AUX4,
+ K_AUX5,
+ K_AUX6,
+ K_AUX7,
+ K_AUX8,
+ K_AUX9,
+ K_AUX10,
+ K_AUX11,
+ K_AUX12,
+ K_AUX13,
+ K_AUX14,
+ K_AUX15,
+ K_AUX16,
+
+ K_WORLD_0,
+ K_WORLD_1,
+ K_WORLD_2,
+ K_WORLD_3,
+ K_WORLD_4,
+ K_WORLD_5,
+ K_WORLD_6,
+ K_WORLD_7,
+ K_WORLD_8,
+ K_WORLD_9,
+ K_WORLD_10,
+ K_WORLD_11,
+ K_WORLD_12,
+ K_WORLD_13,
+ K_WORLD_14,
+ K_WORLD_15,
+ K_WORLD_16,
+ K_WORLD_17,
+ K_WORLD_18,
+ K_WORLD_19,
+ K_WORLD_20,
+ K_WORLD_21,
+ K_WORLD_22,
+ K_WORLD_23,
+ K_WORLD_24,
+ K_WORLD_25,
+ K_WORLD_26,
+ K_WORLD_27,
+ K_WORLD_28,
+ K_WORLD_29,
+ K_WORLD_30,
+ K_WORLD_31,
+ K_WORLD_32,
+ K_WORLD_33,
+ K_WORLD_34,
+ K_WORLD_35,
+ K_WORLD_36,
+ K_WORLD_37,
+ K_WORLD_38,
+ K_WORLD_39,
+ K_WORLD_40,
+ K_WORLD_41,
+ K_WORLD_42,
+ K_WORLD_43,
+ K_WORLD_44,
+ K_WORLD_45,
+ K_WORLD_46,
+ K_WORLD_47,
+ K_WORLD_48,
+ K_WORLD_49,
+ K_WORLD_50,
+ K_WORLD_51,
+ K_WORLD_52,
+ K_WORLD_53,
+ K_WORLD_54,
+ K_WORLD_55,
+ K_WORLD_56,
+ K_WORLD_57,
+ K_WORLD_58,
+ K_WORLD_59,
+ K_WORLD_60,
+ K_WORLD_61,
+ K_WORLD_62,
+ K_WORLD_63,
+ K_WORLD_64,
+ K_WORLD_65,
+ K_WORLD_66,
+ K_WORLD_67,
+ K_WORLD_68,
+ K_WORLD_69,
+ K_WORLD_70,
+ K_WORLD_71,
+ K_WORLD_72,
+ K_WORLD_73,
+ K_WORLD_74,
+ K_WORLD_75,
+ K_WORLD_76,
+ K_WORLD_77,
+ K_WORLD_78,
+ K_WORLD_79,
+ K_WORLD_80,
+ K_WORLD_81,
+ K_WORLD_82,
+ K_WORLD_83,
+ K_WORLD_84,
+ K_WORLD_85,
+ K_WORLD_86,
+ K_WORLD_87,
+ K_WORLD_88,
+ K_WORLD_89,
+ K_WORLD_90,
+ K_WORLD_91,
+ K_WORLD_92,
+ K_WORLD_93,
+ K_WORLD_94,
+ K_WORLD_95,
+
+ K_SUPER,
+ K_COMPOSE,
+ K_MODE,
+ K_HELP,
+ K_PRINT,
+ K_SYSREQ,
+ K_SCROLLOCK,
+ K_BREAK,
+ K_MENU,
+ K_EURO,
+ K_UNDO,
+
+ MAX_KEYS
+} keyNum_t;
+
+// MAX_KEYS replaces K_LAST_KEY, however some mods may have used K_LAST_KEY
+// in detecting binds, so we leave it defined to the old hardcoded value
+// of maxiumum keys to prevent mods from crashing older versions of the engine
+#define K_LAST_KEY 256
+
+// The menu code needs to get both key and char events, but
+// to avoid duplicating the paths, the char events are just
+// distinguished by or'ing in K_CHAR_FLAG (ugly)
+#define K_CHAR_FLAG 1024
+
+#endif
diff --git a/src/renderer/tr_types.h b/src/renderer/tr_types.h
new file mode 100644
index 0000000..5aa08ae
--- /dev/null
+++ b/src/renderer/tr_types.h
@@ -0,0 +1,239 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+//
+#ifndef __TR_TYPES_H
+#define __TR_TYPES_H
+
+
+#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces
+#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing
+
+// renderfx flags
+#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items)
+#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites)
+#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob)
+#define RF_DEPTHHACK 8 // for view weapon Z crunching
+#define RF_NOSHADOW 64 // don't add stencil shadows
+
+#define RF_LIGHTING_ORIGIN 128 // use refEntity->lightingOrigin instead of refEntity->origin
+ // for lighting. This allows entities to sink into the floor
+ // with their origin going solid, and allows all parts of a
+ // player to get the same lighting
+#define RF_SHADOW_PLANE 256 // use refEntity->shadowPlane
+#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous
+ // animation without needing to know the frame count
+
+// refdef flags
+#define RDF_NOWORLDMODEL 1 // used for player configuration screen
+#define RDF_HYPERSPACE 4 // teleportation effect
+
+typedef struct {
+ vec3_t xyz;
+ float st[2];
+ byte modulate[4];
+} polyVert_t;
+
+typedef struct poly_s {
+ qhandle_t hShader;
+ int numVerts;
+ polyVert_t *verts;
+} poly_t;
+
+typedef enum {
+ RT_MODEL,
+ RT_POLY,
+ RT_SPRITE,
+ RT_BEAM,
+ RT_RAIL_CORE,
+ RT_RAIL_RINGS,
+ RT_LIGHTNING,
+ RT_PORTALSURFACE, // doesn't draw anything, just info for portals
+
+ RT_MAX_REF_ENTITY_TYPE
+} refEntityType_t;
+
+typedef struct {
+ refEntityType_t reType;
+ int renderfx;
+
+ qhandle_t hModel; // opaque type outside refresh
+
+ // most recent data
+ vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN)
+ float shadowPlane; // projection shadows go here, stencils go slightly lower
+
+ vec3_t axis[3]; // rotation vectors
+ qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale
+ float origin[3]; // also used as MODEL_BEAM's "from"
+ int frame; // also used as MODEL_BEAM's diameter
+
+ // previous data for frame interpolation
+ float oldorigin[3]; // also used as MODEL_BEAM's "to"
+ int oldframe;
+ float backlerp; // 0.0 = current, 1.0 = old
+
+ // texturing
+ int skinNum; // inline skin index
+ qhandle_t customSkin; // NULL for default skin
+ qhandle_t customShader; // use one image for the entire thing
+
+ // misc
+ byte shaderRGBA[4]; // colors used by rgbgen entity shaders
+ float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers
+ float shaderTime; // subtracted from refdef time to control effect start times
+
+ // extra sprite information
+ float radius;
+ float rotation;
+} refEntity_t;
+
+
+#define MAX_RENDER_STRINGS 8
+#define MAX_RENDER_STRING_LENGTH 32
+
+typedef struct {
+ int x, y, width, height;
+ float fov_x, fov_y;
+ vec3_t vieworg;
+ vec3_t viewaxis[3]; // transformation matrix
+
+ // time in milliseconds for shader effects and other time dependent rendering issues
+ int time;
+
+ int rdflags; // RDF_NOWORLDMODEL, etc
+
+ // 1 bits will prevent the associated area from rendering at all
+ byte areamask[MAX_MAP_AREA_BYTES];
+
+ // text messages for deform text shaders
+ char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH];
+} refdef_t;
+
+
+typedef enum {
+ STEREO_CENTER,
+ STEREO_LEFT,
+ STEREO_RIGHT
+} stereoFrame_t;
+
+
+/*
+** glconfig_t
+**
+** Contains variables specific to the OpenGL configuration
+** being run right now. These are constant once the OpenGL
+** subsystem is initialized.
+*/
+typedef enum {
+ TC_NONE,
+ TC_S3TC
+} textureCompression_t;
+
+typedef enum {
+ GLDRV_ICD, // driver is integrated with window system
+ // WARNING: there are tests that check for
+ // > GLDRV_ICD for minidriverness, so this
+ // should always be the lowest value in this
+ // enum set
+ GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver
+ GLDRV_VOODOO // driver is a 3Dfx standalone driver
+} glDriverType_t;
+
+typedef enum {
+ GLHW_GENERIC, // where everthing works the way it should
+ GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is
+ // the hardware type then there can NOT exist a secondary
+ // display adapter
+ GLHW_RIVA128, // where you can't interpolate alpha
+ GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures
+ GLHW_PERMEDIA2 // where you don't have src*dst
+} glHardwareType_t;
+
+typedef struct {
+ char renderer_string[MAX_STRING_CHARS];
+ char vendor_string[MAX_STRING_CHARS];
+ char version_string[MAX_STRING_CHARS];
+ char extensions_string[BIG_INFO_STRING];
+
+ int maxTextureSize; // queried from GL
+ int maxActiveTextures; // multitexture ability
+
+ int colorBits, depthBits, stencilBits;
+
+ glDriverType_t driverType;
+ glHardwareType_t hardwareType;
+
+ qboolean deviceSupportsGamma;
+ textureCompression_t textureCompression;
+ qboolean textureEnvAddAvailable;
+
+ int vidWidth, vidHeight;
+ // aspect is the screen's physical width / height, which may be different
+ // than scrWidth / scrHeight if the pixels are non-square
+ // normal screens should be 4/3, but wide aspect monitors may be 16/9
+ float windowAspect;
+
+ int displayFrequency;
+
+ // synonymous with "does rendering consume the entire screen?", therefore
+ // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that
+ // used CDS.
+ qboolean isFullscreen;
+ qboolean stereoEnabled;
+ qboolean smpActive; // dual processor
+
+ qboolean textureFilterAnisotropic;
+ int maxAnisotropy;
+
+} glconfig_t;
+
+// FIXME: VM should be OS agnostic .. in theory
+
+/*
+#ifdef Q3_VM
+
+#define _3DFX_DRIVER_NAME "Voodoo"
+#define OPENGL_DRIVER_NAME "Default"
+
+#elif defined(_WIN32)
+*/
+
+#if defined(Q3_VM) || defined(_WIN32)
+
+#define _3DFX_DRIVER_NAME "3dfxvgl"
+#define OPENGL_DRIVER_NAME "opengl32"
+
+#elif defined(MACOS_X)
+
+#define _3DFX_DRIVER_NAME "libMesaVoodooGL.dylib"
+#define OPENGL_DRIVER_NAME "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib"
+
+#else
+
+#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so"
+// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524
+#define OPENGL_DRIVER_NAME "libGL.so.1"
+
+#endif // !defined _WIN32
+
+#endif // __TR_TYPES_H
diff --git a/src/ui/menudef.h b/src/ui/menudef.h
new file mode 100644
index 0000000..d4ed0b2
--- /dev/null
+++ b/src/ui/menudef.h
@@ -0,0 +1,362 @@
+
+#define ITEM_TYPE_TEXT 0 // simple text
+#define ITEM_TYPE_BUTTON 1 // button, basically text with a border
+#define ITEM_TYPE_RADIOBUTTON 2 // toggle button, may be grouped
+#define ITEM_TYPE_CHECKBOX 3 // check box
+#define ITEM_TYPE_EDITFIELD 4 // editable text, associated with a cvar
+#define ITEM_TYPE_COMBO 5 // drop down list
+#define ITEM_TYPE_LISTBOX 6 // scrollable list
+#define ITEM_TYPE_MODEL 7 // model
+#define ITEM_TYPE_OWNERDRAW 8 // owner draw, name specs what it is
+#define ITEM_TYPE_NUMERICFIELD 9 // editable text, associated with a cvar
+#define ITEM_TYPE_SLIDER 10 // mouse speed, volume, etc.
+#define ITEM_TYPE_YESNO 11 // yes no cvar setting
+#define ITEM_TYPE_MULTI 12 // multiple list setting, enumerated
+#define ITEM_TYPE_BIND 13 // multiple list setting, enumerated
+
+#define ITEM_ALIGN_LEFT 0 // left alignment
+#define ITEM_ALIGN_CENTER 1 // center alignment
+#define ITEM_ALIGN_RIGHT 2 // right alignment
+
+#define ITEM_TEXTSTYLE_NORMAL 0 // normal text
+#define ITEM_TEXTSTYLE_BLINK 1 // fast blinking
+#define ITEM_TEXTSTYLE_PULSE 2 // slow pulsing
+#define ITEM_TEXTSTYLE_SHADOWED 3 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_OUTLINED 4 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_SHADOWEDMORE 6 // drop shadow ( need a color for this )
+#define ITEM_TEXTSTYLE_NEON 7 // drop shadow ( need a color for this )
+
+#define WINDOW_BORDER_NONE 0 // no border
+#define WINDOW_BORDER_FULL 1 // full border based on border color ( single pixel )
+#define WINDOW_BORDER_HORZ 2 // horizontal borders only
+#define WINDOW_BORDER_VERT 3 // vertical borders only
+#define WINDOW_BORDER_KCGRADIENT 4 // horizontal border using the gradient bars
+
+#define WINDOW_STYLE_EMPTY 0 // no background
+#define WINDOW_STYLE_FILLED 1 // filled with background color
+#define WINDOW_STYLE_GRADIENT 2 // gradient bar based on background color
+#define WINDOW_STYLE_SHADER 3 // gradient bar based on background color
+#define WINDOW_STYLE_TEAMCOLOR 4 // team color
+#define WINDOW_STYLE_CINEMATIC 5 // cinematic
+
+#define MENU_TRUE 1 // uh.. true
+#define MENU_FALSE 0 // and false
+
+#define HUD_VERTICAL 0x00
+#define HUD_HORIZONTAL 0x01
+
+// list box element types
+#define LISTBOX_TEXT 0x00
+#define LISTBOX_IMAGE 0x01
+
+// list feeders
+#define FEEDER_HEADS 0x00 // model heads
+#define FEEDER_MAPS 0x01 // text maps based on game type
+#define FEEDER_SERVERS 0x02 // servers
+#define FEEDER_CLANS 0x03 // clan names
+#define FEEDER_ALLMAPS 0x04 // all maps available, in graphic format
+#define FEEDER_ALIENTEAM_LIST 0x05 // red team members
+#define FEEDER_HUMANTEAM_LIST 0x06 // blue team members
+#define FEEDER_PLAYER_LIST 0x07 // players
+#define FEEDER_TEAM_LIST 0x08 // team members for team voting
+#define FEEDER_MODS 0x09 // team members for team voting
+#define FEEDER_DEMOS 0x0a // team members for team voting
+#define FEEDER_SCOREBOARD 0x0b // team members for team voting
+#define FEEDER_Q3HEADS 0x0c // model heads
+#define FEEDER_SERVERSTATUS 0x0d // server status
+#define FEEDER_FINDPLAYER 0x0e // find player
+#define FEEDER_CINEMATICS 0x0f // cinematics
+
+//TA: tremulous menus
+#define FEEDER_TREMTEAMS 0x10 //teams
+#define FEEDER_TREMALIENCLASSES 0x11 //alien classes
+#define FEEDER_TREMHUMANITEMS 0x12 //human items
+#define FEEDER_TREMHUMANARMOURYBUY 0x13 //human buy
+#define FEEDER_TREMHUMANARMOURYSELL 0x14 //human sell
+#define FEEDER_TREMALIENUPGRADE 0x15 //alien upgrade
+#define FEEDER_TREMALIENBUILD 0x16 //alien buildables
+#define FEEDER_TREMHUMANBUILD 0x17 //human buildables
+//TA: tremulous menus
+#define FEEDER_IGNORE_LIST 0x18 //ignored players
+
+// display flags
+#define CG_SHOW_BLUE_TEAM_HAS_REDFLAG 0x00000001
+#define CG_SHOW_RED_TEAM_HAS_BLUEFLAG 0x00000002
+#define CG_SHOW_ANYTEAMGAME 0x00000004
+#define CG_SHOW_HARVESTER 0x00000008
+#define CG_SHOW_ONEFLAG 0x00000010
+#define CG_SHOW_CTF 0x00000020
+#define CG_SHOW_OBELISK 0x00000040
+#define CG_SHOW_HEALTHCRITICAL 0x00000080
+#define CG_SHOW_SINGLEPLAYER 0x00000100
+#define CG_SHOW_TOURNAMENT 0x00000200
+#define CG_SHOW_DURINGINCOMINGVOICE 0x00000400
+#define CG_SHOW_IF_PLAYER_HAS_FLAG 0x00000800
+#define CG_SHOW_LANPLAYONLY 0x00001000
+#define CG_SHOW_MINED 0x00002000
+#define CG_SHOW_HEALTHOK 0x00004000
+#define CG_SHOW_TEAMINFO 0x00008000
+#define CG_SHOW_NOTEAMINFO 0x00010000
+#define CG_SHOW_OTHERTEAMHASFLAG 0x00020000
+#define CG_SHOW_YOURTEAMHASENEMYFLAG 0x00040000
+#define CG_SHOW_ANYNONTEAMGAME 0x00080000
+#define CG_SHOW_2DONLY 0x10000000
+
+
+#define UI_SHOW_LEADER 0x00000001
+#define UI_SHOW_NOTLEADER 0x00000002
+#define UI_SHOW_FAVORITESERVERS 0x00000004
+#define UI_SHOW_ANYNONTEAMGAME 0x00000008
+#define UI_SHOW_ANYTEAMGAME 0x00000010
+#define UI_SHOW_NEWHIGHSCORE 0x00000020
+#define UI_SHOW_DEMOAVAILABLE 0x00000040
+#define UI_SHOW_NEWBESTTIME 0x00000080
+#define UI_SHOW_FFA 0x00000100
+#define UI_SHOW_NOTFFA 0x00000200
+#define UI_SHOW_NETANYNONTEAMGAME 0x00000400
+#define UI_SHOW_NETANYTEAMGAME 0x00000800
+#define UI_SHOW_NOTFAVORITESERVERS 0x00001000
+
+#define UI_SHOW_VOTEACTIVE 0x00002000
+#define UI_SHOW_CANVOTE 0x00004000
+#define UI_SHOW_TEAMVOTEACTIVE 0x00008000
+#define UI_SHOW_CANTEAMVOTE 0x00010000
+
+#define UI_SHOW_NOTSPECTATING 0x00020000
+
+// owner draw types
+// ideally these should be done outside of this file but
+// this makes it much easier for the macro expansion to
+// convert them for the designers ( from the .menu files )
+#define CG_OWNERDRAW_BASE 1
+#define CG_PLAYER_ARMOR_ICON 1
+#define CG_PLAYER_ARMOR_VALUE 2
+#define CG_PLAYER_HEAD 3
+#define CG_PLAYER_HEALTH 4
+#define CG_PLAYER_HEALTH_BAR 92
+#define CG_PLAYER_HEALTH_CROSS 99
+#define CG_PLAYER_AMMO_ICON 5
+#define CG_PLAYER_AMMO_VALUE 6
+#define CG_PLAYER_CLIPS_VALUE 70
+#define CG_PLAYER_BUILD_TIMER 115
+#define CG_PLAYER_CREDITS_VALUE 71
+#define CG_PLAYER_BANK_VALUE 72
+#define CG_PLAYER_CREDITS_VALUE_NOPAD 106
+#define CG_PLAYER_BANK_VALUE_NOPAD 107
+#define CG_PLAYER_STAMINA 73
+#define CG_PLAYER_STAMINA_1 93
+#define CG_PLAYER_STAMINA_2 94
+#define CG_PLAYER_STAMINA_3 95
+#define CG_PLAYER_STAMINA_4 96
+#define CG_PLAYER_STAMINA_BOLT 97
+#define CG_PLAYER_BOOST_BOLT 112
+#define CG_PLAYER_CLIPS_RING 98
+#define CG_PLAYER_BUILD_TIMER_RING 113
+#define CG_PLAYER_SELECT 74
+#define CG_PLAYER_SELECTTEXT 75
+#define CG_PLAYER_WEAPONICON 111
+#define CG_PLAYER_WALLCLIMBING 103
+#define CG_PLAYER_BOOSTED 104
+#define CG_PLAYER_POISON_BARBS 105
+#define CG_PLAYER_ALIEN_SENSE 108
+#define CG_PLAYER_HUMAN_SCANNER 109
+#define CG_PLAYER_USABLE_BUILDABLE 110
+#define CG_SELECTEDPLAYER_HEAD 7
+#define CG_SELECTEDPLAYER_NAME 8
+#define CG_SELECTEDPLAYER_LOCATION 9
+#define CG_SELECTEDPLAYER_STATUS 10
+#define CG_SELECTEDPLAYER_WEAPON 11
+#define CG_SELECTEDPLAYER_POWERUP 12
+
+#define CG_FLAGCARRIER_HEAD 13
+#define CG_FLAGCARRIER_NAME 14
+#define CG_FLAGCARRIER_LOCATION 15
+#define CG_FLAGCARRIER_STATUS 16
+#define CG_FLAGCARRIER_WEAPON 17
+#define CG_FLAGCARRIER_POWERUP 18
+
+#define CG_PLAYER_ITEM 19
+#define CG_PLAYER_SCORE 20
+
+#define CG_BLUE_FLAGHEAD 21
+#define CG_BLUE_FLAGSTATUS 22
+#define CG_BLUE_FLAGNAME 23
+#define CG_RED_FLAGHEAD 24
+#define CG_RED_FLAGSTATUS 25
+#define CG_RED_FLAGNAME 26
+
+#define CG_BLUE_SCORE 27
+#define CG_RED_SCORE 28
+#define CG_RED_NAME 29
+#define CG_BLUE_NAME 30
+#define CG_HARVESTER_SKULLS 31 // only shows in harvester
+#define CG_ONEFLAG_STATUS 32 // only shows in one flag
+#define CG_PLAYER_LOCATION 33
+#define CG_TEAM_COLOR 34
+#define CG_CTF_POWERUP 35
+
+#define CG_AREA_POWERUP 36
+#define CG_AREA_LAGOMETER 37 // painted with old system
+#define CG_PLAYER_HASFLAG 38
+#define CG_GAME_TYPE 39 // not done
+
+#define CG_SELECTEDPLAYER_ARMOR 40
+#define CG_SELECTEDPLAYER_HEALTH 41
+#define CG_PLAYER_STATUS 42
+#define CG_FRAGGED_MSG 43 // painted with old system
+#define CG_PROXMINED_MSG 44 // painted with old system
+#define CG_AREA_FPSINFO 45 // painted with old system
+#define CG_GAME_STATUS 49
+#define CG_KILLER 50
+#define CG_PLAYER_ARMOR_ICON2D 51
+#define CG_PLAYER_AMMO_ICON2D 52
+#define CG_ACCURACY 53
+#define CG_ASSISTS 54
+#define CG_DEFEND 55
+#define CG_EXCELLENT 56
+#define CG_IMPRESSIVE 57
+#define CG_PERFECT 58
+#define CG_GAUNTLET 59
+#define CG_SPECTATORS 60
+#define CG_TEAMINFO 61
+#define CG_VOICE_HEAD 62
+#define CG_VOICE_NAME 63
+#define CG_PLAYER_HASFLAG2D 64
+#define CG_HARVESTER_SKULLS2D 65 // only shows in harvester
+#define CG_CAPFRAGLIMIT 66
+#define CG_1STPLACE 67
+#define CG_2NDPLACE 68
+#define CG_CAPTURES 69
+
+//TA: loading screen
+#define CG_LOAD_LEVELSHOT 76
+#define CG_LOAD_MEDIA 77
+#define CG_LOAD_MEDIA_LABEL 78
+#define CG_LOAD_BUILDABLES 79
+#define CG_LOAD_BUILDABLES_LABEL 80
+#define CG_LOAD_CHARMODEL 81
+#define CG_LOAD_CHARMODEL_LABEL 82
+#define CG_LOAD_OVERALL 83
+#define CG_LOAD_LEVELNAME 84
+#define CG_LOAD_MOTD 85
+#define CG_LOAD_HOSTNAME 86
+
+#define CG_FPS 87
+#define CG_FPS_FIXED 100
+#define CG_TIMER 88
+#define CG_TIMER_MINS 101
+#define CG_TIMER_SECS 102
+#define CG_SNAPSHOT 89
+#define CG_LAGOMETER 90
+#define CG_PLAYER_CROSSHAIRNAMES 114
+#define CG_STAGE_REPORT_TEXT 116
+#define CG_DEMO_PLAYBACK 117
+#define CG_DEMO_RECORDING 118
+
+#define CG_CONSOLE 91
+#define CG_TUTORIAL 119
+#define CG_CLOCK 120
+
+
+
+#define UI_OWNERDRAW_BASE 200
+#define UI_HANDICAP 200
+#define UI_PLAYERMODEL 202
+#define UI_CLANNAME 203
+#define UI_CLANLOGO 204
+#define UI_GAMETYPE 205
+#define UI_MAPPREVIEW 206
+#define UI_SKILL 207
+#define UI_BLUETEAMNAME 208
+#define UI_REDTEAMNAME 209
+#define UI_BLUETEAM1 210
+#define UI_BLUETEAM2 211
+#define UI_BLUETEAM3 212
+#define UI_BLUETEAM4 213
+#define UI_BLUETEAM5 214
+#define UI_REDTEAM1 215
+#define UI_REDTEAM2 216
+#define UI_REDTEAM3 217
+#define UI_REDTEAM4 218
+#define UI_REDTEAM5 219
+#define UI_NETSOURCE 220
+#define UI_NETMAPPREVIEW 221
+#define UI_NETFILTER 222
+#define UI_TIER 223
+#define UI_OPPONENTMODEL 224
+#define UI_TIERMAP1 225
+#define UI_TIERMAP2 226
+#define UI_TIERMAP3 227
+#define UI_PLAYERLOGO 228
+#define UI_OPPONENTLOGO 229
+#define UI_PLAYERLOGO_METAL 230
+#define UI_OPPONENTLOGO_METAL 231
+#define UI_PLAYERLOGO_NAME 232
+#define UI_OPPONENTLOGO_NAME 233
+#define UI_TIER_MAPNAME 234
+#define UI_TIER_GAMETYPE 235
+#define UI_ALLMAPS_SELECTION 236
+#define UI_OPPONENT_NAME 237
+#define UI_VOTE_KICK 238
+#define UI_BOTNAME 239
+#define UI_BOTSKILL 240
+#define UI_REDBLUE 241
+#define UI_SELECTEDPLAYER 243
+#define UI_MAPCINEMATIC 244
+#define UI_NETGAMETYPE 245
+#define UI_NETMAPCINEMATIC 246
+#define UI_SERVERREFRESHDATE 247
+#define UI_SERVERMOTD 248
+#define UI_GLINFO 249
+#define UI_KEYBINDSTATUS 250
+#define UI_CLANCINEMATIC 251
+#define UI_MAP_TIMETOBEAT 252
+#define UI_JOINGAMETYPE 253
+#define UI_PREVIEWCINEMATIC 254
+#define UI_STARTMAPCINEMATIC 255
+#define UI_MAPS_SELECTION 256
+
+//TA:
+//#define UI_DIALOG 257
+#define UI_TEAMINFOPANE 258
+#define UI_ACLASSINFOPANE 259
+#define UI_AUPGRADEINFOPANE 260
+#define UI_HITEMINFOPANE 261
+#define UI_HBUYINFOPANE 262
+#define UI_HSELLINFOPANE 263
+#define UI_ABUILDINFOPANE 264
+#define UI_HBUILDINFOPANE 265
+
+#define UI_PLAYERLIST_SELECTION 266
+#define UI_TEAMLIST_SELECTION 267
+
+#define VOICECHAT_GETFLAG "getflag" // command someone to get the flag
+#define VOICECHAT_OFFENSE "offense" // command someone to go on offense
+#define VOICECHAT_DEFEND "defend" // command someone to go on defense
+#define VOICECHAT_DEFENDFLAG "defendflag" // command someone to defend the flag
+#define VOICECHAT_PATROL "patrol" // command someone to go on patrol (roam)
+#define VOICECHAT_CAMP "camp" // command someone to camp (we don't have sounds for this one)
+#define VOICECHAT_FOLLOWME "followme" // command someone to follow you
+#define VOICECHAT_RETURNFLAG "returnflag" // command someone to return our flag
+#define VOICECHAT_FOLLOWFLAGCARRIER "followflagcarrier" // command someone to follow the flag carrier
+#define VOICECHAT_YES "yes" // yes, affirmative, etc.
+#define VOICECHAT_NO "no" // no, negative, etc.
+#define VOICECHAT_ONGETFLAG "ongetflag" // I'm getting the flag
+#define VOICECHAT_ONOFFENSE "onoffense" // I'm on offense
+#define VOICECHAT_ONDEFENSE "ondefense" // I'm on defense
+#define VOICECHAT_ONPATROL "onpatrol" // I'm on patrol (roaming)
+#define VOICECHAT_ONCAMPING "oncamp" // I'm camping somewhere
+#define VOICECHAT_ONFOLLOW "onfollow" // I'm following
+#define VOICECHAT_ONFOLLOWCARRIER "onfollowcarrier" // I'm following the flag carrier
+#define VOICECHAT_ONRETURNFLAG "onreturnflag" // I'm returning our flag
+#define VOICECHAT_INPOSITION "inposition" // I'm in position
+#define VOICECHAT_IHAVEFLAG "ihaveflag" // I have the flag
+#define VOICECHAT_BASEATTACK "baseattack" // the base is under attack
+#define VOICECHAT_ENEMYHASFLAG "enemyhasflag" // the enemy has our flag (CTF)
+#define VOICECHAT_STARTLEADER "startleader" // I'm the leader
+#define VOICECHAT_STOPLEADER "stopleader" // I resign leadership
+#define VOICECHAT_TRASH "trash" // lots of trash talk
+#define VOICECHAT_WHOISLEADER "whoisleader" // who is the team leader
+#define VOICECHAT_WANTONDEFENSE "wantondefense" // I want to be on defense
+#define VOICECHAT_WANTONOFFENSE "wantonoffense" // I want to be on offense
diff --git a/src/ui/ui_atoms.c b/src/ui/ui_atoms.c
new file mode 100644
index 0000000..64efa91
--- /dev/null
+++ b/src/ui/ui_atoms.c
@@ -0,0 +1,519 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/**********************************************************************
+ UI_ATOMS.C
+
+ User interface building blocks and support functions.
+**********************************************************************/
+#include "ui_local.h"
+
+qboolean m_entersound; // after a frame, so caching won't disrupt the sound
+
+void QDECL Com_Error( int level, const char *error, ... ) {
+ va_list argptr;
+ char text[1024];
+
+ va_start (argptr, error);
+ vsprintf (text, error, argptr);
+ va_end (argptr);
+
+ trap_Error( va("%s", text) );
+}
+
+void QDECL Com_Printf( const char *msg, ... ) {
+ va_list argptr;
+ char text[1024];
+
+ va_start (argptr, msg);
+ vsprintf (text, msg, argptr);
+ va_end (argptr);
+
+ trap_Print( va("%s", text) );
+}
+
+qboolean newUI = qfalse;
+
+
+/*
+=================
+UI_ClampCvar
+=================
+*/
+float UI_ClampCvar( float min, float max, float value )
+{
+ if ( value < min ) return min;
+ if ( value > max ) return max;
+ return value;
+}
+
+/*
+=================
+UI_StartDemoLoop
+=================
+*/
+void UI_StartDemoLoop( void ) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, "d1\n" );
+}
+
+char *UI_Argv( int arg ) {
+ static char buffer[MAX_STRING_CHARS];
+
+ trap_Argv( arg, buffer, sizeof( buffer ) );
+
+ return buffer;
+}
+
+
+char *UI_Cvar_VariableString( const char *var_name ) {
+ static char buffer[MAX_STRING_CHARS];
+
+ trap_Cvar_VariableStringBuffer( var_name, buffer, sizeof( buffer ) );
+
+ return buffer;
+}
+
+
+
+void UI_SetBestScores(postGameInfo_t *newInfo, qboolean postGame) {
+ trap_Cvar_Set("ui_scoreAccuracy", va("%i%%", newInfo->accuracy));
+ trap_Cvar_Set("ui_scoreImpressives", va("%i", newInfo->impressives));
+ trap_Cvar_Set("ui_scoreExcellents", va("%i", newInfo->excellents));
+ trap_Cvar_Set("ui_scoreDefends", va("%i", newInfo->defends));
+ trap_Cvar_Set("ui_scoreAssists", va("%i", newInfo->assists));
+ trap_Cvar_Set("ui_scoreGauntlets", va("%i", newInfo->gauntlets));
+ trap_Cvar_Set("ui_scoreScore", va("%i", newInfo->score));
+ trap_Cvar_Set("ui_scorePerfect", va("%i", newInfo->perfects));
+ trap_Cvar_Set("ui_scoreTeam", va("%i to %i", newInfo->redScore, newInfo->blueScore));
+ trap_Cvar_Set("ui_scoreBase", va("%i", newInfo->baseScore));
+ trap_Cvar_Set("ui_scoreTimeBonus", va("%i", newInfo->timeBonus));
+ trap_Cvar_Set("ui_scoreSkillBonus", va("%i", newInfo->skillBonus));
+ trap_Cvar_Set("ui_scoreShutoutBonus", va("%i", newInfo->shutoutBonus));
+ trap_Cvar_Set("ui_scoreTime", va("%02i:%02i", newInfo->time / 60, newInfo->time % 60));
+ trap_Cvar_Set("ui_scoreCaptures", va("%i", newInfo->captures));
+ if (postGame) {
+ trap_Cvar_Set("ui_scoreAccuracy2", va("%i%%", newInfo->accuracy));
+ trap_Cvar_Set("ui_scoreImpressives2", va("%i", newInfo->impressives));
+ trap_Cvar_Set("ui_scoreExcellents2", va("%i", newInfo->excellents));
+ trap_Cvar_Set("ui_scoreDefends2", va("%i", newInfo->defends));
+ trap_Cvar_Set("ui_scoreAssists2", va("%i", newInfo->assists));
+ trap_Cvar_Set("ui_scoreGauntlets2", va("%i", newInfo->gauntlets));
+ trap_Cvar_Set("ui_scoreScore2", va("%i", newInfo->score));
+ trap_Cvar_Set("ui_scorePerfect2", va("%i", newInfo->perfects));
+ trap_Cvar_Set("ui_scoreTeam2", va("%i to %i", newInfo->redScore, newInfo->blueScore));
+ trap_Cvar_Set("ui_scoreBase2", va("%i", newInfo->baseScore));
+ trap_Cvar_Set("ui_scoreTimeBonus2", va("%i", newInfo->timeBonus));
+ trap_Cvar_Set("ui_scoreSkillBonus2", va("%i", newInfo->skillBonus));
+ trap_Cvar_Set("ui_scoreShutoutBonus2", va("%i", newInfo->shutoutBonus));
+ trap_Cvar_Set("ui_scoreTime2", va("%02i:%02i", newInfo->time / 60, newInfo->time % 60));
+ trap_Cvar_Set("ui_scoreCaptures2", va("%i", newInfo->captures));
+ }
+}
+
+void UI_LoadBestScores(const char *map, int game) {
+ char fileName[MAX_QPATH];
+ fileHandle_t f;
+ postGameInfo_t newInfo;
+ memset(&newInfo, 0, sizeof(postGameInfo_t));
+ Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game);
+ if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) {
+ int size = 0;
+ trap_FS_Read(&size, sizeof(int), f);
+ if (size == sizeof(postGameInfo_t)) {
+ trap_FS_Read(&newInfo, sizeof(postGameInfo_t), f);
+ }
+ trap_FS_FCloseFile(f);
+ }
+ UI_SetBestScores(&newInfo, qfalse);
+
+ Com_sprintf(fileName, MAX_QPATH, "demos/%s_%d.dm_%d", map, game, (int)trap_Cvar_VariableValue("protocol"));
+ uiInfo.demoAvailable = qfalse;
+ if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) {
+ uiInfo.demoAvailable = qtrue;
+ trap_FS_FCloseFile(f);
+ }
+}
+
+/*
+===============
+UI_ClearScores
+===============
+*/
+void UI_ClearScores( void ) {
+ char gameList[4096];
+ char *gameFile;
+ int i, len, count, size;
+ fileHandle_t f;
+ postGameInfo_t newInfo;
+
+ count = trap_FS_GetFileList( "games", "game", gameList, sizeof(gameList) );
+
+ size = sizeof(postGameInfo_t);
+ memset(&newInfo, 0, size);
+
+ if (count > 0) {
+ gameFile = gameList;
+ for ( i = 0; i < count; i++ ) {
+ len = strlen(gameFile);
+ if (trap_FS_FOpenFile(va("games/%s",gameFile), &f, FS_WRITE) >= 0) {
+ trap_FS_Write(&size, sizeof(int), f);
+ trap_FS_Write(&newInfo, size, f);
+ trap_FS_FCloseFile(f);
+ }
+ gameFile += len + 1;
+ }
+ }
+
+ UI_SetBestScores(&newInfo, qfalse);
+
+}
+
+
+
+static void UI_Cache_f( void ) {
+ Display_CacheAll();
+}
+
+/*
+=======================
+UI_CalcPostGameStats
+=======================
+*/
+static void UI_CalcPostGameStats( void ) {
+ char map[MAX_QPATH];
+ char fileName[MAX_QPATH];
+ char info[MAX_INFO_STRING];
+ fileHandle_t f;
+ int size, game, time, adjustedTime;
+ postGameInfo_t oldInfo;
+ postGameInfo_t newInfo;
+ qboolean newHigh = qfalse;
+
+ trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) );
+ Q_strncpyz( map, Info_ValueForKey( info, "mapname" ), sizeof(map) );
+ game = atoi(Info_ValueForKey(info, "g_gametype"));
+
+ // compose file name
+ Com_sprintf(fileName, MAX_QPATH, "games/%s_%i.game", map, game);
+ // see if we have one already
+ memset(&oldInfo, 0, sizeof(postGameInfo_t));
+ if (trap_FS_FOpenFile(fileName, &f, FS_READ) >= 0) {
+ // if so load it
+ size = 0;
+ trap_FS_Read(&size, sizeof(int), f);
+ if (size == sizeof(postGameInfo_t)) {
+ trap_FS_Read(&oldInfo, sizeof(postGameInfo_t), f);
+ }
+ trap_FS_FCloseFile(f);
+ }
+
+ newInfo.accuracy = atoi(UI_Argv(3));
+ newInfo.impressives = atoi(UI_Argv(4));
+ newInfo.excellents = atoi(UI_Argv(5));
+ newInfo.defends = atoi(UI_Argv(6));
+ newInfo.assists = atoi(UI_Argv(7));
+ newInfo.gauntlets = atoi(UI_Argv(8));
+ newInfo.baseScore = atoi(UI_Argv(9));
+ newInfo.perfects = atoi(UI_Argv(10));
+ newInfo.redScore = atoi(UI_Argv(11));
+ newInfo.blueScore = atoi(UI_Argv(12));
+ time = atoi(UI_Argv(13));
+ newInfo.captures = atoi(UI_Argv(14));
+
+ newInfo.time = (time - trap_Cvar_VariableValue("ui_matchStartTime")) / 1000;
+ adjustedTime = uiInfo.mapList[ui_currentMap.integer].timeToBeat[game];
+ if (newInfo.time < adjustedTime) {
+ newInfo.timeBonus = (adjustedTime - newInfo.time) * 10;
+ } else {
+ newInfo.timeBonus = 0;
+ }
+
+ if (newInfo.redScore > newInfo.blueScore && newInfo.blueScore <= 0) {
+ newInfo.shutoutBonus = 100;
+ } else {
+ newInfo.shutoutBonus = 0;
+ }
+
+ newInfo.skillBonus = trap_Cvar_VariableValue("g_spSkill");
+ if (newInfo.skillBonus <= 0) {
+ newInfo.skillBonus = 1;
+ }
+ newInfo.score = newInfo.baseScore + newInfo.shutoutBonus + newInfo.timeBonus;
+ newInfo.score *= newInfo.skillBonus;
+
+ // see if the score is higher for this one
+ newHigh = (newInfo.redScore > newInfo.blueScore && newInfo.score > oldInfo.score);
+
+ if (newHigh) {
+ // if so write out the new one
+ uiInfo.newHighScoreTime = uiInfo.uiDC.realTime + 20000;
+ if (trap_FS_FOpenFile(fileName, &f, FS_WRITE) >= 0) {
+ size = sizeof(postGameInfo_t);
+ trap_FS_Write(&size, sizeof(int), f);
+ trap_FS_Write(&newInfo, sizeof(postGameInfo_t), f);
+ trap_FS_FCloseFile(f);
+ }
+ }
+
+ if (newInfo.time < oldInfo.time) {
+ uiInfo.newBestTime = uiInfo.uiDC.realTime + 20000;
+ }
+
+ // put back all the ui overrides
+ trap_Cvar_Set("capturelimit", UI_Cvar_VariableString("ui_saveCaptureLimit"));
+ trap_Cvar_Set("fraglimit", UI_Cvar_VariableString("ui_saveFragLimit"));
+ trap_Cvar_Set("cg_drawTimer", UI_Cvar_VariableString("ui_drawTimer"));
+ trap_Cvar_Set("g_doWarmup", UI_Cvar_VariableString("ui_doWarmup"));
+ trap_Cvar_Set("g_Warmup", UI_Cvar_VariableString("ui_Warmup"));
+ trap_Cvar_Set("sv_pure", UI_Cvar_VariableString("ui_pure"));
+ trap_Cvar_Set("g_friendlyFire", UI_Cvar_VariableString("ui_friendlyFire"));
+
+ UI_SetBestScores(&newInfo, qtrue);
+ UI_ShowPostGame(newHigh);
+
+
+}
+
+
+/*
+=================
+UI_ConsoleCommand
+=================
+*/
+qboolean UI_ConsoleCommand( int realTime )
+{
+ char *cmd;
+ char *arg1;
+
+ uiInfo.uiDC.frameTime = realTime - uiInfo.uiDC.realTime;
+ uiInfo.uiDC.realTime = realTime;
+
+ cmd = UI_Argv( 0 );
+
+ // ensure minimum menu data is available
+ //Menu_Cache();
+
+ if ( Q_stricmp (cmd, "ui_test") == 0 ) {
+ UI_ShowPostGame(qtrue);
+ }
+
+ if ( Q_stricmp (cmd, "ui_report") == 0 ) {
+ UI_Report();
+ return qtrue;
+ }
+
+ if ( Q_stricmp (cmd, "ui_load") == 0 ) {
+ UI_Load();
+ return qtrue;
+ }
+
+ if ( Q_stricmp (cmd, "remapShader") == 0 ) {
+ if (trap_Argc() == 4) {
+ char shader1[MAX_QPATH];
+ char shader2[MAX_QPATH];
+ Q_strncpyz(shader1, UI_Argv(1), sizeof(shader1));
+ Q_strncpyz(shader2, UI_Argv(2), sizeof(shader2));
+ trap_R_RemapShader(shader1, shader2, UI_Argv(3));
+ return qtrue;
+ }
+ }
+
+ if ( Q_stricmp (cmd, "postgame") == 0 ) {
+ UI_CalcPostGameStats();
+ return qtrue;
+ }
+
+ if ( Q_stricmp (cmd, "ui_cache") == 0 ) {
+ UI_Cache_f();
+ return qtrue;
+ }
+
+ if ( Q_stricmp (cmd, "ui_teamOrders") == 0 ) {
+ //UI_TeamOrdersMenu_f();
+ return qtrue;
+ }
+
+ if( Q_stricmp ( cmd, "menu" ) == 0 )
+ {
+ arg1 = UI_Argv( 1 );
+
+ if( Menu_Count( ) > 0 )
+ {
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_ActivateByName( arg1 );
+ return qtrue;
+ }
+ }
+
+ if( Q_stricmp ( cmd, "closemenus" ) == 0 )
+ {
+ if( Menu_Count( ) > 0 )
+ {
+ trap_Key_SetCatcher( trap_Key_GetCatcher( ) & ~KEYCATCH_UI );
+ trap_Key_ClearStates( );
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll( );
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+}
+
+/*
+=================
+UI_Shutdown
+=================
+*/
+void UI_Shutdown( void ) {
+}
+
+/*
+================
+UI_AdjustFrom640
+
+Adjusted for resolution and screen aspect ratio
+================
+*/
+void UI_AdjustFrom640( float *x, float *y, float *w, float *h ) {
+ // expect valid pointers
+#if 0
+ *x = *x * uiInfo.uiDC.scale + uiInfo.uiDC.bias;
+ *y *= uiInfo.uiDC.scale;
+ *w *= uiInfo.uiDC.scale;
+ *h *= uiInfo.uiDC.scale;
+#endif
+
+ *x *= uiInfo.uiDC.xscale;
+ *y *= uiInfo.uiDC.yscale;
+ *w *= uiInfo.uiDC.xscale;
+ *h *= uiInfo.uiDC.yscale;
+
+}
+
+void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname ) {
+ qhandle_t hShader;
+
+ hShader = trap_R_RegisterShaderNoMip( picname );
+ UI_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader );
+}
+
+void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader ) {
+ float s0;
+ float s1;
+ float t0;
+ float t1;
+
+ if( w < 0 ) { // flip about vertical
+ w = -w;
+ s0 = 1;
+ s1 = 0;
+ }
+ else {
+ s0 = 0;
+ s1 = 1;
+ }
+
+ if( h < 0 ) { // flip about horizontal
+ h = -h;
+ t0 = 1;
+ t1 = 0;
+ }
+ else {
+ t0 = 0;
+ t1 = 1;
+ }
+
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, w, h, s0, t0, s1, t1, hShader );
+}
+
+/*
+================
+UI_FillRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void UI_FillRect( float x, float y, float width, float height, const float *color ) {
+ trap_R_SetColor( color );
+
+ UI_AdjustFrom640( &x, &y, &width, &height );
+ trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+
+ trap_R_SetColor( NULL );
+}
+
+void UI_DrawSides(float x, float y, float w, float h) {
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x + w - 1, y, 1, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+}
+
+void UI_DrawTopBottom(float x, float y, float w, float h) {
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x, y + h - 1, w, 1, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+}
+/*
+================
+UI_DrawRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void UI_DrawRect( float x, float y, float width, float height, const float *color ) {
+ trap_R_SetColor( color );
+
+ UI_DrawTopBottom(x, y, width, height);
+ UI_DrawSides(x, y, width, height);
+
+ trap_R_SetColor( NULL );
+}
+
+void UI_SetColor( const float *rgba ) {
+ trap_R_SetColor( rgba );
+}
+
+void UI_UpdateScreen( void ) {
+ trap_UpdateScreen();
+}
+
+
+void UI_DrawTextBox (int x, int y, int width, int lines)
+{
+ UI_FillRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorBlack );
+ UI_DrawRect( x + BIGCHAR_WIDTH/2, y + BIGCHAR_HEIGHT/2, ( width + 1 ) * BIGCHAR_WIDTH, ( lines + 1 ) * BIGCHAR_HEIGHT, colorWhite );
+}
+
+qboolean UI_CursorInRect (int x, int y, int width, int height)
+{
+ if (uiInfo.uiDC.cursorx < x ||
+ uiInfo.uiDC.cursory < y ||
+ uiInfo.uiDC.cursorx > x+width ||
+ uiInfo.uiDC.cursory > y+height)
+ return qfalse;
+
+ return qtrue;
+}
diff --git a/src/ui/ui_gameinfo.c b/src/ui/ui_gameinfo.c
new file mode 100644
index 0000000..43639e5
--- /dev/null
+++ b/src/ui/ui_gameinfo.c
@@ -0,0 +1,333 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+//
+// gameinfo.c
+//
+
+#include "ui_local.h"
+
+
+//
+// arena and bot info
+//
+
+
+int ui_numBots;
+static char *ui_botInfos[MAX_BOTS];
+
+static int ui_numArenas;
+static char *ui_arenaInfos[MAX_ARENAS];
+
+/*
+===============
+UI_ParseInfos
+===============
+*/
+int UI_ParseInfos( char *buf, int max, char *infos[] ) {
+ char *token;
+ int count;
+ char key[MAX_TOKEN_CHARS];
+ char info[MAX_INFO_STRING];
+
+ count = 0;
+
+ while ( 1 ) {
+ token = COM_Parse( &buf );
+ if ( !token[0] ) {
+ break;
+ }
+ if ( strcmp( token, "{" ) ) {
+ Com_Printf( "Missing { in info file\n" );
+ break;
+ }
+
+ if ( count == max ) {
+ Com_Printf( "Max infos exceeded\n" );
+ break;
+ }
+
+ info[0] = '\0';
+ while ( 1 ) {
+ token = COM_ParseExt( &buf, qtrue );
+ if ( !token[0] ) {
+ Com_Printf( "Unexpected end of info file\n" );
+ break;
+ }
+ if ( !strcmp( token, "}" ) ) {
+ break;
+ }
+ Q_strncpyz( key, token, sizeof( key ) );
+
+ token = COM_ParseExt( &buf, qfalse );
+ if ( !token[0] ) {
+ strcpy( token, "<NULL>" );
+ }
+ Info_SetValueForKey( info, key, token );
+ }
+ //NOTE: extra space for arena number
+ infos[count] = UI_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1);
+ if (infos[count]) {
+ strcpy(infos[count], info);
+ count++;
+ }
+ }
+ return count;
+}
+
+/*
+===============
+UI_LoadArenasFromFile
+===============
+*/
+static void UI_LoadArenasFromFile( char *filename ) {
+ int len;
+ fileHandle_t f;
+ char buf[MAX_ARENAS_TEXT];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( !f ) {
+ trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
+ return;
+ }
+ if ( len >= MAX_ARENAS_TEXT ) {
+ trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) );
+ trap_FS_FCloseFile( f );
+ return;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ ui_numArenas += UI_ParseInfos( buf, MAX_ARENAS - ui_numArenas, &ui_arenaInfos[ui_numArenas] );
+}
+
+/*
+=================
+UI_MapNameCompare
+=================
+*/
+static int UI_MapNameCompare( const void *a, const void *b )
+{
+ mapInfo *A = (mapInfo *)a;
+ mapInfo *B = (mapInfo *)b;
+
+ return Q_stricmp( A->mapName, B->mapName );
+}
+
+/*
+===============
+UI_LoadArenas
+===============
+*/
+void UI_LoadArenas( void ) {
+ int numdirs;
+ char filename[128];
+ char dirlist[1024];
+ char* dirptr;
+ int i, n;
+ int dirlen;
+ char *type;
+
+ ui_numArenas = 0;
+ uiInfo.mapCount = 0;
+
+ // get all arenas from .arena files
+ numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 );
+ dirptr = dirlist;
+ for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
+ dirlen = strlen(dirptr);
+ strcpy(filename, "scripts/");
+ strcat(filename, dirptr);
+ UI_LoadArenasFromFile(filename);
+ }
+ trap_Print( va( "[skipnotify]%i arenas parsed\n", ui_numArenas ) );
+ if (UI_OutOfMemory()) {
+ trap_Print(S_COLOR_YELLOW"WARNING: not anough memory in pool to load all arenas\n");
+ }
+
+ for( n = 0; n < ui_numArenas; n++ )
+ {
+ // determine type
+ type = Info_ValueForKey( ui_arenaInfos[ n ], "type" );
+ // if no type specified, it will be treated as "ffa"
+
+ if( *type && strstr( type, "tremulous" ) )
+ uiInfo.mapList[ uiInfo.mapCount ].typeBits |= ( 1 << 0 );
+ else
+ continue; //not a trem map
+
+ uiInfo.mapList[uiInfo.mapCount].cinematic = -1;
+ uiInfo.mapList[uiInfo.mapCount].mapLoadName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "map"));
+ uiInfo.mapList[uiInfo.mapCount].mapName = String_Alloc(Info_ValueForKey(ui_arenaInfos[n], "longname"));
+ uiInfo.mapList[uiInfo.mapCount].levelShot = -1;
+ uiInfo.mapList[uiInfo.mapCount].imageName = String_Alloc(va("levelshots/%s", uiInfo.mapList[uiInfo.mapCount].mapLoadName));
+
+ uiInfo.mapCount++;
+ if( uiInfo.mapCount >= MAX_MAPS )
+ break;
+ }
+
+ qsort( uiInfo.mapList, uiInfo.mapCount, sizeof( mapInfo ), UI_MapNameCompare );
+}
+
+
+/*
+===============
+UI_LoadBotsFromFile
+===============
+*/
+static void UI_LoadBotsFromFile( char *filename ) {
+ int len;
+ fileHandle_t f;
+ char buf[MAX_BOTS_TEXT];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( !f ) {
+ trap_Print( va( S_COLOR_RED "file not found: %s\n", filename ) );
+ return;
+ }
+ if ( len >= MAX_BOTS_TEXT ) {
+ trap_Print( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) );
+ trap_FS_FCloseFile( f );
+ return;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ COM_Compress(buf);
+
+ ui_numBots += UI_ParseInfos( buf, MAX_BOTS - ui_numBots, &ui_botInfos[ui_numBots] );
+}
+
+/*
+===============
+UI_LoadBots
+===============
+*/
+void UI_LoadBots( void ) {
+ vmCvar_t botsFile;
+ int numdirs;
+ char filename[128];
+ char dirlist[1024];
+ char* dirptr;
+ int i;
+ int dirlen;
+
+ ui_numBots = 0;
+
+ trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM );
+ if( *botsFile.string ) {
+ UI_LoadBotsFromFile(botsFile.string);
+ }
+ else {
+ UI_LoadBotsFromFile("scripts/bots.txt");
+ }
+
+ // get all bots from .bot files
+ numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 );
+ dirptr = dirlist;
+ for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
+ dirlen = strlen(dirptr);
+ strcpy(filename, "scripts/");
+ strcat(filename, dirptr);
+ UI_LoadBotsFromFile(filename);
+ }
+ trap_Print( va( "%i bots parsed\n", ui_numBots ) );
+}
+
+
+/*
+===============
+UI_GetBotInfoByNumber
+===============
+*/
+char *UI_GetBotInfoByNumber( int num ) {
+ if( num < 0 || num >= ui_numBots ) {
+ trap_Print( va( S_COLOR_RED "Invalid bot number: %i\n", num ) );
+ return NULL;
+ }
+ return ui_botInfos[num];
+}
+
+
+/*
+===============
+UI_GetBotInfoByName
+===============
+*/
+char *UI_GetBotInfoByName( const char *name ) {
+ int n;
+ char *value;
+
+ for ( n = 0; n < ui_numBots ; n++ ) {
+ value = Info_ValueForKey( ui_botInfos[n], "name" );
+ if ( !Q_stricmp( value, name ) ) {
+ return ui_botInfos[n];
+ }
+ }
+
+ return NULL;
+}
+
+int UI_GetNumBots() {
+ return ui_numBots;
+}
+
+
+char *UI_GetBotNameByNumber( int num ) {
+ char *info = UI_GetBotInfoByNumber(num);
+ if (info) {
+ return Info_ValueForKey( info, "name" );
+ }
+ return "Sarge";
+}
+
+void UI_ServerInfo( void )
+{
+ char info[ MAX_INFO_VALUE ];
+
+ info[0] = '\0';
+ if( trap_GetConfigString( CS_SERVERINFO, info, sizeof( info ) ) )
+ {
+ trap_Cvar_Set( "ui_serverinfo_mapname",
+ Info_ValueForKey( info, "mapname" ) );
+ trap_Cvar_Set( "ui_serverinfo_timelimit",
+ Info_ValueForKey( info, "timelimit" ) );
+ trap_Cvar_Set( "ui_serverinfo_sd",
+ Info_ValueForKey( info, "g_suddenDeathTime" ) );
+ trap_Cvar_Set( "ui_serverinfo_hostname",
+ Info_ValueForKey( info, "sv_hostname" ) );
+ trap_Cvar_Set( "ui_serverinfo_maxclients",
+ Info_ValueForKey( info, "sv_maxclients" ) );
+ trap_Cvar_Set( "ui_serverinfo_version",
+ Info_ValueForKey( info, "version" ) );
+ trap_Cvar_Set( "ui_serverinfo_unlagged",
+ Info_ValueForKey( info, "g_unlagged" ) );
+ trap_Cvar_Set( "ui_serverinfo_ff",
+ Info_ValueForKey( info, "ff" ) );
+ }
+}
diff --git a/src/ui/ui_local.h b/src/ui/ui_local.h
new file mode 100644
index 0000000..7afdf65
--- /dev/null
+++ b/src/ui/ui_local.h
@@ -0,0 +1,1207 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifndef __UI_LOCAL_H__
+#define __UI_LOCAL_H__
+
+#include "../qcommon/q_shared.h"
+#include "../renderer/tr_types.h"
+#include "ui_public.h"
+#include "../client/keycodes.h"
+#include "../game/bg_public.h"
+#include "ui_shared.h"
+
+// global display context
+
+extern vmCvar_t ui_ffa_fraglimit;
+extern vmCvar_t ui_ffa_timelimit;
+
+extern vmCvar_t ui_tourney_fraglimit;
+extern vmCvar_t ui_tourney_timelimit;
+
+extern vmCvar_t ui_team_fraglimit;
+extern vmCvar_t ui_team_timelimit;
+extern vmCvar_t ui_team_friendly;
+
+extern vmCvar_t ui_ctf_capturelimit;
+extern vmCvar_t ui_ctf_timelimit;
+extern vmCvar_t ui_ctf_friendly;
+
+extern vmCvar_t ui_arenasFile;
+extern vmCvar_t ui_botsFile;
+extern vmCvar_t ui_spScores1;
+extern vmCvar_t ui_spScores2;
+extern vmCvar_t ui_spScores3;
+extern vmCvar_t ui_spScores4;
+extern vmCvar_t ui_spScores5;
+extern vmCvar_t ui_spAwards;
+extern vmCvar_t ui_spVideos;
+extern vmCvar_t ui_spSkill;
+
+extern vmCvar_t ui_spSelection;
+
+extern vmCvar_t ui_browserMaster;
+extern vmCvar_t ui_browserGameType;
+extern vmCvar_t ui_browserSortKey;
+extern vmCvar_t ui_browserShowFull;
+extern vmCvar_t ui_browserShowEmpty;
+
+extern vmCvar_t ui_brassTime;
+extern vmCvar_t ui_drawCrosshair;
+extern vmCvar_t ui_drawCrosshairNames;
+extern vmCvar_t ui_marks;
+
+extern vmCvar_t ui_server1;
+extern vmCvar_t ui_server2;
+extern vmCvar_t ui_server3;
+extern vmCvar_t ui_server4;
+extern vmCvar_t ui_server5;
+extern vmCvar_t ui_server6;
+extern vmCvar_t ui_server7;
+extern vmCvar_t ui_server8;
+extern vmCvar_t ui_server9;
+extern vmCvar_t ui_server10;
+extern vmCvar_t ui_server11;
+extern vmCvar_t ui_server12;
+extern vmCvar_t ui_server13;
+extern vmCvar_t ui_server14;
+extern vmCvar_t ui_server15;
+extern vmCvar_t ui_server16;
+
+extern vmCvar_t ui_captureLimit;
+extern vmCvar_t ui_fragLimit;
+extern vmCvar_t ui_gameType;
+extern vmCvar_t ui_netGameType;
+extern vmCvar_t ui_actualNetGameType;
+extern vmCvar_t ui_joinGameType;
+extern vmCvar_t ui_netSource;
+extern vmCvar_t ui_serverFilterType;
+extern vmCvar_t ui_dedicated;
+extern vmCvar_t ui_opponentName;
+extern vmCvar_t ui_menuFiles;
+extern vmCvar_t ui_currentTier;
+extern vmCvar_t ui_currentMap;
+extern vmCvar_t ui_currentNetMap;
+extern vmCvar_t ui_mapIndex;
+extern vmCvar_t ui_currentOpponent;
+extern vmCvar_t ui_selectedPlayer;
+extern vmCvar_t ui_selectedPlayerName;
+extern vmCvar_t ui_lastServerRefresh_0;
+extern vmCvar_t ui_lastServerRefresh_1;
+extern vmCvar_t ui_lastServerRefresh_2;
+extern vmCvar_t ui_lastServerRefresh_3;
+extern vmCvar_t ui_singlePlayerActive;
+extern vmCvar_t ui_scoreAccuracy;
+extern vmCvar_t ui_scoreImpressives;
+extern vmCvar_t ui_scoreExcellents;
+extern vmCvar_t ui_scoreDefends;
+extern vmCvar_t ui_scoreAssists;
+extern vmCvar_t ui_scoreGauntlets;
+extern vmCvar_t ui_scoreScore;
+extern vmCvar_t ui_scorePerfect;
+extern vmCvar_t ui_scoreTeam;
+extern vmCvar_t ui_scoreBase;
+extern vmCvar_t ui_scoreTimeBonus;
+extern vmCvar_t ui_scoreSkillBonus;
+extern vmCvar_t ui_scoreShutoutBonus;
+extern vmCvar_t ui_scoreTime;
+extern vmCvar_t ui_smallFont;
+extern vmCvar_t ui_bigFont;
+extern vmCvar_t ui_serverStatusTimeOut;
+
+//TA: bank values
+extern vmCvar_t ui_bank;
+
+
+//
+// ui_qmenu.c
+//
+
+#define RCOLUMN_OFFSET ( BIGCHAR_WIDTH )
+#define LCOLUMN_OFFSET (-BIGCHAR_WIDTH )
+
+#define SLIDER_RANGE 10
+#define MAX_EDIT_LINE 256
+
+#define MAX_MENUDEPTH 8
+#define MAX_MENUITEMS 128
+
+#define MTYPE_NULL 0
+#define MTYPE_SLIDER 1
+#define MTYPE_ACTION 2
+#define MTYPE_SPINCONTROL 3
+#define MTYPE_FIELD 4
+#define MTYPE_RADIOBUTTON 5
+#define MTYPE_BITMAP 6
+#define MTYPE_TEXT 7
+#define MTYPE_SCROLLLIST 8
+#define MTYPE_PTEXT 9
+#define MTYPE_BTEXT 10
+
+#define QMF_BLINK 0x00000001
+#define QMF_SMALLFONT 0x00000002
+#define QMF_LEFT_JUSTIFY 0x00000004
+#define QMF_CENTER_JUSTIFY 0x00000008
+#define QMF_RIGHT_JUSTIFY 0x00000010
+#define QMF_NUMBERSONLY 0x00000020 // edit field is only numbers
+#define QMF_HIGHLIGHT 0x00000040
+#define QMF_HIGHLIGHT_IF_FOCUS 0x00000080 // steady focus
+#define QMF_PULSEIFFOCUS 0x00000100 // pulse if focus
+#define QMF_HASMOUSEFOCUS 0x00000200
+#define QMF_NOONOFFTEXT 0x00000400
+#define QMF_MOUSEONLY 0x00000800 // only mouse input allowed
+#define QMF_HIDDEN 0x00001000 // skips drawing
+#define QMF_GRAYED 0x00002000 // grays and disables
+#define QMF_INACTIVE 0x00004000 // disables any input
+#define QMF_NODEFAULTINIT 0x00008000 // skip default initialization
+#define QMF_OWNERDRAW 0x00010000
+#define QMF_PULSE 0x00020000
+#define QMF_LOWERCASE 0x00040000 // edit field is all lower case
+#define QMF_UPPERCASE 0x00080000 // edit field is all upper case
+#define QMF_SILENT 0x00100000
+
+// callback notifications
+#define QM_GOTFOCUS 1
+#define QM_LOSTFOCUS 2
+#define QM_ACTIVATED 3
+
+typedef struct _tag_menuframework
+{
+ int cursor;
+ int cursor_prev;
+
+ int nitems;
+ void *items[MAX_MENUITEMS];
+
+ void (*draw) (void);
+ sfxHandle_t (*key) (int key);
+
+ qboolean wrapAround;
+ qboolean fullscreen;
+ qboolean showlogo;
+} menuframework_s;
+
+typedef struct
+{
+ int type;
+ const char *name;
+ int id;
+ int x, y;
+ int left;
+ int top;
+ int right;
+ int bottom;
+ menuframework_s *parent;
+ int menuPosition;
+ unsigned flags;
+
+ void (*callback)( void *self, int event );
+ void (*statusbar)( void *self );
+ void (*ownerdraw)( void *self );
+} menucommon_s;
+
+typedef struct {
+ int cursor;
+ int scroll;
+ int widthInChars;
+ char buffer[MAX_EDIT_LINE];
+ int maxchars;
+} mfield_t;
+
+typedef struct
+{
+ menucommon_s generic;
+ mfield_t field;
+} menufield_s;
+
+typedef struct
+{
+ menucommon_s generic;
+
+ float minvalue;
+ float maxvalue;
+ float curvalue;
+
+ float range;
+} menuslider_s;
+
+typedef struct
+{
+ menucommon_s generic;
+
+ int oldvalue;
+ int curvalue;
+ int numitems;
+ int top;
+
+ const char **itemnames;
+
+ int width;
+ int height;
+ int columns;
+ int seperation;
+} menulist_s;
+
+typedef struct
+{
+ menucommon_s generic;
+} menuaction_s;
+
+typedef struct
+{
+ menucommon_s generic;
+ int curvalue;
+} menuradiobutton_s;
+
+typedef struct
+{
+ menucommon_s generic;
+ char* focuspic;
+ char* errorpic;
+ qhandle_t shader;
+ qhandle_t focusshader;
+ int width;
+ int height;
+ float* focuscolor;
+} menubitmap_s;
+
+typedef struct
+{
+ menucommon_s generic;
+ char* string;
+ int style;
+ float* color;
+} menutext_s;
+
+extern void Menu_Cache( void );
+extern void Menu_Focus( menucommon_s *m );
+extern void Menu_AddItem( menuframework_s *menu, void *item );
+extern void Menu_AdjustCursor( menuframework_s *menu, int dir );
+extern void Menu_Draw( menuframework_s *menu );
+extern void *Menu_ItemAtCursor( menuframework_s *m );
+extern sfxHandle_t Menu_ActivateItem( menuframework_s *s, menucommon_s* item );
+extern void Menu_SetCursor( menuframework_s *s, int cursor );
+extern void Menu_SetCursorToItem( menuframework_s *m, void* ptr );
+extern sfxHandle_t Menu_DefaultKey( menuframework_s *s, int key );
+extern void Bitmap_Init( menubitmap_s *b );
+extern void Bitmap_Draw( menubitmap_s *b );
+extern void ScrollList_Draw( menulist_s *l );
+extern sfxHandle_t ScrollList_Key( menulist_s *l, int key );
+extern sfxHandle_t menu_in_sound;
+extern sfxHandle_t menu_move_sound;
+extern sfxHandle_t menu_out_sound;
+extern sfxHandle_t menu_buzz_sound;
+extern sfxHandle_t menu_null_sound;
+extern sfxHandle_t weaponChangeSound;
+extern vec4_t menu_text_color;
+extern vec4_t menu_grayed_color;
+extern vec4_t menu_dark_color;
+extern vec4_t menu_highlight_color;
+extern vec4_t menu_red_color;
+extern vec4_t menu_black_color;
+extern vec4_t menu_dim_color;
+extern vec4_t color_black;
+extern vec4_t color_white;
+extern vec4_t color_yellow;
+extern vec4_t color_blue;
+extern vec4_t color_orange;
+extern vec4_t color_red;
+extern vec4_t color_dim;
+extern vec4_t name_color;
+extern vec4_t list_color;
+extern vec4_t listbar_color;
+extern vec4_t text_color_disabled;
+extern vec4_t text_color_normal;
+extern vec4_t text_color_highlight;
+
+extern char *ui_medalNames[];
+extern char *ui_medalPicNames[];
+extern char *ui_medalSounds[];
+
+//
+// ui_mfield.c
+//
+extern void MField_Clear( mfield_t *edit );
+extern void MField_KeyDownEvent( mfield_t *edit, int key );
+extern void MField_CharEvent( mfield_t *edit, int ch );
+extern void MField_Draw( mfield_t *edit, int x, int y, int style, vec4_t color );
+extern void MenuField_Init( menufield_s* m );
+extern void MenuField_Draw( menufield_s *f );
+extern sfxHandle_t MenuField_Key( menufield_s* m, int* key );
+
+//
+// ui_main.c
+//
+void UI_Report( void );
+void UI_Load( void );
+void UI_LoadMenus(const char *menuFile, qboolean reset);
+void _UI_SetActiveMenu( uiMenuCommand_t menu );
+int UI_AdjustTimeByGame(int time);
+void UI_ShowPostGame(qboolean newHigh);
+void UI_ClearScores( void );
+void UI_LoadArenas(void);
+void UI_ServerInfo(void);
+
+//
+// ui_menu.c
+//
+extern void MainMenu_Cache( void );
+extern void UI_MainMenu(void);
+extern void UI_RegisterCvars( void );
+extern void UI_UpdateCvars( void );
+
+//
+// ui_credits.c
+//
+extern void UI_CreditMenu( void );
+
+//
+// ui_ingame.c
+//
+extern void InGame_Cache( void );
+extern void UI_InGameMenu(void);
+
+//
+// ui_confirm.c
+//
+extern void ConfirmMenu_Cache( void );
+extern void UI_ConfirmMenu( const char *question, void (*draw)( void ), void (*action)( qboolean result ) );
+
+//
+// ui_setup.c
+//
+extern void UI_SetupMenu_Cache( void );
+extern void UI_SetupMenu(void);
+
+//
+// ui_team.c
+//
+extern void UI_TeamMainMenu( void );
+extern void TeamMain_Cache( void );
+
+//
+// ui_connect.c
+//
+extern void UI_DrawConnectScreen( qboolean overlay );
+
+//
+// ui_controls2.c
+//
+extern void UI_ControlsMenu( void );
+extern void Controls_Cache( void );
+
+//
+// ui_demo2.c
+//
+extern void UI_DemosMenu( void );
+extern void Demos_Cache( void );
+
+//
+// ui_cinematics.c
+//
+extern void UI_CinematicsMenu( void );
+extern void UI_CinematicsMenu_f( void );
+extern void UI_CinematicsMenu_Cache( void );
+
+//
+// ui_mods.c
+//
+extern void UI_ModsMenu( void );
+extern void UI_ModsMenu_Cache( void );
+
+//
+// ui_playermodel.c
+//
+extern void UI_PlayerModelMenu( void );
+extern void PlayerModel_Cache( void );
+
+//
+// ui_playersettings.c
+//
+extern void UI_PlayerSettingsMenu( void );
+extern void PlayerSettings_Cache( void );
+
+//
+// ui_preferences.c
+//
+extern void UI_PreferencesMenu( void );
+extern void Preferences_Cache( void );
+
+//
+// ui_specifyleague.c
+//
+extern void UI_SpecifyLeagueMenu( void );
+extern void SpecifyLeague_Cache( void );
+
+//
+// ui_specifyserver.c
+//
+extern void UI_SpecifyServerMenu( void );
+extern void SpecifyServer_Cache( void );
+
+//
+// ui_servers2.c
+//
+#define MAX_FAVORITESERVERS 16
+
+extern void UI_ArenaServersMenu( void );
+extern void ArenaServers_Cache( void );
+
+//
+// ui_startserver.c
+//
+extern void UI_StartServerMenu( qboolean multiplayer );
+extern void StartServer_Cache( void );
+extern void ServerOptions_Cache( void );
+extern void UI_BotSelectMenu( char *bot );
+extern void UI_BotSelectMenu_Cache( void );
+
+//
+// ui_serverinfo.c
+//
+extern void UI_ServerInfoMenu( void );
+extern void ServerInfo_Cache( void );
+
+//
+// ui_video.c
+//
+extern void UI_GraphicsOptionsMenu( void );
+extern void GraphicsOptions_Cache( void );
+extern void DriverInfo_Cache( void );
+
+//
+// ui_players.c
+//
+
+//FIXME ripped from cg_local.h
+typedef struct {
+ int oldFrame;
+ int oldFrameTime; // time when ->oldFrame was exactly on
+
+ int frame;
+ int frameTime; // time when ->frame will be exactly on
+
+ float backlerp;
+
+ float yawAngle;
+ qboolean yawing;
+ float pitchAngle;
+ qboolean pitching;
+
+ int animationNumber; // may include ANIM_TOGGLEBIT
+ animation_t *animation;
+ int animationTime; // time when the first frame of the animation will be exact
+} lerpFrame_t;
+
+typedef struct {
+ // model info
+ qhandle_t legsModel;
+ qhandle_t legsSkin;
+ lerpFrame_t legs;
+
+ qhandle_t torsoModel;
+ qhandle_t torsoSkin;
+ lerpFrame_t torso;
+
+ qhandle_t headModel;
+ qhandle_t headSkin;
+
+ animation_t animations[MAX_PLAYER_TOTALANIMATIONS];
+
+ qhandle_t weaponModel;
+ qhandle_t barrelModel;
+ qhandle_t flashModel;
+ vec3_t flashDlightColor;
+ int muzzleFlashTime;
+
+ // currently in use drawing parms
+ vec3_t viewAngles;
+ vec3_t moveAngles;
+ weapon_t currentWeapon;
+ int legsAnim;
+ int torsoAnim;
+
+ // animation vars
+ weapon_t weapon;
+ weapon_t lastWeapon;
+ weapon_t pendingWeapon;
+ int weaponTimer;
+ int pendingLegsAnim;
+ int torsoAnimationTimer;
+
+ int pendingTorsoAnim;
+ int legsAnimationTimer;
+
+ qboolean chat;
+ qboolean newModel;
+
+ qboolean barrelSpinning;
+ float barrelAngle;
+ int barrelTime;
+
+ int realWeapon;
+} playerInfo_t;
+
+void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time );
+void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName );
+void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNum, qboolean chat );
+qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName , const char *headName, const char *teamName);
+
+//
+// ui_atoms.c
+//
+// this is only used in the old ui, the new ui has it's own version
+typedef struct {
+ int frametime;
+ int realtime;
+ int cursorx;
+ int cursory;
+ glconfig_t glconfig;
+ qboolean debug;
+ qhandle_t whiteShader;
+ qhandle_t charset;
+ qhandle_t charsetProp;
+ qhandle_t charsetPropGlow;
+ qhandle_t charsetPropB;
+ qhandle_t cursor;
+ qhandle_t rb_on;
+ qhandle_t rb_off;
+ float scale;
+ float bias;
+ qboolean demoversion;
+ qboolean firstdraw;
+} uiStatic_t;
+
+
+// new ui stuff
+#define UI_NUMFX 7
+#define MAX_HEADS 64
+#define MAX_ALIASES 64
+#define MAX_HEADNAME 32
+#define MAX_TEAMS 64
+#define MAX_GAMETYPES 16
+#define MAX_MAPS 128
+#define MAX_SPMAPS 16
+#define PLAYERS_PER_TEAM 5
+#define MAX_PINGREQUESTS 32
+#define MAX_ADDRESSLENGTH 64
+#define MAX_HOSTNAMELENGTH 22
+#define MAX_MAPNAMELENGTH 16
+#define MAX_STATUSLENGTH 64
+#define MAX_LISTBOXWIDTH 59
+#define UI_FONT_THRESHOLD 0.1
+#define MAX_DISPLAY_SERVERS 2048
+#define MAX_SERVERSTATUS_LINES 128
+#define MAX_SERVERSTATUS_TEXT 1024
+#define MAX_FOUNDPLAYER_SERVERS 16
+#define TEAM_MEMBERS 5
+#define GAMES_ALL 0
+#define GAMES_FFA 1
+#define GAMES_TEAMPLAY 2
+#define GAMES_TOURNEY 3
+#define GAMES_CTF 4
+#define MAPS_PER_TIER 3
+#define MAX_TIERS 16
+#define MAX_MODS 64
+#define MAX_DEMOS 256
+#define MAX_MOVIES 256
+#define MAX_PLAYERMODELS 256
+
+
+typedef struct {
+ const char *name;
+ const char *imageName;
+ qhandle_t headImage;
+ const char *base;
+ qboolean active;
+ int reference;
+} characterInfo;
+
+typedef struct {
+ const char *name;
+ const char *ai;
+ const char *action;
+} aliasInfo;
+
+typedef struct {
+ const char *teamName;
+ const char *imageName;
+ const char *teamMembers[TEAM_MEMBERS];
+ qhandle_t teamIcon;
+ qhandle_t teamIcon_Metal;
+ qhandle_t teamIcon_Name;
+ int cinematic;
+} teamInfo;
+
+typedef struct {
+ const char *gameType;
+ int gtEnum;
+} gameTypeInfo;
+
+typedef struct {
+ const char *mapName;
+ const char *mapLoadName;
+ const char *imageName;
+ const char *opponentName;
+ int teamMembers;
+ int typeBits;
+ int cinematic;
+ int timeToBeat[MAX_GAMETYPES];
+ qhandle_t levelShot;
+ qboolean active;
+} mapInfo;
+
+typedef struct {
+ const char *tierName;
+ const char *maps[MAPS_PER_TIER];
+ int gameTypes[MAPS_PER_TIER];
+ qhandle_t mapHandles[MAPS_PER_TIER];
+} tierInfo;
+
+typedef struct serverFilter_s {
+ const char *description;
+ const char *basedir;
+} serverFilter_t;
+
+typedef struct {
+ char adrstr[MAX_ADDRESSLENGTH];
+ int start;
+} pinglist_t;
+
+
+typedef struct serverStatus_s {
+ pinglist_t pingList[MAX_PINGREQUESTS];
+ int numqueriedservers;
+ int currentping;
+ int nextpingtime;
+ int maxservers;
+ int refreshtime;
+ int numServers;
+ int sortKey;
+ int sortDir;
+ qboolean sorted;
+ int lastCount;
+ qboolean refreshActive;
+ int currentServer;
+ int displayServers[MAX_DISPLAY_SERVERS];
+ int numDisplayServers;
+ int numPlayersOnServers;
+ int nextDisplayRefresh;
+ int nextSortTime;
+ qhandle_t currentServerPreview;
+ int currentServerCinematic;
+ int motdLen;
+ int motdWidth;
+ int motdPaintX;
+ int motdPaintX2;
+ int motdOffset;
+ int motdTime;
+ char motd[MAX_STRING_CHARS];
+} serverStatus_t;
+
+
+typedef struct {
+ char adrstr[MAX_ADDRESSLENGTH];
+ char name[MAX_ADDRESSLENGTH];
+ int startTime;
+ int serverNum;
+ qboolean valid;
+} pendingServer_t;
+
+typedef struct {
+ int num;
+ pendingServer_t server[MAX_SERVERSTATUSREQUESTS];
+} pendingServerStatus_t;
+
+typedef struct {
+ char address[MAX_ADDRESSLENGTH];
+ char *lines[MAX_SERVERSTATUS_LINES][4];
+ char text[MAX_SERVERSTATUS_TEXT];
+ char pings[MAX_CLIENTS * 3];
+ int numLines;
+} serverStatusInfo_t;
+
+typedef struct {
+ const char *modName;
+ const char *modDescr;
+} modInfo_t;
+
+//TA: tremulous menus
+#define MAX_INFOPANE_TEXT 4096
+#define MAX_INFOPANE_GRAPHICS 16
+#define MAX_INFOPANES 128
+
+typedef enum
+{
+ INFOPANE_TOP,
+ INFOPANE_BOTTOM,
+ INFOPANE_LEFT,
+ INFOPANE_RIGHT
+} tremIPSide_t;
+
+typedef struct
+{
+ qhandle_t graphic;
+
+ tremIPSide_t side;
+ int offset;
+
+ int width, height;
+} tremIPGraphic_t;
+
+typedef struct
+{
+ const char *name;
+ char text[ MAX_INFOPANE_TEXT ];
+ int align;
+
+ tremIPGraphic_t graphics[ MAX_INFOPANE_GRAPHICS ];
+ int numGraphics;
+} tremInfoPane_t;
+
+typedef struct
+{
+ const char *text;
+ const char *cmd;
+ tremInfoPane_t *infopane;
+} tremMenuItem_t;
+//TA: tremulous menus
+
+typedef struct {
+ displayContextDef_t uiDC;
+ int newHighScoreTime;
+ int newBestTime;
+ int showPostGameTime;
+ qboolean newHighScore;
+ qboolean demoAvailable;
+ qboolean soundHighScore;
+
+ int characterCount;
+ int botIndex;
+ characterInfo characterList[MAX_HEADS];
+
+ int aliasCount;
+ aliasInfo aliasList[MAX_ALIASES];
+
+ int teamCount;
+ teamInfo teamList[MAX_TEAMS];
+
+ int numGameTypes;
+ gameTypeInfo gameTypes[MAX_GAMETYPES];
+
+ int numJoinGameTypes;
+ gameTypeInfo joinGameTypes[MAX_GAMETYPES];
+
+ int redBlue;
+ int playerCount;
+ int myTeamCount;
+ int teamIndex;
+ int playerRefresh;
+ int playerIndex;
+ int playerNumber;
+ int myPlayerIndex;
+ int ignoreIndex;
+ qboolean teamLeader;
+ char playerNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char rawPlayerNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char teamNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ char rawTeamNames[MAX_CLIENTS][MAX_NAME_LENGTH];
+ int clientNums[MAX_CLIENTS];
+ int teamClientNums[MAX_CLIENTS];
+ clientList_t ignoreList[MAX_CLIENTS];
+
+ int mapCount;
+ mapInfo mapList[MAX_MAPS];
+
+
+ int tierCount;
+ tierInfo tierList[MAX_TIERS];
+
+ int skillIndex;
+
+ modInfo_t modList[MAX_MODS];
+ int modCount;
+ int modIndex;
+
+ const char *demoList[MAX_DEMOS];
+ int demoCount;
+ int demoIndex;
+
+ const char *movieList[MAX_MOVIES];
+ int movieCount;
+ int movieIndex;
+ int previewMovie;
+
+ tremInfoPane_t tremInfoPanes[ MAX_INFOPANES ];
+ int tremInfoPaneCount;
+
+//TA: tremulous menus
+ tremMenuItem_t tremTeamList[ 4 ];
+ int tremTeamCount;
+ int tremTeamIndex;
+
+ tremMenuItem_t tremAlienClassList[ 3 ];
+ int tremAlienClassCount;
+ int tremAlienClassIndex;
+
+ tremMenuItem_t tremHumanItemList[ 3 ];
+ int tremHumanItemCount;
+ int tremHumanItemIndex;
+
+ tremMenuItem_t tremHumanArmouryBuyList[ 32 ];
+ int tremHumanArmouryBuyCount;
+ int tremHumanArmouryBuyIndex;
+
+ tremMenuItem_t tremHumanArmourySellList[ 32 ];
+ int tremHumanArmourySellCount;
+ int tremHumanArmourySellIndex;
+
+ tremMenuItem_t tremAlienUpgradeList[ 16 ];
+ int tremAlienUpgradeCount;
+ int tremAlienUpgradeIndex;
+
+ tremMenuItem_t tremAlienBuildList[ 32 ];
+ int tremAlienBuildCount;
+ int tremAlienBuildIndex;
+
+ tremMenuItem_t tremHumanBuildList[ 32 ];
+ int tremHumanBuildCount;
+ int tremHumanBuildIndex;
+//TA: tremulous menus
+
+ serverStatus_t serverStatus;
+
+ // for the showing the status of a server
+ char serverStatusAddress[MAX_ADDRESSLENGTH];
+ serverStatusInfo_t serverStatusInfo;
+ int nextServerStatusRefresh;
+
+ // to retrieve the status of server to find a player
+ pendingServerStatus_t pendingServerStatus;
+ char findPlayerName[MAX_STRING_CHARS];
+ char foundPlayerServerAddresses[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH];
+ char foundPlayerServerNames[MAX_FOUNDPLAYER_SERVERS][MAX_ADDRESSLENGTH];
+ int currentFoundPlayerServer;
+ int numFoundPlayerServers;
+ int nextFindPlayerRefresh;
+
+ int currentCrosshair;
+ int startPostGameTime;
+ sfxHandle_t newHighScoreSound;
+
+ int q3HeadCount;
+ char q3HeadNames[MAX_PLAYERMODELS][64];
+ qhandle_t q3HeadIcons[MAX_PLAYERMODELS];
+ int q3SelectedHead;
+
+ int effectsColor;
+
+ qboolean inGameLoad;
+} uiInfo_t;
+
+extern uiInfo_t uiInfo;
+
+
+extern void UI_Init( void );
+extern void UI_Shutdown( void );
+extern void UI_KeyEvent( int key );
+extern void UI_MouseEvent( int dx, int dy );
+extern void UI_Refresh( int realtime );
+extern qboolean UI_ConsoleCommand( int realTime );
+extern float UI_ClampCvar( float min, float max, float value );
+extern void UI_DrawNamedPic( float x, float y, float width, float height, const char *picname );
+extern void UI_DrawHandlePic( float x, float y, float w, float h, qhandle_t hShader );
+extern void UI_FillRect( float x, float y, float width, float height, const float *color );
+extern void UI_DrawRect( float x, float y, float width, float height, const float *color );
+extern void UI_DrawTopBottom(float x, float y, float w, float h);
+extern void UI_DrawSides(float x, float y, float w, float h);
+extern void UI_UpdateScreen( void );
+extern void UI_SetColor( const float *rgba );
+extern void UI_LerpColor(vec4_t a, vec4_t b, vec4_t c, float t);
+extern void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color );
+extern float UI_ProportionalSizeScale( int style );
+extern void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color );
+extern int UI_ProportionalStringWidth( const char* str );
+extern void UI_DrawString( int x, int y, const char* str, int style, vec4_t color );
+extern void UI_DrawChar( int x, int y, int ch, int style, vec4_t color );
+extern qboolean UI_CursorInRect (int x, int y, int width, int height);
+extern void UI_AdjustFrom640( float *x, float *y, float *w, float *h );
+extern void UI_DrawTextBox (int x, int y, int width, int lines);
+extern qboolean UI_IsFullscreen( void );
+extern void UI_SetActiveMenu( uiMenuCommand_t menu );
+extern void UI_PushMenu ( menuframework_s *menu );
+extern void UI_PopMenu (void);
+extern void UI_ForceMenuOff (void);
+extern char *UI_Argv( int arg );
+extern char *UI_Cvar_VariableString( const char *var_name );
+extern void UI_Refresh( int time );
+extern void UI_KeyEvent( int key );
+extern void UI_StartDemoLoop( void );
+extern qboolean m_entersound;
+void UI_LoadBestScores(const char *map, int game);
+extern uiStatic_t uis;
+
+//
+// ui_spLevel.c
+//
+void UI_SPLevelMenu_Cache( void );
+void UI_SPLevelMenu( void );
+void UI_SPLevelMenu_f( void );
+void UI_SPLevelMenu_ReInit( void );
+
+//
+// ui_spArena.c
+//
+void UI_SPArena_Start( const char *arenaInfo );
+
+//
+// ui_spPostgame.c
+//
+void UI_SPPostgameMenu_Cache( void );
+void UI_SPPostgameMenu_f( void );
+
+//
+// ui_spSkill.c
+//
+void UI_SPSkillMenu( const char *arenaInfo );
+void UI_SPSkillMenu_Cache( void );
+
+//
+// ui_syscalls.c
+//
+void trap_Print( const char *string );
+void trap_Error( const char *string );
+int trap_Milliseconds( void );
+void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags );
+void trap_Cvar_Update( vmCvar_t *vmCvar );
+void trap_Cvar_Set( const char *var_name, const char *value );
+float trap_Cvar_VariableValue( const char *var_name );
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+void trap_Cvar_SetValue( const char *var_name, float value );
+void trap_Cvar_Reset( const char *name );
+void trap_Cvar_Create( const char *var_name, const char *var_value, int flags );
+void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize );
+int trap_Argc( void );
+void trap_Argv( int n, char *buffer, int bufferLength );
+void trap_Cmd_ExecuteText( int exec_when, const char *text ); // don't use EXEC_NOW!
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode );
+void trap_FS_Read( void *buffer, int len, fileHandle_t f );
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f );
+void trap_FS_FCloseFile( fileHandle_t f );
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize );
+int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t
+qhandle_t trap_R_RegisterModel( const char *name );
+qhandle_t trap_R_RegisterSkin( const char *name );
+qhandle_t trap_R_RegisterShaderNoMip( const char *name );
+void trap_R_ClearScene( void );
+void trap_R_AddRefEntityToScene( const refEntity_t *re );
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts );
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b );
+void trap_R_RenderScene( const refdef_t *fd );
+void trap_R_SetColor( const float *rgba );
+void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader );
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs );
+void trap_UpdateScreen( void );
+int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName );
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum );
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed );
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen );
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen );
+void trap_Key_SetBinding( int keynum, const char *binding );
+qboolean trap_Key_IsDown( int keynum );
+qboolean trap_Key_GetOverstrikeMode( void );
+void trap_Key_SetOverstrikeMode( qboolean state );
+void trap_Key_ClearStates( void );
+int trap_Key_GetCatcher( void );
+void trap_Key_SetCatcher( int catcher );
+void trap_GetClipboardData( char *buf, int bufsize );
+void trap_GetClientState( uiClientState_t *state );
+void trap_GetGlconfig( glconfig_t *glconfig );
+int trap_GetConfigString( int index, char* buff, int buffsize );
+int trap_LAN_GetServerCount( int source );
+void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen );
+void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen );
+int trap_LAN_GetServerPing( int source, int n );
+int trap_LAN_GetPingQueueCount( void );
+void trap_LAN_ClearPing( int n );
+void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime );
+void trap_LAN_GetPingInfo( int n, char *buf, int buflen );
+void trap_LAN_LoadCachedServers( void );
+void trap_LAN_SaveCachedServers( void );
+void trap_LAN_MarkServerVisible(int source, int n, qboolean visible);
+int trap_LAN_ServerIsVisible( int source, int n);
+qboolean trap_LAN_UpdateVisiblePings( int source );
+int trap_LAN_AddServer(int source, const char *name, const char *addr);
+void trap_LAN_RemoveServer(int source, const char *addr);
+void trap_LAN_ResetPings(int n);
+int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen );
+int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 );
+int trap_MemoryRemaining( void );
+void trap_R_RegisterFont(const char *pFontname, int pointSize, fontInfo_t *font);
+void trap_S_StopBackgroundTrack( void );
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop);
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits);
+e_status trap_CIN_StopCinematic(int handle);
+e_status trap_CIN_RunCinematic (int handle);
+void trap_CIN_DrawCinematic (int handle);
+void trap_CIN_SetExtents (int handle, int x, int y, int w, int h);
+int trap_RealTime(qtime_t *qtime);
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset );
+
+void trap_SetPbClStatus( int status );
+
+//
+// ui_addbots.c
+//
+void UI_AddBots_Cache( void );
+void UI_AddBotsMenu( void );
+
+//
+// ui_removebots.c
+//
+void UI_RemoveBots_Cache( void );
+void UI_RemoveBotsMenu( void );
+
+//
+// ui_teamorders.c
+//
+extern void UI_TeamOrdersMenu( void );
+extern void UI_TeamOrdersMenu_f( void );
+extern void UI_TeamOrdersMenu_Cache( void );
+
+//
+// ui_loadconfig.c
+//
+void UI_LoadConfig_Cache( void );
+void UI_LoadConfigMenu( void );
+
+//
+// ui_saveconfig.c
+//
+void UI_SaveConfigMenu_Cache( void );
+void UI_SaveConfigMenu( void );
+
+//
+// ui_display.c
+//
+void UI_DisplayOptionsMenu_Cache( void );
+void UI_DisplayOptionsMenu( void );
+
+//
+// ui_sound.c
+//
+void UI_SoundOptionsMenu_Cache( void );
+void UI_SoundOptionsMenu( void );
+
+//
+// ui_network.c
+//
+void UI_NetworkOptionsMenu_Cache( void );
+void UI_NetworkOptionsMenu( void );
+
+//
+// ui_gameinfo.c
+//
+typedef enum {
+ AWARD_ACCURACY,
+ AWARD_IMPRESSIVE,
+ AWARD_EXCELLENT,
+ AWARD_GAUNTLET,
+ AWARD_FRAGS,
+ AWARD_PERFECT
+} awardType_t;
+
+const char *UI_GetArenaInfoByNumber( int num );
+const char *UI_GetArenaInfoByMap( const char *map );
+const char *UI_GetSpecialArenaInfo( const char *tag );
+int UI_GetNumArenas( void );
+int UI_GetNumSPArenas( void );
+int UI_GetNumSPTiers( void );
+
+char *UI_GetBotInfoByNumber( int num );
+char *UI_GetBotInfoByName( const char *name );
+int UI_GetNumBots( void );
+void UI_LoadBots( void );
+char *UI_GetBotNameByNumber( int num );
+
+void UI_GetBestScore( int level, int *score, int *skill );
+void UI_SetBestScore( int level, int score );
+int UI_TierCompleted( int levelWon );
+qboolean UI_ShowTierVideo( int tier );
+qboolean UI_CanShowTierVideo( int tier );
+int UI_GetCurrentGame( void );
+void UI_NewGame( void );
+void UI_LogAwardData( int award, int data );
+int UI_GetAwardLevel( int award );
+
+void UI_SPUnlock_f( void );
+void UI_SPUnlockMedals_f( void );
+
+void UI_InitGameinfo( void );
+
+//
+// ui_login.c
+//
+void Login_Cache( void );
+void UI_LoginMenu( void );
+
+//
+// ui_signup.c
+//
+void Signup_Cache( void );
+void UI_SignupMenu( void );
+
+//
+// ui_rankstatus.c
+//
+void RankStatus_Cache( void );
+void UI_RankStatusMenu( void );
+
+
+// new ui
+
+#define ASSET_BACKGROUND "uiBackground"
+
+// for tracking sp game info in Team Arena
+typedef struct postGameInfo_s {
+ int score;
+ int redScore;
+ int blueScore;
+ int perfects;
+ int accuracy;
+ int impressives;
+ int excellents;
+ int defends;
+ int assists;
+ int gauntlets;
+ int captures;
+ int time;
+ int timeBonus;
+ int shutoutBonus;
+ int skillBonus;
+ int baseScore;
+} postGameInfo_t;
+
+
+
+#endif
diff --git a/src/ui/ui_main.c b/src/ui/ui_main.c
new file mode 100644
index 0000000..b193f4f
--- /dev/null
+++ b/src/ui/ui_main.c
@@ -0,0 +1,6500 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+/*
+=======================================================================
+
+USER INTERFACE MAIN
+
+=======================================================================
+*/
+
+// use this to get a demo build without an explicit demo build, i.e. to get the demo ui files to build
+//#define PRE_RELEASE_TADEMO
+
+#include "ui_local.h"
+
+uiInfo_t uiInfo;
+
+static const char *MonthAbbrev[] = {
+ "Jan","Feb","Mar",
+ "Apr","May","Jun",
+ "Jul","Aug","Sep",
+ "Oct","Nov","Dec"
+};
+
+
+static const char *skillLevels[] = {
+ "I Can Win",
+ "Bring It On",
+ "Hurt Me Plenty",
+ "Hardcore",
+ "Nightmare"
+};
+
+static const int numSkillLevels = sizeof(skillLevels) / sizeof(const char*);
+
+
+static const char *netSources[] = {
+ "LAN",
+ "Mplayer",
+ "Internet",
+ "Favorites"
+};
+static const int numNetSources = sizeof(netSources) / sizeof(const char*);
+
+static const serverFilter_t serverFilters[] = {
+ {"All", "" },
+ {"Quake 3 Arena", "" },
+ {"Team Arena", "missionpack" },
+ {"Rocket Arena", "arena" },
+ {"Alliance", "alliance20" },
+ {"Weapons Factory Arena", "wfa" },
+ {"OSP", "osp" },
+};
+
+static const char *teamArenaGameTypes[] = {
+ "FFA",
+ "TOURNAMENT",
+ "SP",
+ "TEAM DM",
+ "CTF",
+ "1FCTF",
+ "OVERLOAD",
+ "HARVESTER",
+ "TEAMTOURNAMENT"
+};
+
+static int const numTeamArenaGameTypes = sizeof(teamArenaGameTypes) / sizeof(const char*);
+
+
+static const char *teamArenaGameNames[] = {
+ "Free For All",
+ "Tournament",
+ "Single Player",
+ "Team Deathmatch",
+ "Capture the Flag",
+ "One Flag CTF",
+ "Overload",
+ "Harvester",
+ "Team Tournament",
+};
+
+static int const numTeamArenaGameNames = sizeof(teamArenaGameNames) / sizeof(const char*);
+
+
+static const int numServerFilters = sizeof(serverFilters) / sizeof(serverFilter_t);
+
+static const char *sortKeys[] = {
+ "Server Name",
+ "Map Name",
+ "Open Player Spots",
+ "Game Type",
+ "Ping Time"
+};
+static const int numSortKeys = sizeof(sortKeys) / sizeof(const char*);
+
+static char* netnames[] = {
+ "???",
+ "UDP",
+ "IPX",
+ NULL
+};
+
+static int gamecodetoui[] = {4,2,3,0,5,1,6};
+
+
+static void UI_StartServerRefresh(qboolean full);
+static void UI_StopServerRefresh( void );
+static void UI_DoServerRefresh( void );
+static void UI_FeederSelection(float feederID, int index);
+static void UI_BuildServerDisplayList(qboolean force);
+static void UI_BuildServerStatus(qboolean force);
+static void UI_BuildFindPlayerList(qboolean force);
+static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 );
+static int UI_MapCountByGameType(qboolean singlePlayer);
+static int UI_HeadCountByTeam( void );
+static const char *UI_SelectedMap(int index, int *actual);
+static const char *UI_SelectedHead(int index, int *actual);
+static int UI_GetIndexFromSelection(int actual);
+
+int ProcessNewUI( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6 );
+
+/*
+================
+vmMain
+
+This is the only way control passes into the module.
+This must be the very first function compiled into the .qvm file
+================
+*/
+vmCvar_t ui_new;
+vmCvar_t ui_debug;
+vmCvar_t ui_initialized;
+vmCvar_t ui_teamArenaFirstRun;
+
+void _UI_Init( qboolean );
+void _UI_Shutdown( void );
+void _UI_KeyEvent( int key, qboolean down );
+void _UI_MouseEvent( int dx, int dy );
+void _UI_Refresh( int realtime );
+qboolean _UI_IsFullscreen( void );
+intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3,
+ int arg4, int arg5, int arg6, int arg7,
+ int arg8, int arg9, int arg10, int arg11 ) {
+ switch ( command ) {
+ case UI_GETAPIVERSION:
+ return UI_API_VERSION;
+
+ case UI_INIT:
+ _UI_Init(arg0);
+ return 0;
+
+ case UI_SHUTDOWN:
+ _UI_Shutdown();
+ return 0;
+
+ case UI_KEY_EVENT:
+ _UI_KeyEvent( arg0, arg1 );
+ return 0;
+
+ case UI_MOUSE_EVENT:
+ _UI_MouseEvent( arg0, arg1 );
+ return 0;
+
+ case UI_REFRESH:
+ _UI_Refresh( arg0 );
+ return 0;
+
+ case UI_IS_FULLSCREEN:
+ return _UI_IsFullscreen();
+
+ case UI_SET_ACTIVE_MENU:
+ _UI_SetActiveMenu( arg0 );
+ return 0;
+
+ case UI_CONSOLE_COMMAND:
+ return UI_ConsoleCommand(arg0);
+
+ case UI_DRAW_CONNECT_SCREEN:
+ UI_DrawConnectScreen( arg0 );
+ return 0;
+ }
+
+ return -1;
+}
+
+
+
+void AssetCache( void ) {
+ uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR );
+ uiInfo.uiDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR );
+ uiInfo.uiDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN );
+ uiInfo.uiDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP );
+ uiInfo.uiDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT );
+ uiInfo.uiDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT );
+ uiInfo.uiDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB );
+ uiInfo.uiDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR );
+ uiInfo.uiDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB );
+}
+
+void _UI_DrawSides(float x, float y, float w, float h, float size) {
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ size *= uiInfo.uiDC.xscale;
+ trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+}
+
+void _UI_DrawTopBottom(float x, float y, float w, float h, float size) {
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ size *= uiInfo.uiDC.yscale;
+ trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+ trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, uiInfo.uiDC.whiteShader );
+}
+/*
+================
+UI_DrawRect
+
+Coordinates are 640*480 virtual values
+=================
+*/
+void _UI_DrawRect( float x, float y, float width, float height, float size, const float *color ) {
+ trap_R_SetColor( color );
+
+ _UI_DrawTopBottom(x, y, width, height, size);
+ _UI_DrawSides(x, y, width, height, size);
+
+ trap_R_SetColor( NULL );
+}
+
+
+
+
+int Text_Width(const char *text, float scale, int limit) {
+ int count,len;
+ float out;
+ glyphInfo_t *glyph;
+ float useScale;
+ const char *s = text;
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale >= ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ out = 0;
+ if (text) {
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ while (s && *s && count < len) {
+ if ( Q_IsColorString(s) ) {
+ s += 2;
+ continue;
+ } else {
+ glyph = &font->glyphs[(int)*s];
+ out += glyph->xSkip;
+ s++;
+ count++;
+ }
+ }
+ }
+ return out * useScale;
+}
+
+int Text_Height(const char *text, float scale, int limit) {
+ int len, count;
+ float max;
+ glyphInfo_t *glyph;
+ float useScale;
+ const char *s = text; // bk001206 - unsigned
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale >= ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ max = 0;
+ if (text) {
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ while (s && *s && count < len) {
+ if ( Q_IsColorString(s) ) {
+ s += 2;
+ continue;
+ } else {
+ glyph = &font->glyphs[(int)*s];
+ if (max < glyph->height) {
+ max = glyph->height;
+ }
+ s++;
+ count++;
+ }
+ }
+ }
+ return max * useScale;
+}
+
+void Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) {
+ float w, h;
+ w = width * scale;
+ h = height * scale;
+ UI_AdjustFrom640( &x, &y, &w, &h );
+ trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader );
+}
+
+void Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) {
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph;
+ float useScale;
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale >= ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ if (text) {
+ const char *s = text; // bk001206 - unsigned
+ trap_R_SetColor( color );
+ memcpy(&newColor[0], &color[0], sizeof(vec4_t));
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ while (s && *s && count < len) {
+ glyph = &font->glyphs[(int)*s];
+ //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top;
+ //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height);
+ if ( Q_IsColorString( s ) ) {
+ memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) );
+ newColor[3] = color[3];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ } else {
+ float yadj = useScale * glyph->top;
+ if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) {
+ int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2;
+ colorBlack[3] = newColor[3];
+ trap_R_SetColor( colorBlack );
+ Text_PaintChar(x + ofs, y - yadj + ofs,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+ trap_R_SetColor( newColor );
+ colorBlack[3] = 1.0;
+ }
+ else if( style == ITEM_TEXTSTYLE_NEON )
+ {
+ vec4_t glow, outer, inner, white;
+
+ glow[ 0 ] = newColor[ 0 ] * 0.5;
+ glow[ 1 ] = newColor[ 1 ] * 0.5;
+ glow[ 2 ] = newColor[ 2 ] * 0.5;
+ glow[ 3 ] = newColor[ 3 ] * 0.2;
+
+ outer[ 0 ] = newColor[ 0 ];
+ outer[ 1 ] = newColor[ 1 ];
+ outer[ 2 ] = newColor[ 2 ];
+ outer[ 3 ] = newColor[ 3 ];
+
+ inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5;
+ inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5;
+ inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5;
+ inner[ 3 ] = newColor[ 3 ];
+
+ white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f;
+
+ trap_R_SetColor( glow );
+ Text_PaintChar( x - 1.5, y - yadj - 1.5,
+ glyph->imageWidth + 3,
+ glyph->imageHeight + 3,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( outer );
+ Text_PaintChar( x - 1, y - yadj - 1,
+ glyph->imageWidth + 2,
+ glyph->imageHeight + 2,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( inner );
+ Text_PaintChar( x - 0.5, y - yadj - 0.5,
+ glyph->imageWidth + 1,
+ glyph->imageHeight + 1,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( white );
+ }
+
+ Text_PaintChar(x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+
+ x += (glyph->xSkip * useScale) + adjust;
+ s++;
+ count++;
+ }
+ }
+ trap_R_SetColor( NULL );
+ }
+}
+
+void Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) {
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph, *glyph2;
+ float yadj;
+ float useScale;
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale >= ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ if (text) {
+ const char *s = text; // bk001206 - unsigned
+ trap_R_SetColor( color );
+ memcpy(&newColor[0], &color[0], sizeof(vec4_t));
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ glyph2 = &font->glyphs[ (int) cursor]; // bk001206 - possible signed char
+ while (s && *s && count < len) {
+ glyph = &font->glyphs[(int)*s];
+ if ( Q_IsColorString( s ) ) {
+ memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) );
+ newColor[3] = color[3];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ } else {
+ yadj = useScale * glyph->top;
+ if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) {
+ int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2;
+ colorBlack[3] = newColor[3];
+ trap_R_SetColor( colorBlack );
+ Text_PaintChar(x + ofs, y - yadj + ofs,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+ colorBlack[3] = 1.0;
+ trap_R_SetColor( newColor );
+ }
+ else if( style == ITEM_TEXTSTYLE_NEON )
+ {
+ vec4_t glow, outer, inner, white;
+
+ glow[ 0 ] = newColor[ 0 ] * 0.5;
+ glow[ 1 ] = newColor[ 1 ] * 0.5;
+ glow[ 2 ] = newColor[ 2 ] * 0.5;
+ glow[ 3 ] = newColor[ 3 ] * 0.2;
+
+ outer[ 0 ] = newColor[ 0 ];
+ outer[ 1 ] = newColor[ 1 ];
+ outer[ 2 ] = newColor[ 2 ];
+ outer[ 3 ] = newColor[ 3 ];
+
+ inner[ 0 ] = newColor[ 0 ] * 1.5 > 1.0f ? 1.0f : newColor[ 0 ] * 1.5;
+ inner[ 1 ] = newColor[ 1 ] * 1.5 > 1.0f ? 1.0f : newColor[ 1 ] * 1.5;
+ inner[ 2 ] = newColor[ 2 ] * 1.5 > 1.0f ? 1.0f : newColor[ 2 ] * 1.5;
+ inner[ 3 ] = newColor[ 3 ];
+
+ white[ 0 ] = white[ 1 ] = white[ 2 ] = white[ 3 ] = 1.0f;
+
+ trap_R_SetColor( glow );
+ Text_PaintChar( x - 1.5, y - yadj - 1.5,
+ glyph->imageWidth + 3,
+ glyph->imageHeight + 3,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( outer );
+ Text_PaintChar( x - 1, y - yadj - 1,
+ glyph->imageWidth + 2,
+ glyph->imageHeight + 2,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( inner );
+ Text_PaintChar( x - 0.5, y - yadj - 0.5,
+ glyph->imageWidth + 1,
+ glyph->imageHeight + 1,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph );
+
+ trap_R_SetColor( white );
+ }
+
+ Text_PaintChar(x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+
+ // CG_DrawPic(x, y - yadj, scale * uiDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * uiDC.Assets.textFont.glyphs[text[i]].imageHeight, uiDC.Assets.textFont.glyphs[text[i]].glyph);
+ yadj = useScale * glyph2->top;
+ if (count == cursorPos && !((uiInfo.uiDC.realTime/BLINK_DIVISOR) & 1)) {
+ Text_PaintChar(x, y - yadj,
+ glyph2->imageWidth,
+ glyph2->imageHeight,
+ useScale,
+ glyph2->s,
+ glyph2->t,
+ glyph2->s2,
+ glyph2->t2,
+ glyph2->glyph);
+ }
+
+ x += (glyph->xSkip * useScale);
+ s++;
+ count++;
+ }
+ }
+ // need to paint cursor at end of text
+ if (cursorPos == len && !((uiInfo.uiDC.realTime/BLINK_DIVISOR) & 1)) {
+ yadj = useScale * glyph2->top;
+ Text_PaintChar(x, y - yadj,
+ glyph2->imageWidth,
+ glyph2->imageHeight,
+ useScale,
+ glyph2->s,
+ glyph2->t,
+ glyph2->s2,
+ glyph2->t2,
+ glyph2->glyph);
+
+ }
+
+
+ trap_R_SetColor( NULL );
+ }
+}
+
+
+static void Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit) {
+ int len, count;
+ vec4_t newColor;
+ glyphInfo_t *glyph;
+ if (text) {
+ const char *s = text; // bk001206 - unsigned
+ float max = *maxX;
+ float useScale;
+ fontInfo_t *font = &uiInfo.uiDC.Assets.textFont;
+ if (scale <= ui_smallFont.value) {
+ font = &uiInfo.uiDC.Assets.smallFont;
+ } else if (scale > ui_bigFont.value) {
+ font = &uiInfo.uiDC.Assets.bigFont;
+ }
+ useScale = scale * font->glyphScale;
+ trap_R_SetColor( color );
+ len = strlen(text);
+ if (limit > 0 && len > limit) {
+ len = limit;
+ }
+ count = 0;
+ while (s && *s && count < len) {
+ glyph = &font->glyphs[(int)*s];
+ if ( Q_IsColorString( s ) ) {
+ memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) );
+ newColor[3] = color[3];
+ trap_R_SetColor( newColor );
+ s += 2;
+ continue;
+ } else {
+ float yadj = useScale * glyph->top;
+ if (Text_Width(s, useScale, 1) + x > max) {
+ *maxX = 0;
+ break;
+ }
+ Text_PaintChar(x, y - yadj,
+ glyph->imageWidth,
+ glyph->imageHeight,
+ useScale,
+ glyph->s,
+ glyph->t,
+ glyph->s2,
+ glyph->t2,
+ glyph->glyph);
+ x += (glyph->xSkip * useScale) + adjust;
+ *maxX = x;
+ count++;
+ s++;
+ }
+ }
+ trap_R_SetColor( NULL );
+ }
+
+}
+
+
+void UI_ShowPostGame(qboolean newHigh) {
+ trap_Cvar_Set ("cg_cameraOrbit", "0");
+ trap_Cvar_Set("cg_thirdPerson", "0");
+ trap_Cvar_Set( "sv_killserver", "1" );
+ uiInfo.soundHighScore = newHigh;
+ _UI_SetActiveMenu(UIMENU_POSTGAME);
+}
+/*
+=================
+_UI_Refresh
+=================
+*/
+
+void UI_DrawCenteredPic(qhandle_t image, int w, int h) {
+ int x, y;
+ x = (SCREEN_WIDTH - w) / 2;
+ y = (SCREEN_HEIGHT - h) / 2;
+ UI_DrawHandlePic(x, y, w, h, image);
+}
+
+int frameCount = 0;
+int startTime;
+
+#define UI_FPS_FRAMES 4
+void _UI_Refresh( int realtime )
+{
+ static int index;
+ static int previousTimes[UI_FPS_FRAMES];
+
+ //if ( !( trap_Key_GetCatcher() & KEYCATCH_UI ) ) {
+ // return;
+ //}
+
+ uiInfo.uiDC.frameTime = realtime - uiInfo.uiDC.realTime;
+ uiInfo.uiDC.realTime = realtime;
+
+ previousTimes[index % UI_FPS_FRAMES] = uiInfo.uiDC.frameTime;
+ index++;
+ if ( index > UI_FPS_FRAMES ) {
+ int i, total;
+ // average multiple frames together to smooth changes out a bit
+ total = 0;
+ for ( i = 0 ; i < UI_FPS_FRAMES ; i++ ) {
+ total += previousTimes[i];
+ }
+ if ( !total ) {
+ total = 1;
+ }
+ uiInfo.uiDC.FPS = 1000 * UI_FPS_FRAMES / total;
+ }
+
+
+
+ UI_UpdateCvars();
+
+ if (Menu_Count() > 0) {
+ // paint all the menus
+ Menu_PaintAll();
+ // refresh server browser list
+ UI_DoServerRefresh();
+ // refresh server status
+ UI_BuildServerStatus(qfalse);
+ // refresh find player list
+ UI_BuildFindPlayerList(qfalse);
+ }
+
+ // draw cursor
+ UI_SetColor( NULL );
+
+ //TA: don't draw the cursor whilst loading
+ if( Menu_Count( ) > 0 && !trap_Cvar_VariableValue( "ui_loading" ) )
+ UI_DrawHandlePic( uiInfo.uiDC.cursorx-16, uiInfo.uiDC.cursory-16, 32, 32, uiInfo.uiDC.Assets.cursor);
+
+#ifndef NDEBUG
+ if (uiInfo.uiDC.debug)
+ {
+ // cursor coordinates
+ //FIXME
+ //UI_DrawString( 0, 0, va("(%d,%d)",uis.cursorx,uis.cursory), UI_LEFT|UI_SMALLFONT, colorRed );
+ }
+#endif
+
+}
+
+/*
+=================
+_UI_Shutdown
+=================
+*/
+void _UI_Shutdown( void ) {
+ trap_LAN_SaveCachedServers();
+}
+
+char *defaultMenu = NULL;
+
+char *GetMenuBuffer(const char *filename) {
+ int len;
+ fileHandle_t f;
+ static char buf[MAX_MENUFILE];
+
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( !f ) {
+ trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) );
+ return defaultMenu;
+ }
+ if ( len >= MAX_MENUFILE ) {
+ trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) );
+ trap_FS_FCloseFile( f );
+ return defaultMenu;
+ }
+
+ trap_FS_Read( buf, len, f );
+ buf[len] = 0;
+ trap_FS_FCloseFile( f );
+ //COM_Compress(buf);
+ return buf;
+
+}
+
+qboolean Asset_Parse(int handle) {
+ pc_token_t token;
+ const char *tempStr;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (Q_stricmp(token.string, "{") != 0) {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+
+ memset(&token, 0, sizeof(pc_token_t));
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+
+ if (Q_stricmp(token.string, "}") == 0) {
+ return qtrue;
+ }
+
+ // font
+ if (Q_stricmp(token.string, "font") == 0) {
+ int pointSize;
+ if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) {
+ return qfalse;
+ }
+ trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.textFont);
+ uiInfo.uiDC.Assets.fontRegistered = qtrue;
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "smallFont") == 0) {
+ int pointSize;
+ if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) {
+ return qfalse;
+ }
+ trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.smallFont);
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "bigFont") == 0) {
+ int pointSize;
+ if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle,&pointSize)) {
+ return qfalse;
+ }
+ trap_R_RegisterFont(tempStr, pointSize, &uiInfo.uiDC.Assets.bigFont);
+ continue;
+ }
+
+
+ // gradientbar
+ if (Q_stricmp(token.string, "gradientbar") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr);
+ continue;
+ }
+
+ // enterMenuSound
+ if (Q_stricmp(token.string, "menuEnterSound") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // exitMenuSound
+ if (Q_stricmp(token.string, "menuExitSound") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // itemFocusSound
+ if (Q_stricmp(token.string, "itemFocusSound") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ // menuBuzzSound
+ if (Q_stricmp(token.string, "menuBuzzSound") == 0) {
+ if (!PC_String_Parse(handle, &tempStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse );
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "cursor") == 0) {
+ if (!PC_String_Parse(handle, &uiInfo.uiDC.Assets.cursorStr)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.cursor = trap_R_RegisterShaderNoMip( uiInfo.uiDC.Assets.cursorStr);
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "fadeClamp") == 0) {
+ if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeClamp)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "fadeCycle") == 0) {
+ if (!PC_Int_Parse(handle, &uiInfo.uiDC.Assets.fadeCycle)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "fadeAmount") == 0) {
+ if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.fadeAmount)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "shadowX") == 0) {
+ if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowX)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "shadowY") == 0) {
+ if (!PC_Float_Parse(handle, &uiInfo.uiDC.Assets.shadowY)) {
+ return qfalse;
+ }
+ continue;
+ }
+
+ if (Q_stricmp(token.string, "shadowColor") == 0) {
+ if (!PC_Color_Parse(handle, &uiInfo.uiDC.Assets.shadowColor)) {
+ return qfalse;
+ }
+ uiInfo.uiDC.Assets.shadowFadeClamp = uiInfo.uiDC.Assets.shadowColor[3];
+ continue;
+ }
+
+ }
+ return qfalse;
+}
+
+void Font_Report( void ) {
+ int i;
+ Com_Printf("Font Info\n");
+ Com_Printf("=========\n");
+ for ( i = 32; i < 96; i++) {
+ Com_Printf("Glyph handle %i: %i\n", i, uiInfo.uiDC.Assets.textFont.glyphs[i].glyph);
+ }
+}
+
+void UI_Report( void ) {
+ String_Report();
+ //Font_Report();
+
+}
+
+void UI_ParseMenu(const char *menuFile) {
+ int handle;
+ pc_token_t token;
+
+ /*Com_Printf("Parsing menu file:%s\n", menuFile);*/
+
+ handle = trap_Parse_LoadSource(menuFile);
+ if (!handle) {
+ return;
+ }
+
+ while ( 1 ) {
+ memset(&token, 0, sizeof(pc_token_t));
+ if (!trap_Parse_ReadToken( handle, &token )) {
+ break;
+ }
+
+ //if ( Q_stricmp( token, "{" ) ) {
+ // Com_Printf( "Missing { in menu file\n" );
+ // break;
+ //}
+
+ //if ( menuCount == MAX_MENUS ) {
+ // Com_Printf( "Too many menus!\n" );
+ // break;
+ //}
+
+ if ( token.string[0] == '}' ) {
+ break;
+ }
+
+ if (Q_stricmp(token.string, "assetGlobalDef") == 0) {
+ if (Asset_Parse(handle)) {
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ if (Q_stricmp(token.string, "menudef") == 0) {
+ // start a new menu
+ Menu_New(handle);
+ }
+ }
+ trap_Parse_FreeSource(handle);
+}
+
+/*
+===============
+UI_FindInfoPaneByName
+===============
+*/
+tremInfoPane_t *UI_FindInfoPaneByName( const char *name )
+{
+ int i;
+
+ for( i = 0; i < uiInfo.tremInfoPaneCount; i++ )
+ {
+ if( !Q_stricmp( uiInfo.tremInfoPanes[ i ].name, name ) )
+ return &uiInfo.tremInfoPanes[ i ];
+ }
+
+ //create a dummy infopane demanding the user write the infopane
+ uiInfo.tremInfoPanes[ i ].name = String_Alloc( name );
+ strncpy( uiInfo.tremInfoPanes[ i ].text, "Not implemented.\n\nui/infopanes.def\n", MAX_INFOPANE_TEXT );
+ Q_strcat( uiInfo.tremInfoPanes[ i ].text, MAX_INFOPANE_TEXT, String_Alloc( name ) );
+
+ uiInfo.tremInfoPaneCount++;
+
+ return &uiInfo.tremInfoPanes[ i ];
+}
+
+/*
+===============
+UI_LoadInfoPane
+===============
+*/
+qboolean UI_LoadInfoPane( int handle )
+{
+ pc_token_t token;
+ qboolean valid = qfalse;
+
+ while( 1 )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ if( !Q_stricmp( token.string, "name" ) )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].name = String_Alloc( token.string );
+ valid = qtrue;
+ }
+ else if( !Q_stricmp( token.string, "graphic" ) )
+ {
+ int *graphic;
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ graphic = &uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].numGraphics;
+
+ if( !Q_stricmp( token.string, "top" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_TOP;
+ else if( !Q_stricmp( token.string, "bottom" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_BOTTOM;
+ else if( !Q_stricmp( token.string, "left" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_LEFT;
+ else if( !Q_stricmp( token.string, "right" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].side = INFOPANE_RIGHT;
+ else
+ break;
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ if( !Q_stricmp( token.string, "center" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].offset = -1;
+ else
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].offset = token.intvalue;
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].graphic =
+ trap_R_RegisterShaderNoMip( token.string );
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].width = token.intvalue;
+
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].graphics[ *graphic ].height = token.intvalue;
+
+ //increment graphics
+ (*graphic)++;
+
+ if( *graphic == MAX_INFOPANE_GRAPHICS )
+ break;
+ }
+ else if( !Q_stricmp( token.string, "text" ) )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ Q_strcat( uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].text, MAX_INFOPANE_TEXT, token.string );
+ }
+ else if( !Q_stricmp( token.string, "align" ) )
+ {
+ memset( &token, 0, sizeof( pc_token_t ) );
+
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ if( !Q_stricmp( token.string, "left" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_LEFT;
+ else if( !Q_stricmp( token.string, "right" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_RIGHT;
+ else if( !Q_stricmp( token.string, "center" ) )
+ uiInfo.tremInfoPanes[ uiInfo.tremInfoPaneCount ].align = ITEM_ALIGN_CENTER;
+ }
+ else if( token.string[ 0 ] == '}' )
+ {
+ //reached the end, break
+ break;
+ }
+ else
+ break;
+ }
+
+ if( valid )
+ {
+ uiInfo.tremInfoPaneCount++;
+ return qtrue;
+ }
+ else
+ {
+ return qfalse;
+ }
+}
+
+/*
+===============
+UI_LoadInfoPanes
+===============
+*/
+void UI_LoadInfoPanes( const char *file )
+{
+ pc_token_t token;
+ int handle;
+ int count;
+
+ uiInfo.tremInfoPaneCount = count = 0;
+
+ handle = trap_Parse_LoadSource( file );
+
+ if( !handle )
+ {
+ trap_Error( va( S_COLOR_YELLOW "infopane file not found: %s\n", file ) );
+ return;
+ }
+
+ while( 1 )
+ {
+ if( !trap_Parse_ReadToken( handle, &token ) )
+ break;
+
+ if( token.string[ 0 ] == 0 )
+ break;
+
+ if( token.string[ 0 ] == '{' )
+ {
+ if( UI_LoadInfoPane( handle ) )
+ count++;
+
+ if( count == MAX_INFOPANES )
+ break;
+ }
+ }
+
+ trap_Parse_FreeSource( handle );
+}
+
+qboolean Load_Menu(int handle) {
+ pc_token_t token;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (token.string[0] != '{') {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+
+ if ( token.string[0] == 0 ) {
+ return qfalse;
+ }
+
+ if ( token.string[0] == '}' ) {
+ return qtrue;
+ }
+
+ UI_ParseMenu(token.string);
+ }
+ return qfalse;
+}
+
+void UI_LoadMenus(const char *menuFile, qboolean reset) {
+ pc_token_t token;
+ int handle;
+ int start;
+
+ start = trap_Milliseconds();
+
+ handle = trap_Parse_LoadSource( menuFile );
+ if (!handle) {
+ trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) );
+ handle = trap_Parse_LoadSource( "ui/menus.txt" );
+ if (!handle) {
+ trap_Error( va( S_COLOR_RED "default menu file not found: ui/menus.txt, unable to continue!\n" ) );
+ }
+ }
+
+ ui_new.integer = 1;
+
+ if (reset) {
+ Menu_Reset();
+ }
+
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token))
+ break;
+ if( token.string[0] == 0 || token.string[0] == '}') {
+ break;
+ }
+
+ if ( token.string[0] == '}' ) {
+ break;
+ }
+
+ if (Q_stricmp(token.string, "loadmenu") == 0) {
+ if (Load_Menu(handle)) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ }
+
+ Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start);
+
+ trap_Parse_FreeSource( handle );
+}
+
+void UI_Load( void ) {
+ char lastName[1024];
+ menuDef_t *menu = Menu_GetFocused();
+ char *menuSet = UI_Cvar_VariableString("ui_menuFiles");
+ if (menu && menu->window.name) {
+ strcpy(lastName, menu->window.name);
+ }
+ if (menuSet == NULL || menuSet[0] == '\0') {
+ menuSet = "ui/menus.txt";
+ }
+
+ String_Init();
+
+/* UI_ParseGameInfo("gameinfo.txt");
+ UI_LoadArenas();*/
+
+ UI_LoadMenus(menuSet, qtrue);
+ Menus_CloseAll();
+ Menus_ActivateByName(lastName);
+
+}
+
+static const char *handicapValues[] = {"None","95","90","85","80","75","70","65","60","55","50","45","40","35","30","25","20","15","10","5",NULL};
+
+static void UI_DrawHandicap(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i, h;
+
+ h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") );
+ i = 20 - h / 5;
+
+ Text_Paint(rect->x, rect->y, scale, color, handicapValues[i], 0, 0, textStyle);
+}
+
+static void UI_DrawClanName(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_teamName"), 0, 0, textStyle);
+}
+
+
+static void UI_SetCapFragLimits(qboolean uiVars) {
+ int cap = 5;
+ int frag = 10;
+ if (uiVars) {
+ trap_Cvar_Set("ui_captureLimit", va("%d", cap));
+ trap_Cvar_Set("ui_fragLimit", va("%d", frag));
+ } else {
+ trap_Cvar_Set("capturelimit", va("%d", cap));
+ trap_Cvar_Set("fraglimit", va("%d", frag));
+ }
+}
+// ui_gameType assumes gametype 0 is -1 ALL and will not show
+static void UI_DrawGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_gameType.integer].gameType, 0, 0, textStyle);
+}
+
+static void UI_DrawNetGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (ui_netGameType.integer < 0 || ui_netGameType.integer > uiInfo.numGameTypes) {
+ trap_Cvar_Set("ui_netGameType", "0");
+ trap_Cvar_Set("ui_actualNetGameType", "0");
+ }
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[ui_netGameType.integer].gameType , 0, 0, textStyle);
+}
+
+static void UI_DrawJoinGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (ui_joinGameType.integer < 0 || ui_joinGameType.integer > uiInfo.numJoinGameTypes) {
+ trap_Cvar_Set("ui_joinGameType", "0");
+ }
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.joinGameTypes[ui_joinGameType.integer].gameType , 0, 0, textStyle);
+}
+
+
+
+static int UI_TeamIndexFromName(const char *name) {
+ int i;
+
+ if (name && *name) {
+ for (i = 0; i < uiInfo.teamCount; i++) {
+ if (Q_stricmp(name, uiInfo.teamList[i].teamName) == 0) {
+ return i;
+ }
+ }
+ }
+
+ return 0;
+
+}
+
+static void UI_DrawClanLogo(rectDef_t *rect, float scale, vec4_t color) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ trap_R_SetColor( color );
+
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon);
+ trap_R_SetColor(NULL);
+ }
+}
+
+static void UI_DrawClanCinematic(rectDef_t *rect, float scale, vec4_t color) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+
+ if (uiInfo.teamList[i].cinematic >= -2) {
+ if (uiInfo.teamList[i].cinematic == -1) {
+ uiInfo.teamList[i].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.teamList[i].imageName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ }
+ if (uiInfo.teamList[i].cinematic >= 0) {
+ trap_CIN_RunCinematic(uiInfo.teamList[i].cinematic);
+ trap_CIN_SetExtents(uiInfo.teamList[i].cinematic, rect->x, rect->y, rect->w, rect->h);
+ trap_CIN_DrawCinematic(uiInfo.teamList[i].cinematic);
+ } else {
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal);
+ trap_R_SetColor(NULL);
+ uiInfo.teamList[i].cinematic = -2;
+ }
+ } else {
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon);
+ trap_R_SetColor(NULL);
+ }
+ }
+
+}
+
+static void UI_DrawPreviewCinematic(rectDef_t *rect, float scale, vec4_t color) {
+ if (uiInfo.previewMovie > -2) {
+ uiInfo.previewMovie = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.movieList[uiInfo.movieIndex]), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ if (uiInfo.previewMovie >= 0) {
+ trap_CIN_RunCinematic(uiInfo.previewMovie);
+ trap_CIN_SetExtents(uiInfo.previewMovie, rect->x, rect->y, rect->w, rect->h);
+ trap_CIN_DrawCinematic(uiInfo.previewMovie);
+ } else {
+ uiInfo.previewMovie = -2;
+ }
+ }
+
+}
+
+
+#define GRAPHIC_BWIDTH 8.0f
+/*
+===============
+UI_DrawInfoPane
+===============
+*/
+static void UI_DrawInfoPane( tremInfoPane_t *pane, rectDef_t *rect, float text_x, float text_y,
+ float scale, vec4_t color, int textStyle )
+{
+ int i;
+ float maxLeft = 0, maxTop = 0;
+ float maxRight = 0, maxBottom = 0;
+ float x = rect->x - text_x, y = rect->y - text_y, w, h;
+ float xoffset = 0, yoffset = 0;
+ menuDef_t dummyParent;
+ itemDef_t textItem;
+
+ //iterate through graphics
+ for( i = 0; i < pane->numGraphics; i++ )
+ {
+ float width = pane->graphics[ i ].width;
+ float height = pane->graphics[ i ].height;
+ qhandle_t graphic = pane->graphics[ i ].graphic;
+
+ if( pane->graphics[ i ].side == INFOPANE_TOP || pane->graphics[ i ].side == INFOPANE_BOTTOM )
+ {
+ //set horizontal offset of graphic
+ if( pane->graphics[ i ].offset < 0 )
+ xoffset = ( rect->w / 2 ) - ( pane->graphics[ i ].width / 2 );
+ else
+ xoffset = pane->graphics[ i ].offset + GRAPHIC_BWIDTH;
+ }
+ else if( pane->graphics[ i ].side == INFOPANE_LEFT || pane->graphics[ i ].side == INFOPANE_RIGHT )
+ {
+ //set vertical offset of graphic
+ if( pane->graphics[ i ].offset < 0 )
+ yoffset = ( rect->h / 2 ) - ( pane->graphics[ i ].height / 2 );
+ else
+ yoffset = pane->graphics[ i ].offset + GRAPHIC_BWIDTH;
+ }
+
+ if( pane->graphics[ i ].side == INFOPANE_LEFT )
+ {
+ //set the horizontal offset of the text
+ if( pane->graphics[ i ].width > maxLeft )
+ maxLeft = pane->graphics[ i ].width + GRAPHIC_BWIDTH;
+
+ xoffset = GRAPHIC_BWIDTH;
+ }
+ else if( pane->graphics[ i ].side == INFOPANE_RIGHT )
+ {
+ if( pane->graphics[ i ].width > maxRight )
+ maxRight = pane->graphics[ i ].width + GRAPHIC_BWIDTH;
+
+ xoffset = rect->w - width - GRAPHIC_BWIDTH;
+ }
+ else if( pane->graphics[ i ].side == INFOPANE_TOP )
+ {
+ //set the vertical offset of the text
+ if( pane->graphics[ i ].height > maxTop )
+ maxTop = pane->graphics[ i ].height + GRAPHIC_BWIDTH;
+
+ yoffset = GRAPHIC_BWIDTH;
+ }
+ else if( pane->graphics[ i ].side == INFOPANE_BOTTOM )
+ {
+ if( pane->graphics[ i ].height > maxBottom )
+ maxBottom = pane->graphics[ i ].height + GRAPHIC_BWIDTH;
+
+ yoffset = rect->h - height - GRAPHIC_BWIDTH;
+ }
+
+ //draw the graphic
+ UI_DrawHandlePic( x + xoffset, y + yoffset, width, height, graphic );
+ }
+
+ //offset the text
+ x = rect->x + maxLeft;
+ y = rect->y + maxTop;
+ w = rect->w - ( maxLeft + maxRight + 16 + ( 2 * text_x ) ); //16 to ensure text within frame
+ h = rect->h - ( maxTop + maxBottom );
+
+ textItem.text = pane->text;
+
+ textItem.parent = &dummyParent;
+ memcpy( textItem.window.foreColor, color, sizeof( vec4_t ) );
+ textItem.window.flags = 0;
+
+ switch( pane->align )
+ {
+ case ITEM_ALIGN_LEFT:
+ textItem.window.rect.x = x;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ textItem.window.rect.x = x + w;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ textItem.window.rect.x = x + ( w / 2 );
+ break;
+
+ default:
+ textItem.window.rect.x = x;
+ break;
+ }
+
+ textItem.window.rect.y = y;
+ textItem.window.rect.w = w;
+ textItem.window.rect.h = h;
+ textItem.window.borderSize = 0;
+ textItem.textRect.x = 0;
+ textItem.textRect.y = 0;
+ textItem.textRect.w = 0;
+ textItem.textRect.h = 0;
+ textItem.textalignment = pane->align;
+ textItem.textalignx = text_x;
+ textItem.textaligny = text_y;
+ textItem.textscale = scale;
+ textItem.textStyle = textStyle;
+
+ textItem.enableCvar = NULL;
+ textItem.cvarTest = NULL;
+
+ //hack to utilise existing autowrap code
+ Item_Text_AutoWrapped_Paint( &textItem );
+}
+
+
+static void UI_DrawSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i;
+ i = trap_Cvar_VariableValue( "g_spSkill" );
+ if (i < 1 || i > numSkillLevels) {
+ i = 1;
+ }
+ Text_Paint(rect->x, rect->y, scale, color, skillLevels[i-1],0, 0, textStyle);
+}
+
+
+static void UI_DrawTeamName(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int textStyle) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ Text_Paint(rect->x, rect->y, scale, color, va("%s: %s", (blue) ? "Blue" : "Red", uiInfo.teamList[i].teamName),0, 0, textStyle);
+ }
+}
+
+static void UI_DrawTeamMember(rectDef_t *rect, float scale, vec4_t color, qboolean blue, int num, int textStyle) {
+ // 0 - None
+ // 1 - Human
+ // 2..NumCharacters - Bot
+ int value = trap_Cvar_VariableValue(va(blue ? "ui_blueteam%i" : "ui_redteam%i", num));
+ const char *text;
+ if (value <= 0) {
+ text = "Closed";
+ } else if (value == 1) {
+ text = "Human";
+ } else {
+ value -= 2;
+
+ if( value >= UI_GetNumBots( ) )
+ value = 0;
+
+ text = UI_GetBotNameByNumber(value);
+ }
+ Text_Paint(rect->x, rect->y, scale, color, text, 0, 0, textStyle);
+}
+
+static void UI_DrawMapPreview(rectDef_t *rect, float scale, vec4_t color, qboolean net) {
+ int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer;
+ if (map < 0 || map > uiInfo.mapCount) {
+ if (net) {
+ ui_currentNetMap.integer = 0;
+ trap_Cvar_Set("ui_currentNetMap", "0");
+ } else {
+ ui_currentMap.integer = 0;
+ trap_Cvar_Set("ui_currentMap", "0");
+ }
+ map = 0;
+ }
+
+ if (uiInfo.mapList[map].levelShot == -1) {
+ uiInfo.mapList[map].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[map].imageName);
+ }
+
+ if (uiInfo.mapList[map].levelShot > 0) {
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.mapList[map].levelShot);
+ } else {
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen"));
+ }
+}
+
+
+static void UI_DrawMapTimeToBeat(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int minutes, seconds, time;
+ if (ui_currentMap.integer < 0 || ui_currentMap.integer > uiInfo.mapCount) {
+ ui_currentMap.integer = 0;
+ trap_Cvar_Set("ui_currentMap", "0");
+ }
+
+ time = uiInfo.mapList[ui_currentMap.integer].timeToBeat[uiInfo.gameTypes[ui_gameType.integer].gtEnum];
+
+ minutes = time / 60;
+ seconds = time % 60;
+
+ Text_Paint(rect->x, rect->y, scale, color, va("%02i:%02i", minutes, seconds), 0, 0, textStyle);
+}
+
+
+
+static void UI_DrawMapCinematic(rectDef_t *rect, float scale, vec4_t color, qboolean net) {
+
+ int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer;
+ if (map < 0 || map > uiInfo.mapCount) {
+ if (net) {
+ ui_currentNetMap.integer = 0;
+ trap_Cvar_Set("ui_currentNetMap", "0");
+ } else {
+ ui_currentMap.integer = 0;
+ trap_Cvar_Set("ui_currentMap", "0");
+ }
+ map = 0;
+ }
+
+ if (uiInfo.mapList[map].cinematic >= -1) {
+ if (uiInfo.mapList[map].cinematic == -1) {
+ uiInfo.mapList[map].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[map].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ }
+ if (uiInfo.mapList[map].cinematic >= 0) {
+ trap_CIN_RunCinematic(uiInfo.mapList[map].cinematic);
+ trap_CIN_SetExtents(uiInfo.mapList[map].cinematic, rect->x, rect->y, rect->w, rect->h);
+ trap_CIN_DrawCinematic(uiInfo.mapList[map].cinematic);
+ } else {
+ uiInfo.mapList[map].cinematic = -2;
+ }
+ } else {
+ UI_DrawMapPreview(rect, scale, color, net);
+ }
+}
+
+
+
+static qboolean updateModel = qtrue;
+static qboolean q3Model = qfalse;
+
+static void UI_DrawPlayerModel(rectDef_t *rect) {
+ static playerInfo_t info;
+ char model[MAX_QPATH];
+ char team[256];
+ char head[256];
+ vec3_t viewangles;
+ vec3_t moveangles;
+
+ if (trap_Cvar_VariableValue("ui_Q3Model")) {
+ strcpy(model, UI_Cvar_VariableString("model"));
+ strcpy(head, UI_Cvar_VariableString("headmodel"));
+ if (!q3Model) {
+ q3Model = qtrue;
+ updateModel = qtrue;
+ }
+ team[0] = '\0';
+ } else {
+
+ strcpy(team, UI_Cvar_VariableString("ui_teamName"));
+ strcpy(model, UI_Cvar_VariableString("team_model"));
+ strcpy(head, UI_Cvar_VariableString("team_headmodel"));
+ if (q3Model) {
+ q3Model = qfalse;
+ updateModel = qtrue;
+ }
+ }
+ if (updateModel) {
+ memset( &info, 0, sizeof(playerInfo_t) );
+ viewangles[YAW] = 180 - 10;
+ viewangles[PITCH] = 0;
+ viewangles[ROLL] = 0;
+ VectorClear( moveangles );
+ UI_PlayerInfo_SetModel( &info, model, head, team);
+ UI_PlayerInfo_SetInfo( &info, LEGS_IDLE, TORSO_STAND, viewangles, vec3_origin, WP_MACHINEGUN, qfalse );
+// UI_RegisterClientModelname( &info, model, head, team);
+ updateModel = qfalse;
+ }
+
+ UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info, uiInfo.uiDC.realTime / 2);
+
+}
+
+static void UI_DrawNetSource(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (ui_netSource.integer < 0 || ui_netSource.integer > numNetSources) {
+ ui_netSource.integer = 0;
+ }
+ Text_Paint(rect->x, rect->y, scale, color, va("Source: %s", netSources[ui_netSource.integer]), 0, 0, textStyle);
+}
+
+static void UI_DrawNetMapPreview(rectDef_t *rect, float scale, vec4_t color) {
+
+ if (uiInfo.serverStatus.currentServerPreview > 0) {
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.serverStatus.currentServerPreview);
+ } else {
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, trap_R_RegisterShaderNoMip("gfx/2d/load_screen"));
+ }
+}
+
+static void UI_DrawNetMapCinematic(rectDef_t *rect, float scale, vec4_t color) {
+ if (ui_currentNetMap.integer < 0 || ui_currentNetMap.integer > uiInfo.mapCount) {
+ ui_currentNetMap.integer = 0;
+ trap_Cvar_Set("ui_currentNetMap", "0");
+ }
+
+ if (uiInfo.serverStatus.currentServerCinematic >= 0) {
+ trap_CIN_RunCinematic(uiInfo.serverStatus.currentServerCinematic);
+ trap_CIN_SetExtents(uiInfo.serverStatus.currentServerCinematic, rect->x, rect->y, rect->w, rect->h);
+ trap_CIN_DrawCinematic(uiInfo.serverStatus.currentServerCinematic);
+ } else {
+ UI_DrawNetMapPreview(rect, scale, color);
+ }
+}
+
+
+
+static void UI_DrawNetFilter(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) {
+ ui_serverFilterType.integer = 0;
+ }
+ Text_Paint(rect->x, rect->y, scale, color, va("Filter: %s", serverFilters[ui_serverFilterType.integer].description), 0, 0, textStyle);
+}
+
+
+static void UI_DrawTier(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i;
+ i = trap_Cvar_VariableValue( "ui_currentTier" );
+ if (i < 0 || i >= uiInfo.tierCount) {
+ i = 0;
+ }
+ Text_Paint(rect->x, rect->y, scale, color, va("Tier: %s", uiInfo.tierList[i].tierName),0, 0, textStyle);
+}
+
+static void UI_DrawTierMap(rectDef_t *rect, int index) {
+ int i;
+ i = trap_Cvar_VariableValue( "ui_currentTier" );
+ if (i < 0 || i >= uiInfo.tierCount) {
+ i = 0;
+ }
+
+ if (uiInfo.tierList[i].mapHandles[index] == -1) {
+ uiInfo.tierList[i].mapHandles[index] = trap_R_RegisterShaderNoMip(va("levelshots/%s", uiInfo.tierList[i].maps[index]));
+ }
+
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.tierList[i].mapHandles[index]);
+}
+
+static const char *UI_EnglishMapName(const char *map) {
+ int i;
+ for (i = 0; i < uiInfo.mapCount; i++) {
+ if (Q_stricmp(map, uiInfo.mapList[i].mapLoadName) == 0) {
+ return uiInfo.mapList[i].mapName;
+ }
+ }
+ return "";
+}
+
+static void UI_DrawTierMapName(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i, j;
+ i = trap_Cvar_VariableValue( "ui_currentTier" );
+ if (i < 0 || i >= uiInfo.tierCount) {
+ i = 0;
+ }
+ j = trap_Cvar_VariableValue("ui_currentMap");
+ if (j < 0 || j > MAPS_PER_TIER) {
+ j = 0;
+ }
+
+ Text_Paint(rect->x, rect->y, scale, color, UI_EnglishMapName(uiInfo.tierList[i].maps[j]), 0, 0, textStyle);
+}
+
+static void UI_DrawTierGameType(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int i, j;
+ i = trap_Cvar_VariableValue( "ui_currentTier" );
+ if (i < 0 || i >= uiInfo.tierCount) {
+ i = 0;
+ }
+ j = trap_Cvar_VariableValue("ui_currentMap");
+ if (j < 0 || j > MAPS_PER_TIER) {
+ j = 0;
+ }
+
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.gameTypes[uiInfo.tierList[i].gameTypes[j]].gameType , 0, 0, textStyle);
+}
+
+
+static const char *UI_AIFromName(const char *name) {
+ int j;
+ for (j = 0; j < uiInfo.aliasCount; j++) {
+ if (Q_stricmp(uiInfo.aliasList[j].name, name) == 0) {
+ return uiInfo.aliasList[j].ai;
+ }
+ }
+ return "James";
+}
+
+static qboolean updateOpponentModel = qtrue;
+static void UI_DrawOpponent(rectDef_t *rect) {
+ static playerInfo_t info2;
+ char model[MAX_QPATH];
+ char headmodel[MAX_QPATH];
+ char team[256];
+ vec3_t viewangles;
+ vec3_t moveangles;
+
+ if (updateOpponentModel) {
+
+ strcpy(model, UI_Cvar_VariableString("ui_opponentModel"));
+ strcpy(headmodel, UI_Cvar_VariableString("ui_opponentModel"));
+ team[0] = '\0';
+
+ memset( &info2, 0, sizeof(playerInfo_t) );
+ viewangles[YAW] = 180 - 10;
+ viewangles[PITCH] = 0;
+ viewangles[ROLL] = 0;
+ VectorClear( moveangles );
+ UI_PlayerInfo_SetModel( &info2, model, headmodel, "");
+ UI_PlayerInfo_SetInfo( &info2, LEGS_IDLE, TORSO_STAND, viewangles, vec3_origin, WP_MACHINEGUN, qfalse );
+ UI_RegisterClientModelname( &info2, model, headmodel, team);
+ updateOpponentModel = qfalse;
+ }
+
+ UI_DrawPlayer( rect->x, rect->y, rect->w, rect->h, &info2, uiInfo.uiDC.realTime / 2);
+
+}
+
+static void UI_NextOpponent( void ) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ i++;
+ if (i >= uiInfo.teamCount) {
+ i = 0;
+ }
+ if (i == j) {
+ i++;
+ if ( i >= uiInfo.teamCount) {
+ i = 0;
+ }
+ }
+ trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName );
+}
+
+static void UI_PriorOpponent( void ) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ int j = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ i--;
+ if (i < 0) {
+ i = uiInfo.teamCount - 1;
+ }
+ if (i == j) {
+ i--;
+ if ( i < 0) {
+ i = uiInfo.teamCount - 1;
+ }
+ }
+ trap_Cvar_Set( "ui_opponentName", uiInfo.teamList[i].teamName );
+}
+
+static void UI_DrawPlayerLogo(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawPlayerLogoMetal(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawPlayerLogoName(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawOpponentLogo(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawOpponentLogoMetal(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Metal );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawOpponentLogoName(rectDef_t *rect, vec3_t color) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+ if (uiInfo.teamList[i].teamIcon == -1) {
+ uiInfo.teamList[i].teamIcon = trap_R_RegisterShaderNoMip(uiInfo.teamList[i].imageName);
+ uiInfo.teamList[i].teamIcon_Metal = trap_R_RegisterShaderNoMip(va("%s_metal",uiInfo.teamList[i].imageName));
+ uiInfo.teamList[i].teamIcon_Name = trap_R_RegisterShaderNoMip(va("%s_name", uiInfo.teamList[i].imageName));
+ }
+
+ trap_R_SetColor( color );
+ UI_DrawHandlePic( rect->x, rect->y, rect->w, rect->h, uiInfo.teamList[i].teamIcon_Name );
+ trap_R_SetColor( NULL );
+}
+
+static void UI_DrawAllMapsSelection(rectDef_t *rect, float scale, vec4_t color, int textStyle, qboolean net) {
+ int map = (net) ? ui_currentNetMap.integer : ui_currentMap.integer;
+ if (map >= 0 && map < uiInfo.mapCount) {
+ Text_Paint(rect->x, rect->y, scale, color, uiInfo.mapList[map].mapName, 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawPlayerListSelection( rectDef_t *rect, float scale,
+ vec4_t color, int textStyle )
+{
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ Text_Paint(rect->x, rect->y, scale, color,
+ uiInfo.rawPlayerNames[ uiInfo.playerIndex ],
+ 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawTeamListSelection( rectDef_t *rect, float scale,
+ vec4_t color, int textStyle )
+{
+ if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount )
+ {
+ Text_Paint(rect->x, rect->y, scale, color,
+ uiInfo.rawTeamNames[ uiInfo.teamIndex ],
+ 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawOpponentName(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ Text_Paint(rect->x, rect->y, scale, color, UI_Cvar_VariableString("ui_opponentName"), 0, 0, textStyle);
+}
+
+
+static int UI_OwnerDrawWidth(int ownerDraw, float scale) {
+ int i, h, value;
+ const char *text;
+ const char *s = NULL;
+
+ switch( ownerDraw )
+ {
+ case UI_HANDICAP:
+ h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") );
+ i = 20 - h / 5;
+ s = handicapValues[i];
+ break;
+ case UI_CLANNAME:
+ s = UI_Cvar_VariableString("ui_teamName");
+ break;
+ case UI_GAMETYPE:
+ s = uiInfo.gameTypes[ui_gameType.integer].gameType;
+ break;
+ case UI_SKILL:
+ i = trap_Cvar_VariableValue( "g_spSkill" );
+ if (i < 1 || i > numSkillLevels) {
+ i = 1;
+ }
+ s = skillLevels[i-1];
+ break;
+ case UI_BLUETEAMNAME:
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_blueTeam"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ s = va("%s: %s", "Blue", uiInfo.teamList[i].teamName);
+ }
+ break;
+ case UI_REDTEAMNAME:
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_redTeam"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ s = va("%s: %s", "Red", uiInfo.teamList[i].teamName);
+ }
+ break;
+ case UI_BLUETEAM1:
+ case UI_BLUETEAM2:
+ case UI_BLUETEAM3:
+ case UI_BLUETEAM4:
+ case UI_BLUETEAM5:
+ value = trap_Cvar_VariableValue(va("ui_blueteam%i", ownerDraw-UI_BLUETEAM1 + 1));
+ if (value <= 0) {
+ text = "Closed";
+ } else if (value == 1) {
+ text = "Human";
+ } else {
+ value -= 2;
+ if (value >= uiInfo.aliasCount) {
+ value = 0;
+ }
+ text = uiInfo.aliasList[value].name;
+ }
+ s = va("%i. %s", ownerDraw-UI_BLUETEAM1 + 1, text);
+ break;
+ case UI_REDTEAM1:
+ case UI_REDTEAM2:
+ case UI_REDTEAM3:
+ case UI_REDTEAM4:
+ case UI_REDTEAM5:
+ value = trap_Cvar_VariableValue(va("ui_redteam%i", ownerDraw-UI_REDTEAM1 + 1));
+ if (value <= 0) {
+ text = "Closed";
+ } else if (value == 1) {
+ text = "Human";
+ } else {
+ value -= 2;
+ if (value >= uiInfo.aliasCount) {
+ value = 0;
+ }
+ text = uiInfo.aliasList[value].name;
+ }
+ s = va("%i. %s", ownerDraw-UI_REDTEAM1 + 1, text);
+ break;
+ case UI_NETSOURCE:
+ if (ui_netSource.integer < 0 || ui_netSource.integer > uiInfo.numJoinGameTypes) {
+ ui_netSource.integer = 0;
+ }
+ s = va("Source: %s", netSources[ui_netSource.integer]);
+ break;
+ case UI_NETFILTER:
+ if (ui_serverFilterType.integer < 0 || ui_serverFilterType.integer > numServerFilters) {
+ ui_serverFilterType.integer = 0;
+ }
+ s = va("Filter: %s", serverFilters[ui_serverFilterType.integer].description );
+ break;
+ case UI_TIER:
+ break;
+ case UI_TIER_MAPNAME:
+ break;
+ case UI_TIER_GAMETYPE:
+ break;
+ case UI_ALLMAPS_SELECTION:
+ break;
+ case UI_PLAYERLIST_SELECTION:
+ break;
+ case UI_TEAMLIST_SELECTION:
+ break;
+ case UI_OPPONENT_NAME:
+ break;
+ case UI_KEYBINDSTATUS:
+ if (Display_KeyBindPending()) {
+ s = "Waiting for new key... Press ESCAPE to cancel";
+ } else {
+ s = "Press ENTER or CLICK to change, Press BACKSPACE to clear";
+ }
+ break;
+ case UI_SERVERREFRESHDATE:
+ s = UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer));
+ break;
+ default:
+ break;
+ }
+
+ if (s) {
+ return Text_Width(s, scale, 0);
+ }
+ return 0;
+}
+
+static void UI_DrawBotName(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ int value = uiInfo.botIndex;
+ const char *text = "";
+
+ if( value >= UI_GetNumBots( ) )
+ value = 0;
+
+ text = UI_GetBotNameByNumber( value );
+
+ Text_Paint(rect->x, rect->y, scale, color, text, 0, 0, textStyle);
+}
+
+static void UI_DrawBotSkill(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (uiInfo.skillIndex >= 0 && uiInfo.skillIndex < numSkillLevels) {
+ Text_Paint(rect->x, rect->y, scale, color, skillLevels[uiInfo.skillIndex], 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawRedBlue(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ Text_Paint(rect->x, rect->y, scale, color, (uiInfo.redBlue == 0) ? "Red" : "Blue", 0, 0, textStyle);
+}
+
+/*
+===============
+UI_BuildPlayerList
+===============
+*/
+static void UI_BuildPlayerList( void ) {
+ uiClientState_t cs;
+ int n, count, team, team2, playerTeamNumber;
+ char info[MAX_INFO_STRING];
+
+ trap_GetClientState( &cs );
+ trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING );
+ uiInfo.playerNumber = cs.clientNum;
+ uiInfo.teamLeader = atoi(Info_ValueForKey(info, "tl"));
+ team = atoi(Info_ValueForKey(info, "t"));
+ trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) );
+ count = atoi( Info_ValueForKey( info, "sv_maxclients" ) );
+ uiInfo.playerCount = 0;
+ uiInfo.myTeamCount = 0;
+ uiInfo.myPlayerIndex = 0;
+ playerTeamNumber = 0;
+ for( n = 0; n < count; n++ ) {
+ trap_GetConfigString( CS_PLAYERS + n, info, MAX_INFO_STRING );
+
+ if (info[0]) {
+ BG_ClientListParse( &uiInfo.ignoreList[ uiInfo.playerCount ],
+ Info_ValueForKey( info, "ig" ) );
+ Q_strncpyz( uiInfo.rawPlayerNames[uiInfo.playerCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_strncpyz( uiInfo.playerNames[uiInfo.playerCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_CleanStr( uiInfo.playerNames[uiInfo.playerCount] );
+ uiInfo.clientNums[uiInfo.playerCount] = n;
+ if( n == uiInfo.playerNumber )
+ uiInfo.myPlayerIndex = uiInfo.playerCount;
+ uiInfo.playerCount++;
+ team2 = atoi(Info_ValueForKey(info, "t"));
+ if (team2 == team) {
+ Q_strncpyz( uiInfo.rawTeamNames[uiInfo.myTeamCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_strncpyz( uiInfo.teamNames[uiInfo.myTeamCount],
+ Info_ValueForKey( info, "n" ), MAX_NAME_LENGTH );
+ Q_CleanStr( uiInfo.teamNames[uiInfo.myTeamCount] );
+ uiInfo.teamClientNums[uiInfo.myTeamCount] = n;
+ if (uiInfo.playerNumber == n) {
+ playerTeamNumber = uiInfo.myTeamCount;
+ }
+ uiInfo.myTeamCount++;
+ }
+ }
+ }
+
+ if (!uiInfo.teamLeader) {
+ trap_Cvar_Set("cg_selectedPlayer", va("%d", playerTeamNumber));
+ }
+
+ n = trap_Cvar_VariableValue("cg_selectedPlayer");
+ if (n < 0 || n > uiInfo.myTeamCount) {
+ n = 0;
+ }
+ if (n < uiInfo.myTeamCount) {
+ trap_Cvar_Set("cg_selectedPlayerName", uiInfo.teamNames[n]);
+ }
+}
+
+
+static void UI_DrawSelectedPlayer(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ char name[ MAX_NAME_LENGTH ];
+ char *s;
+
+ if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) {
+ uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
+ UI_BuildPlayerList();
+ }
+ if( uiInfo.teamLeader )
+ s = UI_Cvar_VariableString("cg_selectedPlayerName");
+ else
+ s = UI_Cvar_VariableString("name");
+ Q_strncpyz( name, s, sizeof( name ) );
+ Text_Paint(rect->x, rect->y, scale, color, name, 0, 0, textStyle);
+}
+
+static void UI_DrawServerRefreshDate(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ if (uiInfo.serverStatus.refreshActive) {
+ vec4_t lowLight, newColor;
+ lowLight[0] = 0.8 * color[0];
+ lowLight[1] = 0.8 * color[1];
+ lowLight[2] = 0.8 * color[2];
+ lowLight[3] = 0.8 * color[3];
+ LerpColor(color,lowLight,newColor,0.5+0.5*sin(uiInfo.uiDC.realTime / PULSE_DIVISOR));
+ Text_Paint(rect->x, rect->y, scale, newColor, va("Getting info for %d servers (ESC to cancel)", trap_LAN_GetServerCount(ui_netSource.integer)), 0, 0, textStyle);
+ } else {
+ char buff[64];
+ Q_strncpyz(buff, UI_Cvar_VariableString(va("ui_lastServerRefresh_%i", ui_netSource.integer)), 64);
+ Text_Paint(rect->x, rect->y, scale, color, va("Refresh Time: %s", buff), 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawServerMOTD(rectDef_t *rect, float scale, vec4_t color) {
+ if (uiInfo.serverStatus.motdLen) {
+ float maxX;
+
+ if (uiInfo.serverStatus.motdWidth == -1) {
+ uiInfo.serverStatus.motdWidth = 0;
+ uiInfo.serverStatus.motdPaintX = rect->x + 1;
+ uiInfo.serverStatus.motdPaintX2 = -1;
+ }
+
+ if (uiInfo.serverStatus.motdOffset > uiInfo.serverStatus.motdLen) {
+ uiInfo.serverStatus.motdOffset = 0;
+ uiInfo.serverStatus.motdPaintX = rect->x + 1;
+ uiInfo.serverStatus.motdPaintX2 = -1;
+ }
+
+ if (uiInfo.uiDC.realTime > uiInfo.serverStatus.motdTime) {
+ uiInfo.serverStatus.motdTime = uiInfo.uiDC.realTime + 10;
+ if (uiInfo.serverStatus.motdPaintX <= rect->x + 2) {
+ if (uiInfo.serverStatus.motdOffset < uiInfo.serverStatus.motdLen) {
+ uiInfo.serverStatus.motdPaintX += Text_Width(&uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], scale, 1) - 1;
+ uiInfo.serverStatus.motdOffset++;
+ } else {
+ uiInfo.serverStatus.motdOffset = 0;
+ if (uiInfo.serverStatus.motdPaintX2 >= 0) {
+ uiInfo.serverStatus.motdPaintX = uiInfo.serverStatus.motdPaintX2;
+ } else {
+ uiInfo.serverStatus.motdPaintX = rect->x + rect->w - 2;
+ }
+ uiInfo.serverStatus.motdPaintX2 = -1;
+ }
+ } else {
+ //serverStatus.motdPaintX--;
+ uiInfo.serverStatus.motdPaintX -= 2;
+ if (uiInfo.serverStatus.motdPaintX2 >= 0) {
+ //serverStatus.motdPaintX2--;
+ uiInfo.serverStatus.motdPaintX2 -= 2;
+ }
+ }
+ }
+
+ maxX = rect->x + rect->w - 2;
+ Text_Paint_Limit(&maxX, uiInfo.serverStatus.motdPaintX, rect->y + rect->h - 3, scale, color, &uiInfo.serverStatus.motd[uiInfo.serverStatus.motdOffset], 0, 0);
+ if (uiInfo.serverStatus.motdPaintX2 >= 0) {
+ float maxX2 = rect->x + rect->w - 2;
+ Text_Paint_Limit(&maxX2, uiInfo.serverStatus.motdPaintX2, rect->y + rect->h - 3, scale, color, uiInfo.serverStatus.motd, 0, uiInfo.serverStatus.motdOffset);
+ }
+ if (uiInfo.serverStatus.motdOffset && maxX > 0) {
+ // if we have an offset ( we are skipping the first part of the string ) and we fit the string
+ if (uiInfo.serverStatus.motdPaintX2 == -1) {
+ uiInfo.serverStatus.motdPaintX2 = rect->x + rect->w - 2;
+ }
+ } else {
+ uiInfo.serverStatus.motdPaintX2 = -1;
+ }
+
+ }
+}
+
+static void UI_DrawKeyBindStatus(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+// int ofs = 0; TTimo: unused
+ if (Display_KeyBindPending()) {
+ Text_Paint(rect->x, rect->y, scale, color, "Waiting for new key... Press ESCAPE to cancel", 0, 0, textStyle);
+ } else {
+ Text_Paint(rect->x, rect->y, scale, color, "Press ENTER or CLICK to change, Press BACKSPACE to clear", 0, 0, textStyle);
+ }
+}
+
+static void UI_DrawGLInfo(rectDef_t *rect, float scale, vec4_t color, int textStyle) {
+ char * eptr;
+ char buff[1024];
+ const char *lines[64];
+ int y, numLines, i;
+
+ Text_Paint(rect->x + 2, rect->y, scale, color, va("VENDOR: %s", uiInfo.uiDC.glconfig.vendor_string), 0, 30, textStyle);
+ Text_Paint(rect->x + 2, rect->y + 15, scale, color, va("VERSION: %s: %s", uiInfo.uiDC.glconfig.version_string,uiInfo.uiDC.glconfig.renderer_string), 0, 30, textStyle);
+ Text_Paint(rect->x + 2, rect->y + 30, scale, color, va ("PIXELFORMAT: color(%d-bits) Z(%d-bits) stencil(%d-bits)", uiInfo.uiDC.glconfig.colorBits, uiInfo.uiDC.glconfig.depthBits, uiInfo.uiDC.glconfig.stencilBits), 0, 30, textStyle);
+
+ // build null terminated extension strings
+ // TTimo: https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=399
+ // in TA this was not directly crashing, but displaying a nasty broken shader right in the middle
+ // brought down the string size to 1024, there's not much that can be shown on the screen anyway
+ Q_strncpyz(buff, uiInfo.uiDC.glconfig.extensions_string, 1024);
+ eptr = buff;
+ y = rect->y + 45;
+ numLines = 0;
+ while ( y < rect->y + rect->h && *eptr )
+ {
+ while ( *eptr && *eptr == ' ' )
+ *eptr++ = '\0';
+
+ // track start of valid string
+ if (*eptr && *eptr != ' ') {
+ lines[numLines++] = eptr;
+ }
+
+ while ( *eptr && *eptr != ' ' )
+ eptr++;
+ }
+
+ i = 0;
+ while (i < numLines) {
+ Text_Paint(rect->x + 2, y, scale, color, lines[i++], 0, 20, textStyle);
+ if (i < numLines) {
+ Text_Paint(rect->x + rect->w / 2, y, scale, color, lines[i++], 0, 20, textStyle);
+ }
+ y += 10;
+ if (y > rect->y + rect->h - 11) {
+ break;
+ }
+ }
+
+
+}
+
+// FIXME: table drive
+//
+static void UI_OwnerDraw( float x, float y, float w, float h,
+ float text_x, float text_y, int ownerDraw,
+ int ownerDrawFlags, int align, float special,
+ float scale, vec4_t color, qhandle_t shader, int textStyle )
+{
+ rectDef_t rect;
+ tremInfoPane_t *pane = NULL;
+
+ rect.x = x + text_x;
+ rect.y = y + text_y;
+ rect.w = w;
+ rect.h = h;
+
+ switch( ownerDraw )
+ {
+ case UI_TEAMINFOPANE:
+ if( ( pane = uiInfo.tremTeamList[ uiInfo.tremTeamIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_ACLASSINFOPANE:
+ if( ( pane = uiInfo.tremAlienClassList[ uiInfo.tremAlienClassIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_AUPGRADEINFOPANE:
+ if( ( pane = uiInfo.tremAlienUpgradeList[ uiInfo.tremAlienUpgradeIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HITEMINFOPANE:
+ if( ( pane = uiInfo.tremHumanItemList[ uiInfo.tremHumanItemIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HBUYINFOPANE:
+ if( ( pane = uiInfo.tremHumanArmouryBuyList[ uiInfo.tremHumanArmouryBuyIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HSELLINFOPANE:
+ if( ( pane = uiInfo.tremHumanArmourySellList[ uiInfo.tremHumanArmourySellIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_ABUILDINFOPANE:
+ if( ( pane = uiInfo.tremAlienBuildList[ uiInfo.tremAlienBuildIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HBUILDINFOPANE:
+ if( ( pane = uiInfo.tremHumanBuildList[ uiInfo.tremHumanBuildIndex ].infopane ) )
+ UI_DrawInfoPane( pane, &rect, text_x, text_y, scale, color, textStyle );
+ break;
+
+ case UI_HANDICAP:
+ UI_DrawHandicap(&rect, scale, color, textStyle);
+ break;
+ case UI_PLAYERMODEL:
+ UI_DrawPlayerModel(&rect);
+ break;
+ case UI_CLANNAME:
+ UI_DrawClanName(&rect, scale, color, textStyle);
+ break;
+ case UI_CLANLOGO:
+ UI_DrawClanLogo(&rect, scale, color);
+ break;
+ case UI_CLANCINEMATIC:
+ UI_DrawClanCinematic(&rect, scale, color);
+ break;
+ case UI_PREVIEWCINEMATIC:
+ UI_DrawPreviewCinematic(&rect, scale, color);
+ break;
+ case UI_GAMETYPE:
+ UI_DrawGameType(&rect, scale, color, textStyle);
+ break;
+ case UI_NETGAMETYPE:
+ UI_DrawNetGameType(&rect, scale, color, textStyle);
+ break;
+ case UI_JOINGAMETYPE:
+ UI_DrawJoinGameType(&rect, scale, color, textStyle);
+ break;
+ case UI_MAPPREVIEW:
+ UI_DrawMapPreview(&rect, scale, color, qtrue);
+ break;
+ case UI_MAP_TIMETOBEAT:
+ UI_DrawMapTimeToBeat(&rect, scale, color, textStyle);
+ break;
+ case UI_MAPCINEMATIC:
+ UI_DrawMapCinematic(&rect, scale, color, qfalse);
+ break;
+ case UI_STARTMAPCINEMATIC:
+ UI_DrawMapCinematic(&rect, scale, color, qtrue);
+ break;
+ case UI_SKILL:
+ UI_DrawSkill(&rect, scale, color, textStyle);
+ break;
+ case UI_BLUETEAMNAME:
+ UI_DrawTeamName(&rect, scale, color, qtrue, textStyle);
+ break;
+ case UI_REDTEAMNAME:
+ UI_DrawTeamName(&rect, scale, color, qfalse, textStyle);
+ break;
+ case UI_BLUETEAM1:
+ case UI_BLUETEAM2:
+ case UI_BLUETEAM3:
+ case UI_BLUETEAM4:
+ case UI_BLUETEAM5:
+ UI_DrawTeamMember(&rect, scale, color, qtrue, ownerDraw - UI_BLUETEAM1 + 1, textStyle);
+ break;
+ case UI_REDTEAM1:
+ case UI_REDTEAM2:
+ case UI_REDTEAM3:
+ case UI_REDTEAM4:
+ case UI_REDTEAM5:
+ UI_DrawTeamMember(&rect, scale, color, qfalse, ownerDraw - UI_REDTEAM1 + 1, textStyle);
+ break;
+ case UI_NETSOURCE:
+ UI_DrawNetSource(&rect, scale, color, textStyle);
+ break;
+ case UI_NETMAPPREVIEW:
+ UI_DrawNetMapPreview(&rect, scale, color);
+ break;
+ case UI_NETMAPCINEMATIC:
+ UI_DrawNetMapCinematic(&rect, scale, color);
+ break;
+ case UI_NETFILTER:
+ UI_DrawNetFilter(&rect, scale, color, textStyle);
+ break;
+ case UI_TIER:
+ UI_DrawTier(&rect, scale, color, textStyle);
+ break;
+ case UI_OPPONENTMODEL:
+ UI_DrawOpponent(&rect);
+ break;
+ case UI_TIERMAP1:
+ UI_DrawTierMap(&rect, 0);
+ break;
+ case UI_TIERMAP2:
+ UI_DrawTierMap(&rect, 1);
+ break;
+ case UI_TIERMAP3:
+ UI_DrawTierMap(&rect, 2);
+ break;
+ case UI_PLAYERLOGO:
+ UI_DrawPlayerLogo(&rect, color);
+ break;
+ case UI_PLAYERLOGO_METAL:
+ UI_DrawPlayerLogoMetal(&rect, color);
+ break;
+ case UI_PLAYERLOGO_NAME:
+ UI_DrawPlayerLogoName(&rect, color);
+ break;
+ case UI_OPPONENTLOGO:
+ UI_DrawOpponentLogo(&rect, color);
+ break;
+ case UI_OPPONENTLOGO_METAL:
+ UI_DrawOpponentLogoMetal(&rect, color);
+ break;
+ case UI_OPPONENTLOGO_NAME:
+ UI_DrawOpponentLogoName(&rect, color);
+ break;
+ case UI_TIER_MAPNAME:
+ UI_DrawTierMapName(&rect, scale, color, textStyle);
+ break;
+ case UI_TIER_GAMETYPE:
+ UI_DrawTierGameType(&rect, scale, color, textStyle);
+ break;
+ case UI_ALLMAPS_SELECTION:
+ UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qtrue);
+ break;
+ case UI_MAPS_SELECTION:
+ UI_DrawAllMapsSelection(&rect, scale, color, textStyle, qfalse);
+ break;
+ case UI_PLAYERLIST_SELECTION:
+ UI_DrawPlayerListSelection(&rect, scale, color, textStyle);
+ break;
+ case UI_TEAMLIST_SELECTION:
+ UI_DrawTeamListSelection(&rect, scale, color, textStyle);
+ break;
+ case UI_OPPONENT_NAME:
+ UI_DrawOpponentName(&rect, scale, color, textStyle);
+ break;
+ case UI_BOTNAME:
+ UI_DrawBotName(&rect, scale, color, textStyle);
+ break;
+ case UI_BOTSKILL:
+ UI_DrawBotSkill(&rect, scale, color, textStyle);
+ break;
+ case UI_REDBLUE:
+ UI_DrawRedBlue(&rect, scale, color, textStyle);
+ break;
+ case UI_SELECTEDPLAYER:
+ UI_DrawSelectedPlayer(&rect, scale, color, textStyle);
+ break;
+ case UI_SERVERREFRESHDATE:
+ UI_DrawServerRefreshDate(&rect, scale, color, textStyle);
+ break;
+ case UI_SERVERMOTD:
+ UI_DrawServerMOTD(&rect, scale, color);
+ break;
+ case UI_GLINFO:
+ UI_DrawGLInfo(&rect,scale, color, textStyle);
+ break;
+ case UI_KEYBINDSTATUS:
+ UI_DrawKeyBindStatus(&rect,scale, color, textStyle);
+ break;
+ default:
+ break;
+ }
+
+}
+
+static qboolean UI_OwnerDrawVisible(int flags) {
+ qboolean vis = qtrue;
+ uiClientState_t cs;
+ pTeam_t team;
+ char info[ MAX_INFO_STRING ];
+
+ trap_GetClientState( &cs );
+ trap_GetConfigString( CS_PLAYERS + cs.clientNum, info, MAX_INFO_STRING );
+ team = atoi( Info_ValueForKey( info, "t" ) );
+
+
+ while (flags) {
+
+ if( flags & UI_SHOW_NOTSPECTATING )
+ {
+ if( team == PTE_NONE )
+ vis = qfalse;
+
+ flags &= ~UI_SHOW_NOTSPECTATING;
+ }
+
+ if( flags & UI_SHOW_VOTEACTIVE )
+ {
+ if( !trap_Cvar_VariableValue( "ui_voteActive" ) )
+ vis = qfalse;
+
+ flags &= ~UI_SHOW_VOTEACTIVE;
+ }
+
+ if( flags & UI_SHOW_CANVOTE )
+ {
+ if( trap_Cvar_VariableValue( "ui_voteActive" ) )
+ vis = qfalse;
+
+ flags &= ~UI_SHOW_CANVOTE;
+ }
+
+ if( flags & UI_SHOW_TEAMVOTEACTIVE )
+ {
+ if( team == PTE_ALIENS )
+ {
+ if( !trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) )
+ vis = qfalse;
+ }
+ else if( team == PTE_HUMANS )
+ {
+ if( !trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) )
+ vis = qfalse;
+ }
+
+ flags &= ~UI_SHOW_TEAMVOTEACTIVE;
+ }
+
+ if( flags & UI_SHOW_CANTEAMVOTE )
+ {
+ if( team == PTE_ALIENS )
+ {
+ if( trap_Cvar_VariableValue( "ui_alienTeamVoteActive" ) )
+ vis = qfalse;
+ }
+ else if( team == PTE_HUMANS )
+ {
+ if( trap_Cvar_VariableValue( "ui_humanTeamVoteActive" ) )
+ vis = qfalse;
+ }
+
+ flags &= ~UI_SHOW_CANTEAMVOTE;
+ }
+
+ if (flags & UI_SHOW_LEADER) {
+ // these need to show when this client can give orders to a player or a group
+ if (!uiInfo.teamLeader) {
+ vis = qfalse;
+ } else {
+ // if showing yourself
+ if (ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber) {
+ vis = qfalse;
+ }
+ }
+ flags &= ~UI_SHOW_LEADER;
+ }
+ if (flags & UI_SHOW_NOTLEADER) {
+ // these need to show when this client is assigning their own status or they are NOT the leader
+ if (uiInfo.teamLeader) {
+ // if not showing yourself
+ if (!(ui_selectedPlayer.integer < uiInfo.myTeamCount && uiInfo.teamClientNums[ui_selectedPlayer.integer] == uiInfo.playerNumber)) {
+ vis = qfalse;
+ }
+ // these need to show when this client can give orders to a player or a group
+ }
+ flags &= ~UI_SHOW_NOTLEADER;
+ }
+ if (flags & UI_SHOW_FAVORITESERVERS) {
+ // this assumes you only put this type of display flag on something showing in the proper context
+ if (ui_netSource.integer != AS_FAVORITES) {
+ vis = qfalse;
+ }
+ flags &= ~UI_SHOW_FAVORITESERVERS;
+ }
+ if (flags & UI_SHOW_NOTFAVORITESERVERS) {
+ // this assumes you only put this type of display flag on something showing in the proper context
+ if (ui_netSource.integer == AS_FAVORITES) {
+ vis = qfalse;
+ }
+ flags &= ~UI_SHOW_NOTFAVORITESERVERS;
+ }
+ if (flags & UI_SHOW_NEWHIGHSCORE) {
+ if (uiInfo.newHighScoreTime < uiInfo.uiDC.realTime) {
+ vis = qfalse;
+ } else {
+ if (uiInfo.soundHighScore) {
+ if (trap_Cvar_VariableValue("sv_killserver") == 0) {
+ // wait on server to go down before playing sound
+ trap_S_StartLocalSound(uiInfo.newHighScoreSound, CHAN_ANNOUNCER);
+ uiInfo.soundHighScore = qfalse;
+ }
+ }
+ }
+ flags &= ~UI_SHOW_NEWHIGHSCORE;
+ }
+ if (flags & UI_SHOW_NEWBESTTIME) {
+ if (uiInfo.newBestTime < uiInfo.uiDC.realTime) {
+ vis = qfalse;
+ }
+ flags &= ~UI_SHOW_NEWBESTTIME;
+ }
+ if (flags & UI_SHOW_DEMOAVAILABLE) {
+ if (!uiInfo.demoAvailable) {
+ vis = qfalse;
+ }
+ flags &= ~UI_SHOW_DEMOAVAILABLE;
+ } else {
+ flags = 0;
+ }
+ }
+ return vis;
+}
+
+static qboolean UI_Handicap_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int h;
+ h = Com_Clamp( 5, 100, trap_Cvar_VariableValue("handicap") );
+ if (key == K_MOUSE2) {
+ h -= 5;
+ } else {
+ h += 5;
+ }
+ if (h > 100) {
+ h = 5;
+ } else if (h < 0) {
+ h = 100;
+ }
+ trap_Cvar_Set( "handicap", va( "%i", h) );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_ClanName_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (uiInfo.teamList[i].cinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.teamList[i].cinematic);
+ uiInfo.teamList[i].cinematic = -1;
+ }
+ if (key == K_MOUSE2) {
+ i--;
+ } else {
+ i++;
+ }
+ if (i >= uiInfo.teamCount) {
+ i = 0;
+ } else if (i < 0) {
+ i = uiInfo.teamCount - 1;
+ }
+ trap_Cvar_Set( "ui_teamName", uiInfo.teamList[i].teamName);
+ UI_HeadCountByTeam();
+ UI_FeederSelection(FEEDER_HEADS, 0);
+ updateModel = qtrue;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_GameType_HandleKey(int flags, float *special, int key, qboolean resetMap) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int oldCount = UI_MapCountByGameType(qtrue);
+
+ // hard coded mess here
+ if (key == K_MOUSE2) {
+ ui_gameType.integer--;
+ if (ui_gameType.integer == 2) {
+ ui_gameType.integer = 1;
+ } else if (ui_gameType.integer < 2) {
+ ui_gameType.integer = uiInfo.numGameTypes - 1;
+ }
+ } else {
+ ui_gameType.integer++;
+ if (ui_gameType.integer >= uiInfo.numGameTypes) {
+ ui_gameType.integer = 1;
+ } else if (ui_gameType.integer == 2) {
+ ui_gameType.integer = 3;
+ }
+ }
+
+ trap_Cvar_Set("ui_Q3Model", "0");
+
+ trap_Cvar_Set("ui_gameType", va("%d", ui_gameType.integer));
+ UI_SetCapFragLimits(qtrue);
+ UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);
+ if (resetMap && oldCount != UI_MapCountByGameType(qtrue)) {
+ trap_Cvar_Set( "ui_currentMap", "0");
+ Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, NULL);
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_NetGameType_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+
+ if (key == K_MOUSE2) {
+ ui_netGameType.integer--;
+ } else {
+ ui_netGameType.integer++;
+ }
+
+ if (ui_netGameType.integer < 0) {
+ ui_netGameType.integer = uiInfo.numGameTypes - 1;
+ } else if (ui_netGameType.integer >= uiInfo.numGameTypes) {
+ ui_netGameType.integer = 0;
+ }
+
+ trap_Cvar_Set( "ui_netGameType", va("%d", ui_netGameType.integer));
+ trap_Cvar_Set( "ui_actualnetGameType", va("%d", uiInfo.gameTypes[ui_netGameType.integer].gtEnum));
+ trap_Cvar_Set( "ui_currentNetMap", "0");
+ UI_MapCountByGameType(qfalse);
+ Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, NULL);
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_JoinGameType_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+
+ if (key == K_MOUSE2) {
+ ui_joinGameType.integer--;
+ } else {
+ ui_joinGameType.integer++;
+ }
+
+ if (ui_joinGameType.integer < 0) {
+ ui_joinGameType.integer = uiInfo.numJoinGameTypes - 1;
+ } else if (ui_joinGameType.integer >= uiInfo.numJoinGameTypes) {
+ ui_joinGameType.integer = 0;
+ }
+
+ trap_Cvar_Set( "ui_joinGameType", va("%d", ui_joinGameType.integer));
+ UI_BuildServerDisplayList(qtrue);
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+
+static qboolean UI_Skill_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int i = trap_Cvar_VariableValue( "g_spSkill" );
+
+ if (key == K_MOUSE2) {
+ i--;
+ } else {
+ i++;
+ }
+
+ if (i < 1) {
+ i = numSkillLevels;
+ } else if (i > numSkillLevels) {
+ i = 1;
+ }
+
+ trap_Cvar_Set("g_spSkill", va("%i", i));
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_TeamName_HandleKey(int flags, float *special, int key, qboolean blue) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int i;
+ i = UI_TeamIndexFromName(UI_Cvar_VariableString((blue) ? "ui_blueTeam" : "ui_redTeam"));
+
+ if (key == K_MOUSE2) {
+ i--;
+ } else {
+ i++;
+ }
+
+ if (i >= uiInfo.teamCount) {
+ i = 0;
+ } else if (i < 0) {
+ i = uiInfo.teamCount - 1;
+ }
+
+ trap_Cvar_Set( (blue) ? "ui_blueTeam" : "ui_redTeam", uiInfo.teamList[i].teamName);
+
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_TeamMember_HandleKey(int flags, float *special, int key, qboolean blue, int num) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ // 0 - None
+ // 1 - Human
+ // 2..NumCharacters - Bot
+ char *cvar = va(blue ? "ui_blueteam%i" : "ui_redteam%i", num);
+ int value = trap_Cvar_VariableValue(cvar);
+
+ if (key == K_MOUSE2) {
+ value--;
+ } else {
+ value++;
+ }
+
+ if( value >= UI_GetNumBots( ) + 2 )
+ value = 0;
+ else if( value < 0 )
+ value = UI_GetNumBots( ) + 2 - 1;
+
+ trap_Cvar_Set(cvar, va("%i", value));
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_NetSource_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+
+ if (key == K_MOUSE2) {
+ ui_netSource.integer--;
+ if (ui_netSource.integer == AS_MPLAYER)
+ ui_netSource.integer--;
+ } else {
+ ui_netSource.integer++;
+ if (ui_netSource.integer == AS_MPLAYER)
+ ui_netSource.integer++;
+ }
+
+ if (ui_netSource.integer >= numNetSources) {
+ ui_netSource.integer = 0;
+ } else if (ui_netSource.integer < 0) {
+ ui_netSource.integer = numNetSources - 1;
+ }
+
+ UI_BuildServerDisplayList(qtrue);
+ if (ui_netSource.integer != AS_GLOBAL) {
+ UI_StartServerRefresh(qtrue);
+ }
+ trap_Cvar_Set( "ui_netSource", va("%d", ui_netSource.integer));
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_NetFilter_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+
+ if (key == K_MOUSE2) {
+ ui_serverFilterType.integer--;
+ } else {
+ ui_serverFilterType.integer++;
+ }
+
+ if (ui_serverFilterType.integer >= numServerFilters) {
+ ui_serverFilterType.integer = 0;
+ } else if (ui_serverFilterType.integer < 0) {
+ ui_serverFilterType.integer = numServerFilters - 1;
+ }
+ UI_BuildServerDisplayList(qtrue);
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_OpponentName_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ if (key == K_MOUSE2) {
+ UI_PriorOpponent();
+ } else {
+ UI_NextOpponent();
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_BotName_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int value = uiInfo.botIndex;
+
+ if (key == K_MOUSE2) {
+ value--;
+ } else {
+ value++;
+ }
+
+
+ if( value >= UI_GetNumBots( ) + 2 )
+ value = 0;
+ else if( value < 0 )
+ value = UI_GetNumBots( ) + 2 - 1;
+
+ uiInfo.botIndex = value;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_BotSkill_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ if (key == K_MOUSE2) {
+ uiInfo.skillIndex--;
+ } else {
+ uiInfo.skillIndex++;
+ }
+ if (uiInfo.skillIndex >= numSkillLevels) {
+ uiInfo.skillIndex = 0;
+ } else if (uiInfo.skillIndex < 0) {
+ uiInfo.skillIndex = numSkillLevels-1;
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+static qboolean UI_RedBlue_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ uiInfo.redBlue ^= 1;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+
+static qboolean UI_SelectedPlayer_HandleKey(int flags, float *special, int key) {
+ if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_ENTER || key == K_KP_ENTER) {
+ int selected;
+
+ UI_BuildPlayerList();
+ if (!uiInfo.teamLeader) {
+ return qfalse;
+ }
+ selected = trap_Cvar_VariableValue("cg_selectedPlayer");
+
+ if (key == K_MOUSE2) {
+ selected--;
+ } else {
+ selected++;
+ }
+
+ if (selected > uiInfo.myTeamCount) {
+ selected = 0;
+ } else if (selected < 0) {
+ selected = uiInfo.myTeamCount;
+ }
+
+ if (selected == uiInfo.myTeamCount) {
+ trap_Cvar_Set( "cg_selectedPlayerName", "Everyone");
+ } else {
+ trap_Cvar_Set( "cg_selectedPlayerName", uiInfo.teamNames[selected]);
+ }
+ trap_Cvar_Set( "cg_selectedPlayer", va("%d", selected));
+ }
+ return qfalse;
+}
+
+
+static qboolean UI_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) {
+ switch (ownerDraw) {
+ case UI_HANDICAP:
+ return UI_Handicap_HandleKey(flags, special, key);
+ break;
+ case UI_CLANNAME:
+ return UI_ClanName_HandleKey(flags, special, key);
+ break;
+ case UI_GAMETYPE:
+ return UI_GameType_HandleKey(flags, special, key, qtrue);
+ break;
+ case UI_NETGAMETYPE:
+ return UI_NetGameType_HandleKey(flags, special, key);
+ break;
+ case UI_JOINGAMETYPE:
+ return UI_JoinGameType_HandleKey(flags, special, key);
+ break;
+ case UI_SKILL:
+ return UI_Skill_HandleKey(flags, special, key);
+ break;
+ case UI_BLUETEAMNAME:
+ return UI_TeamName_HandleKey(flags, special, key, qtrue);
+ break;
+ case UI_REDTEAMNAME:
+ return UI_TeamName_HandleKey(flags, special, key, qfalse);
+ break;
+ case UI_BLUETEAM1:
+ case UI_BLUETEAM2:
+ case UI_BLUETEAM3:
+ case UI_BLUETEAM4:
+ case UI_BLUETEAM5:
+ UI_TeamMember_HandleKey(flags, special, key, qtrue, ownerDraw - UI_BLUETEAM1 + 1);
+ break;
+ case UI_REDTEAM1:
+ case UI_REDTEAM2:
+ case UI_REDTEAM3:
+ case UI_REDTEAM4:
+ case UI_REDTEAM5:
+ UI_TeamMember_HandleKey(flags, special, key, qfalse, ownerDraw - UI_REDTEAM1 + 1);
+ break;
+ case UI_NETSOURCE:
+ UI_NetSource_HandleKey(flags, special, key);
+ break;
+ case UI_NETFILTER:
+ UI_NetFilter_HandleKey(flags, special, key);
+ break;
+ case UI_OPPONENT_NAME:
+ UI_OpponentName_HandleKey(flags, special, key);
+ break;
+ case UI_BOTNAME:
+ return UI_BotName_HandleKey(flags, special, key);
+ break;
+ case UI_BOTSKILL:
+ return UI_BotSkill_HandleKey(flags, special, key);
+ break;
+ case UI_REDBLUE:
+ UI_RedBlue_HandleKey(flags, special, key);
+ break;
+ case UI_SELECTEDPLAYER:
+ UI_SelectedPlayer_HandleKey(flags, special, key);
+ break;
+ default:
+ break;
+ }
+
+ return qfalse;
+}
+
+
+static float UI_GetValue(int ownerDraw) {
+ return 0;
+}
+
+/*
+=================
+UI_ServersQsortCompare
+=================
+*/
+static int QDECL UI_ServersQsortCompare( const void *arg1, const void *arg2 ) {
+ return trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey, uiInfo.serverStatus.sortDir, *(int*)arg1, *(int*)arg2);
+}
+
+
+/*
+=================
+UI_ServersSort
+=================
+*/
+void UI_ServersSort(int column, qboolean force) {
+
+ if ( !force ) {
+ if ( uiInfo.serverStatus.sortKey == column ) {
+ return;
+ }
+ }
+
+ uiInfo.serverStatus.sortKey = column;
+ qsort( &uiInfo.serverStatus.displayServers[0], uiInfo.serverStatus.numDisplayServers, sizeof(int), UI_ServersQsortCompare);
+}
+
+
+/*
+===============
+UI_GetCurrentAlienStage
+===============
+*/
+static stage_t UI_GetCurrentAlienStage( void )
+{
+ char buffer[ MAX_TOKEN_CHARS ];
+ stage_t stage, dummy;
+
+ trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) );
+ sscanf( buffer, "%d %d", (int *)&stage , (int *)&dummy );
+
+ return stage;
+}
+
+/*
+===============
+UI_GetCurrentHumanStage
+===============
+*/
+static stage_t UI_GetCurrentHumanStage( void )
+{
+ char buffer[ MAX_TOKEN_CHARS ];
+ stage_t stage, dummy;
+
+ trap_Cvar_VariableStringBuffer( "ui_stages", buffer, sizeof( buffer ) );
+ sscanf( buffer, "%d %d", (int *)&dummy, (int *)&stage );
+
+ return stage;
+}
+
+/*
+===============
+UI_LoadTremTeams
+===============
+*/
+static void UI_LoadTremTeams( void )
+{
+ uiInfo.tremTeamCount = 4;
+
+ uiInfo.tremTeamList[ 0 ].text = String_Alloc( "Aliens" );
+ uiInfo.tremTeamList[ 0 ].cmd = String_Alloc( "cmd team aliens\n" );
+ uiInfo.tremTeamList[ 0 ].infopane = UI_FindInfoPaneByName( "alienteam" );
+
+ uiInfo.tremTeamList[ 1 ].text = String_Alloc( "Humans" );
+ uiInfo.tremTeamList[ 1 ].cmd = String_Alloc( "cmd team humans\n" );
+ uiInfo.tremTeamList[ 1 ].infopane = UI_FindInfoPaneByName( "humanteam" );
+
+ uiInfo.tremTeamList[ 2 ].text = String_Alloc( "Spectate" );
+ uiInfo.tremTeamList[ 2 ].cmd = String_Alloc( "cmd team spectate\n" );
+ uiInfo.tremTeamList[ 2 ].infopane = UI_FindInfoPaneByName( "spectateteam" );
+
+ uiInfo.tremTeamList[ 3 ].text = String_Alloc( "Auto select" );
+ uiInfo.tremTeamList[ 3 ].cmd = String_Alloc( "cmd team auto\n" );
+ uiInfo.tremTeamList[ 3 ].infopane = UI_FindInfoPaneByName( "autoteam" );
+}
+
+/*
+===============
+UI_AddClass
+===============
+*/
+static void UI_AddClass( pClass_t class )
+{
+ uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].text =
+ String_Alloc( BG_FindHumanNameForClassNum( class ) );
+ uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].cmd =
+ String_Alloc( va( "cmd class %s\n", BG_FindNameForClassNum( class ) ) );
+ uiInfo.tremAlienClassList[ uiInfo.tremAlienClassCount ].infopane =
+ UI_FindInfoPaneByName( va( "%sclass", BG_FindNameForClassNum( class ) ) );
+
+ uiInfo.tremAlienClassCount++;
+}
+
+/*
+===============
+UI_LoadTremAlienClasses
+===============
+*/
+static void UI_LoadTremAlienClasses( void )
+{
+ uiInfo.tremAlienClassCount = 0;
+
+ if( BG_ClassIsAllowed( PCL_ALIEN_LEVEL0 ) )
+ UI_AddClass( PCL_ALIEN_LEVEL0 );
+
+ if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0_UPG ) &&
+ BG_FindStagesForClass( PCL_ALIEN_BUILDER0_UPG, UI_GetCurrentAlienStage( ) ) )
+ UI_AddClass( PCL_ALIEN_BUILDER0_UPG );
+ else if( BG_ClassIsAllowed( PCL_ALIEN_BUILDER0 ) )
+ UI_AddClass( PCL_ALIEN_BUILDER0 );
+}
+
+/*
+===============
+UI_AddItem
+===============
+*/
+static void UI_AddItem( weapon_t weapon )
+{
+ uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].text =
+ String_Alloc( BG_FindHumanNameForWeapon( weapon ) );
+ uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].cmd =
+ String_Alloc( va( "cmd class %s\n", BG_FindNameForWeapon( weapon ) ) );
+ uiInfo.tremHumanItemList[ uiInfo.tremHumanItemCount ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( weapon ) ) );
+
+ uiInfo.tremHumanItemCount++;
+}
+
+/*
+===============
+UI_LoadTremHumanItems
+===============
+*/
+static void UI_LoadTremHumanItems( void )
+{
+ uiInfo.tremHumanItemCount = 0;
+
+ if( BG_WeaponIsAllowed( WP_MACHINEGUN ) )
+ UI_AddItem( WP_MACHINEGUN );
+
+ if( BG_WeaponIsAllowed( WP_HBUILD2 ) &&
+ BG_FindStagesForWeapon( WP_HBUILD2, UI_GetCurrentHumanStage( ) ) )
+ UI_AddItem( WP_HBUILD2 );
+ else if( BG_WeaponIsAllowed( WP_HBUILD ) )
+ UI_AddItem( WP_HBUILD );
+}
+
+/*
+===============
+UI_ParseCarriageList
+===============
+*/
+static void UI_ParseCarriageList( int *weapons, int *upgrades )
+{
+ int i;
+ char carriageCvar[ MAX_TOKEN_CHARS ];
+ char *iterator;
+ char buffer[ MAX_TOKEN_CHARS ];
+ char *bufPointer;
+
+ trap_Cvar_VariableStringBuffer( "ui_carriage", carriageCvar, sizeof( carriageCvar ) );
+ iterator = carriageCvar;
+
+ if( weapons )
+ *weapons = 0;
+
+ if( upgrades )
+ *upgrades = 0;
+
+ //simple parser to give rise to weapon/upgrade list
+ while( iterator && iterator[ 0 ] != '$' )
+ {
+ bufPointer = buffer;
+
+ if( iterator[ 0 ] == 'W' )
+ {
+ iterator++;
+
+ while( iterator[ 0 ] != ' ' )
+ *bufPointer++ = *iterator++;
+
+ *bufPointer++ = '\n';
+
+ i = atoi( buffer );
+
+ if( weapons )
+ *weapons |= ( 1 << i );
+ }
+ else if( iterator[ 0 ] == 'U' )
+ {
+ iterator++;
+
+ while( iterator[ 0 ] != ' ' )
+ *bufPointer++ = *iterator++;
+
+ *bufPointer++ = '\n';
+
+ i = atoi( buffer );
+
+ if( upgrades )
+ *upgrades |= ( 1 << i );
+ }
+
+ iterator++;
+ }
+}
+
+/*
+===============
+UI_LoadTremHumanArmouryBuys
+===============
+*/
+static void UI_LoadTremHumanArmouryBuys( void )
+{
+ int i, j = 0;
+ stage_t stage = UI_GetCurrentHumanStage( );
+ int weapons, upgrades;
+ int slots = 0;
+
+ UI_ParseCarriageList( &weapons, &upgrades );
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( weapons & ( 1 << i ) )
+ slots |= BG_FindSlotsForWeapon( i );
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( upgrades & ( 1 << i ) )
+ slots |= BG_FindSlotsForUpgrade( i );
+ }
+
+ uiInfo.tremHumanArmouryBuyCount = 0;
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( BG_FindTeamForWeapon( i ) == WUT_HUMANS &&
+ BG_FindPurchasableForWeapon( i ) &&
+ BG_FindStagesForWeapon( i, stage ) &&
+ BG_WeaponIsAllowed( i ) &&
+ !( BG_FindSlotsForWeapon( i ) & slots ) &&
+ !( weapons & ( 1 << i ) ) )
+ {
+ uiInfo.tremHumanArmouryBuyList[ j ].text =
+ String_Alloc( BG_FindHumanNameForWeapon( i ) );
+ uiInfo.tremHumanArmouryBuyList[ j ].cmd =
+ String_Alloc( va( "cmd buy %s retrigger\n", BG_FindNameForWeapon( i ) ) );
+ uiInfo.tremHumanArmouryBuyList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanArmouryBuyCount++;
+ }
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( BG_FindTeamForUpgrade( i ) == WUT_HUMANS &&
+ BG_FindPurchasableForUpgrade( i ) &&
+ BG_FindStagesForUpgrade( i, stage ) &&
+ BG_UpgradeIsAllowed( i ) &&
+ !( BG_FindSlotsForUpgrade( i ) & slots ) &&
+ !( upgrades & ( 1 << i ) ) )
+ {
+ uiInfo.tremHumanArmouryBuyList[ j ].text =
+ String_Alloc( BG_FindHumanNameForUpgrade( i ) );
+ uiInfo.tremHumanArmouryBuyList[ j ].cmd =
+ String_Alloc( va( "cmd buy %s retrigger\n", BG_FindNameForUpgrade( i ) ) );
+ uiInfo.tremHumanArmouryBuyList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForUpgrade( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanArmouryBuyCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadTremHumanArmourySells
+===============
+*/
+static void UI_LoadTremHumanArmourySells( void )
+{
+ int weapons, upgrades;
+ int i, j = 0;
+
+ uiInfo.tremHumanArmourySellCount = 0;
+ UI_ParseCarriageList( &weapons, &upgrades );
+
+ for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ )
+ {
+ if( weapons & ( 1 << i ) )
+ {
+ uiInfo.tremHumanArmourySellList[ j ].text = String_Alloc( BG_FindHumanNameForWeapon( i ) );
+ uiInfo.tremHumanArmourySellList[ j ].cmd =
+ String_Alloc( va( "cmd sell %s retrigger\n", BG_FindNameForWeapon( i ) ) );
+ uiInfo.tremHumanArmourySellList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForWeapon( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanArmourySellCount++;
+ }
+ }
+
+ for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ )
+ {
+ if( upgrades & ( 1 << i ) )
+ {
+ uiInfo.tremHumanArmourySellList[ j ].text = String_Alloc( BG_FindHumanNameForUpgrade( i ) );
+ uiInfo.tremHumanArmourySellList[ j ].cmd =
+ String_Alloc( va( "cmd sell %s retrigger\n", BG_FindNameForUpgrade( i ) ) );
+ uiInfo.tremHumanArmourySellList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sitem", BG_FindNameForUpgrade( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanArmourySellCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadTremAlienUpgrades
+===============
+*/
+static void UI_LoadTremAlienUpgrades( void )
+{
+ int i, j = 0;
+ int class, credits;
+ char ui_currentClass[ MAX_STRING_CHARS ];
+ stage_t stage = UI_GetCurrentAlienStage( );
+
+ trap_Cvar_VariableStringBuffer( "ui_currentClass", ui_currentClass, MAX_STRING_CHARS );
+ sscanf( ui_currentClass, "%d %d", &class, &credits );
+
+ uiInfo.tremAlienUpgradeCount = 0;
+
+ for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ )
+ {
+ if( BG_ClassCanEvolveFromTo( class, i, credits, 0 ) >= 0 &&
+ BG_FindStagesForClass( i, stage ) &&
+ BG_ClassIsAllowed( i ) )
+ {
+ uiInfo.tremAlienUpgradeList[ j ].text = String_Alloc( BG_FindHumanNameForClassNum( i ) );
+ uiInfo.tremAlienUpgradeList[ j ].cmd =
+ String_Alloc( va( "cmd class %s\n", BG_FindNameForClassNum( i ) ) );
+ uiInfo.tremAlienUpgradeList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sclass", BG_FindNameForClassNum( i ) ) );
+
+ j++;
+
+ uiInfo.tremAlienUpgradeCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadTremAlienBuilds
+===============
+*/
+static void UI_LoadTremAlienBuilds( void )
+{
+ int weapons;
+ int i, j = 0;
+ stage_t stage;
+
+ UI_ParseCarriageList( &weapons, NULL );
+ stage = UI_GetCurrentAlienStage( );
+
+ uiInfo.tremAlienBuildCount = 0;
+
+ for( i = BA_NONE +1; i < BA_NUM_BUILDABLES; i++ )
+ {
+ if( BG_FindTeamForBuildable( i ) == BIT_ALIENS &&
+ BG_FindBuildWeaponForBuildable( i ) & weapons &&
+ BG_FindStagesForBuildable( i, stage ) &&
+ BG_BuildableIsAllowed( i ) )
+ {
+ uiInfo.tremAlienBuildList[ j ].text =
+ String_Alloc( BG_FindHumanNameForBuildable( i ) );
+ uiInfo.tremAlienBuildList[ j ].cmd =
+ String_Alloc( va( "cmd build %s\n", BG_FindNameForBuildable( i ) ) );
+ uiInfo.tremAlienBuildList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sbuild", BG_FindNameForBuildable( i ) ) );
+
+ j++;
+
+ uiInfo.tremAlienBuildCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadTremHumanBuilds
+===============
+*/
+static void UI_LoadTremHumanBuilds( void )
+{
+ int weapons;
+ int i, j = 0;
+ stage_t stage;
+
+ UI_ParseCarriageList( &weapons, NULL );
+ stage = UI_GetCurrentHumanStage( );
+
+ uiInfo.tremHumanBuildCount = 0;
+
+ for( i = BA_NONE +1; i < BA_NUM_BUILDABLES; i++ )
+ {
+ if( BG_FindTeamForBuildable( i ) == BIT_HUMANS &&
+ BG_FindBuildWeaponForBuildable( i ) & weapons &&
+ BG_FindStagesForBuildable( i, stage ) &&
+ BG_BuildableIsAllowed( i ) )
+ {
+ uiInfo.tremHumanBuildList[ j ].text =
+ String_Alloc( BG_FindHumanNameForBuildable( i ) );
+ uiInfo.tremHumanBuildList[ j ].cmd =
+ String_Alloc( va( "cmd build %s\n", BG_FindNameForBuildable( i ) ) );
+ uiInfo.tremHumanBuildList[ j ].infopane =
+ UI_FindInfoPaneByName( va( "%sbuild", BG_FindNameForBuildable( i ) ) );
+
+ j++;
+
+ uiInfo.tremHumanBuildCount++;
+ }
+ }
+}
+
+/*
+===============
+UI_LoadMods
+===============
+*/
+static void UI_LoadMods( void ) {
+ int numdirs;
+ char dirlist[2048];
+ char *dirptr;
+ char *descptr;
+ int i;
+ int dirlen;
+
+ uiInfo.modCount = 0;
+ numdirs = trap_FS_GetFileList( "$modlist", "", dirlist, sizeof(dirlist) );
+ dirptr = dirlist;
+ for( i = 0; i < numdirs; i++ ) {
+ dirlen = strlen( dirptr ) + 1;
+ descptr = dirptr + dirlen;
+ uiInfo.modList[uiInfo.modCount].modName = String_Alloc(dirptr);
+ uiInfo.modList[uiInfo.modCount].modDescr = String_Alloc(descptr);
+ dirptr += dirlen + strlen(descptr) + 1;
+ uiInfo.modCount++;
+ if (uiInfo.modCount >= MAX_MODS) {
+ break;
+ }
+ }
+
+}
+
+
+/*
+===============
+UI_LoadMovies
+===============
+*/
+static void UI_LoadMovies( void ) {
+ char movielist[4096];
+ char *moviename;
+ int i, len;
+
+ uiInfo.movieCount = trap_FS_GetFileList( "video", "roq", movielist, 4096 );
+
+ if (uiInfo.movieCount) {
+ if (uiInfo.movieCount > MAX_MOVIES) {
+ uiInfo.movieCount = MAX_MOVIES;
+ }
+ moviename = movielist;
+ for ( i = 0; i < uiInfo.movieCount; i++ ) {
+ len = strlen( moviename );
+ if (!Q_stricmp(moviename + len - 4,".roq")) {
+ moviename[len-4] = '\0';
+ }
+ Q_strupr(moviename);
+ uiInfo.movieList[i] = String_Alloc(moviename);
+ moviename += len + 1;
+ }
+ }
+
+}
+
+
+
+/*
+===============
+UI_LoadDemos
+===============
+*/
+static void UI_LoadDemos( void ) {
+ char demolist[4096];
+ char demoExt[32];
+ char *demoname;
+ int i, len;
+
+ Com_sprintf(demoExt, sizeof(demoExt), "dm_%d", (int)trap_Cvar_VariableValue("protocol"));
+
+ uiInfo.demoCount = trap_FS_GetFileList( "demos", demoExt, demolist, 4096 );
+
+ Com_sprintf(demoExt, sizeof(demoExt), ".dm_%d", (int)trap_Cvar_VariableValue("protocol"));
+
+ if (uiInfo.demoCount) {
+ if (uiInfo.demoCount > MAX_DEMOS) {
+ uiInfo.demoCount = MAX_DEMOS;
+ }
+ demoname = demolist;
+ for ( i = 0; i < uiInfo.demoCount; i++ ) {
+ len = strlen( demoname );
+ if (!Q_stricmp(demoname + len - strlen(demoExt), demoExt)) {
+ demoname[len-strlen(demoExt)] = '\0';
+ }
+ Q_strupr(demoname);
+ uiInfo.demoList[i] = String_Alloc(demoname);
+ demoname += len + 1;
+ }
+ }
+
+}
+
+
+static qboolean UI_SetNextMap(int actual, int index) {
+ int i;
+ for (i = actual + 1; i < uiInfo.mapCount; i++) {
+ if (uiInfo.mapList[i].active) {
+ Menu_SetFeederSelection(NULL, FEEDER_MAPS, index + 1, "skirmish");
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+
+static void UI_StartSkirmish(qboolean next) {
+ int i, k, g, delay, temp;
+ float skill;
+ char buff[MAX_STRING_CHARS];
+
+ if (next) {
+ int actual;
+ int index = trap_Cvar_VariableValue("ui_mapIndex");
+ UI_MapCountByGameType(qtrue);
+ UI_SelectedMap(index, &actual);
+ if (UI_SetNextMap(actual, index)) {
+ } else {
+ UI_GameType_HandleKey(0, NULL, K_MOUSE1, qfalse);
+ UI_MapCountByGameType(qtrue);
+ Menu_SetFeederSelection(NULL, FEEDER_MAPS, 0, "skirmish");
+ }
+ }
+
+ g = uiInfo.gameTypes[ui_gameType.integer].gtEnum;
+ trap_Cvar_SetValue( "g_gametype", g );
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName) );
+ skill = trap_Cvar_VariableValue( "g_spSkill" );
+ trap_Cvar_Set("ui_scoreMap", uiInfo.mapList[ui_currentMap.integer].mapName);
+
+ k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_opponentName"));
+
+ trap_Cvar_Set("ui_singlePlayerActive", "1");
+
+ // set up sp overrides, will be replaced on postgame
+ temp = trap_Cvar_VariableValue( "capturelimit" );
+ trap_Cvar_Set("ui_saveCaptureLimit", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "fraglimit" );
+ trap_Cvar_Set("ui_saveFragLimit", va("%i", temp));
+
+ UI_SetCapFragLimits(qfalse);
+
+ temp = trap_Cvar_VariableValue( "cg_drawTimer" );
+ trap_Cvar_Set("ui_drawTimer", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "g_doWarmup" );
+ trap_Cvar_Set("ui_doWarmup", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "g_friendlyFire" );
+ trap_Cvar_Set("ui_friendlyFire", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "sv_maxClients" );
+ trap_Cvar_Set("ui_maxClients", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "g_warmup" );
+ trap_Cvar_Set("ui_Warmup", va("%i", temp));
+ temp = trap_Cvar_VariableValue( "sv_pure" );
+ trap_Cvar_Set("ui_pure", va("%i", temp));
+
+ trap_Cvar_Set("cg_cameraOrbit", "0");
+ trap_Cvar_Set("cg_thirdPerson", "0");
+ trap_Cvar_Set("cg_drawTimer", "1");
+ trap_Cvar_Set("g_doWarmup", "1");
+ trap_Cvar_Set("g_warmup", "15");
+ trap_Cvar_Set("sv_pure", "0");
+ trap_Cvar_Set("g_friendlyFire", "0");
+ trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName"));
+ trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName"));
+
+ if (trap_Cvar_VariableValue("ui_recordSPDemo")) {
+ Com_sprintf(buff, MAX_STRING_CHARS, "%s_%i", uiInfo.mapList[ui_currentMap.integer].mapLoadName, g);
+ trap_Cvar_Set("ui_recordSPDemoName", buff);
+ }
+
+ delay = 500;
+
+ {
+ temp = uiInfo.mapList[ui_currentMap.integer].teamMembers * 2;
+ trap_Cvar_Set("sv_maxClients", va("%d", temp));
+ for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers; i++) {
+ Com_sprintf( buff, sizeof(buff), "addbot %s %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, "", delay, uiInfo.teamList[k].teamMembers[i]);
+ trap_Cmd_ExecuteText( EXEC_APPEND, buff );
+ delay += 500;
+ }
+ k = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ for (i =0; i < uiInfo.mapList[ui_currentMap.integer].teamMembers-1; i++) {
+ Com_sprintf( buff, sizeof(buff), "addbot %s %f %s %i %s\n", UI_AIFromName(uiInfo.teamList[k].teamMembers[i]), skill, "", delay, uiInfo.teamList[k].teamMembers[i]);
+ trap_Cmd_ExecuteText( EXEC_APPEND, buff );
+ delay += 500;
+ }
+ }
+}
+
+static void UI_Update(const char *name) {
+ int val = trap_Cvar_VariableValue(name);
+
+ if (Q_stricmp(name, "ui_SetName") == 0) {
+ trap_Cvar_Set( "name", UI_Cvar_VariableString("ui_Name"));
+ } else if (Q_stricmp(name, "ui_setRate") == 0) {
+ float rate = trap_Cvar_VariableValue("rate");
+ if (rate >= 5000) {
+ trap_Cvar_Set("cl_maxpackets", "30");
+ trap_Cvar_Set("cl_packetdup", "1");
+ } else if (rate >= 4000) {
+ trap_Cvar_Set("cl_maxpackets", "15");
+ trap_Cvar_Set("cl_packetdup", "2"); // favor less prediction errors when there's packet loss
+ } else {
+ trap_Cvar_Set("cl_maxpackets", "15");
+ trap_Cvar_Set("cl_packetdup", "1"); // favor lower bandwidth
+ }
+ } else if (Q_stricmp(name, "ui_GetName") == 0) {
+ trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString("name"));
+ } else if (Q_stricmp(name, "r_colorbits") == 0) {
+ switch (val) {
+ case 0:
+ trap_Cvar_SetValue( "r_depthbits", 0 );
+ trap_Cvar_SetValue( "r_stencilbits", 0 );
+ break;
+ case 16:
+ trap_Cvar_SetValue( "r_depthbits", 16 );
+ trap_Cvar_SetValue( "r_stencilbits", 0 );
+ break;
+ case 32:
+ trap_Cvar_SetValue( "r_depthbits", 24 );
+ break;
+ }
+ } else if (Q_stricmp(name, "r_lodbias") == 0) {
+ switch (val) {
+ case 0:
+ trap_Cvar_SetValue( "r_subdivisions", 4 );
+ break;
+ case 1:
+ trap_Cvar_SetValue( "r_subdivisions", 12 );
+ break;
+ case 2:
+ trap_Cvar_SetValue( "r_subdivisions", 20 );
+ break;
+ }
+ } else if (Q_stricmp(name, "ui_glCustom") == 0) {
+ switch (val) {
+ case 0: // high quality
+ trap_Cvar_SetValue( "r_fullScreen", 1 );
+ trap_Cvar_SetValue( "r_subdivisions", 4 );
+ trap_Cvar_SetValue( "r_vertexlight", 0 );
+ trap_Cvar_SetValue( "r_lodbias", 0 );
+ trap_Cvar_SetValue( "r_colorbits", 32 );
+ trap_Cvar_SetValue( "r_depthbits", 24 );
+ trap_Cvar_SetValue( "r_picmip", 0 );
+ trap_Cvar_SetValue( "r_mode", 4 );
+ trap_Cvar_SetValue( "r_texturebits", 32 );
+ trap_Cvar_SetValue( "r_fastSky", 0 );
+ trap_Cvar_SetValue( "r_inGameVideo", 1 );
+ trap_Cvar_SetValue( "cg_shadows", 1 );
+ trap_Cvar_SetValue( "cg_brassTime", 2500 );
+ trap_Cvar_SetValue( "cg_bounceParticles", 1 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" );
+ break;
+ case 1: // normal
+ trap_Cvar_SetValue( "r_fullScreen", 1 );
+ trap_Cvar_SetValue( "r_subdivisions", 12 );
+ trap_Cvar_SetValue( "r_vertexlight", 0 );
+ trap_Cvar_SetValue( "r_lodbias", 0 );
+ trap_Cvar_SetValue( "r_colorbits", 0 );
+ trap_Cvar_SetValue( "r_depthbits", 24 );
+ trap_Cvar_SetValue( "r_picmip", 1 );
+ trap_Cvar_SetValue( "r_mode", 3 );
+ trap_Cvar_SetValue( "r_texturebits", 0 );
+ trap_Cvar_SetValue( "r_fastSky", 0 );
+ trap_Cvar_SetValue( "r_inGameVideo", 1 );
+ trap_Cvar_SetValue( "cg_brassTime", 2500 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_LINEAR" );
+ trap_Cvar_SetValue( "cg_shadows", 0 );
+ trap_Cvar_SetValue( "cg_bounceParticles", 0 );
+ break;
+ case 2: // fast
+ trap_Cvar_SetValue( "r_fullScreen", 1 );
+ trap_Cvar_SetValue( "r_subdivisions", 8 );
+ trap_Cvar_SetValue( "r_vertexlight", 0 );
+ trap_Cvar_SetValue( "r_lodbias", 1 );
+ trap_Cvar_SetValue( "r_colorbits", 0 );
+ trap_Cvar_SetValue( "r_depthbits", 0 );
+ trap_Cvar_SetValue( "r_picmip", 1 );
+ trap_Cvar_SetValue( "r_mode", 3 );
+ trap_Cvar_SetValue( "r_texturebits", 0 );
+ trap_Cvar_SetValue( "cg_shadows", 0 );
+ trap_Cvar_SetValue( "r_fastSky", 1 );
+ trap_Cvar_SetValue( "r_inGameVideo", 0 );
+ trap_Cvar_SetValue( "cg_brassTime", 0 );
+ trap_Cvar_SetValue( "cg_bounceParticles", 0 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" );
+ break;
+ case 3: // fastest
+ trap_Cvar_SetValue( "r_fullScreen", 1 );
+ trap_Cvar_SetValue( "r_subdivisions", 20 );
+ trap_Cvar_SetValue( "r_vertexlight", 1 );
+ trap_Cvar_SetValue( "r_lodbias", 2 );
+ trap_Cvar_SetValue( "r_colorbits", 16 );
+ trap_Cvar_SetValue( "r_depthbits", 16 );
+ trap_Cvar_SetValue( "r_mode", 3 );
+ trap_Cvar_SetValue( "r_picmip", 2 );
+ trap_Cvar_SetValue( "r_texturebits", 16 );
+ trap_Cvar_SetValue( "cg_shadows", 0 );
+ trap_Cvar_SetValue( "cg_brassTime", 0 );
+ trap_Cvar_SetValue( "r_fastSky", 1 );
+ trap_Cvar_SetValue( "r_inGameVideo", 0 );
+ trap_Cvar_SetValue( "cg_bounceParticles", 0 );
+ trap_Cvar_Set( "r_texturemode", "GL_LINEAR_MIPMAP_NEAREST" );
+ break;
+ }
+ } else if (Q_stricmp(name, "ui_mousePitch") == 0) {
+ if (val == 0) {
+ trap_Cvar_SetValue( "m_pitch", 0.022f );
+ } else {
+ trap_Cvar_SetValue( "m_pitch", -0.022f );
+ }
+ }
+}
+
+static void UI_RunMenuScript(char **args) {
+ const char *name, *name2;
+ char buff[1024];
+ const char *cmd;
+
+ if (String_Parse(args, &name)) {
+ if (Q_stricmp(name, "StartServer") == 0) {
+ int i, clients, oldclients;
+ float skill;
+ trap_Cvar_Set("cg_thirdPerson", "0");
+ trap_Cvar_Set("cg_cameraOrbit", "0");
+ trap_Cvar_Set("ui_singlePlayerActive", "0");
+ trap_Cvar_SetValue( "dedicated", Com_Clamp( 0, 2, ui_dedicated.integer ) );
+ trap_Cvar_SetValue( "g_gametype", Com_Clamp( 0, 8, uiInfo.gameTypes[ui_netGameType.integer].gtEnum ) );
+ trap_Cvar_Set("g_redTeam", UI_Cvar_VariableString("ui_teamName"));
+ trap_Cvar_Set("g_blueTeam", UI_Cvar_VariableString("ui_opponentName"));
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "wait ; wait ; map %s\n", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName ) );
+ skill = trap_Cvar_VariableValue( "g_spSkill" );
+ // set max clients based on spots
+ oldclients = trap_Cvar_VariableValue( "sv_maxClients" );
+ clients = 0;
+ for (i = 0; i < PLAYERS_PER_TEAM; i++) {
+ int bot = trap_Cvar_VariableValue( va("ui_blueteam%i", i+1));
+ if (bot >= 0) {
+ clients++;
+ }
+ bot = trap_Cvar_VariableValue( va("ui_redteam%i", i+1));
+ if (bot >= 0) {
+ clients++;
+ }
+ }
+ if (clients == 0) {
+ clients = 8;
+ }
+
+ if (oldclients > clients) {
+ clients = oldclients;
+ }
+
+ trap_Cvar_Set("sv_maxClients", va("%d",clients));
+
+ for (i = 0; i < PLAYERS_PER_TEAM; i++) {
+ int bot = trap_Cvar_VariableValue( va("ui_blueteam%i", i+1));
+ if (bot > 1) {
+ Com_sprintf( buff, sizeof(buff), "addbot %s %f \n", UI_GetBotNameByNumber(bot-2), skill);
+ trap_Cmd_ExecuteText( EXEC_APPEND, buff );
+ }
+ bot = trap_Cvar_VariableValue( va("ui_redteam%i", i+1));
+ if (bot > 1) {
+ Com_sprintf( buff, sizeof(buff), "addbot %s %f \n", UI_GetBotNameByNumber(bot-2), skill);
+ trap_Cmd_ExecuteText( EXEC_APPEND, buff );
+ }
+ }
+ } else if (Q_stricmp(name, "updateSPMenu") == 0) {
+ UI_SetCapFragLimits(qtrue);
+ UI_MapCountByGameType(qtrue);
+ ui_mapIndex.integer = UI_GetIndexFromSelection(ui_currentMap.integer);
+ trap_Cvar_Set("ui_mapIndex", va("%d", ui_mapIndex.integer));
+ Menu_SetFeederSelection(NULL, FEEDER_MAPS, ui_mapIndex.integer, "skirmish");
+ UI_GameType_HandleKey(0, NULL, K_MOUSE1, qfalse);
+ UI_GameType_HandleKey(0, NULL, K_MOUSE2, qfalse);
+ } else if (Q_stricmp(name, "resetDefaults") == 0) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, "exec default.cfg\n");
+ trap_Cmd_ExecuteText( EXEC_APPEND, "cvar_restart\n");
+ Controls_SetDefaults();
+ trap_Cvar_Set("com_introPlayed", "1" );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart\n" );
+ } else if (Q_stricmp(name, "loadArenas") == 0) {
+ UI_LoadArenas();
+ UI_MapCountByGameType(qfalse);
+ Menu_SetFeederSelection(NULL, FEEDER_ALLMAPS, 0, "createserver");
+ } else if (Q_stricmp(name, "loadServerInfo") == 0) {
+ UI_ServerInfo();
+ } else if (Q_stricmp(name, "saveControls") == 0) {
+ Controls_SetConfig(qtrue);
+ } else if (Q_stricmp(name, "loadControls") == 0) {
+ Controls_GetConfig();
+ } else if (Q_stricmp(name, "clearError") == 0) {
+ trap_Cvar_Set("com_errorMessage", "");
+ } else if (Q_stricmp(name, "loadGameInfo") == 0) {
+/* UI_ParseGameInfo("gameinfo.txt");
+ UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);*/
+ } else if (Q_stricmp(name, "resetScores") == 0) {
+ UI_ClearScores();
+ } else if (Q_stricmp(name, "RefreshServers") == 0) {
+ UI_StartServerRefresh(qtrue);
+ UI_BuildServerDisplayList(qtrue);
+ } else if (Q_stricmp(name, "InitServerList") == 0) {
+ int time = trap_RealTime( NULL );
+ int last;
+ int sortColumn;
+
+ // set up default sorting
+ if(!uiInfo.serverStatus.sorted && Int_Parse(args, &sortColumn))
+ {
+ uiInfo.serverStatus.sortKey = sortColumn;
+ uiInfo.serverStatus.sortDir = 0;
+ }
+
+ // refresh if older than 3 days or if list is empty
+ last = atoi( UI_Cvar_VariableString( va( "ui_lastServerRefresh_%i_time",
+ ui_netSource.integer ) ) );
+ if( trap_LAN_GetServerCount( ui_netSource.integer ) < 1 ||
+ ( time - last ) > 3600 )
+ {
+ UI_StartServerRefresh(qtrue);
+ UI_BuildServerDisplayList(qtrue);
+ }
+ } else if (Q_stricmp(name, "RefreshFilter") == 0) {
+ UI_StartServerRefresh(qfalse);
+ UI_BuildServerDisplayList(qtrue);
+ } else if (Q_stricmp(name, "RunSPDemo") == 0) {
+ if (uiInfo.demoAvailable) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s_%i\n", uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum));
+ }
+ } else if (Q_stricmp(name, "LoadDemos") == 0) {
+ UI_LoadDemos();
+ } else if (Q_stricmp(name, "LoadMovies") == 0) {
+ UI_LoadMovies();
+ } else if (Q_stricmp(name, "LoadMods") == 0) {
+ UI_LoadMods();
+ }
+
+//TA: tremulous menus
+ else if( Q_stricmp( name, "LoadTeams" ) == 0 )
+ UI_LoadTremTeams( );
+ else if( Q_stricmp( name, "JoinTeam" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremTeamList[ uiInfo.tremTeamIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanItems" ) == 0 )
+ UI_LoadTremHumanItems( );
+ else if( Q_stricmp( name, "SpawnWithHumanItem" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremHumanItemList[ uiInfo.tremHumanItemIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadAlienClasses" ) == 0 )
+ UI_LoadTremAlienClasses( );
+ else if( Q_stricmp( name, "SpawnAsAlienClass" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremAlienClassList[ uiInfo.tremAlienClassIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanArmouryBuys" ) == 0 )
+ UI_LoadTremHumanArmouryBuys( );
+ else if( Q_stricmp( name, "BuyFromArmoury" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremHumanArmouryBuyList[ uiInfo.tremHumanArmouryBuyIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanArmourySells" ) == 0 )
+ UI_LoadTremHumanArmourySells( );
+ else if( Q_stricmp( name, "SellToArmoury" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremHumanArmourySellList[ uiInfo.tremHumanArmourySellIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadAlienUpgrades" ) == 0 )
+ {
+ UI_LoadTremAlienUpgrades( );
+
+ //disallow the menu if it would be empty
+ if( uiInfo.tremAlienUpgradeCount <= 0 )
+ Menus_CloseAll( );
+ }
+ else if( Q_stricmp( name, "UpgradeToNewClass" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremAlienUpgradeList[ uiInfo.tremAlienUpgradeIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadAlienBuilds" ) == 0 )
+ UI_LoadTremAlienBuilds( );
+ else if( Q_stricmp( name, "BuildAlienBuildable" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremAlienBuildList[ uiInfo.tremAlienBuildIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "LoadHumanBuilds" ) == 0 )
+ UI_LoadTremHumanBuilds( );
+ else if( Q_stricmp( name, "BuildHumanBuildable" ) == 0 )
+ {
+ if( ( cmd = uiInfo.tremHumanBuildList[ uiInfo.tremHumanBuildIndex ].cmd ) )
+ trap_Cmd_ExecuteText( EXEC_APPEND, cmd );
+ }
+ else if( Q_stricmp( name, "PTRCRestore" ) == 0 )
+ {
+ int len;
+ char text[ 16 ];
+ fileHandle_t f;
+ char command[ 32 ];
+
+ // load the file
+ len = trap_FS_FOpenFile( "ptrc.cfg", &f, FS_READ );
+
+ if( len > 0 && ( len < sizeof( text ) - 1 ) )
+ {
+ trap_FS_Read( text, len, f );
+ text[ len ] = 0;
+ trap_FS_FCloseFile( f );
+
+ Com_sprintf( command, 32, "ptrcrestore %s", text );
+
+ trap_Cmd_ExecuteText( EXEC_APPEND, command );
+ }
+ }
+//TA: tremulous menus
+
+ else if (Q_stricmp(name, "playMovie") == 0) {
+ if (uiInfo.previewMovie >= 0) {
+ trap_CIN_StopCinematic(uiInfo.previewMovie);
+ }
+ trap_Cmd_ExecuteText( EXEC_APPEND, va("cinematic %s.roq 2\n", uiInfo.movieList[uiInfo.movieIndex]));
+ } else if (Q_stricmp(name, "RunMod") == 0) {
+ trap_Cvar_Set( "fs_game", uiInfo.modList[uiInfo.modIndex].modName);
+ trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" );
+ } else if (Q_stricmp(name, "RunDemo") == 0) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va("demo %s\n", uiInfo.demoList[uiInfo.demoIndex]));
+ } else if (Q_stricmp(name, "Tremulous") == 0) {
+ trap_Cvar_Set( "fs_game", "");
+ trap_Cmd_ExecuteText( EXEC_APPEND, "vid_restart;" );
+ } else if (Q_stricmp(name, "closeJoin") == 0) {
+ if (uiInfo.serverStatus.refreshActive) {
+ UI_StopServerRefresh();
+ uiInfo.serverStatus.nextDisplayRefresh = 0;
+ uiInfo.nextServerStatusRefresh = 0;
+ uiInfo.nextFindPlayerRefresh = 0;
+ UI_BuildServerDisplayList(qtrue);
+ } else {
+ Menus_CloseByName("joinserver");
+ Menus_OpenByName("main");
+ }
+ } else if (Q_stricmp(name, "StopRefresh") == 0) {
+ UI_StopServerRefresh();
+ uiInfo.serverStatus.nextDisplayRefresh = 0;
+ uiInfo.nextServerStatusRefresh = 0;
+ uiInfo.nextFindPlayerRefresh = 0;
+ } else if (Q_stricmp(name, "UpdateFilter") == 0) {
+ if (ui_netSource.integer == AS_LOCAL) {
+ UI_StartServerRefresh(qtrue);
+ }
+ UI_BuildServerDisplayList(qtrue);
+ UI_FeederSelection(FEEDER_SERVERS, 0);
+ } else if (Q_stricmp(name, "ServerStatus") == 0) {
+ trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], uiInfo.serverStatusAddress, sizeof(uiInfo.serverStatusAddress));
+ UI_BuildServerStatus(qtrue);
+ } else if (Q_stricmp(name, "FoundPlayerServerStatus") == 0) {
+ Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress));
+ UI_BuildServerStatus(qtrue);
+ Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL);
+ } else if (Q_stricmp(name, "FindPlayer") == 0) {
+ UI_BuildFindPlayerList(qtrue);
+ // clear the displayed server status info
+ uiInfo.serverStatusInfo.numLines = 0;
+ Menu_SetFeederSelection(NULL, FEEDER_FINDPLAYER, 0, NULL);
+ } else if (Q_stricmp(name, "JoinServer") == 0) {
+ trap_Cvar_Set("cg_thirdPerson", "0");
+ trap_Cvar_Set("cg_cameraOrbit", "0");
+ trap_Cvar_Set("ui_singlePlayerActive", "0");
+ if (uiInfo.serverStatus.currentServer >= 0 && uiInfo.serverStatus.currentServer < uiInfo.serverStatus.numDisplayServers) {
+ trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, 1024);
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", buff ) );
+ }
+ } else if (Q_stricmp(name, "FoundPlayerJoinServer") == 0) {
+ trap_Cvar_Set("ui_singlePlayerActive", "0");
+ if (uiInfo.currentFoundPlayerServer >= 0 && uiInfo.currentFoundPlayerServer < uiInfo.numFoundPlayerServers) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "connect %s\n", uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer] ) );
+ }
+ } else if (Q_stricmp(name, "Quit") == 0) {
+ trap_Cvar_Set("ui_singlePlayerActive", "0");
+ trap_Cmd_ExecuteText( EXEC_NOW, "quit");
+ } else if (Q_stricmp(name, "Controls") == 0) {
+ trap_Cvar_Set( "cl_paused", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_CloseAll();
+ Menus_ActivateByName("setup_menu2");
+ } else if (Q_stricmp(name, "Leave") == 0) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, "disconnect\n" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_CloseAll();
+ Menus_ActivateByName("main");
+ } else if (Q_stricmp(name, "ServerSort") == 0) {
+ int sortColumn;
+ if (Int_Parse(args, &sortColumn)) {
+ // if same column we're already sorting on then flip the direction
+ if (sortColumn == uiInfo.serverStatus.sortKey) {
+ uiInfo.serverStatus.sortDir = !uiInfo.serverStatus.sortDir;
+ }
+ // make sure we sort again
+ UI_ServersSort(sortColumn, qtrue);
+ uiInfo.serverStatus.sorted = qtrue;
+ }
+ } else if (Q_stricmp(name, "nextSkirmish") == 0) {
+ UI_StartSkirmish(qtrue);
+ } else if (Q_stricmp(name, "SkirmishStart") == 0) {
+ UI_StartSkirmish(qfalse);
+ } else if (Q_stricmp(name, "closeingame") == 0) {
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+ } else if (Q_stricmp(name, "voteMap") == 0) {
+ if (ui_currentNetMap.integer >=0 && ui_currentNetMap.integer < uiInfo.mapCount) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va("callvote map %s\n",uiInfo.mapList[ui_currentNetMap.integer].mapLoadName) );
+ }
+ }
+ else if( Q_stricmp( name, "voteKick" ) == 0 )
+ {
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote kick %d\n",
+ uiInfo.clientNums[ uiInfo.playerIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteMute" ) == 0 )
+ {
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote mute %d\n",
+ uiInfo.clientNums[ uiInfo.playerIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteUnMute" ) == 0 )
+ {
+ if( uiInfo.playerIndex >= 0 && uiInfo.playerIndex < uiInfo.playerCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callvote unmute %d\n",
+ uiInfo.clientNums[ uiInfo.playerIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteTeamKick" ) == 0 )
+ {
+ if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote kick %d\n",
+ uiInfo.teamClientNums[ uiInfo.teamIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteTeamDenyBuild" ) == 0 )
+ {
+ if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote denybuild %d\n",
+ uiInfo.teamClientNums[ uiInfo.teamIndex ] ) );
+ }
+ }
+ else if( Q_stricmp( name, "voteTeamAllowBuild" ) == 0 )
+ {
+ if( uiInfo.teamIndex >= 0 && uiInfo.teamIndex < uiInfo.myTeamCount )
+ {
+ trap_Cmd_ExecuteText( EXEC_APPEND, va( "callteamvote allowbuild %d\n",
+ uiInfo.teamClientNums[ uiInfo.teamIndex ] ) );
+ }
+ }
+ else if (Q_stricmp(name, "addFavorite") == 0) {
+ if (ui_netSource.integer != AS_FAVORITES) {
+ char name[MAX_NAME_LENGTH];
+ char addr[MAX_NAME_LENGTH];
+ int res;
+
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS);
+ name[0] = addr[0] = '\0';
+ Q_strncpyz(name, Info_ValueForKey(buff, "hostname"), MAX_NAME_LENGTH);
+ Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH);
+ if (strlen(name) > 0 && strlen(addr) > 0) {
+ res = trap_LAN_AddServer(AS_FAVORITES, name, addr);
+ if (res == 0) {
+ // server already in the list
+ Com_Printf("Favorite already in list\n");
+ }
+ else if (res == -1) {
+ // list full
+ Com_Printf("Favorite list full\n");
+ }
+ else {
+ // successfully added
+ Com_Printf("Added favorite server %s\n", addr);
+ }
+ }
+ }
+ } else if (Q_stricmp(name, "deleteFavorite") == 0) {
+ if (ui_netSource.integer == AS_FAVORITES) {
+ char addr[MAX_NAME_LENGTH];
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.serverStatus.currentServer], buff, MAX_STRING_CHARS);
+ addr[0] = '\0';
+ Q_strncpyz(addr, Info_ValueForKey(buff, "addr"), MAX_NAME_LENGTH);
+ if (strlen(addr) > 0) {
+ trap_LAN_RemoveServer(AS_FAVORITES, addr);
+ }
+ }
+ } else if (Q_stricmp(name, "createFavorite") == 0) {
+ if (ui_netSource.integer == AS_FAVORITES) {
+ char name[MAX_NAME_LENGTH];
+ char addr[MAX_NAME_LENGTH];
+ int res;
+
+ name[0] = addr[0] = '\0';
+ Q_strncpyz(name, UI_Cvar_VariableString("ui_favoriteName"), MAX_NAME_LENGTH);
+ Q_strncpyz(addr, UI_Cvar_VariableString("ui_favoriteAddress"), MAX_NAME_LENGTH);
+ if (strlen(name) > 0 && strlen(addr) > 0) {
+ res = trap_LAN_AddServer(AS_FAVORITES, name, addr);
+ if (res == 0) {
+ // server already in the list
+ Com_Printf("Favorite already in list\n");
+ }
+ else if (res == -1) {
+ // list full
+ Com_Printf("Favorite list full\n");
+ }
+ else {
+ // successfully added
+ Com_Printf("Added favorite server %s\n", addr);
+ }
+ }
+ }
+ } else if (Q_stricmp(name, "orders") == 0) {
+ const char *orders;
+ if (String_Parse(args, &orders)) {
+ int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer");
+ if (selectedPlayer < uiInfo.myTeamCount) {
+ strcpy(buff, orders);
+ trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
+ } else {
+ int i;
+ for (i = 0; i < uiInfo.myTeamCount; i++) {
+ if (Q_stricmp(UI_Cvar_VariableString("name"), uiInfo.teamNames[i]) == 0) {
+ continue;
+ }
+ strcpy(buff, orders);
+ trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamNames[i]) );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
+ }
+ }
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+ }
+ } else if (Q_stricmp(name, "voiceOrdersTeam") == 0) {
+ const char *orders;
+ if (String_Parse(args, &orders)) {
+ int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer");
+ if (selectedPlayer == uiInfo.myTeamCount) {
+ trap_Cmd_ExecuteText( EXEC_APPEND, orders );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
+ }
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+ }
+ } else if (Q_stricmp(name, "voiceOrders") == 0) {
+ const char *orders;
+ if (String_Parse(args, &orders)) {
+ int selectedPlayer = trap_Cvar_VariableValue("cg_selectedPlayer");
+ if (selectedPlayer < uiInfo.myTeamCount) {
+ strcpy(buff, orders);
+ trap_Cmd_ExecuteText( EXEC_APPEND, va(buff, uiInfo.teamClientNums[selectedPlayer]) );
+ trap_Cmd_ExecuteText( EXEC_APPEND, "\n" );
+ }
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+ }
+ } else if (Q_stricmp(name, "glCustom") == 0) {
+ trap_Cvar_Set("ui_glCustom", "4");
+ } else if (Q_stricmp(name, "update") == 0) {
+ if (String_Parse(args, &name2))
+ UI_Update(name2);
+ } else if (Q_stricmp(name, "InitIgnoreList") == 0) {
+ UI_BuildPlayerList();
+ } else if (Q_stricmp(name, "ToggleIgnore") == 0) {
+ if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount )
+ {
+ if( BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) )
+ {
+ BG_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "unignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ else
+ {
+ BG_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "ignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ }
+ } else if (Q_stricmp(name, "IgnorePlayer") == 0) {
+ if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount )
+ {
+ if( !BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) )
+ {
+ BG_ClientListAdd( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "ignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ }
+ } else if (Q_stricmp(name, "UnIgnorePlayer") == 0) {
+ if( uiInfo.ignoreIndex >= 0 && uiInfo.ignoreIndex < uiInfo.playerCount )
+ {
+ if( BG_ClientListTest( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) )
+ {
+ BG_ClientListRemove( &uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] );
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "unignore %i\n",
+ uiInfo.clientNums[ uiInfo.ignoreIndex ] ) );
+ }
+ }
+ } else if (Q_stricmp(name, "setPbClStatus") == 0) {
+ int stat;
+ if ( Int_Parse( args, &stat ) )
+ trap_SetPbClStatus( stat );
+ }
+ else {
+ Com_Printf("unknown UI script %s\n", name);
+ }
+ }
+}
+
+static void UI_GetTeamColor(vec4_t *color) {
+}
+
+/*
+==================
+UI_MapCountByGameType
+==================
+*/
+static int UI_MapCountByGameType(qboolean singlePlayer) {
+ int i, c, game;
+ c = 0;
+ game = singlePlayer ? uiInfo.gameTypes[ui_gameType.integer].gtEnum : uiInfo.gameTypes[ui_netGameType.integer].gtEnum;
+
+ for (i = 0; i < uiInfo.mapCount; i++) {
+ uiInfo.mapList[i].active = qfalse;
+ if ( uiInfo.mapList[i].typeBits & (1 << game)) {
+ if (singlePlayer) {
+ if (!(uiInfo.mapList[i].typeBits & (1 << 2))) {
+ continue;
+ }
+ }
+ c++;
+ uiInfo.mapList[i].active = qtrue;
+ }
+ }
+ return c;
+}
+
+qboolean UI_hasSkinForBase(const char *base, const char *team) {
+ char test[1024];
+
+ Com_sprintf( test, sizeof( test ), "models/players/%s/%s/lower_default.skin", base, team );
+
+ if (trap_FS_FOpenFile(test, NULL, FS_READ)) {
+ return qtrue;
+ }
+ Com_sprintf( test, sizeof( test ), "models/players/characters/%s/%s/lower_default.skin", base, team );
+
+ if (trap_FS_FOpenFile(test, NULL, FS_READ)) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+UI_MapCountByTeam
+==================
+*/
+static int UI_HeadCountByTeam( void ) {
+ static int init = 0;
+ int i, j, k, c, tIndex;
+
+ c = 0;
+ if (!init) {
+ for (i = 0; i < uiInfo.characterCount; i++) {
+ uiInfo.characterList[i].reference = 0;
+ for (j = 0; j < uiInfo.teamCount; j++) {
+ if (UI_hasSkinForBase(uiInfo.characterList[i].base, uiInfo.teamList[j].teamName)) {
+ uiInfo.characterList[i].reference |= (1<<j);
+ }
+ }
+ }
+ init = 1;
+ }
+
+ tIndex = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+
+ // do names
+ for (i = 0; i < uiInfo.characterCount; i++) {
+ uiInfo.characterList[i].active = qfalse;
+ for(j = 0; j < TEAM_MEMBERS; j++) {
+ if (uiInfo.teamList[tIndex].teamMembers[j] != NULL) {
+ if (uiInfo.characterList[i].reference&(1<<tIndex)) {// && Q_stricmp(uiInfo.teamList[tIndex].teamMembers[j], uiInfo.characterList[i].name)==0) {
+ uiInfo.characterList[i].active = qtrue;
+ c++;
+ break;
+ }
+ }
+ }
+ }
+
+ // and then aliases
+ for(j = 0; j < TEAM_MEMBERS; j++) {
+ for(k = 0; k < uiInfo.aliasCount; k++) {
+ if (uiInfo.aliasList[k].name != NULL) {
+ if (Q_stricmp(uiInfo.teamList[tIndex].teamMembers[j], uiInfo.aliasList[k].name)==0) {
+ for (i = 0; i < uiInfo.characterCount; i++) {
+ if (uiInfo.characterList[i].headImage != -1 && uiInfo.characterList[i].reference&(1<<tIndex) && Q_stricmp(uiInfo.aliasList[k].ai, uiInfo.characterList[i].name)==0) {
+ if (uiInfo.characterList[i].active == qfalse) {
+ uiInfo.characterList[i].active = qtrue;
+ c++;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ return c;
+}
+
+/*
+==================
+UI_InsertServerIntoDisplayList
+==================
+*/
+static void UI_InsertServerIntoDisplayList(int num, int position) {
+ int i;
+
+ if (position < 0 || position > uiInfo.serverStatus.numDisplayServers ) {
+ return;
+ }
+ //
+ uiInfo.serverStatus.numDisplayServers++;
+ for (i = uiInfo.serverStatus.numDisplayServers; i > position; i--) {
+ uiInfo.serverStatus.displayServers[i] = uiInfo.serverStatus.displayServers[i-1];
+ }
+ uiInfo.serverStatus.displayServers[position] = num;
+}
+
+/*
+==================
+UI_RemoveServerFromDisplayList
+==================
+*/
+static void UI_RemoveServerFromDisplayList(int num) {
+ int i, j;
+
+ for (i = 0; i < uiInfo.serverStatus.numDisplayServers; i++) {
+ if (uiInfo.serverStatus.displayServers[i] == num) {
+ uiInfo.serverStatus.numDisplayServers--;
+ for (j = i; j < uiInfo.serverStatus.numDisplayServers; j++) {
+ uiInfo.serverStatus.displayServers[j] = uiInfo.serverStatus.displayServers[j+1];
+ }
+ return;
+ }
+ }
+}
+
+/*
+==================
+UI_BinaryServerInsertion
+==================
+*/
+static void UI_BinaryServerInsertion(int num) {
+ int mid, offset, res, len;
+
+ // use binary search to insert server
+ len = uiInfo.serverStatus.numDisplayServers;
+ mid = len;
+ offset = 0;
+ res = 0;
+ while(mid > 0) {
+ mid = len >> 1;
+ //
+ res = trap_LAN_CompareServers( ui_netSource.integer, uiInfo.serverStatus.sortKey,
+ uiInfo.serverStatus.sortDir, num, uiInfo.serverStatus.displayServers[offset+mid]);
+ // if equal
+ if (res == 0) {
+ UI_InsertServerIntoDisplayList(num, offset+mid);
+ return;
+ }
+ // if larger
+ else if (res == 1) {
+ offset += mid;
+ len -= mid;
+ }
+ // if smaller
+ else {
+ len -= mid;
+ }
+ }
+ if (res == 1) {
+ offset++;
+ }
+ UI_InsertServerIntoDisplayList(num, offset);
+}
+
+/*
+==================
+UI_BuildServerDisplayList
+==================
+*/
+static void UI_BuildServerDisplayList(qboolean force) {
+ int i, count, clients, maxClients, ping, game, len, visible;
+ char info[MAX_STRING_CHARS];
+// qboolean startRefresh = qtrue; TTimo: unused
+ static int numinvisible;
+
+ if (!(force || uiInfo.uiDC.realTime > uiInfo.serverStatus.nextDisplayRefresh)) {
+ return;
+ }
+ // if we shouldn't reset
+ if ( force == 2 ) {
+ force = 0;
+ }
+
+ // do motd updates here too
+ trap_Cvar_VariableStringBuffer( "cl_motdString", uiInfo.serverStatus.motd, sizeof(uiInfo.serverStatus.motd) );
+ len = strlen(uiInfo.serverStatus.motd);
+ if (len != uiInfo.serverStatus.motdLen) {
+ uiInfo.serverStatus.motdLen = len;
+ uiInfo.serverStatus.motdWidth = -1;
+ }
+
+ if (force) {
+ numinvisible = 0;
+ // clear number of displayed servers
+ uiInfo.serverStatus.numDisplayServers = 0;
+ uiInfo.serverStatus.numPlayersOnServers = 0;
+ // set list box index to zero
+ Menu_SetFeederSelection(NULL, FEEDER_SERVERS, 0, NULL);
+ // mark all servers as visible so we store ping updates for them
+ trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue);
+ }
+
+ // get the server count (comes from the master)
+ count = trap_LAN_GetServerCount(ui_netSource.integer);
+ if (count == -1 || (ui_netSource.integer == AS_LOCAL && count == 0) ) {
+ // still waiting on a response from the master
+ uiInfo.serverStatus.numDisplayServers = 0;
+ uiInfo.serverStatus.numPlayersOnServers = 0;
+ uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 500;
+ return;
+ }
+
+ visible = qfalse;
+ for (i = 0; i < count; i++) {
+ // if we already got info for this server
+ if (!trap_LAN_ServerIsVisible(ui_netSource.integer, i)) {
+ continue;
+ }
+ visible = qtrue;
+ // get the ping for this server
+ ping = trap_LAN_GetServerPing(ui_netSource.integer, i);
+ if (ping > 0 || ui_netSource.integer == AS_FAVORITES) {
+
+ trap_LAN_GetServerInfo(ui_netSource.integer, i, info, MAX_STRING_CHARS);
+
+ clients = atoi(Info_ValueForKey(info, "clients"));
+ uiInfo.serverStatus.numPlayersOnServers += clients;
+
+ if (ui_browserShowEmpty.integer == 0) {
+ if (clients == 0) {
+ trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
+ continue;
+ }
+ }
+
+ if (ui_browserShowFull.integer == 0) {
+ maxClients = atoi(Info_ValueForKey(info, "sv_maxclients"));
+ if (clients == maxClients) {
+ trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
+ continue;
+ }
+ }
+
+ if (uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum != -1) {
+ game = atoi(Info_ValueForKey(info, "gametype"));
+ if (game != uiInfo.joinGameTypes[ui_joinGameType.integer].gtEnum) {
+ trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
+ continue;
+ }
+ }
+
+ // make sure we never add a favorite server twice
+ if (ui_netSource.integer == AS_FAVORITES) {
+ UI_RemoveServerFromDisplayList(i);
+ }
+ // insert the server into the list
+ UI_BinaryServerInsertion(i);
+ // done with this server
+ if (ping > 0) {
+ trap_LAN_MarkServerVisible(ui_netSource.integer, i, qfalse);
+ numinvisible++;
+ }
+ }
+ }
+
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime;
+
+ // if there were no servers visible for ping updates
+ if (!visible) {
+// UI_StopServerRefresh();
+// uiInfo.serverStatus.nextDisplayRefresh = 0;
+ }
+}
+
+typedef struct
+{
+ char *name, *altName;
+} serverStatusCvar_t;
+
+serverStatusCvar_t serverStatusCvars[] = {
+ {"sv_hostname", "Name"},
+ {"Address", ""},
+ {"gamename", "Game name"},
+ {"g_gametype", "Game type"},
+ {"mapname", "Map"},
+ {"version", ""},
+ {"protocol", ""},
+ {"timelimit", ""},
+ {"fraglimit", ""},
+ {NULL, NULL}
+};
+
+/*
+==================
+UI_SortServerStatusInfo
+==================
+*/
+static void UI_SortServerStatusInfo( serverStatusInfo_t *info ) {
+ int i, j, index;
+ char *tmp1, *tmp2;
+
+ // FIXME: if "gamename" == "baseq3" or "missionpack" then
+ // replace the gametype number by FFA, CTF etc.
+ //
+ index = 0;
+ for (i = 0; serverStatusCvars[i].name; i++) {
+ for (j = 0; j < info->numLines; j++) {
+ if ( !info->lines[j][1] || info->lines[j][1][0] ) {
+ continue;
+ }
+ if ( !Q_stricmp(serverStatusCvars[i].name, info->lines[j][0]) ) {
+ // swap lines
+ tmp1 = info->lines[index][0];
+ tmp2 = info->lines[index][3];
+ info->lines[index][0] = info->lines[j][0];
+ info->lines[index][3] = info->lines[j][3];
+ info->lines[j][0] = tmp1;
+ info->lines[j][3] = tmp2;
+ //
+ if ( strlen(serverStatusCvars[i].altName) ) {
+ info->lines[index][0] = serverStatusCvars[i].altName;
+ }
+ index++;
+ }
+ }
+ }
+}
+
+/*
+==================
+UI_GetServerStatusInfo
+==================
+*/
+static int UI_GetServerStatusInfo( const char *serverAddress, serverStatusInfo_t *info ) {
+ char *p, *score, *ping, *name;
+ int i, len;
+
+ if (!info) {
+ trap_LAN_ServerStatus( serverAddress, NULL, 0);
+ return qfalse;
+ }
+ memset(info, 0, sizeof(*info));
+ if ( trap_LAN_ServerStatus( serverAddress, info->text, sizeof(info->text)) ) {
+ Q_strncpyz(info->address, serverAddress, sizeof(info->address));
+ p = info->text;
+ info->numLines = 0;
+ info->lines[info->numLines][0] = "Address";
+ info->lines[info->numLines][1] = "";
+ info->lines[info->numLines][2] = "";
+ info->lines[info->numLines][3] = info->address;
+ info->numLines++;
+ // get the cvars
+ while (p && *p) {
+ p = strchr(p, '\\');
+ if (!p) break;
+ *p++ = '\0';
+ if (*p == '\\')
+ break;
+ info->lines[info->numLines][0] = p;
+ info->lines[info->numLines][1] = "";
+ info->lines[info->numLines][2] = "";
+ p = strchr(p, '\\');
+ if (!p) break;
+ *p++ = '\0';
+ info->lines[info->numLines][3] = p;
+
+ info->numLines++;
+ if (info->numLines >= MAX_SERVERSTATUS_LINES)
+ break;
+ }
+ // get the player list
+ if (info->numLines < MAX_SERVERSTATUS_LINES-3) {
+ // empty line
+ info->lines[info->numLines][0] = "";
+ info->lines[info->numLines][1] = "";
+ info->lines[info->numLines][2] = "";
+ info->lines[info->numLines][3] = "";
+ info->numLines++;
+ // header
+ info->lines[info->numLines][0] = "num";
+ info->lines[info->numLines][1] = "score";
+ info->lines[info->numLines][2] = "ping";
+ info->lines[info->numLines][3] = "name";
+ info->numLines++;
+ // parse players
+ i = 0;
+ len = 0;
+ while (p && *p) {
+ if (*p == '\\')
+ *p++ = '\0';
+ if (!p)
+ break;
+ score = p;
+ p = strchr(p, ' ');
+ if (!p)
+ break;
+ *p++ = '\0';
+ ping = p;
+ p = strchr(p, ' ');
+ if (!p)
+ break;
+ *p++ = '\0';
+ name = p;
+ Com_sprintf(&info->pings[len], sizeof(info->pings)-len, "%d", i);
+ info->lines[info->numLines][0] = &info->pings[len];
+ len += strlen(&info->pings[len]) + 1;
+ info->lines[info->numLines][1] = score;
+ info->lines[info->numLines][2] = ping;
+ info->lines[info->numLines][3] = name;
+ info->numLines++;
+ if (info->numLines >= MAX_SERVERSTATUS_LINES)
+ break;
+ p = strchr(p, '\\');
+ if (!p)
+ break;
+ *p++ = '\0';
+ //
+ i++;
+ }
+ }
+ UI_SortServerStatusInfo( info );
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==================
+stristr
+==================
+*/
+static char *stristr(char *str, char *charset) {
+ int i;
+
+ while(*str) {
+ for (i = 0; charset[i] && str[i]; i++) {
+ if (toupper(charset[i]) != toupper(str[i])) break;
+ }
+ if (!charset[i]) return str;
+ str++;
+ }
+ return NULL;
+}
+
+/*
+==================
+UI_BuildFindPlayerList
+==================
+*/
+static void UI_BuildFindPlayerList(qboolean force) {
+ static int numFound, numTimeOuts;
+ int i, j, resend;
+ serverStatusInfo_t info;
+ char name[MAX_NAME_LENGTH+2];
+ char infoString[MAX_STRING_CHARS];
+
+ if (!force) {
+ if (!uiInfo.nextFindPlayerRefresh || uiInfo.nextFindPlayerRefresh > uiInfo.uiDC.realTime) {
+ return;
+ }
+ }
+ else {
+ memset(&uiInfo.pendingServerStatus, 0, sizeof(uiInfo.pendingServerStatus));
+ uiInfo.numFoundPlayerServers = 0;
+ uiInfo.currentFoundPlayerServer = 0;
+ trap_Cvar_VariableStringBuffer( "ui_findPlayer", uiInfo.findPlayerName, sizeof(uiInfo.findPlayerName));
+ Q_CleanStr(uiInfo.findPlayerName);
+ // should have a string of some length
+ if (!strlen(uiInfo.findPlayerName)) {
+ uiInfo.nextFindPlayerRefresh = 0;
+ return;
+ }
+ // set resend time
+ resend = ui_serverStatusTimeOut.integer / 2 - 10;
+ if (resend < 50) {
+ resend = 50;
+ }
+ trap_Cvar_Set("cl_serverStatusResendTime", va("%d", resend));
+ // reset all server status requests
+ trap_LAN_ServerStatus( NULL, NULL, 0);
+ //
+ uiInfo.numFoundPlayerServers = 1;
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
+ sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]),
+ "searching %d...", uiInfo.pendingServerStatus.num);
+ numFound = 0;
+ numTimeOuts++;
+ }
+ for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
+ // if this pending server is valid
+ if (uiInfo.pendingServerStatus.server[i].valid) {
+ // try to get the server status for this server
+ if (UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, &info ) ) {
+ //
+ numFound++;
+ // parse through the server status lines
+ for (j = 0; j < info.numLines; j++) {
+ // should have ping info
+ if ( !info.lines[j][2] || !info.lines[j][2][0] ) {
+ continue;
+ }
+ // clean string first
+ Q_strncpyz(name, info.lines[j][3], sizeof(name));
+ Q_CleanStr(name);
+ // if the player name is a substring
+ if (stristr(name, uiInfo.findPlayerName)) {
+ // add to found server list if we have space (always leave space for a line with the number found)
+ if (uiInfo.numFoundPlayerServers < MAX_FOUNDPLAYER_SERVERS-1) {
+ //
+ Q_strncpyz(uiInfo.foundPlayerServerAddresses[uiInfo.numFoundPlayerServers-1],
+ uiInfo.pendingServerStatus.server[i].adrstr,
+ sizeof(uiInfo.foundPlayerServerAddresses[0]));
+ Q_strncpyz(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
+ uiInfo.pendingServerStatus.server[i].name,
+ sizeof(uiInfo.foundPlayerServerNames[0]));
+ uiInfo.numFoundPlayerServers++;
+ }
+ else {
+ // can't add any more so we're done
+ uiInfo.pendingServerStatus.num = uiInfo.serverStatus.numDisplayServers;
+ }
+ }
+ }
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
+ sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]),
+ "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound);
+ // retrieved the server status so reuse this spot
+ uiInfo.pendingServerStatus.server[i].valid = qfalse;
+ }
+ }
+ // if empty pending slot or timed out
+ if (!uiInfo.pendingServerStatus.server[i].valid ||
+ uiInfo.pendingServerStatus.server[i].startTime < uiInfo.uiDC.realTime - ui_serverStatusTimeOut.integer) {
+ if (uiInfo.pendingServerStatus.server[i].valid) {
+ numTimeOuts++;
+ }
+ // reset server status request for this address
+ UI_GetServerStatusInfo( uiInfo.pendingServerStatus.server[i].adrstr, NULL );
+ // reuse pending slot
+ uiInfo.pendingServerStatus.server[i].valid = qfalse;
+ // if we didn't try to get the status of all servers in the main browser yet
+ if (uiInfo.pendingServerStatus.num < uiInfo.serverStatus.numDisplayServers) {
+ uiInfo.pendingServerStatus.server[i].startTime = uiInfo.uiDC.realTime;
+ trap_LAN_GetServerAddressString(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num],
+ uiInfo.pendingServerStatus.server[i].adrstr, sizeof(uiInfo.pendingServerStatus.server[i].adrstr));
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[uiInfo.pendingServerStatus.num], infoString, sizeof(infoString));
+ Q_strncpyz(uiInfo.pendingServerStatus.server[i].name, Info_ValueForKey(infoString, "hostname"), sizeof(uiInfo.pendingServerStatus.server[0].name));
+ uiInfo.pendingServerStatus.server[i].valid = qtrue;
+ uiInfo.pendingServerStatus.num++;
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1],
+ sizeof(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1]),
+ "searching %d/%d...", uiInfo.pendingServerStatus.num, numFound);
+ }
+ }
+ }
+ for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
+ if (uiInfo.pendingServerStatus.server[i].valid) {
+ break;
+ }
+ }
+ // if still trying to retrieve server status info
+ if (i < MAX_SERVERSTATUSREQUESTS) {
+ uiInfo.nextFindPlayerRefresh = uiInfo.uiDC.realTime + 25;
+ }
+ else {
+ // add a line that shows the number of servers found
+ if (!uiInfo.numFoundPlayerServers) {
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]), "no servers found");
+ }
+ else {
+ Com_sprintf(uiInfo.foundPlayerServerNames[uiInfo.numFoundPlayerServers-1], sizeof(uiInfo.foundPlayerServerAddresses[0]),
+ "%d server%s found with player %s", uiInfo.numFoundPlayerServers-1,
+ uiInfo.numFoundPlayerServers == 2 ? "":"s", uiInfo.findPlayerName);
+ }
+ uiInfo.nextFindPlayerRefresh = 0;
+ // show the server status info for the selected server
+ UI_FeederSelection(FEEDER_FINDPLAYER, uiInfo.currentFoundPlayerServer);
+ }
+}
+
+/*
+==================
+UI_BuildServerStatus
+==================
+*/
+static void UI_BuildServerStatus(qboolean force) {
+
+ if (uiInfo.nextFindPlayerRefresh) {
+ return;
+ }
+ if (!force) {
+ if (!uiInfo.nextServerStatusRefresh || uiInfo.nextServerStatusRefresh > uiInfo.uiDC.realTime) {
+ return;
+ }
+ }
+ else {
+ Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL);
+ uiInfo.serverStatusInfo.numLines = 0;
+ // reset all server status requests
+ trap_LAN_ServerStatus( NULL, NULL, 0);
+ }
+ if (uiInfo.serverStatus.currentServer < 0 || uiInfo.serverStatus.currentServer > uiInfo.serverStatus.numDisplayServers || uiInfo.serverStatus.numDisplayServers == 0) {
+ return;
+ }
+ if (UI_GetServerStatusInfo( uiInfo.serverStatusAddress, &uiInfo.serverStatusInfo ) ) {
+ uiInfo.nextServerStatusRefresh = 0;
+ UI_GetServerStatusInfo( uiInfo.serverStatusAddress, NULL );
+ }
+ else {
+ uiInfo.nextServerStatusRefresh = uiInfo.uiDC.realTime + 500;
+ }
+}
+
+/*
+==================
+UI_FeederCount
+==================
+*/
+static int UI_FeederCount(float feederID) {
+
+ if (feederID == FEEDER_HEADS) {
+ return UI_HeadCountByTeam();
+ } else if (feederID == FEEDER_Q3HEADS) {
+ return uiInfo.q3HeadCount;
+ } else if (feederID == FEEDER_CINEMATICS) {
+ return uiInfo.movieCount;
+ } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) {
+ return UI_MapCountByGameType(feederID == FEEDER_MAPS ? qtrue : qfalse);
+ } else if (feederID == FEEDER_SERVERS) {
+ return uiInfo.serverStatus.numDisplayServers;
+ } else if (feederID == FEEDER_SERVERSTATUS) {
+ return uiInfo.serverStatusInfo.numLines;
+ } else if (feederID == FEEDER_FINDPLAYER) {
+ return uiInfo.numFoundPlayerServers;
+ } else if (feederID == FEEDER_PLAYER_LIST) {
+ if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) {
+ uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
+ UI_BuildPlayerList();
+ }
+ return uiInfo.playerCount;
+ } else if (feederID == FEEDER_TEAM_LIST) {
+ if (uiInfo.uiDC.realTime > uiInfo.playerRefresh) {
+ uiInfo.playerRefresh = uiInfo.uiDC.realTime + 3000;
+ UI_BuildPlayerList();
+ }
+ return uiInfo.myTeamCount;
+ } else if (feederID == FEEDER_IGNORE_LIST) {
+ return uiInfo.playerCount;
+ } else if (feederID == FEEDER_MODS) {
+ return uiInfo.modCount;
+ } else if (feederID == FEEDER_DEMOS) {
+ return uiInfo.demoCount;
+ }
+
+//TA: tremulous menus
+ else if( feederID == FEEDER_TREMTEAMS )
+ return uiInfo.tremTeamCount;
+ else if( feederID == FEEDER_TREMHUMANITEMS )
+ return uiInfo.tremHumanItemCount;
+ else if( feederID == FEEDER_TREMALIENCLASSES )
+ return uiInfo.tremAlienClassCount;
+ else if( feederID == FEEDER_TREMHUMANARMOURYBUY )
+ return uiInfo.tremHumanArmouryBuyCount;
+ else if( feederID == FEEDER_TREMHUMANARMOURYSELL )
+ return uiInfo.tremHumanArmourySellCount;
+ else if( feederID == FEEDER_TREMALIENUPGRADE )
+ return uiInfo.tremAlienUpgradeCount;
+ else if( feederID == FEEDER_TREMALIENBUILD )
+ return uiInfo.tremAlienBuildCount;
+ else if( feederID == FEEDER_TREMHUMANBUILD )
+ return uiInfo.tremHumanBuildCount;
+//TA: tremulous menus
+
+ return 0;
+}
+
+static const char *UI_SelectedMap(int index, int *actual) {
+ int i, c;
+ c = 0;
+ *actual = 0;
+ for (i = 0; i < uiInfo.mapCount; i++) {
+ if (uiInfo.mapList[i].active) {
+ if (c == index) {
+ *actual = i;
+ return uiInfo.mapList[i].mapName;
+ } else {
+ c++;
+ }
+ }
+ }
+ return "";
+}
+
+static const char *UI_SelectedHead(int index, int *actual) {
+ int i, c;
+ c = 0;
+ *actual = 0;
+ for (i = 0; i < uiInfo.characterCount; i++) {
+ if (uiInfo.characterList[i].active) {
+ if (c == index) {
+ *actual = i;
+ return uiInfo.characterList[i].name;
+ } else {
+ c++;
+ }
+ }
+ }
+ return "";
+}
+
+static int UI_GetIndexFromSelection(int actual) {
+ int i, c;
+ c = 0;
+ for (i = 0; i < uiInfo.mapCount; i++) {
+ if (uiInfo.mapList[i].active) {
+ if (i == actual) {
+ return c;
+ }
+ c++;
+ }
+ }
+ return 0;
+}
+
+static void UI_UpdatePendingPings( void ) {
+ trap_LAN_ResetPings(ui_netSource.integer);
+ uiInfo.serverStatus.refreshActive = qtrue;
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
+
+}
+
+static const char *UI_FeederItemText(float feederID, int index, int column, qhandle_t *handle) {
+ static char info[MAX_STRING_CHARS];
+ static char hostname[1024];
+ static char clientBuff[32];
+ static int lastColumn = -1;
+ static int lastTime = 0;
+ *handle = -1;
+ if (feederID == FEEDER_HEADS) {
+ int actual;
+ return UI_SelectedHead(index, &actual);
+ } else if (feederID == FEEDER_Q3HEADS) {
+ if (index >= 0 && index < uiInfo.q3HeadCount) {
+ return uiInfo.q3HeadNames[index];
+ }
+ } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) {
+ int actual;
+ return UI_SelectedMap(index, &actual);
+ } else if (feederID == FEEDER_SERVERS) {
+ if (index >= 0 && index < uiInfo.serverStatus.numDisplayServers) {
+ int ping;
+ if (lastColumn != column || lastTime > uiInfo.uiDC.realTime + 5000) {
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS);
+ lastColumn = column;
+ lastTime = uiInfo.uiDC.realTime;
+ }
+
+ ping = atoi(Info_ValueForKey(info, "ping"));
+ if (ping == -1) {
+ // if we ever see a ping that is out of date, do a server refresh
+ // UI_UpdatePendingPings();
+ }
+ switch (column) {
+ case SORT_HOST :
+ if (ping <= 0) {
+ return Info_ValueForKey(info, "addr");
+ } else {
+ if ( ui_netSource.integer == AS_LOCAL ) {
+ Com_sprintf( hostname, sizeof(hostname), "%s [%s]",
+ Info_ValueForKey(info, "hostname"),
+ netnames[atoi(Info_ValueForKey(info, "nettype"))] );
+ return hostname;
+ }
+ else
+ {
+ char *text;
+
+ Com_sprintf( hostname, sizeof(hostname), "%s", Info_ValueForKey(info, "hostname"));
+
+ // Strip leading whitespace
+ text = hostname;
+ while( *text != '\0' && *text == ' ' )
+ text++;
+
+ return text;
+ }
+ }
+ case SORT_MAP :
+ return Info_ValueForKey(info, "mapname");
+ case SORT_CLIENTS :
+ Com_sprintf( clientBuff, sizeof(clientBuff), "%s (%s)", Info_ValueForKey(info, "clients"), Info_ValueForKey(info, "sv_maxclients"));
+ return clientBuff;
+ case SORT_PING :
+ if (ping <= 0) {
+ return "...";
+ } else {
+ return Info_ValueForKey(info, "ping");
+ }
+ }
+ }
+ } else if (feederID == FEEDER_SERVERSTATUS) {
+ if ( index >= 0 && index < uiInfo.serverStatusInfo.numLines ) {
+ if ( column >= 0 && column < 4 ) {
+ return uiInfo.serverStatusInfo.lines[index][column];
+ }
+ }
+ } else if (feederID == FEEDER_FINDPLAYER) {
+ if ( index >= 0 && index < uiInfo.numFoundPlayerServers ) {
+ //return uiInfo.foundPlayerServerAddresses[index];
+ return uiInfo.foundPlayerServerNames[index];
+ }
+ } else if (feederID == FEEDER_PLAYER_LIST) {
+ if (index >= 0 && index < uiInfo.playerCount) {
+ return uiInfo.playerNames[index];
+ }
+ } else if (feederID == FEEDER_TEAM_LIST) {
+ if (index >= 0 && index < uiInfo.myTeamCount) {
+ return uiInfo.teamNames[index];
+ }
+ } else if (feederID == FEEDER_IGNORE_LIST) {
+ if (index >= 0 && index < uiInfo.playerCount) {
+ switch( column )
+ {
+ case 1:
+ // am I ignoring him
+ return ( BG_ClientListTest(&uiInfo.ignoreList[ uiInfo.myPlayerIndex ],
+ uiInfo.clientNums[ index ] ) ) ? "X" : "";
+ case 2:
+ // is he ignoring me
+ return ( BG_ClientListTest( &uiInfo.ignoreList[ index ],
+ uiInfo.playerNumber ) ) ? "X" : "";
+ default:
+ return uiInfo.playerNames[index];
+ }
+ }
+ } else if (feederID == FEEDER_MODS) {
+ if (index >= 0 && index < uiInfo.modCount) {
+ if (uiInfo.modList[index].modDescr && *uiInfo.modList[index].modDescr) {
+ return uiInfo.modList[index].modDescr;
+ } else {
+ return uiInfo.modList[index].modName;
+ }
+ }
+ } else if (feederID == FEEDER_CINEMATICS) {
+ if (index >= 0 && index < uiInfo.movieCount) {
+ return uiInfo.movieList[index];
+ }
+ } else if (feederID == FEEDER_DEMOS) {
+ if (index >= 0 && index < uiInfo.demoCount) {
+ return uiInfo.demoList[index];
+ }
+ }
+
+//TA: tremulous menus
+ else if( feederID == FEEDER_TREMTEAMS )
+ {
+ if( index >= 0 && index < uiInfo.tremTeamCount )
+ return uiInfo.tremTeamList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANITEMS )
+ {
+ if( index >= 0 && index < uiInfo.tremHumanItemCount )
+ return uiInfo.tremHumanItemList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMALIENCLASSES )
+ {
+ if( index >= 0 && index < uiInfo.tremAlienClassCount )
+ return uiInfo.tremAlienClassList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANARMOURYBUY )
+ {
+ if( index >= 0 && index < uiInfo.tremHumanArmouryBuyCount )
+ return uiInfo.tremHumanArmouryBuyList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANARMOURYSELL )
+ {
+ if( index >= 0 && index < uiInfo.tremHumanArmourySellCount )
+ return uiInfo.tremHumanArmourySellList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMALIENUPGRADE )
+ {
+ if( index >= 0 && index < uiInfo.tremAlienUpgradeCount )
+ return uiInfo.tremAlienUpgradeList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMALIENBUILD )
+ {
+ if( index >= 0 && index < uiInfo.tremAlienBuildCount )
+ return uiInfo.tremAlienBuildList[ index ].text;
+ }
+ else if( feederID == FEEDER_TREMHUMANBUILD )
+ {
+ if( index >= 0 && index < uiInfo.tremHumanBuildCount )
+ return uiInfo.tremHumanBuildList[ index ].text;
+ }
+//TA: tremulous menus
+
+ return "";
+}
+
+
+static qhandle_t UI_FeederItemImage(float feederID, int index) {
+ if (feederID == FEEDER_HEADS) {
+ int actual;
+ UI_SelectedHead(index, &actual);
+ index = actual;
+ if (index >= 0 && index < uiInfo.characterCount) {
+ if (uiInfo.characterList[index].headImage == -1) {
+ uiInfo.characterList[index].headImage = trap_R_RegisterShaderNoMip(uiInfo.characterList[index].imageName);
+ }
+ return uiInfo.characterList[index].headImage;
+ }
+ } else if (feederID == FEEDER_Q3HEADS) {
+ if (index >= 0 && index < uiInfo.q3HeadCount) {
+ return uiInfo.q3HeadIcons[index];
+ }
+ } else if (feederID == FEEDER_ALLMAPS || feederID == FEEDER_MAPS) {
+ int actual;
+ UI_SelectedMap(index, &actual);
+ index = actual;
+ if (index >= 0 && index < uiInfo.mapCount) {
+ if (uiInfo.mapList[index].levelShot == -1) {
+ uiInfo.mapList[index].levelShot = trap_R_RegisterShaderNoMip(uiInfo.mapList[index].imageName);
+ }
+ return uiInfo.mapList[index].levelShot;
+ }
+ }
+ return 0;
+}
+
+static void UI_FeederSelection(float feederID, int index) {
+ static char info[MAX_STRING_CHARS];
+ if (feederID == FEEDER_HEADS) {
+ int actual;
+ UI_SelectedHead(index, &actual);
+ index = actual;
+ if (index >= 0 && index < uiInfo.characterCount) {
+ trap_Cvar_Set( "team_model", va("%s", uiInfo.characterList[index].base));
+ trap_Cvar_Set( "team_headmodel", va("*%s", uiInfo.characterList[index].name));
+ updateModel = qtrue;
+ }
+ } else if (feederID == FEEDER_Q3HEADS) {
+ if (index >= 0 && index < uiInfo.q3HeadCount) {
+ trap_Cvar_Set( "model", uiInfo.q3HeadNames[index]);
+ trap_Cvar_Set( "headmodel", uiInfo.q3HeadNames[index]);
+ updateModel = qtrue;
+ }
+ } else if (feederID == FEEDER_MAPS || feederID == FEEDER_ALLMAPS) {
+ int actual, map;
+ map = (feederID == FEEDER_ALLMAPS) ? ui_currentNetMap.integer : ui_currentMap.integer;
+ if (uiInfo.mapList[map].cinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.mapList[map].cinematic);
+ uiInfo.mapList[map].cinematic = -1;
+ }
+ UI_SelectedMap(index, &actual);
+ trap_Cvar_Set("ui_mapIndex", va("%d", index));
+ ui_mapIndex.integer = index;
+
+ if (feederID == FEEDER_MAPS) {
+ ui_currentMap.integer = actual;
+ trap_Cvar_Set("ui_currentMap", va("%d", actual));
+ uiInfo.mapList[ui_currentMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);
+ trap_Cvar_Set("ui_opponentModel", uiInfo.mapList[ui_currentMap.integer].opponentName);
+ updateOpponentModel = qtrue;
+ } else {
+ ui_currentNetMap.integer = actual;
+ trap_Cvar_Set("ui_currentNetMap", va("%d", actual));
+ uiInfo.mapList[ui_currentNetMap.integer].cinematic = trap_CIN_PlayCinematic(va("%s.roq", uiInfo.mapList[ui_currentNetMap.integer].mapLoadName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ }
+
+ } else if (feederID == FEEDER_SERVERS) {
+ const char *mapName = NULL;
+ uiInfo.serverStatus.currentServer = index;
+ trap_LAN_GetServerInfo(ui_netSource.integer, uiInfo.serverStatus.displayServers[index], info, MAX_STRING_CHARS);
+ uiInfo.serverStatus.currentServerPreview = trap_R_RegisterShaderNoMip(va("levelshots/%s", Info_ValueForKey(info, "mapname")));
+ if (uiInfo.serverStatus.currentServerCinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic);
+ uiInfo.serverStatus.currentServerCinematic = -1;
+ }
+ mapName = Info_ValueForKey(info, "mapname");
+ if (mapName && *mapName) {
+ uiInfo.serverStatus.currentServerCinematic = trap_CIN_PlayCinematic(va("%s.roq", mapName), 0, 0, 0, 0, (CIN_loop | CIN_silent) );
+ }
+ } else if (feederID == FEEDER_SERVERSTATUS) {
+ //
+ } else if (feederID == FEEDER_FINDPLAYER) {
+ uiInfo.currentFoundPlayerServer = index;
+ //
+ if ( index < uiInfo.numFoundPlayerServers-1) {
+ // build a new server status for this server
+ Q_strncpyz(uiInfo.serverStatusAddress, uiInfo.foundPlayerServerAddresses[uiInfo.currentFoundPlayerServer], sizeof(uiInfo.serverStatusAddress));
+ Menu_SetFeederSelection(NULL, FEEDER_SERVERSTATUS, 0, NULL);
+ UI_BuildServerStatus(qtrue);
+ }
+ } else if (feederID == FEEDER_PLAYER_LIST) {
+ uiInfo.playerIndex = index;
+ } else if (feederID == FEEDER_TEAM_LIST) {
+ uiInfo.teamIndex = index;
+ } else if (feederID == FEEDER_IGNORE_LIST) {
+ uiInfo.ignoreIndex = index;
+ } else if (feederID == FEEDER_MODS) {
+ uiInfo.modIndex = index;
+ } else if (feederID == FEEDER_CINEMATICS) {
+ uiInfo.movieIndex = index;
+ if (uiInfo.previewMovie >= 0) {
+ trap_CIN_StopCinematic(uiInfo.previewMovie);
+ }
+ uiInfo.previewMovie = -1;
+ } else if (feederID == FEEDER_DEMOS) {
+ uiInfo.demoIndex = index;
+ }
+
+//TA: tremulous menus
+ else if( feederID == FEEDER_TREMTEAMS )
+ uiInfo.tremTeamIndex = index;
+ else if( feederID == FEEDER_TREMHUMANITEMS )
+ uiInfo.tremHumanItemIndex = index;
+ else if( feederID == FEEDER_TREMALIENCLASSES )
+ uiInfo.tremAlienClassIndex = index;
+ else if( feederID == FEEDER_TREMHUMANARMOURYBUY )
+ uiInfo.tremHumanArmouryBuyIndex = index;
+ else if( feederID == FEEDER_TREMHUMANARMOURYSELL )
+ uiInfo.tremHumanArmourySellIndex = index;
+ else if( feederID == FEEDER_TREMALIENUPGRADE )
+ uiInfo.tremAlienUpgradeIndex = index;
+ else if( feederID == FEEDER_TREMALIENBUILD )
+ uiInfo.tremAlienBuildIndex = index;
+ else if( feederID == FEEDER_TREMHUMANBUILD )
+ uiInfo.tremHumanBuildIndex = index;
+//TA: tremulous menus
+}
+
+static void UI_Pause(qboolean b) {
+ if (b) {
+ // pause the game and set the ui keycatcher
+ trap_Cvar_Set( "cl_paused", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ } else {
+ // unpause the game and clear the ui keycatcher
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ }
+}
+
+static int UI_PlayCinematic(const char *name, float x, float y, float w, float h) {
+ return trap_CIN_PlayCinematic(name, x, y, w, h, (CIN_loop | CIN_silent));
+}
+
+static void UI_StopCinematic(int handle) {
+ if (handle >= 0) {
+ trap_CIN_StopCinematic(handle);
+ } else {
+ handle = abs(handle);
+ if (handle == UI_MAPCINEMATIC) {
+ if (uiInfo.mapList[ui_currentMap.integer].cinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.mapList[ui_currentMap.integer].cinematic);
+ uiInfo.mapList[ui_currentMap.integer].cinematic = -1;
+ }
+ } else if (handle == UI_NETMAPCINEMATIC) {
+ if (uiInfo.serverStatus.currentServerCinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.serverStatus.currentServerCinematic);
+ uiInfo.serverStatus.currentServerCinematic = -1;
+ }
+ } else if (handle == UI_CLANCINEMATIC) {
+ int i = UI_TeamIndexFromName(UI_Cvar_VariableString("ui_teamName"));
+ if (i >= 0 && i < uiInfo.teamCount) {
+ if (uiInfo.teamList[i].cinematic >= 0) {
+ trap_CIN_StopCinematic(uiInfo.teamList[i].cinematic);
+ uiInfo.teamList[i].cinematic = -1;
+ }
+ }
+ }
+ }
+}
+
+static void UI_DrawCinematic(int handle, float x, float y, float w, float h) {
+ trap_CIN_SetExtents(handle, x, y, w, h);
+ trap_CIN_DrawCinematic(handle);
+}
+
+static void UI_RunCinematicFrame(int handle) {
+ trap_CIN_RunCinematic(handle);
+}
+
+
+/*
+=================
+UI_Init
+=================
+*/
+void _UI_Init( qboolean inGameLoad ) {
+ const char *menuSet;
+ int start;
+
+ BG_InitClassOverrides( );
+ BG_InitAllowedGameElements( );
+
+ //uiInfo.inGameLoad = inGameLoad;
+
+ UI_RegisterCvars();
+ UI_InitMemory();
+
+ // cache redundant calulations
+ trap_GetGlconfig( &uiInfo.uiDC.glconfig );
+
+ // for 640x480 virtualized screen
+ uiInfo.uiDC.yscale = uiInfo.uiDC.glconfig.vidHeight * (1.0/480.0);
+ uiInfo.uiDC.xscale = uiInfo.uiDC.glconfig.vidWidth * (1.0/640.0);
+ if ( uiInfo.uiDC.glconfig.vidWidth * 480 > uiInfo.uiDC.glconfig.vidHeight * 640 ) {
+ // wide screen
+ uiInfo.uiDC.bias = 0.5 * ( uiInfo.uiDC.glconfig.vidWidth - ( uiInfo.uiDC.glconfig.vidHeight * (640.0/480.0) ) );
+ }
+ else {
+ // no wide screen
+ uiInfo.uiDC.bias = 0;
+ }
+
+
+ //UI_Load();
+ uiInfo.uiDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip;
+ uiInfo.uiDC.setColor = &UI_SetColor;
+ uiInfo.uiDC.drawHandlePic = &UI_DrawHandlePic;
+ uiInfo.uiDC.drawStretchPic = &trap_R_DrawStretchPic;
+ uiInfo.uiDC.drawText = &Text_Paint;
+ uiInfo.uiDC.textWidth = &Text_Width;
+ uiInfo.uiDC.textHeight = &Text_Height;
+ uiInfo.uiDC.registerModel = &trap_R_RegisterModel;
+ uiInfo.uiDC.modelBounds = &trap_R_ModelBounds;
+ uiInfo.uiDC.fillRect = &UI_FillRect;
+ uiInfo.uiDC.drawRect = &_UI_DrawRect;
+ uiInfo.uiDC.drawSides = &_UI_DrawSides;
+ uiInfo.uiDC.drawTopBottom = &_UI_DrawTopBottom;
+ uiInfo.uiDC.clearScene = &trap_R_ClearScene;
+ uiInfo.uiDC.drawSides = &_UI_DrawSides;
+ uiInfo.uiDC.addRefEntityToScene = &trap_R_AddRefEntityToScene;
+ uiInfo.uiDC.renderScene = &trap_R_RenderScene;
+ uiInfo.uiDC.registerFont = &trap_R_RegisterFont;
+ uiInfo.uiDC.ownerDrawItem = &UI_OwnerDraw;
+ uiInfo.uiDC.getValue = &UI_GetValue;
+ uiInfo.uiDC.ownerDrawVisible = &UI_OwnerDrawVisible;
+ uiInfo.uiDC.runScript = &UI_RunMenuScript;
+ uiInfo.uiDC.getTeamColor = &UI_GetTeamColor;
+ uiInfo.uiDC.setCVar = trap_Cvar_Set;
+ uiInfo.uiDC.getCVarString = trap_Cvar_VariableStringBuffer;
+ uiInfo.uiDC.getCVarValue = trap_Cvar_VariableValue;
+ uiInfo.uiDC.drawTextWithCursor = &Text_PaintWithCursor;
+ uiInfo.uiDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode;
+ uiInfo.uiDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode;
+ uiInfo.uiDC.startLocalSound = &trap_S_StartLocalSound;
+ uiInfo.uiDC.ownerDrawHandleKey = &UI_OwnerDrawHandleKey;
+ uiInfo.uiDC.feederCount = &UI_FeederCount;
+ uiInfo.uiDC.feederItemImage = &UI_FeederItemImage;
+ uiInfo.uiDC.feederItemText = &UI_FeederItemText;
+ uiInfo.uiDC.feederSelection = &UI_FeederSelection;
+ uiInfo.uiDC.setBinding = &trap_Key_SetBinding;
+ uiInfo.uiDC.getBindingBuf = &trap_Key_GetBindingBuf;
+ uiInfo.uiDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf;
+ uiInfo.uiDC.executeText = &trap_Cmd_ExecuteText;
+ uiInfo.uiDC.Error = &Com_Error;
+ uiInfo.uiDC.Print = &Com_Printf;
+ uiInfo.uiDC.Pause = &UI_Pause;
+ uiInfo.uiDC.ownerDrawWidth = &UI_OwnerDrawWidth;
+ uiInfo.uiDC.registerSound = &trap_S_RegisterSound;
+ uiInfo.uiDC.startBackgroundTrack = &trap_S_StartBackgroundTrack;
+ uiInfo.uiDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack;
+ uiInfo.uiDC.playCinematic = &UI_PlayCinematic;
+ uiInfo.uiDC.stopCinematic = &UI_StopCinematic;
+ uiInfo.uiDC.drawCinematic = &UI_DrawCinematic;
+ uiInfo.uiDC.runCinematicFrame = &UI_RunCinematicFrame;
+
+ Init_Display(&uiInfo.uiDC);
+
+ String_Init();
+
+ uiInfo.uiDC.whiteShader = trap_R_RegisterShaderNoMip( "white" );
+
+ AssetCache();
+
+ start = trap_Milliseconds();
+
+ uiInfo.teamCount = 0;
+ uiInfo.characterCount = 0;
+ uiInfo.aliasCount = 0;
+
+/* UI_ParseTeamInfo("teaminfo.txt");
+ UI_LoadTeams();
+ UI_ParseGameInfo("gameinfo.txt");*/
+
+ menuSet = UI_Cvar_VariableString("ui_menuFiles");
+ if (menuSet == NULL || menuSet[0] == '\0') {
+ menuSet = "ui/menus.txt";
+ }
+
+#if 0
+ if (uiInfo.inGameLoad) {
+ UI_LoadMenus("ui/ingame.txt", qtrue);
+ } else { // bk010222: left this: UI_LoadMenus(menuSet, qtrue);
+ }
+#else
+ UI_LoadMenus(menuSet, qtrue);
+ UI_LoadMenus("ui/ingame.txt", qfalse);
+ UI_LoadMenus("ui/tremulous.txt", qfalse);
+
+ UI_LoadInfoPanes( "ui/infopanes.def" );
+
+ if( uiInfo.uiDC.debug )
+ {
+ int i, j;
+
+ for( i = 0; i < uiInfo.tremInfoPaneCount; i++ )
+ {
+ Com_Printf( "name: %s\n", uiInfo.tremInfoPanes[ i ].name );
+
+ Com_Printf( "text: %s\n", uiInfo.tremInfoPanes[ i ].text );
+
+ for( j = 0; j < uiInfo.tremInfoPanes[ i ].numGraphics; j++ )
+ Com_Printf( "graphic %d: %d %d %d %d\n", j, uiInfo.tremInfoPanes[ i ].graphics[ j ].side,
+ uiInfo.tremInfoPanes[ i ].graphics[ j ].offset,
+ uiInfo.tremInfoPanes[ i ].graphics[ j ].width,
+ uiInfo.tremInfoPanes[ i ].graphics[ j ].height );
+ }
+ }
+#endif
+
+ Menus_CloseAll();
+
+ trap_LAN_LoadCachedServers();
+ UI_LoadBestScores(uiInfo.mapList[ui_currentMap.integer].mapLoadName, uiInfo.gameTypes[ui_gameType.integer].gtEnum);
+
+ // sets defaults for ui temp cvars
+ uiInfo.effectsColor = gamecodetoui[(int)trap_Cvar_VariableValue("color1")-1];
+ uiInfo.currentCrosshair = (int)trap_Cvar_VariableValue("cg_drawCrosshair");
+ trap_Cvar_Set("ui_mousePitch", (trap_Cvar_VariableValue("m_pitch") >= 0) ? "0" : "1");
+
+ uiInfo.serverStatus.currentServerCinematic = -1;
+ uiInfo.previewMovie = -1;
+
+ if (trap_Cvar_VariableValue("ui_TeamArenaFirstRun") == 0) {
+ trap_Cvar_Set("s_volume", "0.8");
+ trap_Cvar_Set("s_musicvolume", "0.5");
+ trap_Cvar_Set("ui_TeamArenaFirstRun", "1");
+ }
+
+ trap_Cvar_Register(NULL, "debug_protocol", "", 0 );
+
+ trap_Cvar_Set("ui_actualNetGameType", va("%d", ui_netGameType.integer));
+}
+
+
+/*
+=================
+UI_KeyEvent
+=================
+*/
+void _UI_KeyEvent( int key, qboolean down ) {
+
+ if (Menu_Count() > 0) {
+ menuDef_t *menu = Menu_GetFocused();
+ if (menu) {
+ if (key == K_ESCAPE && down && !Menus_AnyFullScreenVisible()) {
+ Menus_CloseAll();
+ } else {
+ Menu_HandleKey(menu, key, down );
+ }
+ } else {
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ }
+ }
+
+ //if ((s > 0) && (s != menu_null_sound)) {
+ // trap_S_StartLocalSound( s, CHAN_LOCAL_SOUND );
+ //}
+}
+
+/*
+=================
+UI_MouseEvent
+=================
+*/
+void _UI_MouseEvent( int dx, int dy )
+{
+ // update mouse screen position
+ uiInfo.uiDC.cursorx += dx;
+ if (uiInfo.uiDC.cursorx < 0)
+ uiInfo.uiDC.cursorx = 0;
+ else if (uiInfo.uiDC.cursorx > SCREEN_WIDTH)
+ uiInfo.uiDC.cursorx = SCREEN_WIDTH;
+
+ uiInfo.uiDC.cursory += dy;
+ if (uiInfo.uiDC.cursory < 0)
+ uiInfo.uiDC.cursory = 0;
+ else if (uiInfo.uiDC.cursory > SCREEN_HEIGHT)
+ uiInfo.uiDC.cursory = SCREEN_HEIGHT;
+
+ if (Menu_Count() > 0) {
+ //menuDef_t *menu = Menu_GetFocused();
+ //Menu_HandleMouseMove(menu, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory);
+ Display_MouseMove(NULL, uiInfo.uiDC.cursorx, uiInfo.uiDC.cursory);
+ }
+
+}
+
+void UI_LoadNonIngame( void ) {
+ const char *menuSet = UI_Cvar_VariableString("ui_menuFiles");
+ if (menuSet == NULL || menuSet[0] == '\0') {
+ menuSet = "ui/menus.txt";
+ }
+ UI_LoadMenus(menuSet, qfalse);
+ uiInfo.inGameLoad = qfalse;
+}
+
+void _UI_SetActiveMenu( uiMenuCommand_t menu ) {
+ char buf[256];
+
+ // this should be the ONLY way the menu system is brought up
+ // enusure minumum menu data is cached
+ if (Menu_Count() > 0) {
+ vec3_t v;
+ v[0] = v[1] = v[2] = 0;
+ switch ( menu ) {
+ case UIMENU_NONE:
+ trap_Key_SetCatcher( trap_Key_GetCatcher() & ~KEYCATCH_UI );
+ trap_Key_ClearStates();
+ trap_Cvar_Set( "cl_paused", "0" );
+ Menus_CloseAll();
+
+ return;
+ case UIMENU_MAIN:
+ //trap_Cvar_Set( "sv_killserver", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ //trap_S_StartLocalSound( trap_S_RegisterSound("sound/misc/menu_background.wav", qfalse) , CHAN_LOCAL_SOUND );
+ //trap_S_StartBackgroundTrack("sound/misc/menu_background.wav", NULL);
+ if (uiInfo.inGameLoad) {
+ UI_LoadNonIngame();
+ }
+ Menus_CloseAll();
+ Menus_ActivateByName("main");
+ trap_Cvar_Set( "ui_loading", "0" );
+ trap_Cvar_VariableStringBuffer("com_errorMessage", buf, sizeof(buf));
+ if (strlen(buf)) {
+ if (!ui_singlePlayerActive.integer) {
+ if( trap_Cvar_VariableValue( "com_errorCode" ) == ERR_SERVERDISCONNECT )
+ Menus_ActivateByName("drop_popmenu");
+ else
+ Menus_ActivateByName("error_popmenu");
+ } else {
+ trap_Cvar_Set("com_errorMessage", "");
+ }
+ }
+ return;
+ case UIMENU_TEAM:
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ Menus_ActivateByName("team");
+ return;
+ case UIMENU_POSTGAME:
+ //trap_Cvar_Set( "sv_killserver", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ if (uiInfo.inGameLoad) {
+ UI_LoadNonIngame();
+ }
+ Menus_CloseAll();
+ Menus_ActivateByName("endofgame");
+ return;
+ case UIMENU_INGAME:
+ trap_Cvar_Set( "cl_paused", "1" );
+ trap_Key_SetCatcher( KEYCATCH_UI );
+ UI_BuildPlayerList();
+ Menus_CloseAll();
+ Menus_ActivateByName("ingame");
+ return;
+ }
+ }
+}
+
+qboolean _UI_IsFullscreen( void ) {
+ return Menus_AnyFullScreenVisible();
+}
+
+
+
+static connstate_t lastConnState;
+static char lastLoadingText[MAX_INFO_VALUE];
+
+static void UI_ReadableSize ( char *buf, int bufsize, int value )
+{
+ if (value > 1024*1024*1024 ) { // gigs
+ Com_sprintf( buf, bufsize, "%d", value / (1024*1024*1024) );
+ Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d GB",
+ (value % (1024*1024*1024))*100 / (1024*1024*1024) );
+ } else if (value > 1024*1024 ) { // megs
+ Com_sprintf( buf, bufsize, "%d", value / (1024*1024) );
+ Com_sprintf( buf+strlen(buf), bufsize-strlen(buf), ".%02d MB",
+ (value % (1024*1024))*100 / (1024*1024) );
+ } else if (value > 1024 ) { // kilos
+ Com_sprintf( buf, bufsize, "%d KB", value / 1024 );
+ } else { // bytes
+ Com_sprintf( buf, bufsize, "%d bytes", value );
+ }
+}
+
+// Assumes time is in msec
+static void UI_PrintTime ( char *buf, int bufsize, int time ) {
+ time /= 1000; // change to seconds
+
+ if (time > 3600) { // in the hours range
+ Com_sprintf( buf, bufsize, "%d hr %d min", time / 3600, (time % 3600) / 60 );
+ } else if (time > 60) { // mins
+ Com_sprintf( buf, bufsize, "%d min %d sec", time / 60, time % 60 );
+ } else { // secs
+ Com_sprintf( buf, bufsize, "%d sec", time );
+ }
+}
+
+void Text_PaintCenter(float x, float y, float scale, vec4_t color, const char *text, float adjust) {
+ int len = Text_Width(text, scale, 0);
+ Text_Paint(x - len / 2, y, scale, color, text, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE);
+}
+
+void Text_PaintCenter_AutoWrapped(float x, float y, float xmax, float ystep, float scale, vec4_t color, const char *str, float adjust) {
+ int width;
+ char *s1,*s2,*s3;
+ char c_bcp;
+ char buf[1024];
+
+ if (!str || str[0]=='\0')
+ return;
+
+ Q_strncpyz(buf, str, sizeof(buf));
+ s1 = s2 = s3 = buf;
+
+ while (1) {
+ do {
+ s3++;
+ } while (*s3!=' ' && *s3!='\0');
+ c_bcp = *s3;
+ *s3 = '\0';
+ width = Text_Width(s1, scale, 0);
+ *s3 = c_bcp;
+ if (width > xmax) {
+ if (s1==s2)
+ {
+ // fuck, don't have a clean cut, we'll overflow
+ s2 = s3;
+ }
+ *s2 = '\0';
+ Text_PaintCenter(x, y, scale, color, s1, adjust);
+ y += ystep;
+ if (c_bcp == '\0')
+ {
+ // that was the last word
+ // we could start a new loop, but that wouldn't be much use
+ // even if the word is too long, we would overflow it (see above)
+ // so just print it now if needed
+ s2++;
+ if (*s2 != '\0') // if we are printing an overflowing line we have s2 == s3
+ Text_PaintCenter(x, y, scale, color, s2, adjust);
+ break;
+ }
+ s2++;
+ s1 = s2;
+ s3 = s2;
+ }
+ else
+ {
+ s2 = s3;
+ if (c_bcp == '\0') // we reached the end
+ {
+ Text_PaintCenter(x, y, scale, color, s1, adjust);
+ break;
+ }
+ }
+ }
+}
+
+
+static void UI_DisplayDownloadInfo( const char *downloadName, float centerPoint, float yStart, float scale ) {
+ static char dlText[] = "Downloading:";
+ static char etaText[] = "Estimated time left:";
+ static char xferText[] = "Transfer rate:";
+
+ int downloadSize, downloadCount, downloadTime;
+ char dlSizeBuf[64], totalSizeBuf[64], xferRateBuf[64], dlTimeBuf[64];
+ int xferRate;
+ int leftWidth;
+ const char *s;
+
+ downloadSize = trap_Cvar_VariableValue( "cl_downloadSize" );
+ downloadCount = trap_Cvar_VariableValue( "cl_downloadCount" );
+ downloadTime = trap_Cvar_VariableValue( "cl_downloadTime" );
+
+ leftWidth = 320;
+
+ UI_SetColor(colorWhite);
+ Text_PaintCenter(centerPoint, yStart + 112, scale, colorWhite, dlText, 0);
+ Text_PaintCenter(centerPoint, yStart + 192, scale, colorWhite, etaText, 0);
+ Text_PaintCenter(centerPoint, yStart + 248, scale, colorWhite, xferText, 0);
+
+ if (downloadSize > 0) {
+ s = va( "%s (%d%%)", downloadName, downloadCount * 100 / downloadSize );
+ } else {
+ s = downloadName;
+ }
+
+ Text_PaintCenter(centerPoint, yStart+136, scale, colorWhite, s, 0);
+
+ UI_ReadableSize( dlSizeBuf, sizeof dlSizeBuf, downloadCount );
+ UI_ReadableSize( totalSizeBuf, sizeof totalSizeBuf, downloadSize );
+
+ if (downloadCount < 4096 || !downloadTime) {
+ Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0);
+ Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0);
+ } else {
+ if ((uiInfo.uiDC.realTime - downloadTime) / 1000) {
+ xferRate = downloadCount / ((uiInfo.uiDC.realTime - downloadTime) / 1000);
+ } else {
+ xferRate = 0;
+ }
+ UI_ReadableSize( xferRateBuf, sizeof xferRateBuf, xferRate );
+
+ // Extrapolate estimated completion time
+ if (downloadSize && xferRate) {
+ int n = downloadSize / xferRate; // estimated time for entire d/l in secs
+
+ // We do it in K (/1024) because we'd overflow around 4MB
+ UI_PrintTime ( dlTimeBuf, sizeof dlTimeBuf,
+ (n - (((downloadCount/1024) * n) / (downloadSize/1024))) * 1000);
+
+ Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, dlTimeBuf, 0);
+ Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0);
+ } else {
+ Text_PaintCenter(leftWidth, yStart+216, scale, colorWhite, "estimating", 0);
+ if (downloadSize) {
+ Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s of %s copied)", dlSizeBuf, totalSizeBuf), 0);
+ } else {
+ Text_PaintCenter(leftWidth, yStart+160, scale, colorWhite, va("(%s copied)", dlSizeBuf), 0);
+ }
+ }
+
+ if (xferRate) {
+ Text_PaintCenter(leftWidth, yStart+272, scale, colorWhite, va("%s/Sec", xferRateBuf), 0);
+ }
+ }
+}
+
+/*
+========================
+UI_DrawConnectScreen
+
+This will also be overlaid on the cgame info screen during loading
+to prevent it from blinking away too rapidly on local or lan games.
+========================
+*/
+void UI_DrawConnectScreen( qboolean overlay ) {
+ char *s;
+ uiClientState_t cstate;
+ char info[MAX_INFO_VALUE];
+ char text[256];
+ float centerPoint, yStart, scale;
+
+ menuDef_t *menu = Menus_FindByName("Connect");
+
+
+ if ( !overlay && menu ) {
+ Menu_Paint(menu, qtrue);
+ }
+
+ if (!overlay) {
+ centerPoint = 320;
+ yStart = 130;
+ scale = 0.5f;
+ } else {
+ centerPoint = 320;
+ yStart = 32;
+ scale = 0.6f;
+ return;
+ }
+
+ // see what information we should display
+ trap_GetClientState( &cstate );
+
+ info[0] = '\0';
+ if( trap_GetConfigString( CS_SERVERINFO, info, sizeof(info) ) ) {
+ Text_PaintCenter(centerPoint, yStart, scale, colorWhite, va( "Loading %s", Info_ValueForKey( info, "mapname" )), 0);
+ }
+
+ if (!Q_stricmp(cstate.servername,"localhost")) {
+ Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite, va("Starting up..."), ITEM_TEXTSTYLE_SHADOWEDMORE);
+ } else {
+ strcpy(text, va("Connecting to %s", cstate.servername));
+ Text_PaintCenter(centerPoint, yStart + 48, scale, colorWhite,text , ITEM_TEXTSTYLE_SHADOWEDMORE);
+ }
+
+
+ // display global MOTD at bottom
+ Text_PaintCenter(centerPoint, 600, scale, colorWhite, Info_ValueForKey( cstate.updateInfoString, "motd" ), 0);
+ // print any server info (server full, bad version, etc)
+ if ( cstate.connState < CA_CONNECTED ) {
+ Text_PaintCenter_AutoWrapped(centerPoint, yStart + 176, 630, 20, scale, colorWhite, cstate.messageString, 0);
+ }
+
+ if ( lastConnState > cstate.connState ) {
+ lastLoadingText[0] = '\0';
+ }
+ lastConnState = cstate.connState;
+
+ switch ( cstate.connState ) {
+ case CA_CONNECTING:
+ s = va("Awaiting connection...%i", cstate.connectPacketCount);
+ break;
+ case CA_CHALLENGING:
+ s = va("Awaiting challenge...%i", cstate.connectPacketCount);
+ break;
+ case CA_CONNECTED: {
+ char downloadName[MAX_INFO_VALUE];
+
+ trap_Cvar_VariableStringBuffer( "cl_downloadName", downloadName, sizeof(downloadName) );
+ if (*downloadName) {
+ UI_DisplayDownloadInfo( downloadName, centerPoint, yStart, scale );
+ return;
+ }
+ }
+ s = "Awaiting gamestate...";
+ break;
+ case CA_LOADING:
+ return;
+ case CA_PRIMED:
+ return;
+ default:
+ return;
+ }
+
+
+ if (Q_stricmp(cstate.servername,"localhost")) {
+ Text_PaintCenter(centerPoint, yStart + 80, scale, colorWhite, s, 0);
+ }
+
+ // password required / connection rejected information goes here
+}
+
+
+/*
+================
+cvars
+================
+*/
+
+typedef struct {
+ vmCvar_t *vmCvar;
+ char *cvarName;
+ char *defaultString;
+ int cvarFlags;
+} cvarTable_t;
+
+vmCvar_t ui_ffa_fraglimit;
+vmCvar_t ui_ffa_timelimit;
+
+vmCvar_t ui_tourney_fraglimit;
+vmCvar_t ui_tourney_timelimit;
+
+vmCvar_t ui_team_fraglimit;
+vmCvar_t ui_team_timelimit;
+vmCvar_t ui_team_friendly;
+
+vmCvar_t ui_ctf_capturelimit;
+vmCvar_t ui_ctf_timelimit;
+vmCvar_t ui_ctf_friendly;
+
+vmCvar_t ui_arenasFile;
+vmCvar_t ui_botsFile;
+vmCvar_t ui_spScores1;
+vmCvar_t ui_spScores2;
+vmCvar_t ui_spScores3;
+vmCvar_t ui_spScores4;
+vmCvar_t ui_spScores5;
+vmCvar_t ui_spAwards;
+vmCvar_t ui_spVideos;
+vmCvar_t ui_spSkill;
+
+vmCvar_t ui_spSelection;
+
+vmCvar_t ui_browserMaster;
+vmCvar_t ui_browserGameType;
+vmCvar_t ui_browserSortKey;
+vmCvar_t ui_browserShowFull;
+vmCvar_t ui_browserShowEmpty;
+
+vmCvar_t ui_brassTime;
+vmCvar_t ui_drawCrosshair;
+vmCvar_t ui_drawCrosshairNames;
+vmCvar_t ui_marks;
+
+vmCvar_t ui_server1;
+vmCvar_t ui_server2;
+vmCvar_t ui_server3;
+vmCvar_t ui_server4;
+vmCvar_t ui_server5;
+vmCvar_t ui_server6;
+vmCvar_t ui_server7;
+vmCvar_t ui_server8;
+vmCvar_t ui_server9;
+vmCvar_t ui_server10;
+vmCvar_t ui_server11;
+vmCvar_t ui_server12;
+vmCvar_t ui_server13;
+vmCvar_t ui_server14;
+vmCvar_t ui_server15;
+vmCvar_t ui_server16;
+
+vmCvar_t ui_redteam;
+vmCvar_t ui_redteam1;
+vmCvar_t ui_redteam2;
+vmCvar_t ui_redteam3;
+vmCvar_t ui_redteam4;
+vmCvar_t ui_redteam5;
+vmCvar_t ui_blueteam;
+vmCvar_t ui_blueteam1;
+vmCvar_t ui_blueteam2;
+vmCvar_t ui_blueteam3;
+vmCvar_t ui_blueteam4;
+vmCvar_t ui_blueteam5;
+vmCvar_t ui_teamName;
+vmCvar_t ui_dedicated;
+vmCvar_t ui_gameType;
+vmCvar_t ui_netGameType;
+vmCvar_t ui_actualNetGameType;
+vmCvar_t ui_joinGameType;
+vmCvar_t ui_netSource;
+vmCvar_t ui_serverFilterType;
+vmCvar_t ui_opponentName;
+vmCvar_t ui_menuFiles;
+vmCvar_t ui_currentTier;
+vmCvar_t ui_currentMap;
+vmCvar_t ui_currentNetMap;
+vmCvar_t ui_mapIndex;
+vmCvar_t ui_currentOpponent;
+vmCvar_t ui_selectedPlayer;
+vmCvar_t ui_selectedPlayerName;
+vmCvar_t ui_lastServerRefresh_0;
+vmCvar_t ui_lastServerRefresh_1;
+vmCvar_t ui_lastServerRefresh_2;
+vmCvar_t ui_lastServerRefresh_3;
+vmCvar_t ui_lastServerRefresh_0_time;
+vmCvar_t ui_lastServerRefresh_1_time;
+vmCvar_t ui_lastServerRefresh_2_time;
+vmCvar_t ui_lastServerRefresh_3_time;
+vmCvar_t ui_singlePlayerActive;
+vmCvar_t ui_scoreAccuracy;
+vmCvar_t ui_scoreImpressives;
+vmCvar_t ui_scoreExcellents;
+vmCvar_t ui_scoreCaptures;
+vmCvar_t ui_scoreDefends;
+vmCvar_t ui_scoreAssists;
+vmCvar_t ui_scoreGauntlets;
+vmCvar_t ui_scoreScore;
+vmCvar_t ui_scorePerfect;
+vmCvar_t ui_scoreTeam;
+vmCvar_t ui_scoreBase;
+vmCvar_t ui_scoreTimeBonus;
+vmCvar_t ui_scoreSkillBonus;
+vmCvar_t ui_scoreShutoutBonus;
+vmCvar_t ui_scoreTime;
+vmCvar_t ui_captureLimit;
+vmCvar_t ui_fragLimit;
+vmCvar_t ui_smallFont;
+vmCvar_t ui_bigFont;
+vmCvar_t ui_findPlayer;
+vmCvar_t ui_Q3Model;
+vmCvar_t ui_hudFiles;
+vmCvar_t ui_recordSPDemo;
+vmCvar_t ui_realCaptureLimit;
+vmCvar_t ui_realWarmUp;
+vmCvar_t ui_serverStatusTimeOut;
+
+//TA: bank values
+vmCvar_t ui_bank;
+vmCvar_t ui_winner;
+
+
+// bk001129 - made static to avoid aliasing
+static cvarTable_t cvarTable[] = {
+ { &ui_ffa_fraglimit, "ui_ffa_fraglimit", "20", CVAR_ARCHIVE },
+ { &ui_ffa_timelimit, "ui_ffa_timelimit", "0", CVAR_ARCHIVE },
+
+ { &ui_tourney_fraglimit, "ui_tourney_fraglimit", "0", CVAR_ARCHIVE },
+ { &ui_tourney_timelimit, "ui_tourney_timelimit", "15", CVAR_ARCHIVE },
+
+ { &ui_team_fraglimit, "ui_team_fraglimit", "0", CVAR_ARCHIVE },
+ { &ui_team_timelimit, "ui_team_timelimit", "20", CVAR_ARCHIVE },
+ { &ui_team_friendly, "ui_team_friendly", "1", CVAR_ARCHIVE },
+
+ { &ui_ctf_capturelimit, "ui_ctf_capturelimit", "8", CVAR_ARCHIVE },
+ { &ui_ctf_timelimit, "ui_ctf_timelimit", "30", CVAR_ARCHIVE },
+ { &ui_ctf_friendly, "ui_ctf_friendly", "0", CVAR_ARCHIVE },
+
+ { &ui_arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM },
+ { &ui_botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM },
+ { &ui_spScores1, "g_spScores1", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spScores2, "g_spScores2", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spScores3, "g_spScores3", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spScores4, "g_spScores4", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spScores5, "g_spScores5", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spAwards, "g_spAwards", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spVideos, "g_spVideos", "", CVAR_ARCHIVE | CVAR_ROM },
+ { &ui_spSkill, "g_spSkill", "2", CVAR_ARCHIVE },
+
+ { &ui_spSelection, "ui_spSelection", "", CVAR_ROM },
+ { &ui_winner, "ui_winner", "", CVAR_ROM },
+
+ { &ui_browserMaster, "ui_browserMaster", "0", CVAR_ARCHIVE },
+ { &ui_browserGameType, "ui_browserGameType", "0", CVAR_ARCHIVE },
+ { &ui_browserSortKey, "ui_browserSortKey", "4", CVAR_ARCHIVE },
+ { &ui_browserShowFull, "ui_browserShowFull", "1", CVAR_ARCHIVE },
+ { &ui_browserShowEmpty, "ui_browserShowEmpty", "1", CVAR_ARCHIVE },
+
+ { &ui_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE },
+ { &ui_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE },
+ { &ui_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE },
+ { &ui_marks, "cg_marks", "1", CVAR_ARCHIVE },
+
+ { &ui_server1, "server1", "", CVAR_ARCHIVE },
+ { &ui_server2, "server2", "", CVAR_ARCHIVE },
+ { &ui_server3, "server3", "", CVAR_ARCHIVE },
+ { &ui_server4, "server4", "", CVAR_ARCHIVE },
+ { &ui_server5, "server5", "", CVAR_ARCHIVE },
+ { &ui_server6, "server6", "", CVAR_ARCHIVE },
+ { &ui_server7, "server7", "", CVAR_ARCHIVE },
+ { &ui_server8, "server8", "", CVAR_ARCHIVE },
+ { &ui_server9, "server9", "", CVAR_ARCHIVE },
+ { &ui_server10, "server10", "", CVAR_ARCHIVE },
+ { &ui_server11, "server11", "", CVAR_ARCHIVE },
+ { &ui_server12, "server12", "", CVAR_ARCHIVE },
+ { &ui_server13, "server13", "", CVAR_ARCHIVE },
+ { &ui_server14, "server14", "", CVAR_ARCHIVE },
+ { &ui_server15, "server15", "", CVAR_ARCHIVE },
+ { &ui_server16, "server16", "", CVAR_ARCHIVE },
+ { &ui_new, "ui_new", "0", CVAR_TEMP },
+ { &ui_debug, "ui_debug", "0", CVAR_TEMP },
+ { &ui_initialized, "ui_initialized", "0", CVAR_TEMP },
+ { &ui_teamName, "ui_teamName", "Pagans", CVAR_ARCHIVE },
+ { &ui_opponentName, "ui_opponentName", "Stroggs", CVAR_ARCHIVE },
+ { &ui_redteam, "ui_redteam", "Pagans", CVAR_ARCHIVE },
+ { &ui_blueteam, "ui_blueteam", "Stroggs", CVAR_ARCHIVE },
+ { &ui_dedicated, "ui_dedicated", "0", CVAR_ARCHIVE },
+ { &ui_gameType, "ui_gametype", "3", CVAR_ARCHIVE },
+ { &ui_joinGameType, "ui_joinGametype", "0", CVAR_ARCHIVE },
+ { &ui_netGameType, "ui_netGametype", "3", CVAR_ARCHIVE },
+ { &ui_actualNetGameType, "ui_actualNetGametype", "3", CVAR_ARCHIVE },
+ { &ui_redteam1, "ui_redteam1", "0", CVAR_ARCHIVE },
+ { &ui_redteam2, "ui_redteam2", "0", CVAR_ARCHIVE },
+ { &ui_redteam3, "ui_redteam3", "0", CVAR_ARCHIVE },
+ { &ui_redteam4, "ui_redteam4", "0", CVAR_ARCHIVE },
+ { &ui_redteam5, "ui_redteam5", "0", CVAR_ARCHIVE },
+ { &ui_blueteam1, "ui_blueteam1", "0", CVAR_ARCHIVE },
+ { &ui_blueteam2, "ui_blueteam2", "0", CVAR_ARCHIVE },
+ { &ui_blueteam3, "ui_blueteam3", "0", CVAR_ARCHIVE },
+ { &ui_blueteam4, "ui_blueteam4", "0", CVAR_ARCHIVE },
+ { &ui_blueteam5, "ui_blueteam5", "0", CVAR_ARCHIVE },
+ { &ui_netSource, "ui_netSource", "0", CVAR_ARCHIVE },
+ { &ui_menuFiles, "ui_menuFiles", "ui/menus.txt", CVAR_ARCHIVE },
+ { &ui_currentTier, "ui_currentTier", "0", CVAR_ARCHIVE },
+ { &ui_currentMap, "ui_currentMap", "0", CVAR_ARCHIVE },
+ { &ui_currentNetMap, "ui_currentNetMap", "0", CVAR_ARCHIVE },
+ { &ui_mapIndex, "ui_mapIndex", "0", CVAR_ARCHIVE },
+ { &ui_currentOpponent, "ui_currentOpponent", "0", CVAR_ARCHIVE },
+ { &ui_selectedPlayer, "cg_selectedPlayer", "0", CVAR_ARCHIVE},
+ { &ui_selectedPlayerName, "cg_selectedPlayerName", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_0, "ui_lastServerRefresh_0_time", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_1, "ui_lastServerRefresh_1_time", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_2, "ui_lastServerRefresh_2_time", "", CVAR_ARCHIVE},
+ { &ui_lastServerRefresh_3, "ui_lastServerRefresh_3_time", "", CVAR_ARCHIVE},
+ { &ui_singlePlayerActive, "ui_singlePlayerActive", "0", 0},
+ { &ui_scoreAccuracy, "ui_scoreAccuracy", "0", CVAR_ARCHIVE},
+ { &ui_scoreImpressives, "ui_scoreImpressives", "0", CVAR_ARCHIVE},
+ { &ui_scoreExcellents, "ui_scoreExcellents", "0", CVAR_ARCHIVE},
+ { &ui_scoreCaptures, "ui_scoreCaptures", "0", CVAR_ARCHIVE},
+ { &ui_scoreDefends, "ui_scoreDefends", "0", CVAR_ARCHIVE},
+ { &ui_scoreAssists, "ui_scoreAssists", "0", CVAR_ARCHIVE},
+ { &ui_scoreGauntlets, "ui_scoreGauntlets", "0",CVAR_ARCHIVE},
+ { &ui_scoreScore, "ui_scoreScore", "0", CVAR_ARCHIVE},
+ { &ui_scorePerfect, "ui_scorePerfect", "0", CVAR_ARCHIVE},
+ { &ui_scoreTeam, "ui_scoreTeam", "0 to 0", CVAR_ARCHIVE},
+ { &ui_scoreBase, "ui_scoreBase", "0", CVAR_ARCHIVE},
+ { &ui_scoreTime, "ui_scoreTime", "00:00", CVAR_ARCHIVE},
+ { &ui_scoreTimeBonus, "ui_scoreTimeBonus", "0", CVAR_ARCHIVE},
+ { &ui_scoreSkillBonus, "ui_scoreSkillBonus", "0", CVAR_ARCHIVE},
+ { &ui_scoreShutoutBonus, "ui_scoreShutoutBonus", "0", CVAR_ARCHIVE},
+ { &ui_fragLimit, "ui_fragLimit", "10", 0},
+ { &ui_captureLimit, "ui_captureLimit", "5", 0},
+ { &ui_smallFont, "ui_smallFont", "0.2", CVAR_ARCHIVE},
+ { &ui_bigFont, "ui_bigFont", "0.5", CVAR_ARCHIVE},
+ { &ui_findPlayer, "ui_findPlayer", "Sarge", CVAR_ARCHIVE},
+ { &ui_Q3Model, "ui_q3model", "0", CVAR_ARCHIVE},
+ { &ui_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE},
+ { &ui_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE},
+ { &ui_teamArenaFirstRun, "ui_teamArenaFirstRun", "0", CVAR_ARCHIVE},
+ { &ui_realWarmUp, "g_warmup", "20", CVAR_ARCHIVE},
+ { &ui_realCaptureLimit, "capturelimit", "8", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART},
+ { &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE},
+
+ { &ui_bank, "ui_bank", "0", 0 },
+
+};
+
+// bk001129 - made static to avoid aliasing
+static int cvarTableSize = sizeof(cvarTable) / sizeof(cvarTable[0]);
+
+
+/*
+=================
+UI_RegisterCvars
+=================
+*/
+void UI_RegisterCvars( void ) {
+ int i;
+ cvarTable_t *cv;
+
+ for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) {
+ trap_Cvar_Register( cv->vmCvar, cv->cvarName, cv->defaultString, cv->cvarFlags );
+ }
+}
+
+/*
+=================
+UI_UpdateCvars
+=================
+*/
+void UI_UpdateCvars( void ) {
+ int i;
+ cvarTable_t *cv;
+
+ for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) {
+ trap_Cvar_Update( cv->vmCvar );
+ }
+}
+
+
+/*
+=================
+ArenaServers_StopRefresh
+=================
+*/
+static void UI_StopServerRefresh( void )
+{
+ int count;
+
+ if (!uiInfo.serverStatus.refreshActive) {
+ // not currently refreshing
+ return;
+ }
+ uiInfo.serverStatus.refreshActive = qfalse;
+ Com_Printf("%d servers listed in browser with %d players.\n",
+ uiInfo.serverStatus.numDisplayServers,
+ uiInfo.serverStatus.numPlayersOnServers);
+ count = trap_LAN_GetServerCount(ui_netSource.integer);
+ if (count - uiInfo.serverStatus.numDisplayServers > 0) {
+ Com_Printf("%d servers not listed due to packet loss or pings higher than %d\n",
+ count - uiInfo.serverStatus.numDisplayServers,
+ (int) trap_Cvar_VariableValue("cl_maxPing"));
+ }
+
+}
+
+/*
+=================
+UI_DoServerRefresh
+=================
+*/
+static void UI_DoServerRefresh( void )
+{
+ qboolean wait = qfalse;
+
+ if (!uiInfo.serverStatus.refreshActive) {
+ return;
+ }
+ if (ui_netSource.integer != AS_FAVORITES) {
+ if (ui_netSource.integer == AS_LOCAL) {
+ if (!trap_LAN_GetServerCount(ui_netSource.integer)) {
+ wait = qtrue;
+ }
+ } else {
+ if (trap_LAN_GetServerCount(ui_netSource.integer) < 0) {
+ wait = qtrue;
+ }
+ }
+ }
+
+ if (uiInfo.uiDC.realTime < uiInfo.serverStatus.refreshtime) {
+ if (wait) {
+ return;
+ }
+ }
+
+ // if still trying to retrieve pings
+ if (trap_LAN_UpdateVisiblePings(ui_netSource.integer)) {
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
+ } else if (!wait) {
+ // get the last servers in the list
+ UI_BuildServerDisplayList(2);
+ // stop the refresh
+ UI_StopServerRefresh();
+ }
+ //
+ UI_BuildServerDisplayList(qfalse);
+}
+
+/*
+=================
+UI_StartServerRefresh
+=================
+*/
+static void UI_StartServerRefresh(qboolean full)
+{
+ int i;
+ char *ptr;
+ int time;
+ qtime_t q;
+
+ time = trap_RealTime(&q);
+ trap_Cvar_Set( va("ui_lastServerRefresh_%i_time", ui_netSource.integer ),
+ va( "%i", time ) );
+ trap_Cvar_Set( va("ui_lastServerRefresh_%i", ui_netSource.integer),
+ va("%s-%i, %i at %i:%02i", MonthAbbrev[q.tm_mon],q.tm_mday, 1900+q.tm_year,q.tm_hour,q.tm_min));
+
+ if (!full) {
+ UI_UpdatePendingPings();
+ return;
+ }
+
+ uiInfo.serverStatus.refreshActive = qtrue;
+ uiInfo.serverStatus.nextDisplayRefresh = uiInfo.uiDC.realTime + 1000;
+ // clear number of displayed servers
+ uiInfo.serverStatus.numDisplayServers = 0;
+ uiInfo.serverStatus.numPlayersOnServers = 0;
+ // mark all servers as visible so we store ping updates for them
+ trap_LAN_MarkServerVisible(ui_netSource.integer, -1, qtrue);
+ // reset all the pings
+ trap_LAN_ResetPings(ui_netSource.integer);
+ //
+ if( ui_netSource.integer == AS_LOCAL ) {
+ trap_Cmd_ExecuteText( EXEC_NOW, "localservers\n" );
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 1000;
+ return;
+ }
+
+ uiInfo.serverStatus.refreshtime = uiInfo.uiDC.realTime + 5000;
+ if( ui_netSource.integer == AS_GLOBAL || ui_netSource.integer == AS_MPLAYER ) {
+ if( ui_netSource.integer == AS_GLOBAL ) {
+ i = 0;
+ }
+ else {
+ i = 1;
+ }
+
+ ptr = UI_Cvar_VariableString("debug_protocol");
+ if (strlen(ptr)) {
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %s full empty\n", i, ptr));
+ }
+ else {
+ trap_Cmd_ExecuteText( EXEC_NOW, va( "globalservers %d %d full empty\n", i, (int)trap_Cvar_VariableValue( "protocol" ) ) );
+ }
+ }
+}
+
diff --git a/src/ui/ui_players.c b/src/ui/ui_players.c
new file mode 100644
index 0000000..5dbfdd3
--- /dev/null
+++ b/src/ui/ui_players.c
@@ -0,0 +1,1369 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+// ui_players.c
+
+#include "ui_local.h"
+
+
+#define UI_TIMER_GESTURE 2300
+#define UI_TIMER_JUMP 1000
+#define UI_TIMER_LAND 130
+#define UI_TIMER_WEAPON_SWITCH 300
+#define UI_TIMER_ATTACK 500
+#define UI_TIMER_MUZZLE_FLASH 20
+#define UI_TIMER_WEAPON_DELAY 250
+
+#define JUMP_HEIGHT 56
+
+#define SWINGSPEED 0.3f
+
+#define SPIN_SPEED 0.9f
+#define COAST_TIME 1000
+
+
+static int dp_realtime;
+static float jumpHeight;
+sfxHandle_t weaponChangeSound;
+
+
+/*
+===============
+UI_PlayerInfo_SetWeapon
+===============
+*/
+static void UI_PlayerInfo_SetWeapon( playerInfo_t *pi, weapon_t weaponNum )
+{
+ //TA: FIXME: this is probably useless for trem
+/* gitem_t * item;
+ char path[MAX_QPATH];
+
+ pi->currentWeapon = weaponNum;
+tryagain:
+ pi->realWeapon = weaponNum;
+ pi->weaponModel = 0;
+ pi->barrelModel = 0;
+ pi->flashModel = 0;
+
+ if ( weaponNum == WP_NONE ) {
+ return;
+ }
+
+ if ( item->classname ) {
+ pi->weaponModel = trap_R_RegisterModel( item->world_model[0] );
+ }
+
+ if( pi->weaponModel == 0 ) {
+ if( weaponNum == WP_MACHINEGUN ) {
+ weaponNum = WP_NONE;
+ goto tryagain;
+ }
+ weaponNum = WP_MACHINEGUN;
+ goto tryagain;
+ }
+
+ if ( weaponNum == WP_MACHINEGUN ) {
+ strcpy( path, item->world_model[0] );
+ COM_StripExtension( path, path );
+ strcat( path, "_barrel.md3" );
+ pi->barrelModel = trap_R_RegisterModel( path );
+ }
+
+ strcpy( path, item->world_model[0] );
+ COM_StripExtension( path, path );
+ strcat( path, "_flash.md3" );
+ pi->flashModel = trap_R_RegisterModel( path );
+
+ switch( weaponNum ) {
+ case WP_GAUNTLET:
+ MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
+ break;
+
+ case WP_MACHINEGUN:
+ MAKERGB( pi->flashDlightColor, 1, 1, 0 );
+ break;
+
+ case WP_SHOTGUN:
+ MAKERGB( pi->flashDlightColor, 1, 1, 0 );
+ break;
+
+ case WP_GRENADE_LAUNCHER:
+ MAKERGB( pi->flashDlightColor, 1, 0.7f, 0.5f );
+ break;
+
+ case WP_ROCKET_LAUNCHER:
+ MAKERGB( pi->flashDlightColor, 1, 0.75f, 0 );
+ break;
+
+ case WP_TESLAGEN:
+ MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
+ break;
+
+ case WP_RAILGUN:
+ MAKERGB( pi->flashDlightColor, 1, 0.5f, 0 );
+ break;
+
+ case WP_BFG:
+ MAKERGB( pi->flashDlightColor, 1, 0.7f, 1 );
+ break;
+
+ case WP_GRAPPLING_HOOK:
+ MAKERGB( pi->flashDlightColor, 0.6f, 0.6f, 1 );
+ break;
+
+ default:
+ MAKERGB( pi->flashDlightColor, 1, 1, 1 );
+ break;
+ }*/
+}
+
+
+/*
+===============
+UI_ForceLegsAnim
+===============
+*/
+static void UI_ForceLegsAnim( playerInfo_t *pi, int anim ) {
+ pi->legsAnim = ( ( pi->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
+
+ if ( anim == LEGS_JUMP ) {
+ pi->legsAnimationTimer = UI_TIMER_JUMP;
+ }
+}
+
+
+/*
+===============
+UI_SetLegsAnim
+===============
+*/
+static void UI_SetLegsAnim( playerInfo_t *pi, int anim ) {
+ if ( pi->pendingLegsAnim ) {
+ anim = pi->pendingLegsAnim;
+ pi->pendingLegsAnim = 0;
+ }
+ UI_ForceLegsAnim( pi, anim );
+}
+
+
+/*
+===============
+UI_ForceTorsoAnim
+===============
+*/
+static void UI_ForceTorsoAnim( playerInfo_t *pi, int anim ) {
+ pi->torsoAnim = ( ( pi->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim;
+
+ if ( anim == TORSO_GESTURE ) {
+ pi->torsoAnimationTimer = UI_TIMER_GESTURE;
+ }
+
+ if ( anim == TORSO_ATTACK || anim == TORSO_ATTACK2 ) {
+ pi->torsoAnimationTimer = UI_TIMER_ATTACK;
+ }
+}
+
+
+/*
+===============
+UI_SetTorsoAnim
+===============
+*/
+static void UI_SetTorsoAnim( playerInfo_t *pi, int anim ) {
+ if ( pi->pendingTorsoAnim ) {
+ anim = pi->pendingTorsoAnim;
+ pi->pendingTorsoAnim = 0;
+ }
+
+ UI_ForceTorsoAnim( pi, anim );
+}
+
+
+/*
+===============
+UI_TorsoSequencing
+===============
+*/
+static void UI_TorsoSequencing( playerInfo_t *pi ) {
+ int currentAnim;
+
+ currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
+
+ if ( pi->weapon != pi->currentWeapon ) {
+ if ( currentAnim != TORSO_DROP ) {
+ pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
+ UI_ForceTorsoAnim( pi, TORSO_DROP );
+ }
+ }
+
+ if ( pi->torsoAnimationTimer > 0 ) {
+ return;
+ }
+
+ if( currentAnim == TORSO_GESTURE ) {
+ UI_SetTorsoAnim( pi, TORSO_STAND );
+ return;
+ }
+
+ if( currentAnim == TORSO_ATTACK || currentAnim == TORSO_ATTACK2 ) {
+ UI_SetTorsoAnim( pi, TORSO_STAND );
+ return;
+ }
+
+ if ( currentAnim == TORSO_DROP ) {
+ UI_PlayerInfo_SetWeapon( pi, pi->weapon );
+ pi->torsoAnimationTimer = UI_TIMER_WEAPON_SWITCH;
+ UI_ForceTorsoAnim( pi, TORSO_RAISE );
+ return;
+ }
+
+ if ( currentAnim == TORSO_RAISE ) {
+ UI_SetTorsoAnim( pi, TORSO_STAND );
+ return;
+ }
+}
+
+
+/*
+===============
+UI_LegsSequencing
+===============
+*/
+static void UI_LegsSequencing( playerInfo_t *pi ) {
+ int currentAnim;
+
+ currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
+
+ if ( pi->legsAnimationTimer > 0 ) {
+ if ( currentAnim == LEGS_JUMP ) {
+ jumpHeight = JUMP_HEIGHT * sin( M_PI * ( UI_TIMER_JUMP - pi->legsAnimationTimer ) / UI_TIMER_JUMP );
+ }
+ return;
+ }
+
+ if ( currentAnim == LEGS_JUMP ) {
+ UI_ForceLegsAnim( pi, LEGS_LAND );
+ pi->legsAnimationTimer = UI_TIMER_LAND;
+ jumpHeight = 0;
+ return;
+ }
+
+ if ( currentAnim == LEGS_LAND ) {
+ UI_SetLegsAnim( pi, LEGS_IDLE );
+ return;
+ }
+}
+
+
+/*
+======================
+UI_PositionEntityOnTag
+======================
+*/
+static void UI_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ clipHandle_t parentModel, char *tagName ) {
+ int i;
+ orientation_t lerped;
+
+ // lerp the tag
+ trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
+ 1.0 - parent->backlerp, tagName );
+
+ // FIXME: allow origin offsets along tag?
+ VectorCopy( parent->origin, entity->origin );
+ for ( i = 0 ; i < 3 ; i++ ) {
+ VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
+ }
+
+ // cast away const because of compiler problems
+ MatrixMultiply( lerped.axis, ((refEntity_t*)parent)->axis, entity->axis );
+ entity->backlerp = parent->backlerp;
+}
+
+
+/*
+======================
+UI_PositionRotatedEntityOnTag
+======================
+*/
+static void UI_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent,
+ clipHandle_t parentModel, char *tagName ) {
+ int i;
+ orientation_t lerped;
+ vec3_t tempAxis[3];
+
+ // lerp the tag
+ trap_CM_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame,
+ 1.0 - parent->backlerp, tagName );
+
+ // FIXME: allow origin offsets along tag?
+ VectorCopy( parent->origin, entity->origin );
+ for ( i = 0 ; i < 3 ; i++ ) {
+ VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin );
+ }
+
+ // cast away const because of compiler problems
+ MatrixMultiply( entity->axis, ((refEntity_t *)parent)->axis, tempAxis );
+ MatrixMultiply( lerped.axis, tempAxis, entity->axis );
+}
+
+
+/*
+===============
+UI_SetLerpFrameAnimation
+===============
+*/
+static void UI_SetLerpFrameAnimation( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
+ animation_t *anim;
+
+ lf->animationNumber = newAnimation;
+ newAnimation &= ~ANIM_TOGGLEBIT;
+
+ if ( newAnimation < 0 || newAnimation >= MAX_PLAYER_ANIMATIONS ) {
+ trap_Error( va("Bad animation number: %i", newAnimation) );
+ }
+
+ anim = &ci->animations[ newAnimation ];
+
+ lf->animation = anim;
+ lf->animationTime = lf->frameTime + anim->initialLerp;
+}
+
+
+/*
+===============
+UI_RunLerpFrame
+===============
+*/
+static void UI_RunLerpFrame( playerInfo_t *ci, lerpFrame_t *lf, int newAnimation ) {
+ int f;
+ animation_t *anim;
+
+ // see if the animation sequence is switching
+ if ( newAnimation != lf->animationNumber || !lf->animation ) {
+ UI_SetLerpFrameAnimation( ci, lf, newAnimation );
+ }
+
+ // if we have passed the current frame, move it to
+ // oldFrame and calculate a new frame
+ if ( dp_realtime >= lf->frameTime ) {
+ lf->oldFrame = lf->frame;
+ lf->oldFrameTime = lf->frameTime;
+
+ // get the next frame based on the animation
+ anim = lf->animation;
+ if ( dp_realtime < lf->animationTime ) {
+ lf->frameTime = lf->animationTime; // initial lerp
+ } else {
+ lf->frameTime = lf->oldFrameTime + anim->frameLerp;
+ }
+ f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp;
+ if ( f >= anim->numFrames ) {
+ f -= anim->numFrames;
+ if ( anim->loopFrames ) {
+ f %= anim->loopFrames;
+ f += anim->numFrames - anim->loopFrames;
+ } else {
+ f = anim->numFrames - 1;
+ // the animation is stuck at the end, so it
+ // can immediately transition to another sequence
+ lf->frameTime = dp_realtime;
+ }
+ }
+ lf->frame = anim->firstFrame + f;
+ if ( dp_realtime > lf->frameTime ) {
+ lf->frameTime = dp_realtime;
+ }
+ }
+
+ if ( lf->frameTime > dp_realtime + 200 ) {
+ lf->frameTime = dp_realtime;
+ }
+
+ if ( lf->oldFrameTime > dp_realtime ) {
+ lf->oldFrameTime = dp_realtime;
+ }
+ // calculate current lerp value
+ if ( lf->frameTime == lf->oldFrameTime ) {
+ lf->backlerp = 0;
+ } else {
+ lf->backlerp = 1.0 - (float)( dp_realtime - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime );
+ }
+}
+
+
+/*
+===============
+UI_PlayerAnimation
+===============
+*/
+static void UI_PlayerAnimation( playerInfo_t *pi, int *legsOld, int *legs, float *legsBackLerp,
+ int *torsoOld, int *torso, float *torsoBackLerp ) {
+
+ // legs animation
+ pi->legsAnimationTimer -= uiInfo.uiDC.frameTime;
+ if ( pi->legsAnimationTimer < 0 ) {
+ pi->legsAnimationTimer = 0;
+ }
+
+ UI_LegsSequencing( pi );
+
+ if ( pi->legs.yawing && ( pi->legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) {
+ UI_RunLerpFrame( pi, &pi->legs, LEGS_TURN );
+ } else {
+ UI_RunLerpFrame( pi, &pi->legs, pi->legsAnim );
+ }
+ *legsOld = pi->legs.oldFrame;
+ *legs = pi->legs.frame;
+ *legsBackLerp = pi->legs.backlerp;
+
+ // torso animation
+ pi->torsoAnimationTimer -= uiInfo.uiDC.frameTime;
+ if ( pi->torsoAnimationTimer < 0 ) {
+ pi->torsoAnimationTimer = 0;
+ }
+
+ UI_TorsoSequencing( pi );
+
+ UI_RunLerpFrame( pi, &pi->torso, pi->torsoAnim );
+ *torsoOld = pi->torso.oldFrame;
+ *torso = pi->torso.frame;
+ *torsoBackLerp = pi->torso.backlerp;
+}
+
+
+/*
+==================
+UI_SwingAngles
+==================
+*/
+static void UI_SwingAngles( float destination, float swingTolerance, float clampTolerance,
+ float speed, float *angle, qboolean *swinging ) {
+ float swing;
+ float move;
+ float scale;
+
+ if ( !*swinging ) {
+ // see if a swing should be started
+ swing = AngleSubtract( *angle, destination );
+ if ( swing > swingTolerance || swing < -swingTolerance ) {
+ *swinging = qtrue;
+ }
+ }
+
+ if ( !*swinging ) {
+ return;
+ }
+
+ // modify the speed depending on the delta
+ // so it doesn't seem so linear
+ swing = AngleSubtract( destination, *angle );
+ scale = fabs( swing );
+ if ( scale < swingTolerance * 0.5 ) {
+ scale = 0.5;
+ } else if ( scale < swingTolerance ) {
+ scale = 1.0;
+ } else {
+ scale = 2.0;
+ }
+
+ // swing towards the destination angle
+ if ( swing >= 0 ) {
+ move = uiInfo.uiDC.frameTime * scale * speed;
+ if ( move >= swing ) {
+ move = swing;
+ *swinging = qfalse;
+ }
+ *angle = AngleMod( *angle + move );
+ } else if ( swing < 0 ) {
+ move = uiInfo.uiDC.frameTime * scale * -speed;
+ if ( move <= swing ) {
+ move = swing;
+ *swinging = qfalse;
+ }
+ *angle = AngleMod( *angle + move );
+ }
+
+ // clamp to no more than tolerance
+ swing = AngleSubtract( destination, *angle );
+ if ( swing > clampTolerance ) {
+ *angle = AngleMod( destination - (clampTolerance - 1) );
+ } else if ( swing < -clampTolerance ) {
+ *angle = AngleMod( destination + (clampTolerance - 1) );
+ }
+}
+
+
+/*
+======================
+UI_MovedirAdjustment
+======================
+*/
+static float UI_MovedirAdjustment( playerInfo_t *pi ) {
+ vec3_t relativeAngles;
+ vec3_t moveVector;
+
+ VectorSubtract( pi->viewAngles, pi->moveAngles, relativeAngles );
+ AngleVectors( relativeAngles, moveVector, NULL, NULL );
+ if ( Q_fabs( moveVector[0] ) < 0.01 ) {
+ moveVector[0] = 0.0;
+ }
+ if ( Q_fabs( moveVector[1] ) < 0.01 ) {
+ moveVector[1] = 0.0;
+ }
+
+ if ( moveVector[1] == 0 && moveVector[0] > 0 ) {
+ return 0;
+ }
+ if ( moveVector[1] < 0 && moveVector[0] > 0 ) {
+ return 22;
+ }
+ if ( moveVector[1] < 0 && moveVector[0] == 0 ) {
+ return 45;
+ }
+ if ( moveVector[1] < 0 && moveVector[0] < 0 ) {
+ return -22;
+ }
+ if ( moveVector[1] == 0 && moveVector[0] < 0 ) {
+ return 0;
+ }
+ if ( moveVector[1] > 0 && moveVector[0] < 0 ) {
+ return 22;
+ }
+ if ( moveVector[1] > 0 && moveVector[0] == 0 ) {
+ return -45;
+ }
+
+ return -22;
+}
+
+
+/*
+===============
+UI_PlayerAngles
+===============
+*/
+static void UI_PlayerAngles( playerInfo_t *pi, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) {
+ vec3_t legsAngles, torsoAngles, headAngles;
+ float dest;
+ float adjust;
+
+ VectorCopy( pi->viewAngles, headAngles );
+ headAngles[YAW] = AngleMod( headAngles[YAW] );
+ VectorClear( legsAngles );
+ VectorClear( torsoAngles );
+
+ // --------- yaw -------------
+
+ // allow yaw to drift a bit
+ if ( ( pi->legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE
+ || ( pi->torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) {
+ // if not standing still, always point all in the same direction
+ pi->torso.yawing = qtrue; // always center
+ pi->torso.pitching = qtrue; // always center
+ pi->legs.yawing = qtrue; // always center
+ }
+
+ // adjust legs for movement dir
+ adjust = UI_MovedirAdjustment( pi );
+ legsAngles[YAW] = headAngles[YAW] + adjust;
+ torsoAngles[YAW] = headAngles[YAW] + 0.25 * adjust;
+
+
+ // torso
+ UI_SwingAngles( torsoAngles[YAW], 25, 90, SWINGSPEED, &pi->torso.yawAngle, &pi->torso.yawing );
+ UI_SwingAngles( legsAngles[YAW], 40, 90, SWINGSPEED, &pi->legs.yawAngle, &pi->legs.yawing );
+
+ torsoAngles[YAW] = pi->torso.yawAngle;
+ legsAngles[YAW] = pi->legs.yawAngle;
+
+ // --------- pitch -------------
+
+ // only show a fraction of the pitch angle in the torso
+ if ( headAngles[PITCH] > 180 ) {
+ dest = (-360 + headAngles[PITCH]) * 0.75;
+ } else {
+ dest = headAngles[PITCH] * 0.75;
+ }
+ UI_SwingAngles( dest, 15, 30, 0.1f, &pi->torso.pitchAngle, &pi->torso.pitching );
+ torsoAngles[PITCH] = pi->torso.pitchAngle;
+
+ // pull the angles back out of the hierarchial chain
+ AnglesSubtract( headAngles, torsoAngles, headAngles );
+ AnglesSubtract( torsoAngles, legsAngles, torsoAngles );
+ AnglesToAxis( legsAngles, legs );
+ AnglesToAxis( torsoAngles, torso );
+ AnglesToAxis( headAngles, head );
+}
+
+
+/*
+===============
+UI_PlayerFloatSprite
+===============
+*/
+static void UI_PlayerFloatSprite( playerInfo_t *pi, vec3_t origin, qhandle_t shader ) {
+ refEntity_t ent;
+
+ memset( &ent, 0, sizeof( ent ) );
+ VectorCopy( origin, ent.origin );
+ ent.origin[2] += 48;
+ ent.reType = RT_SPRITE;
+ ent.customShader = shader;
+ ent.radius = 10;
+ ent.renderfx = 0;
+ trap_R_AddRefEntityToScene( &ent );
+}
+
+
+/*
+======================
+UI_MachinegunSpinAngle
+======================
+*/
+float UI_MachinegunSpinAngle( playerInfo_t *pi ) {
+ int delta;
+ float angle;
+ float speed;
+ int torsoAnim;
+
+ delta = dp_realtime - pi->barrelTime;
+ if ( pi->barrelSpinning ) {
+ angle = pi->barrelAngle + delta * SPIN_SPEED;
+ } else {
+ if ( delta > COAST_TIME ) {
+ delta = COAST_TIME;
+ }
+
+ speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME );
+ angle = pi->barrelAngle + delta * speed;
+ }
+
+ torsoAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
+ if( torsoAnim == TORSO_ATTACK2 ) {
+ torsoAnim = TORSO_ATTACK;
+ }
+ if ( pi->barrelSpinning == !(torsoAnim == TORSO_ATTACK) ) {
+ pi->barrelTime = dp_realtime;
+ pi->barrelAngle = AngleMod( angle );
+ pi->barrelSpinning = !!(torsoAnim == TORSO_ATTACK);
+ }
+
+ return angle;
+}
+
+
+/*
+===============
+UI_DrawPlayer
+===============
+*/
+void UI_DrawPlayer( float x, float y, float w, float h, playerInfo_t *pi, int time ) {
+ refdef_t refdef;
+ refEntity_t legs;
+ refEntity_t torso;
+ refEntity_t head;
+ refEntity_t gun;
+ refEntity_t barrel;
+ refEntity_t flash;
+ vec3_t origin;
+ int renderfx;
+ vec3_t mins = {-16, -16, -24};
+ vec3_t maxs = {16, 16, 32};
+ float len;
+ float xx;
+
+ if ( !pi->legsModel || !pi->torsoModel || !pi->headModel || !pi->animations[0].numFrames ) {
+ return;
+ }
+
+ // this allows the ui to cache the player model on the main menu
+ if (w == 0 || h == 0) {
+ return;
+ }
+
+ dp_realtime = time;
+
+ if ( pi->pendingWeapon != -1 && dp_realtime > pi->weaponTimer ) {
+ pi->weapon = pi->pendingWeapon;
+ pi->lastWeapon = pi->pendingWeapon;
+ pi->pendingWeapon = -1;
+ pi->weaponTimer = 0;
+ if( pi->currentWeapon != pi->weapon ) {
+ trap_S_StartLocalSound( weaponChangeSound, CHAN_LOCAL );
+ }
+ }
+
+ UI_AdjustFrom640( &x, &y, &w, &h );
+
+ y -= jumpHeight;
+
+ memset( &refdef, 0, sizeof( refdef ) );
+ memset( &legs, 0, sizeof(legs) );
+ memset( &torso, 0, sizeof(torso) );
+ memset( &head, 0, sizeof(head) );
+
+ refdef.rdflags = RDF_NOWORLDMODEL;
+
+ AxisClear( refdef.viewaxis );
+
+ refdef.x = x;
+ refdef.y = y;
+ refdef.width = w;
+ refdef.height = h;
+
+ refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
+ xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
+ refdef.fov_y = atan2( refdef.height, xx );
+ refdef.fov_y *= ( 360 / (float)M_PI );
+
+ // calculate distance so the player nearly fills the box
+ len = 0.7 * ( maxs[2] - mins[2] );
+ origin[0] = len / tan( DEG2RAD(refdef.fov_x) * 0.5 );
+ origin[1] = 0.5 * ( mins[1] + maxs[1] );
+ origin[2] = -0.5 * ( mins[2] + maxs[2] );
+
+ refdef.time = dp_realtime;
+
+ trap_R_ClearScene();
+
+ // get the rotation information
+ UI_PlayerAngles( pi, legs.axis, torso.axis, head.axis );
+
+ // get the animation state (after rotation, to allow feet shuffle)
+ UI_PlayerAnimation( pi, &legs.oldframe, &legs.frame, &legs.backlerp,
+ &torso.oldframe, &torso.frame, &torso.backlerp );
+
+ renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
+
+ //
+ // add the legs
+ //
+ legs.hModel = pi->legsModel;
+ legs.customSkin = pi->legsSkin;
+
+ VectorCopy( origin, legs.origin );
+
+ VectorCopy( origin, legs.lightingOrigin );
+ legs.renderfx = renderfx;
+ VectorCopy (legs.origin, legs.oldorigin);
+
+ trap_R_AddRefEntityToScene( &legs );
+
+ if (!legs.hModel) {
+ return;
+ }
+
+ //
+ // add the torso
+ //
+ torso.hModel = pi->torsoModel;
+ if (!torso.hModel) {
+ return;
+ }
+
+ torso.customSkin = pi->torsoSkin;
+
+ VectorCopy( origin, torso.lightingOrigin );
+
+ UI_PositionRotatedEntityOnTag( &torso, &legs, pi->legsModel, "tag_torso");
+
+ torso.renderfx = renderfx;
+
+ trap_R_AddRefEntityToScene( &torso );
+
+ //
+ // add the head
+ //
+ head.hModel = pi->headModel;
+ if (!head.hModel) {
+ return;
+ }
+ head.customSkin = pi->headSkin;
+
+ VectorCopy( origin, head.lightingOrigin );
+
+ UI_PositionRotatedEntityOnTag( &head, &torso, pi->torsoModel, "tag_head");
+
+ head.renderfx = renderfx;
+
+ trap_R_AddRefEntityToScene( &head );
+
+ //
+ // add the gun
+ //
+ if ( pi->currentWeapon != WP_NONE ) {
+ memset( &gun, 0, sizeof(gun) );
+ gun.hModel = pi->weaponModel;
+ VectorCopy( origin, gun.lightingOrigin );
+ UI_PositionEntityOnTag( &gun, &torso, pi->torsoModel, "tag_weapon");
+ gun.renderfx = renderfx;
+ trap_R_AddRefEntityToScene( &gun );
+ }
+
+ //
+ // add the spinning barrel
+ //
+ if ( pi->realWeapon == WP_MACHINEGUN ) {
+ vec3_t angles;
+
+ memset( &barrel, 0, sizeof(barrel) );
+ VectorCopy( origin, barrel.lightingOrigin );
+ barrel.renderfx = renderfx;
+
+ barrel.hModel = pi->barrelModel;
+ angles[YAW] = 0;
+ angles[PITCH] = 0;
+ angles[ROLL] = UI_MachinegunSpinAngle( pi );
+/* if( pi->realWeapon == WP_GAUNTLET || pi->realWeapon == WP_BFG ) {
+ angles[PITCH] = angles[ROLL];
+ angles[ROLL] = 0;
+ }*/
+ AnglesToAxis( angles, barrel.axis );
+
+ UI_PositionRotatedEntityOnTag( &barrel, &gun, pi->weaponModel, "tag_barrel");
+
+ trap_R_AddRefEntityToScene( &barrel );
+ }
+
+ //
+ // add muzzle flash
+ //
+ if ( dp_realtime <= pi->muzzleFlashTime ) {
+ if ( pi->flashModel ) {
+ memset( &flash, 0, sizeof(flash) );
+ flash.hModel = pi->flashModel;
+ VectorCopy( origin, flash.lightingOrigin );
+ UI_PositionEntityOnTag( &flash, &gun, pi->weaponModel, "tag_flash");
+ flash.renderfx = renderfx;
+ trap_R_AddRefEntityToScene( &flash );
+ }
+
+ // make a dlight for the flash
+ if ( pi->flashDlightColor[0] || pi->flashDlightColor[1] || pi->flashDlightColor[2] ) {
+ trap_R_AddLightToScene( flash.origin, 200 + (rand()&31), pi->flashDlightColor[0],
+ pi->flashDlightColor[1], pi->flashDlightColor[2] );
+ }
+ }
+
+ //
+ // add the chat icon
+ //
+ if ( pi->chat ) {
+ UI_PlayerFloatSprite( pi, origin, trap_R_RegisterShaderNoMip( "sprites/balloon3" ) );
+ }
+
+ //
+ // add an accent light
+ //
+ origin[0] -= 100; // + = behind, - = in front
+ origin[1] += 100; // + = left, - = right
+ origin[2] += 100; // + = above, - = below
+ trap_R_AddLightToScene( origin, 500, 1.0, 1.0, 1.0 );
+
+ origin[0] -= 100;
+ origin[1] -= 100;
+ origin[2] -= 100;
+ trap_R_AddLightToScene( origin, 500, 1.0, 0.0, 0.0 );
+
+ trap_R_RenderScene( &refdef );
+}
+
+/*
+==========================
+UI_FileExists
+==========================
+*/
+static qboolean UI_FileExists(const char *filename) {
+ int len;
+
+ len = trap_FS_FOpenFile( filename, NULL, FS_READ );
+ if (len>0) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+==========================
+UI_FindClientHeadFile
+==========================
+*/
+static qboolean UI_FindClientHeadFile( char *filename, int length, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) {
+ char *team, *headsFolder;
+ int i;
+
+ team = "default";
+
+ if ( headModelName[0] == '*' ) {
+ headsFolder = "heads/";
+ headModelName++;
+ }
+ else {
+ headsFolder = "";
+ }
+ while(1) {
+ for ( i = 0; i < 2; i++ ) {
+ if ( i == 0 && teamName && *teamName ) {
+ Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext );
+ }
+ else {
+ Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext );
+ }
+ if ( UI_FileExists( filename ) ) {
+ return qtrue;
+ }
+ if ( i == 0 && teamName && *teamName ) {
+ Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext );
+ }
+ else {
+ Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext );
+ }
+ if ( UI_FileExists( filename ) ) {
+ return qtrue;
+ }
+ if ( !teamName || !*teamName ) {
+ break;
+ }
+ }
+ // if tried the heads folder first
+ if ( headsFolder[0] ) {
+ break;
+ }
+ headsFolder = "heads/";
+ }
+
+ return qfalse;
+}
+
+/*
+==========================
+UI_RegisterClientSkin
+==========================
+*/
+static qboolean UI_RegisterClientSkin( playerInfo_t *pi, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName , const char *teamName) {
+ char filename[MAX_QPATH*2];
+
+ if (teamName && *teamName) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/lower_%s.skin", modelName, teamName, skinName );
+ } else {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower_%s.skin", modelName, skinName );
+ }
+ pi->legsSkin = trap_R_RegisterSkin( filename );
+ if (!pi->legsSkin) {
+ if (teamName && *teamName) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/lower_%s.skin", modelName, teamName, skinName );
+ } else {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower_%s.skin", modelName, skinName );
+ }
+ pi->legsSkin = trap_R_RegisterSkin( filename );
+ }
+
+ if (teamName && *teamName) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/%s/upper_%s.skin", modelName, teamName, skinName );
+ } else {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper_%s.skin", modelName, skinName );
+ }
+ pi->torsoSkin = trap_R_RegisterSkin( filename );
+ if (!pi->torsoSkin) {
+ if (teamName && *teamName) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%s/upper_%s.skin", modelName, teamName, skinName );
+ } else {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper_%s.skin", modelName, skinName );
+ }
+ pi->torsoSkin = trap_R_RegisterSkin( filename );
+ }
+
+ if ( UI_FindClientHeadFile( filename, sizeof(filename), teamName, headModelName, headSkinName, "head", "skin" ) ) {
+ pi->headSkin = trap_R_RegisterSkin( filename );
+ }
+
+ if ( !pi->legsSkin || !pi->torsoSkin || !pi->headSkin ) {
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+
+/*
+======================
+UI_ParseAnimationFile
+======================
+*/
+static qboolean UI_ParseAnimationFile( const char *filename, animation_t *animations ) {
+ char *text_p, *prev;
+ int len;
+ int i;
+ char *token;
+ float fps;
+ int skip;
+ char text[20000];
+ fileHandle_t f;
+
+ memset( animations, 0, sizeof( animation_t ) * MAX_PLAYER_ANIMATIONS );
+
+ // load the file
+ len = trap_FS_FOpenFile( filename, &f, FS_READ );
+ if ( len <= 0 ) {
+ return qfalse;
+ }
+ if ( len >= ( sizeof( text ) - 1 ) ) {
+ Com_Printf( "File %s too long\n", filename );
+ trap_FS_FCloseFile( f );
+ return qfalse;
+ }
+ trap_FS_Read( text, len, f );
+ text[len] = 0;
+ trap_FS_FCloseFile( f );
+
+ COM_Compress(text);
+
+ // parse the text
+ text_p = text;
+ skip = 0; // quite the compiler warning
+
+ // read optional parameters
+ while ( 1 ) {
+ prev = text_p; // so we can unget
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ if ( !Q_stricmp( token, "footsteps" ) ) {
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ continue;
+ } else if ( !Q_stricmp( token, "headoffset" ) ) {
+ for ( i = 0 ; i < 3 ; i++ ) {
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ }
+ continue;
+ } else if ( !Q_stricmp( token, "sex" ) ) {
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ continue;
+ }
+
+ // if it is a number, start parsing animations
+ if ( token[0] >= '0' && token[0] <= '9' ) {
+ text_p = prev; // unget the token
+ break;
+ }
+
+ Com_Printf( "unknown token '%s' is %s\n", token, filename );
+ }
+
+ // read information for each frame
+ for ( i = 0 ; i < MAX_PLAYER_ANIMATIONS ; i++ ) {
+
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ animations[i].firstFrame = atoi( token );
+ // leg only frames are adjusted to not count the upper body only frames
+ if ( i == LEGS_WALKCR ) {
+ skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame;
+ }
+ if ( i >= LEGS_WALKCR ) {
+ animations[i].firstFrame -= skip;
+ }
+
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ animations[i].numFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ animations[i].loopFrames = atoi( token );
+
+ token = COM_Parse( &text_p );
+ if ( !token ) {
+ break;
+ }
+ fps = atof( token );
+ if ( fps == 0 ) {
+ fps = 1;
+ }
+ animations[i].frameLerp = 1000 / fps;
+ animations[i].initialLerp = 1000 / fps;
+ }
+
+ if ( i != MAX_PLAYER_ANIMATIONS ) {
+ Com_Printf( "Error parsing animation file: %s", filename );
+ return qfalse;
+ }
+
+ return qtrue;
+}
+
+/*
+==========================
+UI_RegisterClientModelname
+==========================
+*/
+qboolean UI_RegisterClientModelname( playerInfo_t *pi, const char *modelSkinName, const char *headModelSkinName, const char *teamName ) {
+ char modelName[MAX_QPATH];
+ char skinName[MAX_QPATH];
+ char headModelName[MAX_QPATH];
+ char headSkinName[MAX_QPATH];
+ char filename[MAX_QPATH];
+ char *slash;
+
+ pi->torsoModel = 0;
+ pi->headModel = 0;
+
+ if ( !modelSkinName[0] ) {
+ return qfalse;
+ }
+
+ Q_strncpyz( modelName, modelSkinName, sizeof( modelName ) );
+
+ slash = strchr( modelName, '/' );
+ if ( !slash ) {
+ // modelName did not include a skin name
+ Q_strncpyz( skinName, "default", sizeof( skinName ) );
+ } else {
+ Q_strncpyz( skinName, slash + 1, sizeof( skinName ) );
+ *slash = '\0';
+ }
+
+ Q_strncpyz( headModelName, headModelSkinName, sizeof( headModelName ) );
+ slash = strchr( headModelName, '/' );
+ if ( !slash ) {
+ // modelName did not include a skin name
+ Q_strncpyz( headSkinName, "default", sizeof( skinName ) );
+ } else {
+ Q_strncpyz( headSkinName, slash + 1, sizeof( skinName ) );
+ *slash = '\0';
+ }
+
+ // load cmodels before models so filecache works
+
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName );
+ pi->legsModel = trap_R_RegisterModel( filename );
+ if ( !pi->legsModel ) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName );
+ pi->legsModel = trap_R_RegisterModel( filename );
+ if ( !pi->legsModel ) {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+ }
+
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName );
+ pi->torsoModel = trap_R_RegisterModel( filename );
+ if ( !pi->torsoModel ) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName );
+ pi->torsoModel = trap_R_RegisterModel( filename );
+ if ( !pi->torsoModel ) {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+ }
+
+ if (headModelName && headModelName[0] == '*' ) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] );
+ }
+ else {
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headModelName );
+ }
+ pi->headModel = trap_R_RegisterModel( filename );
+ if ( !pi->headModel && headModelName[0] != '*') {
+ Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName );
+ pi->headModel = trap_R_RegisterModel( filename );
+ }
+
+ if (!pi->headModel) {
+ Com_Printf( "Failed to load model file %s\n", filename );
+ return qfalse;
+ }
+
+ // if any skins failed to load, fall back to default
+ if ( !UI_RegisterClientSkin( pi, modelName, skinName, headModelName, headSkinName, teamName) ) {
+ if ( !UI_RegisterClientSkin( pi, modelName, "default", headModelName, "default", teamName ) ) {
+ Com_Printf( "Failed to load skin file: %s : %s\n", modelName, skinName );
+ return qfalse;
+ }
+ }
+
+ // load the animations
+ Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName );
+ if ( !UI_ParseAnimationFile( filename, pi->animations ) ) {
+ Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName );
+ if ( !UI_ParseAnimationFile( filename, pi->animations ) ) {
+ Com_Printf( "Failed to load animation file %s\n", filename );
+ return qfalse;
+ }
+ }
+
+ return qtrue;
+}
+
+
+/*
+===============
+UI_PlayerInfo_SetModel
+===============
+*/
+void UI_PlayerInfo_SetModel( playerInfo_t *pi, const char *model, const char *headmodel, char *teamName ) {
+ memset( pi, 0, sizeof(*pi) );
+ UI_RegisterClientModelname( pi, model, headmodel, teamName );
+ pi->weapon = WP_MACHINEGUN;
+ pi->currentWeapon = pi->weapon;
+ pi->lastWeapon = pi->weapon;
+ pi->pendingWeapon = -1;
+ pi->weaponTimer = 0;
+ pi->chat = qfalse;
+ pi->newModel = qtrue;
+ UI_PlayerInfo_SetWeapon( pi, pi->weapon );
+}
+
+
+/*
+===============
+UI_PlayerInfo_SetInfo
+===============
+*/
+void UI_PlayerInfo_SetInfo( playerInfo_t *pi, int legsAnim, int torsoAnim, vec3_t viewAngles, vec3_t moveAngles, weapon_t weaponNumber, qboolean chat ) {
+ int currentAnim;
+ weapon_t weaponNum;
+
+ pi->chat = chat;
+
+ // view angles
+ VectorCopy( viewAngles, pi->viewAngles );
+
+ // move angles
+ VectorCopy( moveAngles, pi->moveAngles );
+
+ if ( pi->newModel ) {
+ pi->newModel = qfalse;
+
+ jumpHeight = 0;
+ pi->pendingLegsAnim = 0;
+ UI_ForceLegsAnim( pi, legsAnim );
+ pi->legs.yawAngle = viewAngles[YAW];
+ pi->legs.yawing = qfalse;
+
+ pi->pendingTorsoAnim = 0;
+ UI_ForceTorsoAnim( pi, torsoAnim );
+ pi->torso.yawAngle = viewAngles[YAW];
+ pi->torso.yawing = qfalse;
+
+ if ( weaponNumber != -1 ) {
+ pi->weapon = weaponNumber;
+ pi->currentWeapon = weaponNumber;
+ pi->lastWeapon = weaponNumber;
+ pi->pendingWeapon = -1;
+ pi->weaponTimer = 0;
+ UI_PlayerInfo_SetWeapon( pi, pi->weapon );
+ }
+
+ return;
+ }
+
+ // weapon
+ if ( weaponNumber == -1 ) {
+ pi->pendingWeapon = -1;
+ pi->weaponTimer = 0;
+ }
+ else if ( weaponNumber != WP_NONE ) {
+ pi->pendingWeapon = weaponNumber;
+ pi->weaponTimer = dp_realtime + UI_TIMER_WEAPON_DELAY;
+ }
+ weaponNum = pi->lastWeapon;
+ pi->weapon = weaponNum;
+
+ if ( torsoAnim == BOTH_DEATH1 || legsAnim == BOTH_DEATH1 ) {
+ torsoAnim = legsAnim = BOTH_DEATH1;
+ pi->weapon = pi->currentWeapon = WP_NONE;
+ UI_PlayerInfo_SetWeapon( pi, pi->weapon );
+
+ jumpHeight = 0;
+ pi->pendingLegsAnim = 0;
+ UI_ForceLegsAnim( pi, legsAnim );
+
+ pi->pendingTorsoAnim = 0;
+ UI_ForceTorsoAnim( pi, torsoAnim );
+
+ return;
+ }
+
+ // leg animation
+ currentAnim = pi->legsAnim & ~ANIM_TOGGLEBIT;
+ if ( legsAnim != LEGS_JUMP && ( currentAnim == LEGS_JUMP || currentAnim == LEGS_LAND ) ) {
+ pi->pendingLegsAnim = legsAnim;
+ }
+ else if ( legsAnim != currentAnim ) {
+ jumpHeight = 0;
+ pi->pendingLegsAnim = 0;
+ UI_ForceLegsAnim( pi, legsAnim );
+ }
+
+ // torso animation
+ if ( torsoAnim == TORSO_STAND || torsoAnim == TORSO_STAND2 ) {
+ if ( weaponNum == WP_NONE ) {
+ torsoAnim = TORSO_STAND2;
+ }
+ else {
+ torsoAnim = TORSO_STAND;
+ }
+ }
+
+ if ( torsoAnim == TORSO_ATTACK || torsoAnim == TORSO_ATTACK2 ) {
+ if ( weaponNum == WP_NONE ) {
+ torsoAnim = TORSO_ATTACK2;
+ }
+ else {
+ torsoAnim = TORSO_ATTACK;
+ }
+ pi->muzzleFlashTime = dp_realtime + UI_TIMER_MUZZLE_FLASH;
+ //FIXME play firing sound here
+ }
+
+ currentAnim = pi->torsoAnim & ~ANIM_TOGGLEBIT;
+
+ if ( weaponNum != pi->currentWeapon || currentAnim == TORSO_RAISE || currentAnim == TORSO_DROP ) {
+ pi->pendingTorsoAnim = torsoAnim;
+ }
+ else if ( ( currentAnim == TORSO_GESTURE || currentAnim == TORSO_ATTACK ) && ( torsoAnim != currentAnim ) ) {
+ pi->pendingTorsoAnim = torsoAnim;
+ }
+ else if ( torsoAnim != currentAnim ) {
+ pi->pendingTorsoAnim = 0;
+ UI_ForceTorsoAnim( pi, torsoAnim );
+ }
+}
diff --git a/src/ui/ui_shared.c b/src/ui/ui_shared.c
new file mode 100644
index 0000000..2ca88cb
--- /dev/null
+++ b/src/ui/ui_shared.c
@@ -0,0 +1,6075 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "ui_shared.h"
+
+#define SCROLL_TIME_START 500
+#define SCROLL_TIME_ADJUST 150
+#define SCROLL_TIME_ADJUSTOFFSET 40
+#define SCROLL_TIME_FLOOR 20
+
+typedef struct scrollInfo_s {
+ int nextScrollTime;
+ int nextAdjustTime;
+ int adjustValue;
+ int scrollKey;
+ float xStart;
+ float yStart;
+ itemDef_t *item;
+ qboolean scrollDir;
+} scrollInfo_t;
+
+static scrollInfo_t scrollInfo;
+
+//TA: hack to prevent compiler warnings
+void voidFunction( void *var ) { return; }
+qboolean voidFunction2( itemDef_t *var1, int var2 ) { return qfalse; }
+
+static void (*captureFunc) (void *p) = voidFunction;
+static void *captureData = NULL;
+static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any )
+
+displayContextDef_t *DC = NULL;
+
+static qboolean g_waitingForKey = qfalse;
+static qboolean g_editingField = qfalse;
+
+static itemDef_t *g_bindItem = NULL;
+static itemDef_t *g_editItem = NULL;
+
+menuDef_t Menus[MAX_MENUS]; // defined menus
+int menuCount = 0; // how many
+
+menuDef_t *menuStack[MAX_OPEN_MENUS];
+int openMenuCount = 0;
+
+static qboolean debugMode = qfalse;
+
+#define DOUBLE_CLICK_DELAY 300
+static int lastListBoxClickTime = 0;
+
+void Item_RunScript(itemDef_t *item, const char *s);
+void Item_SetupKeywordHash(void);
+void Menu_SetupKeywordHash(void);
+int BindingIDFromName(const char *name);
+qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down);
+itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu);
+itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu);
+static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y);
+
+#ifdef CGAME
+#define MEM_POOL_SIZE 128 * 1024
+#else
+#define MEM_POOL_SIZE 1024 * 1024
+#endif
+
+//TA: hacked variable name to avoid conflict with new cgame Alloc
+static char UI_memoryPool[MEM_POOL_SIZE];
+static int allocPoint, outOfMemory;
+
+/*
+===============
+UI_Alloc
+===============
+*/
+void *UI_Alloc( int size )
+{
+ char *p;
+
+ if( allocPoint + size > MEM_POOL_SIZE )
+ {
+ outOfMemory = qtrue;
+
+ if( DC->Print )
+ DC->Print( "UI_Alloc: Failure. Out of memory!\n" );
+ //DC->trap_Print(S_COLOR_YELLOW"WARNING: UI Out of Memory!\n");
+ return NULL;
+ }
+
+ p = &UI_memoryPool[ allocPoint ];
+
+ allocPoint += ( size + 15 ) & ~15;
+
+ return p;
+}
+
+/*
+===============
+UI_InitMemory
+===============
+*/
+void UI_InitMemory( void )
+{
+ allocPoint = 0;
+ outOfMemory = qfalse;
+}
+
+qboolean UI_OutOfMemory( )
+{
+ return outOfMemory;
+}
+
+
+
+
+
+#define HASH_TABLE_SIZE 2048
+/*
+================
+return a hash value for the string
+================
+*/
+static long hashForString(const char *str) {
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+ while (str[i] != '\0') {
+ letter = tolower(str[i]);
+ hash+=(long)(letter)*(i+119);
+ i++;
+ }
+ hash &= (HASH_TABLE_SIZE-1);
+ return hash;
+}
+
+typedef struct stringDef_s {
+ struct stringDef_s *next;
+ const char *str;
+} stringDef_t;
+
+static int strPoolIndex = 0;
+static char strPool[STRING_POOL_SIZE];
+
+static int strHandleCount = 0;
+static stringDef_t *strHandle[HASH_TABLE_SIZE];
+
+
+const char *String_Alloc(const char *p) {
+ int len;
+ long hash;
+ stringDef_t *str, *last;
+ static const char *staticNULL = "";
+
+ if (p == NULL) {
+ return NULL;
+ }
+
+ if (*p == 0) {
+ return staticNULL;
+ }
+
+ hash = hashForString(p);
+
+ str = strHandle[hash];
+ while (str) {
+ if (strcmp(p, str->str) == 0) {
+ return str->str;
+ }
+ str = str->next;
+ }
+
+ len = strlen(p);
+ if (len + strPoolIndex + 1 < STRING_POOL_SIZE) {
+ int ph = strPoolIndex;
+ strcpy(&strPool[strPoolIndex], p);
+ strPoolIndex += len + 1;
+
+ str = strHandle[hash];
+ last = str;
+ while (str && str->next) {
+ last = str;
+ str = str->next;
+ }
+
+ str = UI_Alloc(sizeof(stringDef_t));
+ str->next = NULL;
+ str->str = &strPool[ph];
+ if (last) {
+ last->next = str;
+ } else {
+ strHandle[hash] = str;
+ }
+ return &strPool[ph];
+ }
+ return NULL;
+}
+
+void String_Report( void ) {
+ float f;
+ Com_Printf("Memory/String Pool Info\n");
+ Com_Printf("----------------\n");
+ f = strPoolIndex;
+ f /= STRING_POOL_SIZE;
+ f *= 100;
+ Com_Printf("String Pool is %.1f%% full, %i bytes out of %i used.\n", f, strPoolIndex, STRING_POOL_SIZE);
+ f = allocPoint;
+ f /= MEM_POOL_SIZE;
+ f *= 100;
+ Com_Printf("Memory Pool is %.1f%% full, %i bytes out of %i used.\n", f, allocPoint, MEM_POOL_SIZE);
+}
+
+/*
+=================
+String_Init
+=================
+*/
+void String_Init( void )
+{
+ int i;
+ for( i = 0; i < HASH_TABLE_SIZE; i++ )
+ strHandle[ i ] = 0;
+
+ strHandleCount = 0;
+ strPoolIndex = 0;
+ menuCount = 0;
+ openMenuCount = 0;
+ UI_InitMemory( );
+ Item_SetupKeywordHash( );
+ Menu_SetupKeywordHash( );
+
+ if( DC && DC->getBindingBuf )
+ Controls_GetConfig( );
+}
+
+/*
+=================
+PC_SourceWarning
+=================
+*/
+void PC_SourceWarning(int handle, char *format, ...) {
+ int line;
+ char filename[128];
+ va_list argptr;
+ static char string[4096];
+
+ va_start (argptr, format);
+ vsprintf (string, format, argptr);
+ va_end (argptr);
+
+ filename[0] = '\0';
+ line = 0;
+ trap_Parse_SourceFileAndLine(handle, filename, &line);
+
+ Com_Printf(S_COLOR_YELLOW "WARNING: %s, line %d: %s\n", filename, line, string);
+}
+
+/*
+=================
+PC_SourceError
+=================
+*/
+void PC_SourceError(int handle, char *format, ...) {
+ int line;
+ char filename[128];
+ va_list argptr;
+ static char string[4096];
+
+ va_start (argptr, format);
+ vsprintf (string, format, argptr);
+ va_end (argptr);
+
+ filename[0] = '\0';
+ line = 0;
+ trap_Parse_SourceFileAndLine(handle, filename, &line);
+
+ Com_Printf(S_COLOR_RED "ERROR: %s, line %d: %s\n", filename, line, string);
+}
+
+/*
+=================
+LerpColor
+=================
+*/
+void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t)
+{
+ int i;
+
+ // lerp and clamp each component
+ for (i=0; i<4; i++)
+ {
+ c[i] = a[i] + t*(b[i]-a[i]);
+ if (c[i] < 0)
+ c[i] = 0;
+ else if (c[i] > 1.0)
+ c[i] = 1.0;
+ }
+}
+
+/*
+=================
+Float_Parse
+=================
+*/
+qboolean Float_Parse(char **p, float *f) {
+ char *token;
+ token = COM_ParseExt(p, qfalse);
+ if (token && token[0] != 0) {
+ *f = atof(token);
+ return qtrue;
+ } else {
+ return qfalse;
+ }
+}
+
+/*
+=================
+PC_Float_Parse
+=================
+*/
+qboolean PC_Float_Parse(int handle, float *f) {
+ pc_token_t token;
+ int negative = qfalse;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (token.string[0] == '-') {
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ negative = qtrue;
+ }
+ if (token.type != TT_NUMBER) {
+ PC_SourceError(handle, "expected float but found %s\n", token.string);
+ return qfalse;
+ }
+ if (negative)
+ *f = -token.floatvalue;
+ else
+ *f = token.floatvalue;
+ return qtrue;
+}
+
+/*
+=================
+Color_Parse
+=================
+*/
+qboolean Color_Parse(char **p, vec4_t *c) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!Float_Parse(p, &f)) {
+ return qfalse;
+ }
+ (*c)[i] = f;
+ }
+ return qtrue;
+}
+
+/*
+=================
+PC_Color_Parse
+=================
+*/
+qboolean PC_Color_Parse(int handle, vec4_t *c) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ (*c)[i] = f;
+ }
+ return qtrue;
+}
+
+/*
+=================
+Int_Parse
+=================
+*/
+qboolean Int_Parse(char **p, int *i) {
+ char *token;
+ token = COM_ParseExt(p, qfalse);
+
+ if (token && token[0] != 0) {
+ *i = atoi(token);
+ return qtrue;
+ } else {
+ return qfalse;
+ }
+}
+
+/*
+=================
+PC_Int_Parse
+=================
+*/
+qboolean PC_Int_Parse(int handle, int *i) {
+ pc_token_t token;
+ int negative = qfalse;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (token.string[0] == '-') {
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ negative = qtrue;
+ }
+ if (token.type != TT_NUMBER) {
+ PC_SourceError(handle, "expected integer but found %s\n", token.string);
+ return qfalse;
+ }
+ *i = token.intvalue;
+ if (negative)
+ *i = - *i;
+ return qtrue;
+}
+
+/*
+=================
+Rect_Parse
+=================
+*/
+qboolean Rect_Parse(char **p, rectDef_t *r) {
+ if (Float_Parse(p, &r->x)) {
+ if (Float_Parse(p, &r->y)) {
+ if (Float_Parse(p, &r->w)) {
+ if (Float_Parse(p, &r->h)) {
+ return qtrue;
+ }
+ }
+ }
+ }
+ return qfalse;
+}
+
+/*
+=================
+PC_Rect_Parse
+=================
+*/
+qboolean PC_Rect_Parse(int handle, rectDef_t *r) {
+ if (PC_Float_Parse(handle, &r->x)) {
+ if (PC_Float_Parse(handle, &r->y)) {
+ if (PC_Float_Parse(handle, &r->w)) {
+ if (PC_Float_Parse(handle, &r->h)) {
+ return qtrue;
+ }
+ }
+ }
+ }
+ return qfalse;
+}
+
+/*
+=================
+String_Parse
+=================
+*/
+qboolean String_Parse(char **p, const char **out) {
+ char *token;
+
+ token = COM_ParseExt(p, qfalse);
+ if (token && token[0] != 0) {
+ *(out) = String_Alloc(token);
+ return qtrue;
+ }
+ return qfalse;
+}
+
+/*
+=================
+PC_String_Parse
+=================
+*/
+qboolean PC_String_Parse(int handle, const char **out) {
+ pc_token_t token;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+
+ *(out) = String_Alloc(token.string);
+ return qtrue;
+}
+
+/*
+=================
+PC_Script_Parse
+=================
+*/
+qboolean PC_Script_Parse(int handle, const char **out) {
+ char script[1024];
+ pc_token_t token;
+
+ memset(script, 0, sizeof(script));
+ // scripts start with { and have ; separated command lists.. commands are command, arg..
+ // basically we want everything between the { } as it will be interpreted at run time
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (Q_stricmp(token.string, "{") != 0) {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+
+ if (Q_stricmp(token.string, "}") == 0) {
+ *out = String_Alloc(script);
+ return qtrue;
+ }
+
+ if (token.string[1] != '\0') {
+ Q_strcat(script, 1024, va("\"%s\"", token.string));
+ } else {
+ Q_strcat(script, 1024, token.string);
+ }
+ Q_strcat(script, 1024, " ");
+ }
+ return qfalse; // bk001105 - LCC missing return value
+}
+
+// display, window, menu, item code
+//
+
+/*
+==================
+Init_Display
+
+Initializes the display with a structure to all the drawing routines
+==================
+*/
+void Init_Display( displayContextDef_t *dc )
+{
+ DC = dc;
+}
+
+
+
+// type and style painting
+
+void GradientBar_Paint( rectDef_t *rect, vec4_t color )
+{
+ // gradient bar takes two paints
+ DC->setColor( color );
+ DC->drawHandlePic( rect->x, rect->y, rect->w, rect->h, DC->Assets.gradientBar );
+ DC->setColor( NULL );
+}
+
+
+/*
+==================
+Window_Init
+
+Initializes a window structure ( windowDef_t ) with defaults
+
+==================
+*/
+void Window_Init(Window *w) {
+ memset(w, 0, sizeof(windowDef_t));
+ w->borderSize = 1;
+ w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0;
+ w->cinematic = -1;
+}
+
+void Fade(int *flags, float *f, float clamp, int *nextTime, int offsetTime, qboolean bFlags, float fadeAmount) {
+ if (*flags & (WINDOW_FADINGOUT | WINDOW_FADINGIN)) {
+ if (DC->realTime > *nextTime) {
+ *nextTime = DC->realTime + offsetTime;
+ if (*flags & WINDOW_FADINGOUT) {
+ *f -= fadeAmount;
+ if (bFlags && *f <= 0.0) {
+ *flags &= ~(WINDOW_FADINGOUT | WINDOW_VISIBLE);
+ }
+ } else {
+ *f += fadeAmount;
+ if (*f >= clamp) {
+ *f = clamp;
+ if (bFlags) {
+ *flags &= ~WINDOW_FADINGIN;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
+void Window_Paint(Window *w, float fadeAmount, float fadeClamp, float fadeCycle) {
+ //float bordersize = 0;
+ vec4_t color;
+ rectDef_t fillRect = w->rect;
+
+
+ if (debugMode) {
+ color[0] = color[1] = color[2] = color[3] = 1;
+ DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, 1, color);
+ }
+
+ if (w == NULL || (w->style == 0 && w->border == 0)) {
+ return;
+ }
+
+ if (w->border != 0) {
+ fillRect.x += w->borderSize;
+ fillRect.y += w->borderSize;
+ fillRect.w -= w->borderSize + 1;
+ fillRect.h -= w->borderSize + 1;
+ }
+
+ if (w->style == WINDOW_STYLE_FILLED) {
+ // box, but possible a shader that needs filled
+ if (w->background) {
+ Fade(&w->flags, &w->backColor[3], fadeClamp, &w->nextTime, fadeCycle, qtrue, fadeAmount);
+ DC->setColor(w->backColor);
+ DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background);
+ DC->setColor(NULL);
+ } else {
+ DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->backColor);
+ }
+ } else if (w->style == WINDOW_STYLE_GRADIENT) {
+ GradientBar_Paint(&fillRect, w->backColor);
+ // gradient bar
+ } else if (w->style == WINDOW_STYLE_SHADER) {
+ if (w->flags & WINDOW_FORECOLORSET) {
+ DC->setColor(w->foreColor);
+ }
+ DC->drawHandlePic(fillRect.x, fillRect.y, fillRect.w, fillRect.h, w->background);
+ DC->setColor(NULL);
+ } else if (w->style == WINDOW_STYLE_TEAMCOLOR) {
+ if (DC->getTeamColor) {
+ DC->getTeamColor(&color);
+ DC->fillRect(fillRect.x, fillRect.y, fillRect.w, fillRect.h, color);
+ }
+ } else if (w->style == WINDOW_STYLE_CINEMATIC) {
+ if (w->cinematic == -1) {
+ w->cinematic = DC->playCinematic(w->cinematicName, fillRect.x, fillRect.y, fillRect.w, fillRect.h);
+ if (w->cinematic == -1) {
+ w->cinematic = -2;
+ }
+ }
+ if (w->cinematic >= 0) {
+ DC->runCinematicFrame(w->cinematic);
+ DC->drawCinematic(w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h);
+ }
+ }
+
+ if (w->border == WINDOW_BORDER_FULL) {
+ // full
+ // HACK HACK HACK
+ if (w->style == WINDOW_STYLE_TEAMCOLOR) {
+ if (color[0] > 0) {
+ // red
+ color[0] = 1;
+ color[1] = color[2] = .5;
+
+ } else {
+ color[2] = 1;
+ color[0] = color[1] = .5;
+ }
+ color[3] = 1;
+ DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, color);
+ } else {
+ DC->drawRect(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize, w->borderColor);
+ }
+ } else if (w->border == WINDOW_BORDER_HORZ) {
+ // top/bottom
+ DC->setColor(w->borderColor);
+ DC->drawTopBottom(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize);
+ DC->setColor( NULL );
+ } else if (w->border == WINDOW_BORDER_VERT) {
+ // left right
+ DC->setColor(w->borderColor);
+ DC->drawSides(w->rect.x, w->rect.y, w->rect.w, w->rect.h, w->borderSize);
+ DC->setColor( NULL );
+ } else if (w->border == WINDOW_BORDER_KCGRADIENT) {
+ // this is just two gradient bars along each horz edge
+ rectDef_t r = w->rect;
+ r.h = w->borderSize;
+ GradientBar_Paint(&r, w->borderColor);
+ r.y = w->rect.y + w->rect.h - 1;
+ GradientBar_Paint(&r, w->borderColor);
+ }
+
+}
+
+
+void Item_SetScreenCoords(itemDef_t *item, float x, float y) {
+
+ if (item == NULL) {
+ return;
+ }
+
+ if (item->window.border != 0) {
+ x += item->window.borderSize;
+ y += item->window.borderSize;
+ }
+
+ item->window.rect.x = x + item->window.rectClient.x;
+ item->window.rect.y = y + item->window.rectClient.y;
+ item->window.rect.w = item->window.rectClient.w;
+ item->window.rect.h = item->window.rectClient.h;
+
+ // force the text rects to recompute
+ item->textRect.w = 0;
+ item->textRect.h = 0;
+}
+
+// FIXME: consolidate this with nearby stuff
+void Item_UpdatePosition(itemDef_t *item) {
+ float x, y;
+ menuDef_t *menu;
+
+ if (item == NULL || item->parent == NULL) {
+ return;
+ }
+
+ menu = item->parent;
+
+ x = menu->window.rect.x;
+ y = menu->window.rect.y;
+
+ if (menu->window.border != 0) {
+ x += menu->window.borderSize;
+ y += menu->window.borderSize;
+ }
+
+ Item_SetScreenCoords(item, x, y);
+
+}
+
+// menus
+void Menu_UpdatePosition(menuDef_t *menu) {
+ int i;
+ float x, y;
+
+ if (menu == NULL) {
+ return;
+ }
+
+ x = menu->window.rect.x;
+ y = menu->window.rect.y;
+ if (menu->window.border != 0) {
+ x += menu->window.borderSize;
+ y += menu->window.borderSize;
+ }
+
+ for (i = 0; i < menu->itemCount; i++) {
+ Item_SetScreenCoords(menu->items[i], x, y);
+ }
+}
+
+void Menu_PostParse(menuDef_t *menu) {
+ if (menu == NULL) {
+ return;
+ }
+ if (menu->fullScreen) {
+ menu->window.rect.x = 0;
+ menu->window.rect.y = 0;
+ menu->window.rect.w = 640;
+ menu->window.rect.h = 480;
+ }
+ Menu_UpdatePosition(menu);
+}
+
+itemDef_t *Menu_ClearFocus(menuDef_t *menu) {
+ int i;
+ itemDef_t *ret = NULL;
+
+ if (menu == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->window.flags & WINDOW_HASFOCUS) {
+ ret = menu->items[i];
+ }
+ menu->items[i]->window.flags &= ~WINDOW_HASFOCUS;
+ if (menu->items[i]->leaveFocus) {
+ Item_RunScript(menu->items[i], menu->items[i]->leaveFocus);
+ }
+ }
+
+ return ret;
+}
+
+qboolean IsVisible(int flags) {
+ return (flags & WINDOW_VISIBLE && !(flags & WINDOW_FADINGOUT));
+}
+
+qboolean Rect_ContainsPoint(rectDef_t *rect, float x, float y) {
+ if (rect) {
+ if (x > rect->x && x < rect->x + rect->w && y > rect->y && y < rect->y + rect->h) {
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+int Menu_ItemsMatchingGroup(menuDef_t *menu, const char *name) {
+ int i;
+ int count = 0;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) {
+ count++;
+ }
+ }
+ return count;
+}
+
+itemDef_t *Menu_GetMatchingItemByNumber(menuDef_t *menu, int index, const char *name) {
+ int i;
+ int count = 0;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) {
+ if (count == index) {
+ return menu->items[i];
+ }
+ count++;
+ }
+ }
+ return NULL;
+}
+
+
+
+void Script_SetColor(itemDef_t *item, char **args) {
+ const char *name;
+ int i;
+ float f;
+ vec4_t *out;
+ // expecting type of color to set and 4 args for the color
+ if (String_Parse(args, &name)) {
+ out = NULL;
+ if (Q_stricmp(name, "backcolor") == 0) {
+ out = &item->window.backColor;
+ item->window.flags |= WINDOW_BACKCOLORSET;
+ } else if (Q_stricmp(name, "forecolor") == 0) {
+ out = &item->window.foreColor;
+ item->window.flags |= WINDOW_FORECOLORSET;
+ } else if (Q_stricmp(name, "bordercolor") == 0) {
+ out = &item->window.borderColor;
+ }
+
+ if (out) {
+ for (i = 0; i < 4; i++) {
+ if (!Float_Parse(args, &f)) {
+ return;
+ }
+ (*out)[i] = f;
+ }
+ }
+ }
+}
+
+void Script_SetAsset(itemDef_t *item, char **args) {
+ const char *name;
+ // expecting name to set asset to
+ if (String_Parse(args, &name)) {
+ // check for a model
+ if (item->type == ITEM_TYPE_MODEL) {
+ }
+ }
+}
+
+void Script_SetBackground(itemDef_t *item, char **args) {
+ const char *name;
+ // expecting name to set asset to
+ if (String_Parse(args, &name)) {
+ item->window.background = DC->registerShaderNoMip(name);
+ }
+}
+
+
+
+
+itemDef_t *Menu_FindItemByName(menuDef_t *menu, const char *p) {
+ int i;
+ if (menu == NULL || p == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; i < menu->itemCount; i++) {
+ if (Q_stricmp(p, menu->items[i]->window.name) == 0) {
+ return menu->items[i];
+ }
+ }
+
+ return NULL;
+}
+
+void Script_SetTeamColor(itemDef_t *item, char **args) {
+ if (DC->getTeamColor) {
+ int i;
+ vec4_t color;
+ DC->getTeamColor(&color);
+ for (i = 0; i < 4; i++) {
+ item->window.backColor[i] = color[i];
+ }
+ }
+}
+
+void Script_SetItemColor(itemDef_t *item, char **args) {
+ const char *itemname;
+ const char *name;
+ vec4_t color;
+ int i;
+ vec4_t *out;
+ // expecting type of color to set and 4 args for the color
+ if (String_Parse(args, &itemname) && String_Parse(args, &name)) {
+ itemDef_t *item2;
+ int j;
+ int count = Menu_ItemsMatchingGroup(item->parent, itemname);
+
+ if (!Color_Parse(args, &color)) {
+ return;
+ }
+
+ for (j = 0; j < count; j++) {
+ item2 = Menu_GetMatchingItemByNumber(item->parent, j, itemname);
+ if (item2 != NULL) {
+ out = NULL;
+ if (Q_stricmp(name, "backcolor") == 0) {
+ out = &item2->window.backColor;
+ } else if (Q_stricmp(name, "forecolor") == 0) {
+ out = &item2->window.foreColor;
+ item2->window.flags |= WINDOW_FORECOLORSET;
+ } else if (Q_stricmp(name, "bordercolor") == 0) {
+ out = &item2->window.borderColor;
+ }
+
+ if (out) {
+ for (i = 0; i < 4; i++) {
+ (*out)[i] = color[i];
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void Menu_ShowItemByName(menuDef_t *menu, const char *p, qboolean bShow) {
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup(menu, p);
+ for (i = 0; i < count; i++) {
+ item = Menu_GetMatchingItemByNumber(menu, i, p);
+ if (item != NULL) {
+ if (bShow) {
+ item->window.flags |= WINDOW_VISIBLE;
+ } else {
+ item->window.flags &= ~WINDOW_VISIBLE;
+ // stop cinematics playing in the window
+ if (item->window.cinematic >= 0) {
+ DC->stopCinematic(item->window.cinematic);
+ item->window.cinematic = -1;
+ }
+ }
+ }
+ }
+}
+
+void Menu_FadeItemByName(menuDef_t *menu, const char *p, qboolean fadeOut) {
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup(menu, p);
+ for (i = 0; i < count; i++) {
+ item = Menu_GetMatchingItemByNumber(menu, i, p);
+ if (item != NULL) {
+ if (fadeOut) {
+ item->window.flags |= (WINDOW_FADINGOUT | WINDOW_VISIBLE);
+ item->window.flags &= ~WINDOW_FADINGIN;
+ } else {
+ item->window.flags |= (WINDOW_VISIBLE | WINDOW_FADINGIN);
+ item->window.flags &= ~WINDOW_FADINGOUT;
+ }
+ }
+ }
+}
+
+menuDef_t *Menus_FindByName(const char *p) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ if (Q_stricmp(Menus[i].window.name, p) == 0) {
+ return &Menus[i];
+ }
+ }
+ return NULL;
+}
+
+void Menus_ShowByName(const char *p) {
+ menuDef_t *menu = Menus_FindByName(p);
+ if (menu) {
+ Menus_Activate(menu);
+ }
+}
+
+void Menus_OpenByName(const char *p) {
+ Menus_ActivateByName(p);
+}
+
+static void Menu_RunCloseScript(menuDef_t *menu) {
+ if (menu && menu->window.flags & WINDOW_VISIBLE && menu->onClose) {
+ itemDef_t item;
+ item.parent = menu;
+ Item_RunScript(&item, menu->onClose);
+ }
+}
+
+void Menus_CloseByName(const char *p) {
+ menuDef_t *menu = Menus_FindByName(p);
+ if (menu != NULL) {
+ Menu_RunCloseScript(menu);
+ menu->window.flags &= ~(WINDOW_VISIBLE | WINDOW_HASFOCUS);
+ }
+}
+
+void Menus_CloseAll( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ Menu_RunCloseScript(&Menus[i]);
+ Menus[i].window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE);
+ }
+}
+
+
+void Script_Show(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menu_ShowItemByName(item->parent, name, qtrue);
+ }
+}
+
+void Script_Hide(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menu_ShowItemByName(item->parent, name, qfalse);
+ }
+}
+
+void Script_FadeIn(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menu_FadeItemByName(item->parent, name, qfalse);
+ }
+}
+
+void Script_FadeOut(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menu_FadeItemByName(item->parent, name, qtrue);
+ }
+}
+
+
+
+void Script_Open(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menus_OpenByName(name);
+ }
+}
+
+void Script_ConditionalOpen(itemDef_t *item, char **args) {
+ const char *cvar;
+ const char *name1;
+ const char *name2;
+ float val;
+
+ if ( String_Parse(args, &cvar) && String_Parse(args, &name1) && String_Parse(args, &name2) ) {
+ val = DC->getCVarValue( cvar );
+ if ( val == 0.f ) {
+ Menus_OpenByName(name2);
+ } else {
+ Menus_OpenByName(name1);
+ }
+ }
+}
+
+void Script_Close(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ Menus_CloseByName(name);
+ }
+}
+
+void Menu_TransitionItemByName(menuDef_t *menu, const char *p, rectDef_t rectFrom, rectDef_t rectTo, int time, float amt) {
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup(menu, p);
+ for (i = 0; i < count; i++) {
+ item = Menu_GetMatchingItemByNumber(menu, i, p);
+ if (item != NULL) {
+ item->window.flags |= (WINDOW_INTRANSITION | WINDOW_VISIBLE);
+ item->window.offsetTime = time;
+ memcpy(&item->window.rectClient, &rectFrom, sizeof(rectDef_t));
+ memcpy(&item->window.rectEffects, &rectTo, sizeof(rectDef_t));
+ item->window.rectEffects2.x = abs(rectTo.x - rectFrom.x) / amt;
+ item->window.rectEffects2.y = abs(rectTo.y - rectFrom.y) / amt;
+ item->window.rectEffects2.w = abs(rectTo.w - rectFrom.w) / amt;
+ item->window.rectEffects2.h = abs(rectTo.h - rectFrom.h) / amt;
+ Item_UpdatePosition(item);
+ }
+ }
+}
+
+
+void Script_Transition(itemDef_t *item, char **args) {
+ const char *name;
+ rectDef_t rectFrom, rectTo;
+ int time;
+ float amt;
+
+ if (String_Parse(args, &name)) {
+ if ( Rect_Parse(args, &rectFrom) && Rect_Parse(args, &rectTo) && Int_Parse(args, &time) && Float_Parse(args, &amt)) {
+ Menu_TransitionItemByName(item->parent, name, rectFrom, rectTo, time, amt);
+ }
+ }
+}
+
+
+void Menu_OrbitItemByName(menuDef_t *menu, const char *p, float x, float y, float cx, float cy, int time) {
+ itemDef_t *item;
+ int i;
+ int count = Menu_ItemsMatchingGroup(menu, p);
+ for (i = 0; i < count; i++) {
+ item = Menu_GetMatchingItemByNumber(menu, i, p);
+ if (item != NULL) {
+ item->window.flags |= (WINDOW_ORBITING | WINDOW_VISIBLE);
+ item->window.offsetTime = time;
+ item->window.rectEffects.x = cx;
+ item->window.rectEffects.y = cy;
+ item->window.rectClient.x = x;
+ item->window.rectClient.y = y;
+ Item_UpdatePosition(item);
+ }
+ }
+}
+
+
+void Script_Orbit(itemDef_t *item, char **args) {
+ const char *name;
+ float cx, cy, x, y;
+ int time;
+
+ if (String_Parse(args, &name)) {
+ if ( Float_Parse(args, &x) && Float_Parse(args, &y) && Float_Parse(args, &cx) && Float_Parse(args, &cy) && Int_Parse(args, &time) ) {
+ Menu_OrbitItemByName(item->parent, name, x, y, cx, cy, time);
+ }
+ }
+}
+
+
+
+void Script_SetFocus(itemDef_t *item, char **args) {
+ const char *name;
+ itemDef_t *focusItem;
+
+ if (String_Parse(args, &name)) {
+ focusItem = Menu_FindItemByName(item->parent, name);
+ if (focusItem && !(focusItem->window.flags & WINDOW_DECORATION) && !(focusItem->window.flags & WINDOW_HASFOCUS)) {
+ Menu_ClearFocus(item->parent);
+ focusItem->window.flags |= WINDOW_HASFOCUS;
+ if (focusItem->onFocus) {
+ Item_RunScript(focusItem, focusItem->onFocus);
+ }
+ if (DC->Assets.itemFocusSound) {
+ DC->startLocalSound( DC->Assets.itemFocusSound, CHAN_LOCAL_SOUND );
+ }
+ }
+ }
+}
+
+void Script_SetPlayerModel(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ DC->setCVar("team_model", name);
+ }
+}
+
+void Script_SetPlayerHead(itemDef_t *item, char **args) {
+ const char *name;
+ if (String_Parse(args, &name)) {
+ DC->setCVar("team_headmodel", name);
+ }
+}
+
+void Script_SetCvar(itemDef_t *item, char **args) {
+ const char *cvar, *val;
+ if (String_Parse(args, &cvar) && String_Parse(args, &val)) {
+ DC->setCVar(cvar, val);
+ }
+
+}
+
+void Script_Exec(itemDef_t *item, char **args) {
+ const char *val;
+ if (String_Parse(args, &val)) {
+ DC->executeText(EXEC_APPEND, va("%s ; ", val));
+ }
+}
+
+void Script_Play(itemDef_t *item, char **args) {
+ const char *val;
+ if (String_Parse(args, &val)) {
+ DC->startLocalSound(DC->registerSound(val, qfalse), CHAN_LOCAL_SOUND);
+ }
+}
+
+void Script_playLooped(itemDef_t *item, char **args) {
+ const char *val;
+ if (String_Parse(args, &val)) {
+ DC->stopBackgroundTrack();
+ DC->startBackgroundTrack(val, val);
+ }
+}
+
+
+commandDef_t commandList[] =
+{
+ {"fadein", &Script_FadeIn}, // group/name
+ {"fadeout", &Script_FadeOut}, // group/name
+ {"show", &Script_Show}, // group/name
+ {"hide", &Script_Hide}, // group/name
+ {"setcolor", &Script_SetColor}, // works on this
+ {"open", &Script_Open}, // menu
+ {"conditionalopen", &Script_ConditionalOpen}, // menu
+ {"close", &Script_Close}, // menu
+ {"setasset", &Script_SetAsset}, // works on this
+ {"setbackground", &Script_SetBackground}, // works on this
+ {"setitemcolor", &Script_SetItemColor}, // group/name
+ {"setteamcolor", &Script_SetTeamColor}, // sets this background color to team color
+ {"setfocus", &Script_SetFocus}, // sets this background color to team color
+ {"setplayermodel", &Script_SetPlayerModel}, // sets this background color to team color
+ {"setplayerhead", &Script_SetPlayerHead}, // sets this background color to team color
+ {"transition", &Script_Transition}, // group/name
+ {"setcvar", &Script_SetCvar}, // group/name
+ {"exec", &Script_Exec}, // group/name
+ {"play", &Script_Play}, // group/name
+ {"playlooped", &Script_playLooped}, // group/name
+ {"orbit", &Script_Orbit} // group/name
+};
+
+int scriptCommandCount = sizeof(commandList) / sizeof(commandDef_t);
+
+
+void Item_RunScript(itemDef_t *item, const char *s) {
+ char script[1024], *p;
+ int i;
+ qboolean bRan;
+ memset(script, 0, sizeof(script));
+ if (item && s && s[0]) {
+ Q_strcat(script, 1024, s);
+ p = script;
+ while (1) {
+ const char *command;
+ // expect command then arguments, ; ends command, NULL ends script
+ if (!String_Parse(&p, &command)) {
+ return;
+ }
+
+ if (command[0] == ';' && command[1] == '\0') {
+ continue;
+ }
+
+ bRan = qfalse;
+ for (i = 0; i < scriptCommandCount; i++) {
+ if (Q_stricmp(command, commandList[i].name) == 0) {
+ (commandList[i].handler(item, &p));
+ bRan = qtrue;
+ break;
+ }
+ }
+ // not in our auto list, pass to handler
+ if (!bRan) {
+ DC->runScript(&p);
+ }
+ }
+ }
+}
+
+
+qboolean Item_EnableShowViaCvar(itemDef_t *item, int flag) {
+ char script[1024], *p;
+ memset(script, 0, sizeof(script));
+ if (item && item->enableCvar && *item->enableCvar && item->cvarTest && *item->cvarTest) {
+ char buff[1024];
+ DC->getCVarString(item->cvarTest, buff, sizeof(buff));
+
+ Q_strcat(script, 1024, item->enableCvar);
+ p = script;
+ while (1) {
+ const char *val;
+ // expect value then ; or NULL, NULL ends list
+ if (!String_Parse(&p, &val)) {
+ return (item->cvarFlags & flag) ? qfalse : qtrue;
+ }
+
+ if (val[0] == ';' && val[1] == '\0') {
+ continue;
+ }
+
+ // enable it if any of the values are true
+ if (item->cvarFlags & flag) {
+ if (Q_stricmp(buff, val) == 0) {
+ return qtrue;
+ }
+ } else {
+ // disable it if any of the values are true
+ if (Q_stricmp(buff, val) == 0) {
+ return qfalse;
+ }
+ }
+
+ }
+ return (item->cvarFlags & flag) ? qfalse : qtrue;
+ }
+ return qtrue;
+}
+
+
+// will optionaly set focus to this item
+qboolean Item_SetFocus(itemDef_t *item, float x, float y) {
+ int i;
+ itemDef_t *oldFocus;
+ sfxHandle_t *sfx = &DC->Assets.itemFocusSound;
+ qboolean playSound = qfalse;
+ menuDef_t *parent; // bk001206: = (menuDef_t*)item->parent;
+ // sanity check, non-null, not a decoration and does not already have the focus
+ if (item == NULL || item->window.flags & WINDOW_DECORATION || item->window.flags & WINDOW_HASFOCUS || !(item->window.flags & WINDOW_VISIBLE)) {
+ return qfalse;
+ }
+
+ // bk001206 - this can be NULL.
+ parent = (menuDef_t*)item->parent;
+
+ // items can be enabled and disabled based on cvars
+ if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) {
+ return qfalse;
+ }
+
+ if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) {
+ return qfalse;
+ }
+
+ oldFocus = Menu_ClearFocus(item->parent);
+
+ if (item->type == ITEM_TYPE_TEXT) {
+ rectDef_t r;
+ r = item->textRect;
+ r.y -= r.h;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ item->window.flags |= WINDOW_HASFOCUS;
+ if (item->focusSound) {
+ sfx = &item->focusSound;
+ }
+ playSound = qtrue;
+ } else {
+ if (oldFocus) {
+ oldFocus->window.flags |= WINDOW_HASFOCUS;
+ if (oldFocus->onFocus) {
+ Item_RunScript(oldFocus, oldFocus->onFocus);
+ }
+ }
+ }
+ } else {
+ item->window.flags |= WINDOW_HASFOCUS;
+ if (item->onFocus) {
+ Item_RunScript(item, item->onFocus);
+ }
+ if (item->focusSound) {
+ sfx = &item->focusSound;
+ }
+ playSound = qtrue;
+ }
+
+ if (playSound && sfx) {
+ DC->startLocalSound( *sfx, CHAN_LOCAL_SOUND );
+ }
+
+ for (i = 0; i < parent->itemCount; i++) {
+ if (parent->items[i] == item) {
+ parent->cursorItem = i;
+ break;
+ }
+ }
+
+ return qtrue;
+}
+
+int Item_ListBox_MaxScroll(itemDef_t *item) {
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+ int count = DC->feederCount(item->special);
+ int max;
+
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ max = count - (item->window.rect.w / listPtr->elementWidth) + 1;
+ }
+ else {
+ max = count - (item->window.rect.h / listPtr->elementHeight) + 1;
+ }
+ if (max < 0) {
+ return 0;
+ }
+ return max;
+}
+
+int Item_ListBox_ThumbPosition(itemDef_t *item) {
+ float max, pos, size;
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+
+ max = Item_ListBox_MaxScroll(item);
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ size = item->window.rect.w - (SCROLLBAR_SIZE * 2) - 2;
+ if (max > 0) {
+ pos = (size-SCROLLBAR_SIZE) / (float) max;
+ } else {
+ pos = 0;
+ }
+ pos *= listPtr->startPos;
+ return item->window.rect.x + 1 + SCROLLBAR_SIZE + pos;
+ }
+ else {
+ size = item->window.rect.h - (SCROLLBAR_SIZE * 2) - 2;
+ if (max > 0) {
+ pos = (size-SCROLLBAR_SIZE) / (float) max;
+ } else {
+ pos = 0;
+ }
+ pos *= listPtr->startPos;
+ return item->window.rect.y + 1 + SCROLLBAR_SIZE + pos;
+ }
+}
+
+int Item_ListBox_ThumbDrawPosition(itemDef_t *item) {
+ int min, max;
+
+ if (itemCapture == item) {
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ min = item->window.rect.x + SCROLLBAR_SIZE + 1;
+ max = item->window.rect.x + item->window.rect.w - 2*SCROLLBAR_SIZE - 1;
+ if (DC->cursorx >= min + SCROLLBAR_SIZE/2 && DC->cursorx <= max + SCROLLBAR_SIZE/2) {
+ return DC->cursorx - SCROLLBAR_SIZE/2;
+ }
+ else {
+ return Item_ListBox_ThumbPosition(item);
+ }
+ }
+ else {
+ min = item->window.rect.y + SCROLLBAR_SIZE + 1;
+ max = item->window.rect.y + item->window.rect.h - 2*SCROLLBAR_SIZE - 1;
+ if (DC->cursory >= min + SCROLLBAR_SIZE/2 && DC->cursory <= max + SCROLLBAR_SIZE/2) {
+ return DC->cursory - SCROLLBAR_SIZE/2;
+ }
+ else {
+ return Item_ListBox_ThumbPosition(item);
+ }
+ }
+ }
+ else {
+ return Item_ListBox_ThumbPosition(item);
+ }
+}
+
+float Item_Slider_ThumbPosition(itemDef_t *item) {
+ float value, range, x;
+ editFieldDef_t *editDef = item->typeData;
+
+ if (item->text) {
+ x = item->textRect.x + item->textRect.w + 8;
+ } else {
+ x = item->window.rect.x;
+ }
+
+ if (editDef == NULL && item->cvar) {
+ return x;
+ }
+
+ value = DC->getCVarValue(item->cvar);
+
+ if (value < editDef->minVal) {
+ value = editDef->minVal;
+ } else if (value > editDef->maxVal) {
+ value = editDef->maxVal;
+ }
+
+ range = editDef->maxVal - editDef->minVal;
+ value -= editDef->minVal;
+ value /= range;
+ //value /= (editDef->maxVal - editDef->minVal);
+ value *= SLIDER_WIDTH;
+ x += value;
+ // vm fuckage
+ //x = x + (((float)value / editDef->maxVal) * SLIDER_WIDTH);
+ return x;
+}
+
+int Item_Slider_OverSlider(itemDef_t *item, float x, float y) {
+ rectDef_t r;
+
+ r.x = Item_Slider_ThumbPosition(item) - (SLIDER_THUMB_WIDTH / 2);
+ r.y = item->window.rect.y - 2;
+ r.w = SLIDER_THUMB_WIDTH;
+ r.h = SLIDER_THUMB_HEIGHT;
+
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_THUMB;
+ }
+ return 0;
+}
+
+int Item_ListBox_OverLB(itemDef_t *item, float x, float y) {
+ rectDef_t r;
+ listBoxDef_t *listPtr;
+ int thumbstart;
+ int count;
+
+ count = DC->feederCount(item->special);
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ // check if on left arrow
+ r.x = item->window.rect.x;
+ r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE;
+ r.h = r.w = SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_LEFTARROW;
+ }
+ // check if on right arrow
+ r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_RIGHTARROW;
+ }
+ // check if on thumb
+ thumbstart = Item_ListBox_ThumbPosition(item);
+ r.x = thumbstart;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_THUMB;
+ }
+ r.x = item->window.rect.x + SCROLLBAR_SIZE;
+ r.w = thumbstart - r.x;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_PGUP;
+ }
+ r.x = thumbstart + SCROLLBAR_SIZE;
+ r.w = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_PGDN;
+ }
+ } else {
+ r.x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE;
+ r.y = item->window.rect.y;
+ r.h = r.w = SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_LEFTARROW;
+ }
+ r.y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_RIGHTARROW;
+ }
+ thumbstart = Item_ListBox_ThumbPosition(item);
+ r.y = thumbstart;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_THUMB;
+ }
+ r.y = item->window.rect.y + SCROLLBAR_SIZE;
+ r.h = thumbstart - r.y;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_PGUP;
+ }
+ r.y = thumbstart + SCROLLBAR_SIZE;
+ r.h = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ return WINDOW_LB_PGDN;
+ }
+ }
+ return 0;
+}
+
+
+void Item_ListBox_MouseEnter(itemDef_t *item, float x, float y)
+{
+ rectDef_t r;
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+
+ item->window.flags &= ~(WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN);
+ item->window.flags |= Item_ListBox_OverLB(item, x, y);
+
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) {
+ // check for selection hit as we have exausted buttons and thumb
+ if (listPtr->elementStyle == LISTBOX_IMAGE) {
+ r.x = item->window.rect.x;
+ r.y = item->window.rect.y;
+ r.h = item->window.rect.h - SCROLLBAR_SIZE;
+ r.w = item->window.rect.w - listPtr->drawPadding;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ listPtr->cursorPos = (int)((x - r.x) / listPtr->elementWidth) + listPtr->startPos;
+ if (listPtr->cursorPos >= listPtr->endPos) {
+ listPtr->cursorPos = listPtr->endPos;
+ }
+ }
+ } else {
+ // text hit..
+ }
+ }
+ } else if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) {
+ r.x = item->window.rect.x;
+ r.y = item->window.rect.y;
+ r.w = item->window.rect.w - SCROLLBAR_SIZE;
+ r.h = item->window.rect.h - listPtr->drawPadding;
+ if (Rect_ContainsPoint(&r, x, y)) {
+ listPtr->cursorPos = (int)((y - 2 - r.y) / listPtr->elementHeight) + listPtr->startPos;
+ if (listPtr->cursorPos > listPtr->endPos) {
+ listPtr->cursorPos = listPtr->endPos;
+ }
+ }
+ }
+}
+
+void Item_MouseEnter(itemDef_t *item, float x, float y) {
+ rectDef_t r;
+ if (item) {
+ r = item->textRect;
+ r.y -= r.h;
+ // in the text rect?
+
+ // items can be enabled and disabled based on cvars
+ if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) {
+ return;
+ }
+
+ if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(item, CVAR_SHOW)) {
+ return;
+ }
+
+ if (Rect_ContainsPoint(&r, x, y)) {
+ if (!(item->window.flags & WINDOW_MOUSEOVERTEXT)) {
+ Item_RunScript(item, item->mouseEnterText);
+ item->window.flags |= WINDOW_MOUSEOVERTEXT;
+ }
+ if (!(item->window.flags & WINDOW_MOUSEOVER)) {
+ Item_RunScript(item, item->mouseEnter);
+ item->window.flags |= WINDOW_MOUSEOVER;
+ }
+
+ } else {
+ // not in the text rect
+ if (item->window.flags & WINDOW_MOUSEOVERTEXT) {
+ // if we were
+ Item_RunScript(item, item->mouseExitText);
+ item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
+ }
+ if (!(item->window.flags & WINDOW_MOUSEOVER)) {
+ Item_RunScript(item, item->mouseEnter);
+ item->window.flags |= WINDOW_MOUSEOVER;
+ }
+
+ if (item->type == ITEM_TYPE_LISTBOX) {
+ Item_ListBox_MouseEnter(item, x, y);
+ }
+ }
+ }
+}
+
+void Item_MouseLeave(itemDef_t *item) {
+ if (item) {
+ if (item->window.flags & WINDOW_MOUSEOVERTEXT) {
+ Item_RunScript(item, item->mouseExitText);
+ item->window.flags &= ~WINDOW_MOUSEOVERTEXT;
+ }
+ Item_RunScript(item, item->mouseExit);
+ item->window.flags &= ~(WINDOW_LB_RIGHTARROW | WINDOW_LB_LEFTARROW);
+ }
+}
+
+itemDef_t *Menu_HitTest(menuDef_t *menu, float x, float y) {
+ int i;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) {
+ return menu->items[i];
+ }
+ }
+ return NULL;
+}
+
+void Item_SetMouseOver(itemDef_t *item, qboolean focus) {
+ if (item) {
+ if (focus) {
+ item->window.flags |= WINDOW_MOUSEOVER;
+ } else {
+ item->window.flags &= ~WINDOW_MOUSEOVER;
+ }
+ }
+}
+
+
+qboolean Item_OwnerDraw_HandleKey(itemDef_t *item, int key) {
+ if (item && DC->ownerDrawHandleKey) {
+ return DC->ownerDrawHandleKey(item->window.ownerDraw, item->window.ownerDrawFlags, &item->special, key);
+ }
+ return qfalse;
+}
+
+qboolean Item_ListBox_HandleKey(itemDef_t *item, int key, qboolean down, qboolean force) {
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+ int count = DC->feederCount(item->special);
+ int max, viewmax;
+
+ if (force || (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS)) {
+ max = Item_ListBox_MaxScroll(item);
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ viewmax = (item->window.rect.w / listPtr->elementWidth);
+ if ( key == K_LEFTARROW || key == K_KP_LEFTARROW )
+ {
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos--;
+ if (listPtr->cursorPos < 0) {
+ listPtr->cursorPos = 0;
+ }
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos--;
+ if (listPtr->startPos < 0)
+ listPtr->startPos = 0;
+ }
+ return qtrue;
+ }
+ if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW )
+ {
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos++;
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= count) {
+ listPtr->cursorPos = count-1;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos++;
+ if (listPtr->startPos >= count)
+ listPtr->startPos = count-1;
+ }
+ return qtrue;
+ }
+ }
+ else {
+ viewmax = (item->window.rect.h / listPtr->elementHeight);
+ if ( key == K_UPARROW || key == K_KP_UPARROW )
+ {
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos--;
+ if (listPtr->cursorPos < 0) {
+ listPtr->cursorPos = 0;
+ }
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos--;
+ if (listPtr->startPos < 0)
+ listPtr->startPos = 0;
+ }
+ return qtrue;
+ }
+ if ( key == K_DOWNARROW || key == K_KP_DOWNARROW )
+ {
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos++;
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= count) {
+ listPtr->cursorPos = count-1;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos++;
+ if (listPtr->startPos > max)
+ listPtr->startPos = max;
+ }
+ return qtrue;
+ }
+ }
+ // mouse hit
+ if (key == K_MOUSE1 || key == K_MOUSE2) {
+ if (item->window.flags & WINDOW_LB_LEFTARROW) {
+ listPtr->startPos--;
+ if (listPtr->startPos < 0) {
+ listPtr->startPos = 0;
+ }
+ } else if (item->window.flags & WINDOW_LB_RIGHTARROW) {
+ // one down
+ listPtr->startPos++;
+ if (listPtr->startPos > max) {
+ listPtr->startPos = max;
+ }
+ } else if (item->window.flags & WINDOW_LB_PGUP) {
+ // page up
+ listPtr->startPos -= viewmax;
+ if (listPtr->startPos < 0) {
+ listPtr->startPos = 0;
+ }
+ } else if (item->window.flags & WINDOW_LB_PGDN) {
+ // page down
+ listPtr->startPos += viewmax;
+ if (listPtr->startPos > max) {
+ listPtr->startPos = max;
+ }
+ } else if (item->window.flags & WINDOW_LB_THUMB) {
+ // Display_SetCaptureItem(item);
+ } else {
+ // select an item
+ if (DC->realTime < lastListBoxClickTime && listPtr->doubleClick) {
+ Item_RunScript(item, listPtr->doubleClick);
+ }
+ lastListBoxClickTime = DC->realTime + DOUBLE_CLICK_DELAY;
+ if (item->cursorPos != listPtr->cursorPos) {
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ }
+ return qtrue;
+ }
+
+ // Scroll wheel
+ if (key == K_MWHEELUP) {
+ listPtr->startPos--;
+ if (listPtr->startPos < 0) {
+ listPtr->startPos = 0;
+ }
+ return qtrue;
+ }
+ if (key == K_MWHEELDOWN) {
+ listPtr->startPos++;
+ if (listPtr->startPos > max) {
+ listPtr->startPos = max;
+ }
+ return qtrue;
+ }
+
+ // Invoke the doubleClick handler when enter is pressed
+ if( key == K_ENTER )
+ {
+ if( listPtr->doubleClick )
+ Item_RunScript( item, listPtr->doubleClick );
+
+ return qtrue;
+ }
+
+ if ( key == K_HOME || key == K_KP_HOME) {
+ // home
+ listPtr->startPos = 0;
+ return qtrue;
+ }
+ if ( key == K_END || key == K_KP_END) {
+ // end
+ listPtr->startPos = max;
+ return qtrue;
+ }
+ if (key == K_PGUP || key == K_KP_PGUP ) {
+ // page up
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos -= viewmax;
+ if (listPtr->cursorPos < 0) {
+ listPtr->cursorPos = 0;
+ }
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos -= viewmax;
+ if (listPtr->startPos < 0) {
+ listPtr->startPos = 0;
+ }
+ }
+ return qtrue;
+ }
+ if ( key == K_PGDN || key == K_KP_PGDN ) {
+ // page down
+ if (!listPtr->notselectable) {
+ listPtr->cursorPos += viewmax;
+ if (listPtr->cursorPos < listPtr->startPos) {
+ listPtr->startPos = listPtr->cursorPos;
+ }
+ if (listPtr->cursorPos >= count) {
+ listPtr->cursorPos = count-1;
+ }
+ if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
+ listPtr->startPos = listPtr->cursorPos - viewmax + 1;
+ }
+ item->cursorPos = listPtr->cursorPos;
+ DC->feederSelection(item->special, item->cursorPos);
+ }
+ else {
+ listPtr->startPos += viewmax;
+ if (listPtr->startPos > max) {
+ listPtr->startPos = max;
+ }
+ }
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+qboolean Item_YesNo_HandleKey(itemDef_t *item, int key) {
+
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && item->cvar) {
+ if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) {
+ DC->setCVar(item->cvar, va("%i", !DC->getCVarValue(item->cvar)));
+ return qtrue;
+ }
+ }
+
+ return qfalse;
+
+}
+
+int Item_Multi_CountSettings(itemDef_t *item) {
+ multiDef_t *multiPtr = (multiDef_t*)item->typeData;
+ if (multiPtr == NULL) {
+ return 0;
+ }
+ return multiPtr->count;
+}
+
+int Item_Multi_FindCvarByValue(itemDef_t *item) {
+ char buff[1024];
+ float value = 0;
+ int i;
+ multiDef_t *multiPtr = (multiDef_t*)item->typeData;
+ if (multiPtr) {
+ if (multiPtr->strDef) {
+ DC->getCVarString(item->cvar, buff, sizeof(buff));
+ } else {
+ value = DC->getCVarValue(item->cvar);
+ }
+ for (i = 0; i < multiPtr->count; i++) {
+ if (multiPtr->strDef) {
+ if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) {
+ return i;
+ }
+ } else {
+ if (multiPtr->cvarValue[i] == value) {
+ return i;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+const char *Item_Multi_Setting(itemDef_t *item) {
+ char buff[1024];
+ float value = 0;
+ int i;
+ multiDef_t *multiPtr = (multiDef_t*)item->typeData;
+ if (multiPtr) {
+ if (multiPtr->strDef) {
+ DC->getCVarString(item->cvar, buff, sizeof(buff));
+ } else {
+ value = DC->getCVarValue(item->cvar);
+ }
+ for (i = 0; i < multiPtr->count; i++) {
+ if (multiPtr->strDef) {
+ if (Q_stricmp(buff, multiPtr->cvarStr[i]) == 0) {
+ return multiPtr->cvarList[i];
+ }
+ } else {
+ if (multiPtr->cvarValue[i] == value) {
+ return multiPtr->cvarList[i];
+ }
+ }
+ }
+ }
+ return "";
+}
+
+qboolean Item_Multi_HandleKey(itemDef_t *item, int key) {
+ multiDef_t *multiPtr = (multiDef_t*)item->typeData;
+ if (multiPtr) {
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && item->window.flags & WINDOW_HASFOCUS && item->cvar) {
+ if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) {
+ int current = Item_Multi_FindCvarByValue(item) + 1;
+ int max = Item_Multi_CountSettings(item);
+ if ( current < 0 || current >= max ) {
+ current = 0;
+ }
+ if (multiPtr->strDef) {
+ DC->setCVar(item->cvar, multiPtr->cvarStr[current]);
+ } else {
+ float value = multiPtr->cvarValue[current];
+ if (((float)((int) value)) == value) {
+ DC->setCVar(item->cvar, va("%i", (int) value ));
+ }
+ else {
+ DC->setCVar(item->cvar, va("%f", value ));
+ }
+ }
+ return qtrue;
+ }
+ }
+ }
+ return qfalse;
+}
+
+qboolean Item_TextField_HandleKey(itemDef_t *item, int key) {
+ char buff[1024];
+ int len;
+ itemDef_t *newItem = NULL;
+ editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData;
+
+ if (item->cvar) {
+
+ memset(buff, 0, sizeof(buff));
+ DC->getCVarString(item->cvar, buff, sizeof(buff));
+ len = strlen(buff);
+ if (editPtr->maxChars && len > editPtr->maxChars) {
+ len = editPtr->maxChars;
+ }
+ if ( key & K_CHAR_FLAG ) {
+ key &= ~K_CHAR_FLAG;
+
+
+ if (key == 'h' - 'a' + 1 ) { // ctrl-h is backspace
+ if ( item->cursorPos > 0 ) {
+ memmove( &buff[item->cursorPos - 1], &buff[item->cursorPos], len + 1 - item->cursorPos);
+ item->cursorPos--;
+ if (item->cursorPos < editPtr->paintOffset) {
+ editPtr->paintOffset--;
+ }
+ }
+ DC->setCVar(item->cvar, buff);
+ return qtrue;
+ }
+
+
+ //
+ // ignore any non printable chars
+ //
+ if ( key < 32 || !item->cvar) {
+ return qtrue;
+ }
+
+ if (item->type == ITEM_TYPE_NUMERICFIELD) {
+ if (key < '0' || key > '9') {
+ return qfalse;
+ }
+ }
+
+ if (!DC->getOverstrikeMode()) {
+ if (( len == MAX_EDITFIELD - 1 ) || (editPtr->maxChars && len >= editPtr->maxChars)) {
+ return qtrue;
+ }
+ memmove( &buff[item->cursorPos + 1], &buff[item->cursorPos], len + 1 - item->cursorPos );
+ } else {
+ if (editPtr->maxChars && item->cursorPos >= editPtr->maxChars) {
+ return qtrue;
+ }
+ }
+
+ buff[item->cursorPos] = key;
+
+ DC->setCVar(item->cvar, buff);
+
+ if (item->cursorPos < len + 1) {
+ item->cursorPos++;
+ if (editPtr->maxPaintChars && item->cursorPos > editPtr->maxPaintChars) {
+ editPtr->paintOffset++;
+ }
+ }
+
+ } else {
+
+ if ( key == K_DEL || key == K_KP_DEL ) {
+ if ( item->cursorPos < len ) {
+ memmove( buff + item->cursorPos, buff + item->cursorPos + 1, len - item->cursorPos);
+ DC->setCVar(item->cvar, buff);
+ }
+ return qtrue;
+ }
+
+ if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW )
+ {
+ if (editPtr->maxPaintChars && item->cursorPos >= editPtr->maxPaintChars && item->cursorPos < len) {
+ item->cursorPos++;
+ editPtr->paintOffset++;
+ return qtrue;
+ }
+ if (item->cursorPos < len) {
+ item->cursorPos++;
+ }
+ return qtrue;
+ }
+
+ if ( key == K_LEFTARROW || key == K_KP_LEFTARROW )
+ {
+ if ( item->cursorPos > 0 ) {
+ item->cursorPos--;
+ }
+ if (item->cursorPos < editPtr->paintOffset) {
+ editPtr->paintOffset--;
+ }
+ return qtrue;
+ }
+
+ if ( key == K_HOME || key == K_KP_HOME) {// || ( tolower(key) == 'a' && trap_Key_IsDown( K_CTRL ) ) ) {
+ item->cursorPos = 0;
+ editPtr->paintOffset = 0;
+ return qtrue;
+ }
+
+ if ( key == K_END || key == K_KP_END) {// ( tolower(key) == 'e' && trap_Key_IsDown( K_CTRL ) ) ) {
+ item->cursorPos = len;
+ if(item->cursorPos > editPtr->maxPaintChars) {
+ editPtr->paintOffset = len - editPtr->maxPaintChars;
+ }
+ return qtrue;
+ }
+
+ if ( key == K_INS || key == K_KP_INS ) {
+ DC->setOverstrikeMode(!DC->getOverstrikeMode());
+ return qtrue;
+ }
+ }
+
+ if (key == K_TAB || key == K_DOWNARROW || key == K_KP_DOWNARROW) {
+ newItem = Menu_SetNextCursorItem(item->parent);
+ if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) {
+ g_editItem = newItem;
+ }
+ }
+
+ if (key == K_UPARROW || key == K_KP_UPARROW) {
+ newItem = Menu_SetPrevCursorItem(item->parent);
+ if (newItem && (newItem->type == ITEM_TYPE_EDITFIELD || newItem->type == ITEM_TYPE_NUMERICFIELD)) {
+ g_editItem = newItem;
+ }
+ }
+
+ if ( key == K_ENTER || key == K_KP_ENTER || key == K_ESCAPE) {
+ return qfalse;
+ }
+
+ return qtrue;
+ }
+ return qfalse;
+
+}
+
+static void Scroll_ListBox_AutoFunc(void *p) {
+ scrollInfo_t *si = (scrollInfo_t*)p;
+ if (DC->realTime > si->nextScrollTime) {
+ // need to scroll which is done by simulating a click to the item
+ // this is done a bit sideways as the autoscroll "knows" that the item is a listbox
+ // so it calls it directly
+ Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse);
+ si->nextScrollTime = DC->realTime + si->adjustValue;
+ }
+
+ if (DC->realTime > si->nextAdjustTime) {
+ si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
+ if (si->adjustValue > SCROLL_TIME_FLOOR) {
+ si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
+ }
+ }
+}
+
+static void Scroll_ListBox_ThumbFunc(void *p) {
+ scrollInfo_t *si = (scrollInfo_t*)p;
+ rectDef_t r;
+ int pos, max;
+
+ listBoxDef_t *listPtr = (listBoxDef_t*)si->item->typeData;
+ if (si->item->window.flags & WINDOW_HORIZONTAL) {
+ if (DC->cursorx == si->xStart) {
+ return;
+ }
+ r.x = si->item->window.rect.x + SCROLLBAR_SIZE + 1;
+ r.y = si->item->window.rect.y + si->item->window.rect.h - SCROLLBAR_SIZE - 1;
+ r.h = SCROLLBAR_SIZE;
+ r.w = si->item->window.rect.w - (SCROLLBAR_SIZE*2) - 2;
+ max = Item_ListBox_MaxScroll(si->item);
+ //
+ pos = (DC->cursorx - r.x - SCROLLBAR_SIZE/2) * max / (r.w - SCROLLBAR_SIZE);
+ if (pos < 0) {
+ pos = 0;
+ }
+ else if (pos > max) {
+ pos = max;
+ }
+ listPtr->startPos = pos;
+ si->xStart = DC->cursorx;
+ }
+ else if (DC->cursory != si->yStart) {
+
+ r.x = si->item->window.rect.x + si->item->window.rect.w - SCROLLBAR_SIZE - 1;
+ r.y = si->item->window.rect.y + SCROLLBAR_SIZE + 1;
+ r.h = si->item->window.rect.h - (SCROLLBAR_SIZE*2) - 2;
+ r.w = SCROLLBAR_SIZE;
+ max = Item_ListBox_MaxScroll(si->item);
+ //
+ pos = (DC->cursory - r.y - SCROLLBAR_SIZE/2) * max / (r.h - SCROLLBAR_SIZE);
+ if (pos < 0) {
+ pos = 0;
+ }
+ else if (pos > max) {
+ pos = max;
+ }
+ listPtr->startPos = pos;
+ si->yStart = DC->cursory;
+ }
+
+ if (DC->realTime > si->nextScrollTime) {
+ // need to scroll which is done by simulating a click to the item
+ // this is done a bit sideways as the autoscroll "knows" that the item is a listbox
+ // so it calls it directly
+ Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse);
+ si->nextScrollTime = DC->realTime + si->adjustValue;
+ }
+
+ if (DC->realTime > si->nextAdjustTime) {
+ si->nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
+ if (si->adjustValue > SCROLL_TIME_FLOOR) {
+ si->adjustValue -= SCROLL_TIME_ADJUSTOFFSET;
+ }
+ }
+}
+
+static void Scroll_Slider_ThumbFunc(void *p) {
+ float x, value, cursorx;
+ scrollInfo_t *si = (scrollInfo_t*)p;
+ editFieldDef_t *editDef = si->item->typeData;
+
+ if (si->item->text) {
+ x = si->item->textRect.x + si->item->textRect.w + 8;
+ } else {
+ x = si->item->window.rect.x;
+ }
+
+ cursorx = DC->cursorx;
+
+ if (cursorx < x) {
+ cursorx = x;
+ } else if (cursorx > x + SLIDER_WIDTH) {
+ cursorx = x + SLIDER_WIDTH;
+ }
+ value = cursorx - x;
+ value /= SLIDER_WIDTH;
+ value *= (editDef->maxVal - editDef->minVal);
+ value += editDef->minVal;
+ DC->setCVar(si->item->cvar, va("%f", value));
+}
+
+void Item_StartCapture(itemDef_t *item, int key) {
+ int flags;
+ switch (item->type) {
+ case ITEM_TYPE_EDITFIELD:
+ case ITEM_TYPE_NUMERICFIELD:
+
+ case ITEM_TYPE_LISTBOX:
+ {
+ flags = Item_ListBox_OverLB(item, DC->cursorx, DC->cursory);
+ if (flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW)) {
+ scrollInfo.nextScrollTime = DC->realTime + SCROLL_TIME_START;
+ scrollInfo.nextAdjustTime = DC->realTime + SCROLL_TIME_ADJUST;
+ scrollInfo.adjustValue = SCROLL_TIME_START;
+ scrollInfo.scrollKey = key;
+ scrollInfo.scrollDir = (flags & WINDOW_LB_LEFTARROW) ? qtrue : qfalse;
+ scrollInfo.item = item;
+ captureData = &scrollInfo;
+ captureFunc = &Scroll_ListBox_AutoFunc;
+ itemCapture = item;
+ } else if (flags & WINDOW_LB_THUMB) {
+ scrollInfo.scrollKey = key;
+ scrollInfo.item = item;
+ scrollInfo.xStart = DC->cursorx;
+ scrollInfo.yStart = DC->cursory;
+ captureData = &scrollInfo;
+ captureFunc = &Scroll_ListBox_ThumbFunc;
+ itemCapture = item;
+ }
+ break;
+ }
+ case ITEM_TYPE_SLIDER:
+ {
+ flags = Item_Slider_OverSlider(item, DC->cursorx, DC->cursory);
+ if (flags & WINDOW_LB_THUMB) {
+ scrollInfo.scrollKey = key;
+ scrollInfo.item = item;
+ scrollInfo.xStart = DC->cursorx;
+ scrollInfo.yStart = DC->cursory;
+ captureData = &scrollInfo;
+ captureFunc = &Scroll_Slider_ThumbFunc;
+ itemCapture = item;
+ }
+ break;
+ }
+ }
+}
+
+void Item_StopCapture(itemDef_t *item) {
+
+}
+
+qboolean Item_Slider_HandleKey(itemDef_t *item, int key, qboolean down) {
+ float x, value, width, work;
+
+ if (item->window.flags & WINDOW_HASFOCUS && item->cvar && Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) {
+ if (key == K_MOUSE1 || key == K_ENTER || key == K_MOUSE2 || key == K_MOUSE3) {
+ editFieldDef_t *editDef = item->typeData;
+ if (editDef) {
+ rectDef_t testRect;
+ width = SLIDER_WIDTH;
+ if (item->text) {
+ x = item->textRect.x + item->textRect.w + 8;
+ } else {
+ x = item->window.rect.x;
+ }
+
+ testRect = item->window.rect;
+ testRect.x = x;
+ value = (float)SLIDER_THUMB_WIDTH / 2;
+ testRect.x -= value;
+ testRect.w = (SLIDER_WIDTH + (float)SLIDER_THUMB_WIDTH / 2);
+ if (Rect_ContainsPoint(&testRect, DC->cursorx, DC->cursory)) {
+ work = DC->cursorx - x;
+ value = work / width;
+ value *= (editDef->maxVal - editDef->minVal);
+ // vm fuckage
+ // value = (((float)(DC->cursorx - x)/ SLIDER_WIDTH) * (editDef->maxVal - editDef->minVal));
+ value += editDef->minVal;
+ DC->setCVar(item->cvar, va("%f", value));
+ return qtrue;
+ }
+ }
+ }
+ }
+ return qfalse;
+}
+
+
+qboolean Item_HandleKey(itemDef_t *item, int key, qboolean down) {
+
+ if (itemCapture) {
+ Item_StopCapture(itemCapture);
+ itemCapture = NULL;
+ captureFunc = voidFunction;
+ captureData = NULL;
+ } else {
+ // bk001206 - parentheses
+ if ( down && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) {
+ Item_StartCapture(item, key);
+ }
+ }
+
+ if (!down) {
+ return qfalse;
+ }
+
+ switch (item->type) {
+ case ITEM_TYPE_BUTTON:
+ return qfalse;
+ break;
+ case ITEM_TYPE_RADIOBUTTON:
+ return qfalse;
+ break;
+ case ITEM_TYPE_CHECKBOX:
+ return qfalse;
+ break;
+ case ITEM_TYPE_EDITFIELD:
+ case ITEM_TYPE_NUMERICFIELD:
+ //return Item_TextField_HandleKey(item, key);
+ return qfalse;
+ break;
+ case ITEM_TYPE_COMBO:
+ return qfalse;
+ break;
+ case ITEM_TYPE_LISTBOX:
+ return Item_ListBox_HandleKey(item, key, down, qfalse);
+ break;
+ case ITEM_TYPE_YESNO:
+ return Item_YesNo_HandleKey(item, key);
+ break;
+ case ITEM_TYPE_MULTI:
+ return Item_Multi_HandleKey(item, key);
+ break;
+ case ITEM_TYPE_OWNERDRAW:
+ return Item_OwnerDraw_HandleKey(item, key);
+ break;
+ case ITEM_TYPE_BIND:
+ return Item_Bind_HandleKey(item, key, down);
+ break;
+ case ITEM_TYPE_SLIDER:
+ return Item_Slider_HandleKey(item, key, down);
+ break;
+ //case ITEM_TYPE_IMAGE:
+ // Item_Image_Paint(item);
+ // break;
+ default:
+ return qfalse;
+ break;
+ }
+
+ //return qfalse;
+}
+
+void Item_Action(itemDef_t *item) {
+ if (item) {
+ Item_RunScript(item, item->action);
+ }
+}
+
+itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu) {
+ qboolean wrapped = qfalse;
+ int oldCursor = menu->cursorItem;
+
+ if (menu->cursorItem < 0) {
+ menu->cursorItem = menu->itemCount-1;
+ wrapped = qtrue;
+ }
+
+ while (menu->cursorItem > -1) {
+
+ menu->cursorItem--;
+ if (menu->cursorItem < 0 && !wrapped) {
+ wrapped = qtrue;
+ menu->cursorItem = menu->itemCount -1;
+ }
+
+ if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) {
+ Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1);
+ return menu->items[menu->cursorItem];
+ }
+ }
+ menu->cursorItem = oldCursor;
+ return NULL;
+
+}
+
+itemDef_t *Menu_SetNextCursorItem(menuDef_t *menu) {
+
+ qboolean wrapped = qfalse;
+ int oldCursor = menu->cursorItem;
+
+
+ if (menu->cursorItem == -1) {
+ menu->cursorItem = 0;
+ wrapped = qtrue;
+ }
+
+ while (menu->cursorItem < menu->itemCount) {
+
+ menu->cursorItem++;
+ if (menu->cursorItem >= menu->itemCount && !wrapped) {
+ wrapped = qtrue;
+ menu->cursorItem = 0;
+ }
+ if (Item_SetFocus(menu->items[menu->cursorItem], DC->cursorx, DC->cursory)) {
+ Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1);
+ return menu->items[menu->cursorItem];
+ }
+
+ }
+
+ menu->cursorItem = oldCursor;
+ return NULL;
+}
+
+static void Window_CloseCinematic(windowDef_t *window) {
+ if (window->style == WINDOW_STYLE_CINEMATIC && window->cinematic >= 0) {
+ DC->stopCinematic(window->cinematic);
+ window->cinematic = -1;
+ }
+}
+
+static void Menu_CloseCinematics(menuDef_t *menu) {
+ if (menu) {
+ int i;
+ Window_CloseCinematic(&menu->window);
+ for (i = 0; i < menu->itemCount; i++) {
+ Window_CloseCinematic(&menu->items[i]->window);
+ if (menu->items[i]->type == ITEM_TYPE_OWNERDRAW) {
+ DC->stopCinematic(0-menu->items[i]->window.ownerDraw);
+ }
+ }
+ }
+}
+
+static void Display_CloseCinematics( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ Menu_CloseCinematics(&Menus[i]);
+ }
+}
+
+void Menus_Activate(menuDef_t *menu) {
+ menu->window.flags |= (WINDOW_HASFOCUS | WINDOW_VISIBLE);
+ if (menu->onOpen) {
+ itemDef_t item;
+ item.parent = menu;
+ Item_RunScript(&item, menu->onOpen);
+ }
+
+ if (menu->soundName && *menu->soundName) {
+// DC->stopBackgroundTrack(); // you don't want to do this since it will reset s_rawend
+ DC->startBackgroundTrack(menu->soundName, menu->soundName);
+ }
+
+ Display_CloseCinematics();
+
+}
+
+int Display_VisibleMenuCount( void ) {
+ int i, count;
+ count = 0;
+ for (i = 0; i < menuCount; i++) {
+ if (Menus[i].window.flags & (WINDOW_FORCED | WINDOW_VISIBLE)) {
+ count++;
+ }
+ }
+ return count;
+}
+
+void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) {
+ if (menu) {
+ int i;
+ // basically the behaviour we are looking for is if there are windows in the stack.. see if
+ // the cursor is within any of them.. if not close them otherwise activate them and pass the
+ // key on.. force a mouse move to activate focus and script stuff
+ if (down && menu->window.flags & WINDOW_OOB_CLICK) {
+ Menu_RunCloseScript(menu);
+ menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE);
+ }
+
+ for (i = 0; i < menuCount; i++) {
+ if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory)) {
+ Menu_RunCloseScript(menu);
+ menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE);
+ Menus_Activate(&Menus[i]);
+ Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory);
+ Menu_HandleKey(&Menus[i], key, down);
+ }
+ }
+
+ if (Display_VisibleMenuCount() == 0) {
+ if (DC->Pause) {
+ DC->Pause(qfalse);
+ }
+ }
+ Display_CloseCinematics();
+ }
+}
+
+static rectDef_t *Item_CorrectedTextRect(itemDef_t *item) {
+ static rectDef_t rect;
+ memset(&rect, 0, sizeof(rectDef_t));
+ if (item) {
+ rect = item->textRect;
+ if (rect.w) {
+ rect.y -= rect.h;
+ }
+ }
+ return &rect;
+}
+
+void Menu_HandleKey(menuDef_t *menu, int key, qboolean down) {
+ int i;
+ itemDef_t *item = NULL;
+ qboolean inHandler = qfalse;
+
+ if (inHandler) {
+ return;
+ }
+
+ inHandler = qtrue;
+ if (g_waitingForKey && down) {
+ Item_Bind_HandleKey(g_bindItem, key, down);
+ inHandler = qfalse;
+ return;
+ }
+
+ if (g_editingField && down) {
+ if (!Item_TextField_HandleKey(g_editItem, key)) {
+ g_editingField = qfalse;
+ g_editItem = NULL;
+ inHandler = qfalse;
+ return;
+ } else if (key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3) {
+ g_editingField = qfalse;
+ g_editItem = NULL;
+ Display_MouseMove(NULL, DC->cursorx, DC->cursory);
+ } else if (key == K_TAB || key == K_UPARROW || key == K_DOWNARROW) {
+ return;
+ }
+ }
+
+ if (menu == NULL) {
+ inHandler = qfalse;
+ return;
+ }
+
+ // see if the mouse is within the window bounds and if so is this a mouse click
+ if (down && !(menu->window.flags & WINDOW_POPUP) && !Rect_ContainsPoint(&menu->window.rect, DC->cursorx, DC->cursory)) {
+ static qboolean inHandleKey = qfalse;
+ // bk001206 - parentheses
+ if (!inHandleKey && ( key == K_MOUSE1 || key == K_MOUSE2 || key == K_MOUSE3 ) ) {
+ inHandleKey = qtrue;
+ Menus_HandleOOBClick(menu, key, down);
+ inHandleKey = qfalse;
+ inHandler = qfalse;
+ return;
+ }
+ }
+
+ // get the item with focus
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->window.flags & WINDOW_HASFOCUS) {
+ item = menu->items[i];
+ }
+ }
+
+ if (item != NULL) {
+ if (Item_HandleKey(item, key, down)) {
+ Item_Action(item);
+ inHandler = qfalse;
+ return;
+ }
+ }
+
+ if (!down) {
+ inHandler = qfalse;
+ return;
+ }
+
+ // default handling
+ switch ( key ) {
+
+ case K_F11:
+ if (DC->getCVarValue("developer")) {
+ debugMode ^= 1;
+ }
+ break;
+
+ case K_F12:
+ if (DC->getCVarValue("developer")) {
+ DC->executeText(EXEC_APPEND, "screenshot\n");
+ }
+ break;
+ case K_KP_UPARROW:
+ case K_UPARROW:
+ Menu_SetPrevCursorItem(menu);
+ break;
+
+ case K_ESCAPE:
+ if (!g_waitingForKey && menu->onESC) {
+ itemDef_t it;
+ it.parent = menu;
+ Item_RunScript(&it, menu->onESC);
+ }
+ break;
+ case K_TAB:
+ case K_KP_DOWNARROW:
+ case K_DOWNARROW:
+ Menu_SetNextCursorItem(menu);
+ break;
+
+ case K_MOUSE1:
+ case K_MOUSE2:
+ if (item) {
+ if (item->type == ITEM_TYPE_TEXT) {
+ if (Rect_ContainsPoint(Item_CorrectedTextRect(item), DC->cursorx, DC->cursory)) {
+ Item_Action(item);
+ }
+ } else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) {
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) {
+ item->cursorPos = 0;
+ g_editingField = qtrue;
+ g_editItem = item;
+ DC->setOverstrikeMode(qtrue);
+ }
+ } else {
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory)) {
+ Item_Action(item);
+ }
+ }
+ }
+ break;
+
+ case K_JOY1:
+ case K_JOY2:
+ case K_JOY3:
+ case K_JOY4:
+ case K_AUX1:
+ case K_AUX2:
+ case K_AUX3:
+ case K_AUX4:
+ case K_AUX5:
+ case K_AUX6:
+ case K_AUX7:
+ case K_AUX8:
+ case K_AUX9:
+ case K_AUX10:
+ case K_AUX11:
+ case K_AUX12:
+ case K_AUX13:
+ case K_AUX14:
+ case K_AUX15:
+ case K_AUX16:
+ break;
+ case K_KP_ENTER:
+ case K_ENTER:
+ if (item) {
+ if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD) {
+ item->cursorPos = 0;
+ g_editingField = qtrue;
+ g_editItem = item;
+ DC->setOverstrikeMode(qtrue);
+ } else {
+ Item_Action(item);
+ }
+ }
+ break;
+ }
+ inHandler = qfalse;
+}
+
+void ToWindowCoords(float *x, float *y, windowDef_t *window) {
+ if (window->border != 0) {
+ *x += window->borderSize;
+ *y += window->borderSize;
+ }
+ *x += window->rect.x;
+ *y += window->rect.y;
+}
+
+void Rect_ToWindowCoords(rectDef_t *rect, windowDef_t *window) {
+ ToWindowCoords(&rect->x, &rect->y, window);
+}
+
+void Item_SetTextExtents(itemDef_t *item, int *width, int *height, const char *text) {
+ const char *textPtr = (text) ? text : item->text;
+
+ if (textPtr == NULL ) {
+ return;
+ }
+
+ *width = item->textRect.w;
+ *height = item->textRect.h;
+
+ // keeps us from computing the widths and heights more than once
+ if (*width == 0 || (item->type == ITEM_TYPE_OWNERDRAW && item->textalignment == ITEM_ALIGN_CENTER)) {
+ int originalWidth = DC->textWidth(item->text, item->textscale, 0);
+
+ if (item->type == ITEM_TYPE_OWNERDRAW && (item->textalignment == ITEM_ALIGN_CENTER || item->textalignment == ITEM_ALIGN_RIGHT)) {
+ originalWidth += DC->ownerDrawWidth(item->window.ownerDraw, item->textscale);
+ } else if (item->type == ITEM_TYPE_EDITFIELD && item->textalignment == ITEM_ALIGN_CENTER && item->cvar) {
+ char buff[256];
+ DC->getCVarString(item->cvar, buff, 256);
+ originalWidth += DC->textWidth(buff, item->textscale, 0);
+ }
+
+ *width = DC->textWidth(textPtr, item->textscale, 0);
+ *height = DC->textHeight(textPtr, item->textscale, 0);
+ item->textRect.w = *width;
+ item->textRect.h = *height;
+ item->textRect.x = item->textalignx;
+ item->textRect.y = item->textaligny;
+ if (item->textalignment == ITEM_ALIGN_RIGHT) {
+ item->textRect.x = item->textalignx - originalWidth;
+ } else if (item->textalignment == ITEM_ALIGN_CENTER) {
+ item->textRect.x = item->textalignx - originalWidth / 2;
+ }
+
+ ToWindowCoords(&item->textRect.x, &item->textRect.y, &item->window);
+ }
+}
+
+void Item_TextColor(itemDef_t *item, vec4_t *newColor) {
+ vec4_t lowLight;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+
+ Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount);
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) {
+ lowLight[0] = 0.8 * item->window.foreColor[0];
+ lowLight[1] = 0.8 * item->window.foreColor[1];
+ lowLight[2] = 0.8 * item->window.foreColor[2];
+ lowLight[3] = 0.8 * item->window.foreColor[3];
+ LerpColor(item->window.foreColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));
+ } else {
+ memcpy(newColor, &item->window.foreColor, sizeof(vec4_t));
+ // items can be enabled and disabled based on cvars
+ }
+
+ if (item->enableCvar != NULL && *item->enableCvar && item->cvarTest != NULL && *item->cvarTest) {
+ if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) {
+ memcpy(newColor, &parent->disableColor, sizeof(vec4_t));
+ }
+ }
+}
+
+int Item_Text_AutoWrapped_Lines( itemDef_t *item )
+{
+ char text[ 1024 ];
+ const char *p, *textPtr, *newLinePtr;
+ char buff[ 1024 ];
+ int len, textWidth, newLine;
+ int lines = 0;
+
+ textWidth = 0;
+ newLinePtr = NULL;
+
+ if( item->text == NULL )
+ {
+ if( item->cvar == NULL )
+ return 0;
+ else
+ {
+ DC->getCVarString( item->cvar, text, sizeof( text ) );
+ textPtr = text;
+ }
+ }
+ else
+ textPtr = item->text;
+
+ if( *textPtr == '\0' )
+ return 0;
+
+ len = 0;
+ buff[ 0 ] = '\0';
+ newLine = 0;
+ p = textPtr;
+
+ while( p )
+ {
+ textWidth = DC->textWidth( buff, item->textscale, 0 );
+
+ if( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\0' )
+ {
+ newLine = len;
+ newLinePtr = p + 1;
+ }
+
+ //TA: forceably split lines that are too long (where normal splitage has failed)
+ if( textWidth > item->window.rect.w && newLine == 0 && *p != '\n' )
+ {
+ newLine = len;
+ newLinePtr = p;
+ }
+
+ if( ( newLine && textWidth > item->window.rect.w ) || *p == '\n' || *p == '\0' )
+ {
+ if( len )
+ buff[ newLine ] = '\0';
+
+ if( !( *p == '\n' && !*( p + 1 ) ) )
+ lines++;
+
+ if( *p == '\0' )
+ break;
+
+ //
+ p = newLinePtr;
+ len = 0;
+ newLine = 0;
+
+ continue;
+ }
+
+ buff[ len++ ] = *p++;
+ buff[ len ] = '\0';
+ }
+
+ return lines;
+}
+
+#define MAX_AUTOWRAP_CACHE 16
+#define MAX_AUTOWRAP_LINES 32
+#define MAX_AUTOWRAP_TEXT 512
+
+typedef struct
+{
+ //this is used purely for checking for cache hits
+ char text[ MAX_AUTOWRAP_TEXT * MAX_AUTOWRAP_LINES ];
+ rectDef_t rect;
+ int textWidth, textHeight;
+ char lines[ MAX_AUTOWRAP_LINES ][ MAX_AUTOWRAP_TEXT ];
+ int lineOffsets[ MAX_AUTOWRAP_LINES ][ 2 ];
+ int numLines;
+} autoWrapCache_t;
+
+static int cacheIndex = 0;
+static autoWrapCache_t awc[ MAX_AUTOWRAP_CACHE ];
+
+static int checkCache( const char *text, rectDef_t *rect, int width, int height )
+{
+ int i;
+
+ for( i = 0; i < MAX_AUTOWRAP_CACHE; i++ )
+ {
+ if( Q_stricmp( text, awc[ i ].text ) )
+ continue;
+
+ if( rect->x != awc[ i ].rect.x ||
+ rect->y != awc[ i ].rect.y ||
+ rect->w != awc[ i ].rect.w ||
+ rect->h != awc[ i ].rect.h )
+ continue;
+
+ if( awc[ i ].textWidth != width || awc[ i ].textHeight != height )
+ continue;
+
+ //this is a match
+ return i;
+ }
+
+ //no match - autowrap isn't cached
+ return -1;
+}
+
+void Item_Text_AutoWrapped_Paint( itemDef_t *item )
+{
+ char text[ 1024 ];
+ const char *p, *textPtr, *newLinePtr;
+ char buff[ 1024 ];
+ char lastCMod[ 2 ] = { 0, 0 };
+ qboolean forwardColor = qfalse;
+ int width, height, len, textWidth, newLine, newLineWidth;
+ int skipLines, totalLines, lineNum = 0;
+ float y, totalY, diffY;
+ vec4_t color;
+ int cache, i;
+
+ textWidth = 0;
+ newLinePtr = NULL;
+
+ if( item->text == NULL )
+ {
+ if( item->cvar == NULL )
+ return;
+ else
+ {
+ DC->getCVarString( item->cvar, text, sizeof( text ) );
+ textPtr = text;
+ }
+ }
+ else
+ textPtr = item->text;
+
+ if( *textPtr == '\0' )
+ return;
+
+ Item_TextColor( item, &color );
+ Item_SetTextExtents( item, &width, &height, textPtr );
+
+ //check if this block is cached
+ cache = checkCache( textPtr, &item->window.rect, width, height );
+ if( cache >= 0 )
+ {
+ lineNum = awc[ cache ].numLines;
+
+ for( i = 0; i < lineNum; i++ )
+ {
+ item->textRect.x = awc[ cache ].lineOffsets[ i ][ 0 ];
+ item->textRect.y = awc[ cache ].lineOffsets[ i ][ 1 ];
+
+ DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color,
+ awc[ cache ].lines[ i ], 0, 0, item->textStyle );
+ }
+ }
+ else
+ {
+ y = item->textaligny;
+ len = 0;
+ buff[ 0 ] = '\0';
+ newLine = 0;
+ newLineWidth = 0;
+ p = textPtr;
+
+ totalLines = Item_Text_AutoWrapped_Lines( item );
+
+ totalY = totalLines * ( height + 5 );
+ diffY = totalY - item->window.rect.h;
+
+ if( diffY > 0.0f )
+ skipLines = (int)( diffY / ( (float)height + 5.0f ) );
+ else
+ skipLines = 0;
+
+ //set up a cache entry
+ strcpy( awc[ cacheIndex ].text, textPtr );
+ awc[ cacheIndex ].rect.x = item->window.rect.x;
+ awc[ cacheIndex ].rect.y = item->window.rect.y;
+ awc[ cacheIndex ].rect.w = item->window.rect.w;
+ awc[ cacheIndex ].rect.h = item->window.rect.h;
+ awc[ cacheIndex ].textWidth = width;
+ awc[ cacheIndex ].textHeight = height;
+
+ while( p )
+ {
+ textWidth = DC->textWidth( buff, item->textscale, 0 );
+
+ if( *p == '^' )
+ {
+ lastCMod[ 0 ] = p[ 0 ];
+ lastCMod[ 1 ] = p[ 1 ];
+ }
+
+ if( *p == ' ' || *p == '\t' || *p == '\n' || *p == '\0' )
+ {
+ newLine = len;
+ newLinePtr = p+1;
+ newLineWidth = textWidth;
+
+ if( *p == '\n' ) //don't forward colours past deilberate \n's
+ lastCMod[ 0 ] = lastCMod[ 1 ] = 0;
+ else
+ forwardColor = qtrue;
+ }
+
+ //TA: forceably split lines that are too long (where normal splitage has failed)
+ if( textWidth > item->window.rect.w && newLine == 0 && *p != '\n' )
+ {
+ newLine = len;
+ newLinePtr = p;
+ newLineWidth = textWidth;
+
+ forwardColor = qtrue;
+ }
+
+ if( ( newLine && textWidth > item->window.rect.w ) || *p == '\n' || *p == '\0' )
+ {
+ if( len )
+ {
+ if( item->textalignment == ITEM_ALIGN_LEFT )
+ item->textRect.x = item->textalignx;
+ else if( item->textalignment == ITEM_ALIGN_RIGHT )
+ item->textRect.x = item->textalignx - newLineWidth;
+ else if( item->textalignment == ITEM_ALIGN_CENTER )
+ item->textRect.x = item->textalignx - newLineWidth / 2;
+
+ item->textRect.y = y;
+ ToWindowCoords( &item->textRect.x, &item->textRect.y, &item->window );
+ //
+ buff[ newLine ] = '\0';
+
+ if( !skipLines )
+ {
+ DC->drawText( item->textRect.x, item->textRect.y, item->textscale, color, buff, 0, 0, item->textStyle );
+
+ strcpy( awc[ cacheIndex ].lines[ lineNum ], buff );
+ awc[ cacheIndex ].lineOffsets[ lineNum ][ 0 ] = item->textRect.x;
+ awc[ cacheIndex ].lineOffsets[ lineNum ][ 1 ] = item->textRect.y;
+
+ lineNum++;
+ }
+ }
+ if( *p == '\0' )
+ break;
+
+ //
+ if( !skipLines )
+ y += height + 5;
+
+ if( skipLines )
+ skipLines--;
+
+ p = newLinePtr;
+ len = 0;
+ newLine = 0;
+ newLineWidth = 0;
+
+ if( forwardColor && lastCMod[ 0 ] != 0 )
+ {
+ buff[ len++ ] = lastCMod[ 0 ];
+ buff[ len++ ] = lastCMod[ 1 ];
+ buff[ len ] = '\0';
+
+ forwardColor = qfalse;
+ }
+
+ continue;
+ }
+
+ buff[ len++ ] = *p++;
+ buff[ len ] = '\0';
+ }
+
+ //mark the end of the lines list
+ awc[ cacheIndex ].numLines = lineNum;
+
+ //increment cacheIndex
+ cacheIndex = ( cacheIndex + 1 ) % MAX_AUTOWRAP_CACHE;
+ }
+}
+
+void Item_Text_Wrapped_Paint(itemDef_t *item) {
+ char text[1024];
+ const char *p, *start, *textPtr;
+ char buff[1024];
+ int width, height;
+ float x, y;
+ vec4_t color;
+
+ // now paint the text and/or any optional images
+ // default to left
+
+ if (item->text == NULL) {
+ if (item->cvar == NULL) {
+ return;
+ }
+ else {
+ DC->getCVarString(item->cvar, text, sizeof(text));
+ textPtr = text;
+ }
+ }
+ else {
+ textPtr = item->text;
+ }
+ if (*textPtr == '\0') {
+ return;
+ }
+
+ Item_TextColor(item, &color);
+ Item_SetTextExtents(item, &width, &height, textPtr);
+
+ x = item->textRect.x;
+ y = item->textRect.y;
+ start = textPtr;
+ p = strchr(textPtr, '\r');
+ while (p && *p) {
+ strncpy(buff, start, p-start+1);
+ buff[p-start] = '\0';
+ DC->drawText(x, y, item->textscale, color, buff, 0, 0, item->textStyle);
+ y += height + 5;
+ start += p - start + 1;
+ p = strchr(p+1, '\r');
+ }
+ DC->drawText(x, y, item->textscale, color, start, 0, 0, item->textStyle);
+}
+
+void Item_Text_Paint(itemDef_t *item) {
+ char text[1024];
+ const char *textPtr;
+ int height, width;
+ vec4_t color;
+
+ if (item->window.flags & WINDOW_WRAPPED) {
+ Item_Text_Wrapped_Paint(item);
+ return;
+ }
+ if (item->window.flags & WINDOW_AUTOWRAPPED) {
+ Item_Text_AutoWrapped_Paint(item);
+ return;
+ }
+
+ if (item->text == NULL) {
+ if (item->cvar == NULL) {
+ return;
+ }
+ else {
+ DC->getCVarString(item->cvar, text, sizeof(text));
+ textPtr = text;
+ }
+ }
+ else {
+ textPtr = item->text;
+ }
+
+ // this needs to go here as it sets extents for cvar types as well
+ Item_SetTextExtents(item, &width, &height, textPtr);
+
+ if (*textPtr == '\0') {
+ return;
+ }
+
+
+ Item_TextColor(item, &color);
+
+ //FIXME: this is a fucking mess
+/*
+ adjust = 0;
+ if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) {
+ adjust = 0.5;
+ }
+
+ if (item->textStyle == ITEM_TEXTSTYLE_SHADOWED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) {
+ Fade(&item->window.flags, &DC->Assets.shadowColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse);
+ DC->drawText(item->textRect.x + DC->Assets.shadowX, item->textRect.y + DC->Assets.shadowY, item->textscale, DC->Assets.shadowColor, textPtr, adjust);
+ }
+*/
+
+
+// if (item->textStyle == ITEM_TEXTSTYLE_OUTLINED || item->textStyle == ITEM_TEXTSTYLE_OUTLINESHADOWED) {
+// Fade(&item->window.flags, &item->window.outlineColor[3], DC->Assets.fadeClamp, &item->window.nextTime, DC->Assets.fadeCycle, qfalse);
+// /*
+// Text_Paint(item->textRect.x-1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x+1, item->textRect.y-1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x-1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x+1, item->textRect.y, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x-1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust);
+// Text_Paint(item->textRect.x+1, item->textRect.y+1, item->textscale, item->window.foreColor, textPtr, adjust);
+// */
+// DC->drawText(item->textRect.x - 1, item->textRect.y + 1, item->textscale * 1.02, item->window.outlineColor, textPtr, adjust);
+// }
+
+ DC->drawText(item->textRect.x, item->textRect.y, item->textscale, color, textPtr, 0, 0, item->textStyle);
+}
+
+
+
+//float trap_Cvar_VariableValue( const char *var_name );
+//void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize );
+
+void Item_TextField_Paint(itemDef_t *item) {
+ char buff[1024];
+ vec4_t newColor;
+ int offset;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+ editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData;
+
+ Item_Text_Paint(item);
+
+ buff[0] = '\0';
+
+ if (item->cvar) {
+ DC->getCVarString(item->cvar, buff, sizeof(buff));
+ }
+
+ parent = (menuDef_t*)item->parent;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ offset = (item->text && *item->text) ? 8 : 0;
+ if (item->window.flags & WINDOW_HASFOCUS && g_editingField) {
+ char cursor = DC->getOverstrikeMode() ? '_' : '|';
+ DC->drawTextWithCursor(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, item->cursorPos - editPtr->paintOffset , cursor, editPtr->maxPaintChars, item->textStyle);
+ } else {
+ DC->drawText(item->textRect.x + item->textRect.w + offset, item->textRect.y, item->textscale, newColor, buff + editPtr->paintOffset, 0, editPtr->maxPaintChars, item->textStyle);
+ }
+
+}
+
+void Item_YesNo_Paint(itemDef_t *item) {
+ vec4_t newColor;
+ float value;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+
+ value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ if (item->text) {
+ Item_Text_Paint(item);
+ DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0, item->textStyle);
+ } else {
+ DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "Yes" : "No", 0, 0, item->textStyle);
+ }
+}
+
+void Item_Multi_Paint(itemDef_t *item) {
+ vec4_t newColor;
+ const char *text = "";
+ menuDef_t *parent = (menuDef_t*)item->parent;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ text = Item_Multi_Setting(item);
+
+ if (item->text) {
+ Item_Text_Paint(item);
+ DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle);
+ } else {
+ DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, text, 0, 0, item->textStyle);
+ }
+}
+
+
+typedef struct {
+ char *command;
+ int id;
+ int defaultbind1;
+ int defaultbind2;
+ int bind1;
+ int bind2;
+} bind_t;
+
+typedef struct
+{
+ char* name;
+ float defaultvalue;
+ float value;
+} configcvar_t;
+
+
+static bind_t g_bindings[] =
+{
+ { "+scores", K_TAB, -1, -1, -1 },
+ { "+button2", K_ENTER, -1, -1, -1 },
+ { "+speed", K_SHIFT, -1, -1, -1 },
+ { "boost", 'x', -1, -1, -1 }, //TA: human sprinting
+ { "+forward", K_UPARROW, -1, -1, -1 },
+ { "+back", K_DOWNARROW, -1, -1, -1 },
+ { "+moveleft", ',', -1, -1, -1 },
+ { "+moveright", '.', -1, -1, -1 },
+ { "+moveup", K_SPACE, -1, -1, -1 },
+ { "+movedown", 'c', -1, -1, -1 },
+ { "+left", K_LEFTARROW, -1, -1, -1 },
+ { "+right", K_RIGHTARROW, -1, -1, -1 },
+ { "+strafe", K_ALT, -1, -1, -1 },
+ { "+lookup", K_PGDN, -1, -1, -1 },
+ { "+lookdown", K_DEL, -1, -1, -1 },
+ { "+mlook", '/', -1, -1, -1 },
+ { "centerview", K_END, -1, -1, -1 },
+ { "+zoom", -1, -1, -1, -1 },
+ { "weapon 1", '1', -1, -1, -1 },
+ { "weapon 2", '2', -1, -1, -1 },
+ { "weapon 3", '3', -1, -1, -1 },
+ { "weapon 4", '4', -1, -1, -1 },
+ { "weapon 5", '5', -1, -1, -1 },
+ { "weapon 6", '6', -1, -1, -1 },
+ { "weapon 7", '7', -1, -1, -1 },
+ { "weapon 8", '8', -1, -1, -1 },
+ { "weapon 9", '9', -1, -1, -1 },
+ { "weapon 10", '0', -1, -1, -1 },
+ { "weapon 11", -1, -1, -1, -1 },
+ { "weapon 12", -1, -1, -1, -1 },
+ { "weapon 13", -1, -1, -1, -1 },
+ { "+attack", K_MOUSE1, -1, -1, -1 },
+ { "+button5", K_MOUSE2, -1, -1, -1 }, //TA: secondary attack
+ { "reload", 'r', -1, -1, -1 }, //TA: reload
+ { "buy ammo", 'b', -1, -1, -1 }, //TA: buy ammo
+ { "itemact medkit", 'm', -1, -1, -1 }, //TA: use medkit
+ { "+button7", 'q', -1, -1, -1 }, //TA: buildable use
+ { "deconstruct", 'e', -1, -1, -1 }, //TA: buildable destroy
+ { "weapprev", '[', -1, -1, -1 },
+ { "weapnext", ']', -1, -1, -1 },
+ { "+button3", K_MOUSE3, -1, -1, -1 },
+ { "+button4", K_MOUSE4, -1, -1, -1 },
+ { "vote yes", K_F1, -1, -1, -1 },
+ { "vote no", K_F2, -1, -1, -1 },
+ { "teamvote yes", K_F3, -1, -1, -1 },
+ { "teamvote no", K_F4, -1, -1, -1 },
+ { "scoresUp", K_KP_PGUP, -1, -1, -1 },
+ { "scoresDown", K_KP_PGDN, -1, -1, -1 },
+ // bk001205 - this one below was: '-1'
+ { "messagemode", -1, -1, -1, -1 },
+ { "messagemode2", -1, -1, -1, -1 },
+ { "messagemode3", -1, -1, -1, -1 },
+ { "messagemode4", -1, -1, -1, -1 }
+};
+
+
+static const int g_bindCount = sizeof(g_bindings) / sizeof(bind_t);
+
+/*
+=================
+Controls_GetKeyAssignment
+=================
+*/
+static void Controls_GetKeyAssignment (char *command, int *twokeys)
+{
+ int count;
+ int j;
+ char b[256];
+
+ twokeys[0] = twokeys[1] = -1;
+ count = 0;
+
+ for ( j = 0; j < 256; j++ )
+ {
+ DC->getBindingBuf( j, b, 256 );
+ if ( *b == 0 ) {
+ continue;
+ }
+ if ( !Q_stricmp( b, command ) ) {
+ twokeys[count] = j;
+ count++;
+ if (count == 2) {
+ break;
+ }
+ }
+ }
+}
+
+/*
+=================
+Controls_GetConfig
+=================
+*/
+void Controls_GetConfig( void )
+{
+ int i;
+ int twokeys[ 2 ];
+
+ // iterate each command, get its numeric binding
+ for( i = 0; i < g_bindCount; i++ )
+ {
+ Controls_GetKeyAssignment( g_bindings[ i ].command, twokeys );
+
+ g_bindings[ i ].bind1 = twokeys[ 0 ];
+ g_bindings[ i ].bind2 = twokeys[ 1 ];
+ }
+
+ //s_controls.invertmouse.curvalue = DC->getCVarValue( "m_pitch" ) < 0;
+ //s_controls.smoothmouse.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "m_filter" ) );
+ //s_controls.alwaysrun.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_run" ) );
+ //s_controls.autoswitch.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cg_autoswitch" ) );
+ //s_controls.sensitivity.curvalue = UI_ClampCvar( 2, 30, Controls_GetCvarValue( "sensitivity" ) );
+ //s_controls.joyenable.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "in_joystick" ) );
+ //s_controls.joythreshold.curvalue = UI_ClampCvar( 0.05, 0.75, Controls_GetCvarValue( "joy_threshold" ) );
+ //s_controls.freelook.curvalue = UI_ClampCvar( 0, 1, Controls_GetCvarValue( "cl_freelook" ) );
+}
+
+/*
+=================
+Controls_SetConfig
+=================
+*/
+void Controls_SetConfig(qboolean restart)
+{
+ int i;
+
+ // iterate each command, get its numeric binding
+ for (i=0; i < g_bindCount; i++)
+ {
+
+ if (g_bindings[i].bind1 != -1)
+ {
+ DC->setBinding( g_bindings[i].bind1, g_bindings[i].command );
+
+ if (g_bindings[i].bind2 != -1)
+ DC->setBinding( g_bindings[i].bind2, g_bindings[i].command );
+ }
+ }
+
+ //if ( s_controls.invertmouse.curvalue )
+ // DC->setCVar("m_pitch", va("%f),-fabs( DC->getCVarValue( "m_pitch" ) ) );
+ //else
+ // trap_Cvar_SetValue( "m_pitch", fabs( trap_Cvar_VariableValue( "m_pitch" ) ) );
+
+ //trap_Cvar_SetValue( "m_filter", s_controls.smoothmouse.curvalue );
+ //trap_Cvar_SetValue( "cl_run", s_controls.alwaysrun.curvalue );
+ //trap_Cvar_SetValue( "cg_autoswitch", s_controls.autoswitch.curvalue );
+ //trap_Cvar_SetValue( "sensitivity", s_controls.sensitivity.curvalue );
+ //trap_Cvar_SetValue( "in_joystick", s_controls.joyenable.curvalue );
+ //trap_Cvar_SetValue( "joy_threshold", s_controls.joythreshold.curvalue );
+ //trap_Cvar_SetValue( "cl_freelook", s_controls.freelook.curvalue );
+ DC->executeText(EXEC_APPEND, "in_restart\n");
+ //trap_Cmd_ExecuteText( EXEC_APPEND, "in_restart\n" );
+}
+
+/*
+=================
+Controls_SetDefaults
+=================
+*/
+void Controls_SetDefaults( void )
+{
+ int i;
+
+ // iterate each command, set its default binding
+ for (i=0; i < g_bindCount; i++)
+ {
+ g_bindings[i].bind1 = g_bindings[i].defaultbind1;
+ g_bindings[i].bind2 = g_bindings[i].defaultbind2;
+ }
+
+ //s_controls.invertmouse.curvalue = Controls_GetCvarDefault( "m_pitch" ) < 0;
+ //s_controls.smoothmouse.curvalue = Controls_GetCvarDefault( "m_filter" );
+ //s_controls.alwaysrun.curvalue = Controls_GetCvarDefault( "cl_run" );
+ //s_controls.autoswitch.curvalue = Controls_GetCvarDefault( "cg_autoswitch" );
+ //s_controls.sensitivity.curvalue = Controls_GetCvarDefault( "sensitivity" );
+ //s_controls.joyenable.curvalue = Controls_GetCvarDefault( "in_joystick" );
+ //s_controls.joythreshold.curvalue = Controls_GetCvarDefault( "joy_threshold" );
+ //s_controls.freelook.curvalue = Controls_GetCvarDefault( "cl_freelook" );
+}
+
+int BindingIDFromName(const char *name) {
+ int i;
+ for (i=0; i < g_bindCount; i++)
+ {
+ if (Q_stricmp(name, g_bindings[i].command) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+char g_nameBind1[32];
+char g_nameBind2[32];
+
+void BindingFromName(const char *cvar) {
+ int i, b1, b2;
+
+ // iterate each command, set its default binding
+ for (i=0; i < g_bindCount; i++)
+ {
+ if (Q_stricmp(cvar, g_bindings[i].command) == 0) {
+ b1 = g_bindings[i].bind1;
+ if (b1 == -1) {
+ break;
+ }
+ DC->keynumToStringBuf( b1, g_nameBind1, 32 );
+ Q_strupr(g_nameBind1);
+
+ b2 = g_bindings[i].bind2;
+ if (b2 != -1)
+ {
+ DC->keynumToStringBuf( b2, g_nameBind2, 32 );
+ Q_strupr(g_nameBind2);
+ strcat( g_nameBind1, " or " );
+ strcat( g_nameBind1, g_nameBind2 );
+ }
+ return;
+ }
+ }
+ strcpy(g_nameBind1, "???");
+}
+
+void Item_Slider_Paint(itemDef_t *item) {
+ vec4_t newColor;
+ float x, y, value;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+
+ value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ y = item->window.rect.y;
+ if (item->text) {
+ Item_Text_Paint(item);
+ x = item->textRect.x + item->textRect.w + 8;
+ } else {
+ x = item->window.rect.x;
+ }
+ DC->setColor(newColor);
+ DC->drawHandlePic( x, y, SLIDER_WIDTH, SLIDER_HEIGHT, DC->Assets.sliderBar );
+
+ x = Item_Slider_ThumbPosition(item);
+ DC->drawHandlePic( x - (SLIDER_THUMB_WIDTH / 2), y - 2, SLIDER_THUMB_WIDTH, SLIDER_THUMB_HEIGHT, DC->Assets.sliderThumb );
+
+}
+
+void Item_Bind_Paint(itemDef_t *item) {
+ vec4_t newColor, lowLight;
+ float value;
+ int maxChars = 0;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+ editFieldDef_t *editPtr = (editFieldDef_t*)item->typeData;
+ if (editPtr) {
+ maxChars = editPtr->maxPaintChars;
+ }
+
+ value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0;
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+ if (g_bindItem == item) {
+ lowLight[0] = 0.8f * 1.0f;
+ lowLight[1] = 0.8f * 0.0f;
+ lowLight[2] = 0.8f * 0.0f;
+ lowLight[3] = 0.8f * 1.0f;
+ } else {
+ lowLight[0] = 0.8f * parent->focusColor[0];
+ lowLight[1] = 0.8f * parent->focusColor[1];
+ lowLight[2] = 0.8f * parent->focusColor[2];
+ lowLight[3] = 0.8f * parent->focusColor[3];
+ }
+ /*LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(newColor, &parent->focusColor, sizeof(vec4_t));
+ } else {
+ memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t));
+ }
+
+ if (item->text) {
+ Item_Text_Paint(item);
+ BindingFromName(item->cvar);
+ DC->drawText(item->textRect.x + item->textRect.w + 8, item->textRect.y, item->textscale, newColor, g_nameBind1, 0, maxChars, item->textStyle);
+ } else {
+ DC->drawText(item->textRect.x, item->textRect.y, item->textscale, newColor, (value != 0) ? "FIXME" : "FIXME", 0, maxChars, item->textStyle);
+ }
+}
+
+qboolean Display_KeyBindPending( void ) {
+ return g_waitingForKey;
+}
+
+qboolean Item_Bind_HandleKey(itemDef_t *item, int key, qboolean down) {
+ int id;
+ int i;
+
+ if (Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) && !g_waitingForKey)
+ {
+ if (down && (key == K_MOUSE1 || key == K_ENTER)) {
+ g_waitingForKey = qtrue;
+ g_bindItem = item;
+ }
+ return qtrue;
+ }
+ else
+ {
+ if (!g_waitingForKey || g_bindItem == NULL) {
+ return qtrue;
+ }
+
+ if (key & K_CHAR_FLAG) {
+ return qtrue;
+ }
+
+ switch (key)
+ {
+ case K_ESCAPE:
+ g_waitingForKey = qfalse;
+ return qtrue;
+
+ case K_BACKSPACE:
+ id = BindingIDFromName(item->cvar);
+ if (id != -1) {
+ g_bindings[id].bind1 = -1;
+ g_bindings[id].bind2 = -1;
+ }
+ Controls_SetConfig(qtrue);
+ g_waitingForKey = qfalse;
+ g_bindItem = NULL;
+ return qtrue;
+
+ case '`':
+ return qtrue;
+ }
+ }
+
+ if (key != -1)
+ {
+
+ for (i=0; i < g_bindCount; i++)
+ {
+
+ if (g_bindings[i].bind2 == key) {
+ g_bindings[i].bind2 = -1;
+ }
+
+ if (g_bindings[i].bind1 == key)
+ {
+ g_bindings[i].bind1 = g_bindings[i].bind2;
+ g_bindings[i].bind2 = -1;
+ }
+ }
+ }
+
+
+ id = BindingIDFromName(item->cvar);
+
+ if (id != -1) {
+ if (key == -1) {
+ if( g_bindings[id].bind1 != -1 ) {
+ DC->setBinding( g_bindings[id].bind1, "" );
+ g_bindings[id].bind1 = -1;
+ }
+ if( g_bindings[id].bind2 != -1 ) {
+ DC->setBinding( g_bindings[id].bind2, "" );
+ g_bindings[id].bind2 = -1;
+ }
+ }
+ else if (g_bindings[id].bind1 == -1) {
+ g_bindings[id].bind1 = key;
+ }
+ else if (g_bindings[id].bind1 != key && g_bindings[id].bind2 == -1) {
+ g_bindings[id].bind2 = key;
+ }
+ else {
+ DC->setBinding( g_bindings[id].bind1, "" );
+ DC->setBinding( g_bindings[id].bind2, "" );
+ g_bindings[id].bind1 = key;
+ g_bindings[id].bind2 = -1;
+ }
+ }
+
+ Controls_SetConfig(qtrue);
+ g_waitingForKey = qfalse;
+
+ return qtrue;
+}
+
+
+
+void AdjustFrom640(float *x, float *y, float *w, float *h) {
+ //*x = *x * DC->scale + DC->bias;
+ *x *= DC->xscale;
+ *y *= DC->yscale;
+ *w *= DC->xscale;
+ *h *= DC->yscale;
+}
+
+void Item_Model_Paint(itemDef_t *item) {
+ float x, y, w, h;
+ refdef_t refdef;
+ refEntity_t ent;
+ vec3_t mins, maxs, origin;
+ vec3_t angles;
+ modelDef_t *modelPtr = (modelDef_t*)item->typeData;
+
+ if (modelPtr == NULL) {
+ return;
+ }
+
+ // setup the refdef
+ memset( &refdef, 0, sizeof( refdef ) );
+ refdef.rdflags = RDF_NOWORLDMODEL;
+ AxisClear( refdef.viewaxis );
+ x = item->window.rect.x+1;
+ y = item->window.rect.y+1;
+ w = item->window.rect.w-2;
+ h = item->window.rect.h-2;
+
+ AdjustFrom640( &x, &y, &w, &h );
+
+ refdef.x = x;
+ refdef.y = y;
+ refdef.width = w;
+ refdef.height = h;
+
+ DC->modelBounds( item->asset, mins, maxs );
+
+ origin[2] = -0.5 * ( mins[2] + maxs[2] );
+ origin[1] = 0.5 * ( mins[1] + maxs[1] );
+
+ // calculate distance so the model nearly fills the box
+ if (qtrue) {
+ float len = 0.5 * ( maxs[2] - mins[2] );
+ origin[0] = len / 0.268; // len / tan( fov/2 )
+ //origin[0] = len / tan(w/2);
+ } else {
+ origin[0] = item->textscale;
+ }
+ refdef.fov_x = (modelPtr->fov_x) ? modelPtr->fov_x : w;
+ refdef.fov_y = (modelPtr->fov_y) ? modelPtr->fov_y : h;
+
+ //refdef.fov_x = (int)((float)refdef.width / 640.0f * 90.0f);
+ //xx = refdef.width / tan( refdef.fov_x / 360 * M_PI );
+ //refdef.fov_y = atan2( refdef.height, xx );
+ //refdef.fov_y *= ( 360 / M_PI );
+
+ DC->clearScene();
+
+ refdef.time = DC->realTime;
+
+ // add the model
+
+ memset( &ent, 0, sizeof(ent) );
+
+ //adjust = 5.0 * sin( (float)uis.realtime / 500 );
+ //adjust = 360 % (int)((float)uis.realtime / 1000);
+ //VectorSet( angles, 0, 0, 1 );
+
+ // use item storage to track
+ if (modelPtr->rotationSpeed) {
+ if (DC->realTime > item->window.nextTime) {
+ item->window.nextTime = DC->realTime + modelPtr->rotationSpeed;
+ modelPtr->angle = (int)(modelPtr->angle + 1) % 360;
+ }
+ }
+ VectorSet( angles, 0, modelPtr->angle, 0 );
+ AnglesToAxis( angles, ent.axis );
+
+ ent.hModel = item->asset;
+ VectorCopy( origin, ent.origin );
+ VectorCopy( origin, ent.lightingOrigin );
+ ent.renderfx = RF_LIGHTING_ORIGIN | RF_NOSHADOW;
+ VectorCopy( ent.origin, ent.oldorigin );
+
+ DC->addRefEntityToScene( &ent );
+ DC->renderScene( &refdef );
+
+}
+
+
+void Item_Image_Paint(itemDef_t *item) {
+ if (item == NULL) {
+ return;
+ }
+ DC->drawHandlePic(item->window.rect.x+1, item->window.rect.y+1, item->window.rect.w-2, item->window.rect.h-2, item->asset);
+}
+
+void Item_ListBox_Paint(itemDef_t *item) {
+ float x, y, size, thumb;
+ int i, count;
+ qhandle_t image;
+ qhandle_t optionalImage;
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+
+ // the listbox is horizontal or vertical and has a fixed size scroll bar going either direction
+ // elements are enumerated from the DC and either text or image handles are acquired from the DC as well
+ // textscale is used to size the text, textalignx and textaligny are used to size image elements
+ // there is no clipping available so only the last completely visible item is painted
+ count = DC->feederCount(item->special);
+ // default is vertical if horizontal flag is not here
+ if (item->window.flags & WINDOW_HORIZONTAL) {
+ // draw scrollbar in bottom of the window
+ // bar
+ x = item->window.rect.x + 1;
+ y = item->window.rect.y + item->window.rect.h - SCROLLBAR_SIZE - 1;
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowLeft);
+ x += SCROLLBAR_SIZE - 1;
+ size = item->window.rect.w - (SCROLLBAR_SIZE * 2);
+ DC->drawHandlePic(x, y, size+1, SCROLLBAR_SIZE, DC->Assets.scrollBar);
+ x += size - 1;
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowRight);
+ // thumb
+ thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item);
+ if (thumb > x - SCROLLBAR_SIZE - 1) {
+ thumb = x - SCROLLBAR_SIZE - 1;
+ }
+ DC->drawHandlePic(thumb, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb);
+ //
+ listPtr->endPos = listPtr->startPos;
+ size = item->window.rect.w - 2;
+ // items
+ // size contains max available space
+ if (listPtr->elementStyle == LISTBOX_IMAGE) {
+ // fit = 0;
+ x = item->window.rect.x + 1;
+ y = item->window.rect.y + 1;
+ for (i = listPtr->startPos; i < count; i++) {
+ // always draw at least one
+ // which may overdraw the box if it is too small for the element
+ image = DC->feederItemImage(item->special, i);
+ if (image) {
+ DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image);
+ }
+
+ if (i == item->cursorPos) {
+ DC->drawRect(x, y, listPtr->elementWidth-1, listPtr->elementHeight-1, item->window.borderSize, item->window.borderColor);
+ }
+
+ listPtr->endPos++;
+ size -= listPtr->elementWidth;
+ if (size < listPtr->elementWidth) {
+ listPtr->drawPadding = size; //listPtr->elementWidth - size;
+ break;
+ }
+ x += listPtr->elementWidth;
+ // fit++;
+ }
+ } else {
+ //
+ }
+ } else {
+ // draw scrollbar to right side of the window
+ x = item->window.rect.x + item->window.rect.w - SCROLLBAR_SIZE - 1;
+ y = item->window.rect.y + 1;
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowUp);
+ y += SCROLLBAR_SIZE - 1;
+
+ listPtr->endPos = listPtr->startPos;
+ size = item->window.rect.h - (SCROLLBAR_SIZE * 2);
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, size+1, DC->Assets.scrollBar);
+ y += size - 1;
+ DC->drawHandlePic(x, y, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarArrowDown);
+ // thumb
+ thumb = Item_ListBox_ThumbDrawPosition(item);//Item_ListBox_ThumbPosition(item);
+ if (thumb > y - SCROLLBAR_SIZE - 1) {
+ thumb = y - SCROLLBAR_SIZE - 1;
+ }
+ DC->drawHandlePic(x, thumb, SCROLLBAR_SIZE, SCROLLBAR_SIZE, DC->Assets.scrollBarThumb);
+
+ // adjust size for item painting
+ size = item->window.rect.h - 2;
+ if (listPtr->elementStyle == LISTBOX_IMAGE) {
+ // fit = 0;
+ x = item->window.rect.x + 1;
+ y = item->window.rect.y + 1;
+ for (i = listPtr->startPos; i < count; i++) {
+ // always draw at least one
+ // which may overdraw the box if it is too small for the element
+ image = DC->feederItemImage(item->special, i);
+ if (image) {
+ DC->drawHandlePic(x+1, y+1, listPtr->elementWidth - 2, listPtr->elementHeight - 2, image);
+ }
+
+ if (i == item->cursorPos) {
+ DC->drawRect(x, y, listPtr->elementWidth - 1, listPtr->elementHeight - 1, item->window.borderSize, item->window.borderColor);
+ }
+
+ listPtr->endPos++;
+ size -= listPtr->elementWidth;
+ if (size < listPtr->elementHeight) {
+ listPtr->drawPadding = listPtr->elementHeight - size;
+ break;
+ }
+ y += listPtr->elementHeight;
+ // fit++;
+ }
+ } else {
+ x = item->window.rect.x + 1;
+ y = item->window.rect.y + 1;
+ for (i = listPtr->startPos; i < count; i++) {
+ const char *text;
+ // always draw at least one
+ // which may overdraw the box if it is too small for the element
+
+ if (listPtr->numColumns > 0) {
+ int j;
+ for (j = 0; j < listPtr->numColumns; j++) {
+ text = DC->feederItemText(item->special, i, j, &optionalImage);
+ if (optionalImage >= 0) {
+ DC->drawHandlePic(x + 4 + listPtr->columnInfo[j].pos, y - 1 + listPtr->elementHeight / 2, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage);
+ } else if (text) {
+ //TA:
+ int alignOffset = 0.0f, tw;
+
+ tw = DC->textWidth( text, item->textscale, 0 );
+
+ switch( listPtr->columnInfo[ j ].align )
+ {
+ case ITEM_ALIGN_LEFT:
+ alignOffset = 0.0f;
+ break;
+
+ case ITEM_ALIGN_RIGHT:
+ alignOffset = listPtr->columnInfo[ j ].width - tw;
+ break;
+
+ case ITEM_ALIGN_CENTER:
+ alignOffset = ( listPtr->columnInfo[ j ].width / 2.0f ) - ( tw / 2.0f );
+ break;
+
+ default:
+ alignOffset = 0.0f;
+ }
+
+ DC->drawText( x + 4 + listPtr->columnInfo[j].pos + alignOffset, y + listPtr->elementHeight,
+ item->textscale, item->window.foreColor, text, 0,
+ listPtr->columnInfo[j].maxChars, item->textStyle );
+ }
+ }
+ } else {
+ text = DC->feederItemText(item->special, i, 0, &optionalImage);
+ if (optionalImage >= 0) {
+ //DC->drawHandlePic(x + 4 + listPtr->elementHeight, y, listPtr->columnInfo[j].width, listPtr->columnInfo[j].width, optionalImage);
+ } else if (text) {
+ DC->drawText(x + 4, y + listPtr->elementHeight, item->textscale, item->window.foreColor, text, 0, 0, item->textStyle);
+ }
+ }
+
+ if (i == item->cursorPos) {
+ DC->fillRect(x + 2, y + 2, item->window.rect.w - SCROLLBAR_SIZE - 4, listPtr->elementHeight, item->window.outlineColor);
+ }
+
+ listPtr->endPos++;
+ size -= listPtr->elementHeight;
+ if (size < listPtr->elementHeight) {
+ listPtr->drawPadding = listPtr->elementHeight - size;
+ break;
+ }
+ y += listPtr->elementHeight;
+ // fit++;
+ }
+ }
+ }
+
+ //TA: FIXME: hacky fix to off-by-one bug
+ listPtr->endPos--;
+}
+
+
+void Item_OwnerDraw_Paint(itemDef_t *item) {
+ menuDef_t *parent;
+
+ if (item == NULL) {
+ return;
+ }
+ parent = (menuDef_t*)item->parent;
+
+ if (DC->ownerDrawItem) {
+ vec4_t color, lowLight;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+ Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount);
+ memcpy(&color, &item->window.foreColor, sizeof(color));
+ if (item->numColors > 0 && DC->getValue) {
+ // if the value is within one of the ranges then set color to that, otherwise leave at default
+ int i;
+ float f = DC->getValue(item->window.ownerDraw);
+ for (i = 0; i < item->numColors; i++) {
+ if (f >= item->colorRanges[i].low && f <= item->colorRanges[i].high) {
+ memcpy(&color, &item->colorRanges[i].color, sizeof(color));
+ break;
+ }
+ }
+ }
+
+ if (item->window.flags & WINDOW_HASFOCUS) {
+/* lowLight[0] = 0.8 * parent->focusColor[0];
+ lowLight[1] = 0.8 * parent->focusColor[1];
+ lowLight[2] = 0.8 * parent->focusColor[2];
+ lowLight[3] = 0.8 * parent->focusColor[3];
+ LerpColor(parent->focusColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));*/
+ //TA:
+ memcpy(color, &parent->focusColor, sizeof(vec4_t));
+ } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) {
+ lowLight[0] = 0.8 * item->window.foreColor[0];
+ lowLight[1] = 0.8 * item->window.foreColor[1];
+ lowLight[2] = 0.8 * item->window.foreColor[2];
+ lowLight[3] = 0.8 * item->window.foreColor[3];
+ LerpColor(item->window.foreColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR));
+ }
+
+ if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) {
+ memcpy(color, parent->disableColor, sizeof(vec4_t)); // bk001207 - FIXME: Com_Memcpy
+ }
+
+ if (item->text) {
+ Item_Text_Paint(item);
+ if (item->text[0]) {
+ // +8 is an offset kludge to properly align owner draw items that have text combined with them
+ DC->ownerDrawItem(item->textRect.x + item->textRect.w + 8, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle );
+ } else {
+ DC->ownerDrawItem(item->textRect.x + item->textRect.w, item->window.rect.y, item->window.rect.w, item->window.rect.h, 0, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle );
+ }
+ } else {
+ DC->ownerDrawItem(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, item->textalignx, item->textaligny, item->window.ownerDraw, item->window.ownerDrawFlags, item->alignment, item->special, item->textscale, color, item->window.background, item->textStyle );
+ }
+ }
+}
+
+
+void Item_Paint(itemDef_t *item) {
+ vec4_t red;
+ menuDef_t *parent = (menuDef_t*)item->parent;
+ red[0] = red[3] = 1;
+ red[1] = red[2] = 0;
+
+ if (item == NULL) {
+ return;
+ }
+
+ if (item->window.flags & WINDOW_ORBITING) {
+ if (DC->realTime > item->window.nextTime) {
+ float rx, ry, a, c, s, w, h;
+
+ item->window.nextTime = DC->realTime + item->window.offsetTime;
+ // translate
+ w = item->window.rectClient.w / 2;
+ h = item->window.rectClient.h / 2;
+ rx = item->window.rectClient.x + w - item->window.rectEffects.x;
+ ry = item->window.rectClient.y + h - item->window.rectEffects.y;
+ a = 3 * M_PI / 180;
+ c = cos(a);
+ s = sin(a);
+ item->window.rectClient.x = (rx * c - ry * s) + item->window.rectEffects.x - w;
+ item->window.rectClient.y = (rx * s + ry * c) + item->window.rectEffects.y - h;
+ Item_UpdatePosition(item);
+
+ }
+ }
+
+
+ if (item->window.flags & WINDOW_INTRANSITION) {
+ if (DC->realTime > item->window.nextTime) {
+ int done = 0;
+ item->window.nextTime = DC->realTime + item->window.offsetTime;
+ // transition the x,y
+ if (item->window.rectClient.x == item->window.rectEffects.x) {
+ done++;
+ } else {
+ if (item->window.rectClient.x < item->window.rectEffects.x) {
+ item->window.rectClient.x += item->window.rectEffects2.x;
+ if (item->window.rectClient.x > item->window.rectEffects.x) {
+ item->window.rectClient.x = item->window.rectEffects.x;
+ done++;
+ }
+ } else {
+ item->window.rectClient.x -= item->window.rectEffects2.x;
+ if (item->window.rectClient.x < item->window.rectEffects.x) {
+ item->window.rectClient.x = item->window.rectEffects.x;
+ done++;
+ }
+ }
+ }
+ if (item->window.rectClient.y == item->window.rectEffects.y) {
+ done++;
+ } else {
+ if (item->window.rectClient.y < item->window.rectEffects.y) {
+ item->window.rectClient.y += item->window.rectEffects2.y;
+ if (item->window.rectClient.y > item->window.rectEffects.y) {
+ item->window.rectClient.y = item->window.rectEffects.y;
+ done++;
+ }
+ } else {
+ item->window.rectClient.y -= item->window.rectEffects2.y;
+ if (item->window.rectClient.y < item->window.rectEffects.y) {
+ item->window.rectClient.y = item->window.rectEffects.y;
+ done++;
+ }
+ }
+ }
+ if (item->window.rectClient.w == item->window.rectEffects.w) {
+ done++;
+ } else {
+ if (item->window.rectClient.w < item->window.rectEffects.w) {
+ item->window.rectClient.w += item->window.rectEffects2.w;
+ if (item->window.rectClient.w > item->window.rectEffects.w) {
+ item->window.rectClient.w = item->window.rectEffects.w;
+ done++;
+ }
+ } else {
+ item->window.rectClient.w -= item->window.rectEffects2.w;
+ if (item->window.rectClient.w < item->window.rectEffects.w) {
+ item->window.rectClient.w = item->window.rectEffects.w;
+ done++;
+ }
+ }
+ }
+ if (item->window.rectClient.h == item->window.rectEffects.h) {
+ done++;
+ } else {
+ if (item->window.rectClient.h < item->window.rectEffects.h) {
+ item->window.rectClient.h += item->window.rectEffects2.h;
+ if (item->window.rectClient.h > item->window.rectEffects.h) {
+ item->window.rectClient.h = item->window.rectEffects.h;
+ done++;
+ }
+ } else {
+ item->window.rectClient.h -= item->window.rectEffects2.h;
+ if (item->window.rectClient.h < item->window.rectEffects.h) {
+ item->window.rectClient.h = item->window.rectEffects.h;
+ done++;
+ }
+ }
+ }
+
+ Item_UpdatePosition(item);
+
+ if (done == 4) {
+ item->window.flags &= ~WINDOW_INTRANSITION;
+ }
+
+ }
+ }
+
+ if (item->window.ownerDrawFlags && DC->ownerDrawVisible) {
+ if (!DC->ownerDrawVisible(item->window.ownerDrawFlags)) {
+ item->window.flags &= ~WINDOW_VISIBLE;
+ } else {
+ item->window.flags |= WINDOW_VISIBLE;
+ }
+ }
+
+ if (item->cvarFlags & (CVAR_SHOW | CVAR_HIDE)) {
+ if (!Item_EnableShowViaCvar(item, CVAR_SHOW)) {
+ return;
+ }
+ }
+
+ if (item->window.flags & WINDOW_TIMEDVISIBLE) {
+
+ }
+
+ if (!(item->window.flags & WINDOW_VISIBLE)) {
+ return;
+ }
+
+ // paint the rect first..
+ Window_Paint(&item->window, parent->fadeAmount , parent->fadeClamp, parent->fadeCycle);
+
+ if (debugMode) {
+ vec4_t color;
+ rectDef_t *r = Item_CorrectedTextRect(item);
+ color[1] = color[3] = 1;
+ color[0] = color[2] = 0;
+ DC->drawRect(r->x, r->y, r->w, r->h, 1, color);
+ }
+
+ //DC->drawRect(item->window.rect.x, item->window.rect.y, item->window.rect.w, item->window.rect.h, 1, red);
+
+ switch (item->type) {
+ case ITEM_TYPE_OWNERDRAW:
+ Item_OwnerDraw_Paint(item);
+ break;
+ case ITEM_TYPE_TEXT:
+ case ITEM_TYPE_BUTTON:
+ Item_Text_Paint(item);
+ break;
+ case ITEM_TYPE_RADIOBUTTON:
+ break;
+ case ITEM_TYPE_CHECKBOX:
+ break;
+ case ITEM_TYPE_EDITFIELD:
+ case ITEM_TYPE_NUMERICFIELD:
+ Item_TextField_Paint(item);
+ break;
+ case ITEM_TYPE_COMBO:
+ break;
+ case ITEM_TYPE_LISTBOX:
+ Item_ListBox_Paint(item);
+ break;
+ //case ITEM_TYPE_IMAGE:
+ // Item_Image_Paint(item);
+ // break;
+ case ITEM_TYPE_MODEL:
+ Item_Model_Paint(item);
+ break;
+ case ITEM_TYPE_YESNO:
+ Item_YesNo_Paint(item);
+ break;
+ case ITEM_TYPE_MULTI:
+ Item_Multi_Paint(item);
+ break;
+ case ITEM_TYPE_BIND:
+ Item_Bind_Paint(item);
+ break;
+ case ITEM_TYPE_SLIDER:
+ Item_Slider_Paint(item);
+ break;
+ default:
+ break;
+ }
+
+}
+
+void Menu_Init(menuDef_t *menu) {
+ memset(menu, 0, sizeof(menuDef_t));
+ menu->cursorItem = -1;
+ menu->fadeAmount = DC->Assets.fadeAmount;
+ menu->fadeClamp = DC->Assets.fadeClamp;
+ menu->fadeCycle = DC->Assets.fadeCycle;
+ Window_Init(&menu->window);
+}
+
+itemDef_t *Menu_GetFocusedItem(menuDef_t *menu) {
+ int i;
+ if (menu) {
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->window.flags & WINDOW_HASFOCUS) {
+ return menu->items[i];
+ }
+ }
+ }
+ return NULL;
+}
+
+menuDef_t *Menu_GetFocused( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ if (Menus[i].window.flags & WINDOW_HASFOCUS && Menus[i].window.flags & WINDOW_VISIBLE) {
+ return &Menus[i];
+ }
+ }
+ return NULL;
+}
+
+void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down) {
+ if (menu) {
+ int i;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->special == feeder) {
+ Item_ListBox_HandleKey(menu->items[i], (down) ? K_DOWNARROW : K_UPARROW, qtrue, qtrue);
+ return;
+ }
+ }
+ }
+}
+
+
+
+void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name) {
+ if (menu == NULL) {
+ if (name == NULL) {
+ menu = Menu_GetFocused();
+ } else {
+ menu = Menus_FindByName(name);
+ }
+ }
+
+ if (menu) {
+ int i;
+ for (i = 0; i < menu->itemCount; i++) {
+ if (menu->items[i]->special == feeder) {
+ if (index == 0) {
+ listBoxDef_t *listPtr = (listBoxDef_t*)menu->items[i]->typeData;
+ listPtr->cursorPos = 0;
+ listPtr->startPos = 0;
+ }
+ menu->items[i]->cursorPos = index;
+ DC->feederSelection(menu->items[i]->special, menu->items[i]->cursorPos);
+ return;
+ }
+ }
+ }
+}
+
+qboolean Menus_AnyFullScreenVisible( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ if (Menus[i].window.flags & WINDOW_VISIBLE && Menus[i].fullScreen) {
+ return qtrue;
+ }
+ }
+ return qfalse;
+}
+
+menuDef_t *Menus_ActivateByName(const char *p) {
+ int i, j;
+ menuDef_t *m = NULL;
+ menuDef_t *focus = Menu_GetFocused();
+
+ for (i = 0; i < menuCount; i++) {
+ if (Q_stricmp(Menus[i].window.name, p) == 0) {
+ m = &Menus[i];
+ Menus_Activate(m);
+ Menu_HandleMouseMove( m, DC->cursorx, DC->cursory ); //TA: force the item under the cursor to focus
+
+ for( j = 0; j < m->itemCount; j++ ) //TA: reset selection in listboxes when opened
+ {
+ if( m->items[ j ]->type == ITEM_TYPE_LISTBOX )
+ {
+ listBoxDef_t *listPtr = (listBoxDef_t*)m->items[ j ]->typeData;
+ m->items[ j ]->cursorPos = 0;
+ listPtr->startPos = 0;
+ DC->feederSelection( m->items[ j ]->special, 0 );
+ }
+ }
+
+ if (openMenuCount < MAX_OPEN_MENUS && focus != NULL) {
+ menuStack[openMenuCount++] = focus;
+ }
+ } else {
+ Menus[i].window.flags &= ~WINDOW_HASFOCUS;
+ }
+ }
+ Display_CloseCinematics();
+ return m;
+}
+
+
+void Item_Init(itemDef_t *item) {
+ memset(item, 0, sizeof(itemDef_t));
+ item->textscale = 0.55f;
+ Window_Init(&item->window);
+}
+
+void Menu_HandleMouseMove(menuDef_t *menu, float x, float y) {
+ int i, pass;
+ qboolean focusSet = qfalse;
+
+ itemDef_t *overItem;
+ if (menu == NULL) {
+ return;
+ }
+
+ if (!(menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) {
+ return;
+ }
+
+ if (itemCapture) {
+ //Item_MouseMove(itemCapture, x, y);
+ return;
+ }
+
+ if (g_waitingForKey || g_editingField) {
+ return;
+ }
+
+ // FIXME: this is the whole issue of focus vs. mouse over..
+ // need a better overall solution as i don't like going through everything twice
+ for (pass = 0; pass < 2; pass++) {
+ for (i = 0; i < menu->itemCount; i++) {
+ // turn off focus each item
+ // menu->items[i].window.flags &= ~WINDOW_HASFOCUS;
+
+ if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) {
+ continue;
+ }
+
+ // items can be enabled and disabled based on cvars
+ if (menu->items[i]->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_ENABLE)) {
+ continue;
+ }
+
+ if (menu->items[i]->cvarFlags & (CVAR_SHOW | CVAR_HIDE) && !Item_EnableShowViaCvar(menu->items[i], CVAR_SHOW)) {
+ continue;
+ }
+
+
+
+ if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) {
+ if (pass == 1) {
+ overItem = menu->items[i];
+ if (overItem->type == ITEM_TYPE_TEXT && overItem->text) {
+ if (!Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y)) {
+ continue;
+ }
+ }
+ // if we are over an item
+ if (IsVisible(overItem->window.flags)) {
+ // different one
+ Item_MouseEnter(overItem, x, y);
+ // Item_SetMouseOver(overItem, qtrue);
+
+ // if item is not a decoration see if it can take focus
+ if (!focusSet) {
+ focusSet = Item_SetFocus(overItem, x, y);
+ }
+ }
+ }
+ } else if (menu->items[i]->window.flags & WINDOW_MOUSEOVER) {
+ Item_MouseLeave(menu->items[i]);
+ Item_SetMouseOver(menu->items[i], qfalse);
+ }
+ }
+ }
+
+}
+
+void Menu_Paint(menuDef_t *menu, qboolean forcePaint) {
+ int i;
+
+ if (menu == NULL) {
+ return;
+ }
+
+ if (!(menu->window.flags & WINDOW_VISIBLE) && !forcePaint) {
+ return;
+ }
+
+ if (menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible(menu->window.ownerDrawFlags)) {
+ return;
+ }
+
+ if (forcePaint) {
+ menu->window.flags |= WINDOW_FORCED;
+ }
+
+ // draw the background if necessary
+ if (menu->fullScreen) {
+ // implies a background shader
+ // FIXME: make sure we have a default shader if fullscreen is set with no background
+ DC->drawHandlePic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, menu->window.background );
+ } else if (menu->window.background) {
+ // this allows a background shader without being full screen
+ //UI_DrawHandlePic(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, menu->backgroundShader);
+ }
+
+ // paint the background and or border
+ Window_Paint(&menu->window, menu->fadeAmount, menu->fadeClamp, menu->fadeCycle );
+
+ for (i = 0; i < menu->itemCount; i++) {
+ Item_Paint(menu->items[i]);
+ }
+
+ if (debugMode) {
+ vec4_t color;
+ color[0] = color[2] = color[3] = 1;
+ color[1] = 0;
+ DC->drawRect(menu->window.rect.x, menu->window.rect.y, menu->window.rect.w, menu->window.rect.h, 1, color);
+ }
+}
+
+/*
+===============
+Item_ValidateTypeData
+===============
+*/
+void Item_ValidateTypeData(itemDef_t *item) {
+ if (item->typeData) {
+ return;
+ }
+
+ if (item->type == ITEM_TYPE_LISTBOX) {
+ item->typeData = UI_Alloc(sizeof(listBoxDef_t));
+ memset(item->typeData, 0, sizeof(listBoxDef_t));
+ } else if (item->type == ITEM_TYPE_EDITFIELD || item->type == ITEM_TYPE_NUMERICFIELD || item->type == ITEM_TYPE_YESNO || item->type == ITEM_TYPE_BIND || item->type == ITEM_TYPE_SLIDER || item->type == ITEM_TYPE_TEXT) {
+ item->typeData = UI_Alloc(sizeof(editFieldDef_t));
+ memset(item->typeData, 0, sizeof(editFieldDef_t));
+ if (item->type == ITEM_TYPE_EDITFIELD) {
+ if (!((editFieldDef_t *) item->typeData)->maxPaintChars) {
+ ((editFieldDef_t *) item->typeData)->maxPaintChars = MAX_EDITFIELD;
+ }
+ }
+ } else if (item->type == ITEM_TYPE_MULTI) {
+ item->typeData = UI_Alloc(sizeof(multiDef_t));
+ } else if (item->type == ITEM_TYPE_MODEL) {
+ item->typeData = UI_Alloc(sizeof(modelDef_t));
+ }
+}
+
+/*
+===============
+Keyword Hash
+===============
+*/
+
+#define KEYWORDHASH_SIZE 512
+
+typedef struct keywordHash_s
+{
+ char *keyword;
+ qboolean (*func)(itemDef_t *item, int handle);
+ struct keywordHash_s *next;
+} keywordHash_t;
+
+int KeywordHash_Key(char *keyword) {
+ int register hash, i;
+
+ hash = 0;
+ for (i = 0; keyword[i] != '\0'; i++) {
+ if (keyword[i] >= 'A' && keyword[i] <= 'Z')
+ hash += (keyword[i] + ('a' - 'A')) * (119 + i);
+ else
+ hash += keyword[i] * (119 + i);
+ }
+ hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (KEYWORDHASH_SIZE-1);
+ return hash;
+}
+
+void KeywordHash_Add(keywordHash_t *table[], keywordHash_t *key) {
+ int hash;
+
+ hash = KeywordHash_Key(key->keyword);
+/*
+ if (table[hash]) {
+ int collision = qtrue;
+ }
+*/
+ key->next = table[hash];
+ table[hash] = key;
+}
+
+keywordHash_t *KeywordHash_Find(keywordHash_t *table[], char *keyword)
+{
+ keywordHash_t *key;
+ int hash;
+
+ hash = KeywordHash_Key(keyword);
+ for (key = table[hash]; key; key = key->next) {
+ if (!Q_stricmp(key->keyword, keyword))
+ return key;
+ }
+ return NULL;
+}
+
+/*
+===============
+Item Keyword Parse functions
+===============
+*/
+
+// name <string>
+qboolean ItemParse_name( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->window.name)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// name <string>
+qboolean ItemParse_focusSound( itemDef_t *item, int handle ) {
+ const char *temp;
+ if (!PC_String_Parse(handle, &temp)) {
+ return qfalse;
+ }
+ item->focusSound = DC->registerSound(temp, qfalse);
+ return qtrue;
+}
+
+
+// text <string>
+qboolean ItemParse_text( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->text)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// group <string>
+qboolean ItemParse_group( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->window.group)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// asset_model <string>
+qboolean ItemParse_asset_model( itemDef_t *item, int handle ) {
+ const char *temp;
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_String_Parse(handle, &temp)) {
+ return qfalse;
+ }
+ item->asset = DC->registerModel(temp);
+ modelPtr->angle = rand() % 360;
+ return qtrue;
+}
+
+// asset_shader <string>
+qboolean ItemParse_asset_shader( itemDef_t *item, int handle ) {
+ const char *temp;
+
+ if (!PC_String_Parse(handle, &temp)) {
+ return qfalse;
+ }
+ item->asset = DC->registerShaderNoMip(temp);
+ return qtrue;
+}
+
+// model_origin <number> <number> <number>
+qboolean ItemParse_model_origin( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (PC_Float_Parse(handle, &modelPtr->origin[0])) {
+ if (PC_Float_Parse(handle, &modelPtr->origin[1])) {
+ if (PC_Float_Parse(handle, &modelPtr->origin[2])) {
+ return qtrue;
+ }
+ }
+ }
+ return qfalse;
+}
+
+// model_fovx <number>
+qboolean ItemParse_model_fovx( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_Float_Parse(handle, &modelPtr->fov_x)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// model_fovy <number>
+qboolean ItemParse_model_fovy( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_Float_Parse(handle, &modelPtr->fov_y)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// model_rotation <integer>
+qboolean ItemParse_model_rotation( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_Int_Parse(handle, &modelPtr->rotationSpeed)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// model_angle <integer>
+qboolean ItemParse_model_angle( itemDef_t *item, int handle ) {
+ modelDef_t *modelPtr;
+ Item_ValidateTypeData(item);
+ modelPtr = (modelDef_t*)item->typeData;
+
+ if (!PC_Int_Parse(handle, &modelPtr->angle)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// rect <rectangle>
+qboolean ItemParse_rect( itemDef_t *item, int handle ) {
+ if (!PC_Rect_Parse(handle, &item->window.rectClient)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// style <integer>
+qboolean ItemParse_style( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->window.style)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// decoration
+qboolean ItemParse_decoration( itemDef_t *item, int handle ) {
+ item->window.flags |= WINDOW_DECORATION;
+ return qtrue;
+}
+
+// notselectable
+qboolean ItemParse_notselectable( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+ Item_ValidateTypeData(item);
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (item->type == ITEM_TYPE_LISTBOX && listPtr) {
+ listPtr->notselectable = qtrue;
+ }
+ return qtrue;
+}
+
+// manually wrapped
+qboolean ItemParse_wrapped( itemDef_t *item, int handle ) {
+ item->window.flags |= WINDOW_WRAPPED;
+ return qtrue;
+}
+
+// auto wrapped
+qboolean ItemParse_autowrapped( itemDef_t *item, int handle ) {
+ item->window.flags |= WINDOW_AUTOWRAPPED;
+ return qtrue;
+}
+
+
+// horizontalscroll
+qboolean ItemParse_horizontalscroll( itemDef_t *item, int handle ) {
+ item->window.flags |= WINDOW_HORIZONTAL;
+ return qtrue;
+}
+
+// type <integer>
+qboolean ItemParse_type( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->type)) {
+ return qfalse;
+ }
+ Item_ValidateTypeData(item);
+ return qtrue;
+}
+
+// elementwidth, used for listbox image elements
+// uses textalignx for storage
+qboolean ItemParse_elementwidth( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (!PC_Float_Parse(handle, &listPtr->elementWidth)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// elementheight, used for listbox image elements
+// uses textaligny for storage
+qboolean ItemParse_elementheight( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (!PC_Float_Parse(handle, &listPtr->elementHeight)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// feeder <float>
+qboolean ItemParse_feeder( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->special)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// elementtype, used to specify what type of elements a listbox contains
+// uses textstyle for storage
+qboolean ItemParse_elementtype( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (!PC_Int_Parse(handle, &listPtr->elementStyle)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+// columns sets a number of columns and an x pos and width per..
+qboolean ItemParse_columns( itemDef_t *item, int handle ) {
+ int num, i;
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ listPtr = (listBoxDef_t*)item->typeData;
+ if (PC_Int_Parse(handle, &num)) {
+ if (num > MAX_LB_COLUMNS) {
+ num = MAX_LB_COLUMNS;
+ }
+ listPtr->numColumns = num;
+ for (i = 0; i < num; i++) {
+ int pos, width, maxChars, align;
+
+ if( PC_Int_Parse( handle, &pos ) &&
+ PC_Int_Parse( handle, &width ) &&
+ PC_Int_Parse( handle, &maxChars ) &&
+ PC_Int_Parse( handle, &align ) )
+ {
+ listPtr->columnInfo[i].pos = pos;
+ listPtr->columnInfo[i].width = width;
+ listPtr->columnInfo[i].maxChars = maxChars;
+ listPtr->columnInfo[i].align = align;
+ } else {
+ return qfalse;
+ }
+ }
+ } else {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_border( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->window.border)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_bordersize( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->window.borderSize)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_visible( itemDef_t *item, int handle ) {
+ int i;
+
+ if (!PC_Int_Parse(handle, &i)) {
+ return qfalse;
+ }
+ if (i) {
+ item->window.flags |= WINDOW_VISIBLE;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_ownerdraw( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->window.ownerDraw)) {
+ return qfalse;
+ }
+ item->type = ITEM_TYPE_OWNERDRAW;
+ return qtrue;
+}
+
+qboolean ItemParse_align( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->alignment)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textalign( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->textalignment)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textalignx( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->textalignx)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textaligny( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->textaligny)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textscale( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->textscale)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_textstyle( itemDef_t *item, int handle ) {
+ if (!PC_Int_Parse(handle, &item->textStyle)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_backcolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ item->window.backColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_forecolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ item->window.foreColor[i] = f;
+ item->window.flags |= WINDOW_FORECOLORSET;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_bordercolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ item->window.borderColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_outlinecolor( itemDef_t *item, int handle ) {
+ if (!PC_Color_Parse(handle, &item->window.outlineColor)){
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_background( itemDef_t *item, int handle ) {
+ const char *temp;
+
+ if (!PC_String_Parse(handle, &temp)) {
+ return qfalse;
+ }
+ item->window.background = DC->registerShaderNoMip(temp);
+ return qtrue;
+}
+
+qboolean ItemParse_cinematic( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->window.cinematicName)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_doubleClick( itemDef_t *item, int handle ) {
+ listBoxDef_t *listPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData) {
+ return qfalse;
+ }
+
+ listPtr = (listBoxDef_t*)item->typeData;
+
+ if (!PC_Script_Parse(handle, &listPtr->doubleClick)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_onFocus( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->onFocus)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_leaveFocus( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->leaveFocus)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_mouseEnter( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->mouseEnter)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_mouseExit( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->mouseExit)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_mouseEnterText( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->mouseEnterText)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_mouseExitText( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->mouseExitText)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_action( itemDef_t *item, int handle ) {
+ if (!PC_Script_Parse(handle, &item->action)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_special( itemDef_t *item, int handle ) {
+ if (!PC_Float_Parse(handle, &item->special)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_cvarTest( itemDef_t *item, int handle ) {
+ if (!PC_String_Parse(handle, &item->cvarTest)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_cvar( itemDef_t *item, int handle ) {
+ editFieldDef_t *editPtr;
+
+ Item_ValidateTypeData(item);
+ if (!PC_String_Parse(handle, &item->cvar)) {
+ return qfalse;
+ }
+ if (item->typeData) {
+ editPtr = (editFieldDef_t*)item->typeData;
+ editPtr->minVal = -1;
+ editPtr->maxVal = -1;
+ editPtr->defVal = -1;
+ }
+ return qtrue;
+}
+
+qboolean ItemParse_maxChars( itemDef_t *item, int handle ) {
+ editFieldDef_t *editPtr;
+ int maxChars;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+
+ if (!PC_Int_Parse(handle, &maxChars)) {
+ return qfalse;
+ }
+ editPtr = (editFieldDef_t*)item->typeData;
+ editPtr->maxChars = maxChars;
+ return qtrue;
+}
+
+qboolean ItemParse_maxPaintChars( itemDef_t *item, int handle ) {
+ editFieldDef_t *editPtr;
+ int maxChars;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+
+ if (!PC_Int_Parse(handle, &maxChars)) {
+ return qfalse;
+ }
+ editPtr = (editFieldDef_t*)item->typeData;
+ editPtr->maxPaintChars = maxChars;
+ return qtrue;
+}
+
+
+
+qboolean ItemParse_cvarFloat( itemDef_t *item, int handle ) {
+ editFieldDef_t *editPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ editPtr = (editFieldDef_t*)item->typeData;
+ if (PC_String_Parse(handle, &item->cvar) &&
+ PC_Float_Parse(handle, &editPtr->defVal) &&
+ PC_Float_Parse(handle, &editPtr->minVal) &&
+ PC_Float_Parse(handle, &editPtr->maxVal)) {
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_cvarStrList( itemDef_t *item, int handle ) {
+ pc_token_t token;
+ multiDef_t *multiPtr;
+ int pass;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ multiPtr = (multiDef_t*)item->typeData;
+ multiPtr->count = 0;
+ multiPtr->strDef = qtrue;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (*token.string != '{') {
+ return qfalse;
+ }
+
+ pass = 0;
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token)) {
+ PC_SourceError(handle, "end of file inside menu item\n");
+ return qfalse;
+ }
+
+ if (*token.string == '}') {
+ return qtrue;
+ }
+
+ if (*token.string == ',' || *token.string == ';') {
+ continue;
+ }
+
+ if (pass == 0) {
+ multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string);
+ pass = 1;
+ } else {
+ multiPtr->cvarStr[multiPtr->count] = String_Alloc(token.string);
+ pass = 0;
+ multiPtr->count++;
+ if (multiPtr->count >= MAX_MULTI_CVARS) {
+ return qfalse;
+ }
+ }
+
+ }
+ return qfalse; // bk001205 - LCC missing return value
+}
+
+qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle ) {
+ pc_token_t token;
+ multiDef_t *multiPtr;
+
+ Item_ValidateTypeData(item);
+ if (!item->typeData)
+ return qfalse;
+ multiPtr = (multiDef_t*)item->typeData;
+ multiPtr->count = 0;
+ multiPtr->strDef = qfalse;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (*token.string != '{') {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token)) {
+ PC_SourceError(handle, "end of file inside menu item\n");
+ return qfalse;
+ }
+
+ if (*token.string == '}') {
+ return qtrue;
+ }
+
+ if (*token.string == ',' || *token.string == ';') {
+ continue;
+ }
+
+ multiPtr->cvarList[multiPtr->count] = String_Alloc(token.string);
+ if (!PC_Float_Parse(handle, &multiPtr->cvarValue[multiPtr->count])) {
+ return qfalse;
+ }
+
+ multiPtr->count++;
+ if (multiPtr->count >= MAX_MULTI_CVARS) {
+ return qfalse;
+ }
+
+ }
+ return qfalse; // bk001205 - LCC missing return value
+}
+
+
+
+qboolean ItemParse_addColorRange( itemDef_t *item, int handle ) {
+ colorRangeDef_t color;
+
+ if (PC_Float_Parse(handle, &color.low) &&
+ PC_Float_Parse(handle, &color.high) &&
+ PC_Color_Parse(handle, &color.color) ) {
+ if (item->numColors < MAX_COLOR_RANGES) {
+ memcpy(&item->colorRanges[item->numColors], &color, sizeof(color));
+ item->numColors++;
+ }
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_ownerdrawFlag( itemDef_t *item, int handle ) {
+ int i;
+ if (!PC_Int_Parse(handle, &i)) {
+ return qfalse;
+ }
+ item->window.ownerDrawFlags |= i;
+ return qtrue;
+}
+
+qboolean ItemParse_enableCvar( itemDef_t *item, int handle ) {
+ if (PC_Script_Parse(handle, &item->enableCvar)) {
+ item->cvarFlags = CVAR_ENABLE;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_disableCvar( itemDef_t *item, int handle ) {
+ if (PC_Script_Parse(handle, &item->enableCvar)) {
+ item->cvarFlags = CVAR_DISABLE;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_showCvar( itemDef_t *item, int handle ) {
+ if (PC_Script_Parse(handle, &item->enableCvar)) {
+ item->cvarFlags = CVAR_SHOW;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+qboolean ItemParse_hideCvar( itemDef_t *item, int handle ) {
+ if (PC_Script_Parse(handle, &item->enableCvar)) {
+ item->cvarFlags = CVAR_HIDE;
+ return qtrue;
+ }
+ return qfalse;
+}
+
+
+keywordHash_t itemParseKeywords[] = {
+ {"name", ItemParse_name, NULL},
+ {"text", ItemParse_text, NULL},
+ {"group", ItemParse_group, NULL},
+ {"asset_model", ItemParse_asset_model, NULL},
+ {"asset_shader", ItemParse_asset_shader, NULL},
+ {"model_origin", ItemParse_model_origin, NULL},
+ {"model_fovx", ItemParse_model_fovx, NULL},
+ {"model_fovy", ItemParse_model_fovy, NULL},
+ {"model_rotation", ItemParse_model_rotation, NULL},
+ {"model_angle", ItemParse_model_angle, NULL},
+ {"rect", ItemParse_rect, NULL},
+ {"style", ItemParse_style, NULL},
+ {"decoration", ItemParse_decoration, NULL},
+ {"notselectable", ItemParse_notselectable, NULL},
+ {"wrapped", ItemParse_wrapped, NULL},
+ {"autowrapped", ItemParse_autowrapped, NULL},
+ {"horizontalscroll", ItemParse_horizontalscroll, NULL},
+ {"type", ItemParse_type, NULL},
+ {"elementwidth", ItemParse_elementwidth, NULL},
+ {"elementheight", ItemParse_elementheight, NULL},
+ {"feeder", ItemParse_feeder, NULL},
+ {"elementtype", ItemParse_elementtype, NULL},
+ {"columns", ItemParse_columns, NULL},
+ {"border", ItemParse_border, NULL},
+ {"bordersize", ItemParse_bordersize, NULL},
+ {"visible", ItemParse_visible, NULL},
+ {"ownerdraw", ItemParse_ownerdraw, NULL},
+ {"align", ItemParse_align, NULL},
+ {"textalign", ItemParse_textalign, NULL},
+ {"textalignx", ItemParse_textalignx, NULL},
+ {"textaligny", ItemParse_textaligny, NULL},
+ {"textscale", ItemParse_textscale, NULL},
+ {"textstyle", ItemParse_textstyle, NULL},
+ {"backcolor", ItemParse_backcolor, NULL},
+ {"forecolor", ItemParse_forecolor, NULL},
+ {"bordercolor", ItemParse_bordercolor, NULL},
+ {"outlinecolor", ItemParse_outlinecolor, NULL},
+ {"background", ItemParse_background, NULL},
+ {"onFocus", ItemParse_onFocus, NULL},
+ {"leaveFocus", ItemParse_leaveFocus, NULL},
+ {"mouseEnter", ItemParse_mouseEnter, NULL},
+ {"mouseExit", ItemParse_mouseExit, NULL},
+ {"mouseEnterText", ItemParse_mouseEnterText, NULL},
+ {"mouseExitText", ItemParse_mouseExitText, NULL},
+ {"action", ItemParse_action, NULL},
+ {"special", ItemParse_special, NULL},
+ {"cvar", ItemParse_cvar, NULL},
+ {"maxChars", ItemParse_maxChars, NULL},
+ {"maxPaintChars", ItemParse_maxPaintChars, NULL},
+ {"focusSound", ItemParse_focusSound, NULL},
+ {"cvarFloat", ItemParse_cvarFloat, NULL},
+ {"cvarStrList", ItemParse_cvarStrList, NULL},
+ {"cvarFloatList", ItemParse_cvarFloatList, NULL},
+ {"addColorRange", ItemParse_addColorRange, NULL},
+ {"ownerdrawFlag", ItemParse_ownerdrawFlag, NULL},
+ {"enableCvar", ItemParse_enableCvar, NULL},
+ {"cvarTest", ItemParse_cvarTest, NULL},
+ {"disableCvar", ItemParse_disableCvar, NULL},
+ {"showCvar", ItemParse_showCvar, NULL},
+ {"hideCvar", ItemParse_hideCvar, NULL},
+ {"cinematic", ItemParse_cinematic, NULL},
+ {"doubleclick", ItemParse_doubleClick, NULL},
+ {NULL, voidFunction2, NULL}
+};
+
+keywordHash_t *itemParseKeywordHash[KEYWORDHASH_SIZE];
+
+/*
+===============
+Item_SetupKeywordHash
+===============
+*/
+void Item_SetupKeywordHash( void )
+{
+ int i;
+
+ memset( itemParseKeywordHash, 0, sizeof( itemParseKeywordHash ) );
+
+ for( i = 0; itemParseKeywords[ i ].keyword; i++ )
+ KeywordHash_Add( itemParseKeywordHash, &itemParseKeywords[ i ] );
+}
+
+/*
+===============
+Item_Parse
+===============
+*/
+qboolean Item_Parse(int handle, itemDef_t *item) {
+ pc_token_t token;
+ keywordHash_t *key;
+
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (*token.string != '{') {
+ return qfalse;
+ }
+ while ( 1 ) {
+ if (!trap_Parse_ReadToken(handle, &token)) {
+ PC_SourceError(handle, "end of file inside menu item\n");
+ return qfalse;
+ }
+
+ if (*token.string == '}') {
+ return qtrue;
+ }
+
+ key = KeywordHash_Find(itemParseKeywordHash, token.string);
+ if (!key) {
+ PC_SourceError(handle, "unknown menu item keyword %s", token.string);
+ continue;
+ }
+ if ( !key->func(item, handle) ) {
+ PC_SourceError(handle, "couldn't parse menu item keyword %s", token.string);
+ return qfalse;
+ }
+ }
+ return qfalse; // bk001205 - LCC missing return value
+}
+
+
+// Item_InitControls
+// init's special control types
+void Item_InitControls(itemDef_t *item) {
+ if (item == NULL) {
+ return;
+ }
+ if (item->type == ITEM_TYPE_LISTBOX) {
+ listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData;
+ item->cursorPos = 0;
+ if (listPtr) {
+ listPtr->cursorPos = 0;
+ listPtr->startPos = 0;
+ listPtr->endPos = 0;
+ listPtr->cursorPos = 0;
+ }
+ }
+}
+
+/*
+===============
+Menu Keyword Parse functions
+===============
+*/
+
+qboolean MenuParse_font( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_String_Parse(handle, &menu->font)) {
+ return qfalse;
+ }
+ if (!DC->Assets.fontRegistered) {
+ DC->registerFont(menu->font, 48, &DC->Assets.textFont);
+ DC->Assets.fontRegistered = qtrue;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_name( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_String_Parse(handle, &menu->window.name)) {
+ return qfalse;
+ }
+ if (Q_stricmp(menu->window.name, "main") == 0) {
+ // default main as having focus
+ //menu->window.flags |= WINDOW_HASFOCUS;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_fullscreen( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Int_Parse(handle, (int*) &menu->fullScreen)) { // bk001206 - cast qboolean
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_rect( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Rect_Parse(handle, &menu->window.rect)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_style( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Int_Parse(handle, &menu->window.style)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_visible( itemDef_t *item, int handle ) {
+ int i;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Int_Parse(handle, &i)) {
+ return qfalse;
+ }
+ if (i) {
+ menu->window.flags |= WINDOW_VISIBLE;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_onOpen( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Script_Parse(handle, &menu->onOpen)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_onClose( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Script_Parse(handle, &menu->onClose)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_onESC( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Script_Parse(handle, &menu->onESC)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+
+
+qboolean MenuParse_border( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Int_Parse(handle, &menu->window.border)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_borderSize( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Float_Parse(handle, &menu->window.borderSize)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_backcolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->window.backColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_forecolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->window.foreColor[i] = f;
+ menu->window.flags |= WINDOW_FORECOLORSET;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_bordercolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->window.borderColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_focuscolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->focusColor[i] = f;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_disablecolor( itemDef_t *item, int handle ) {
+ int i;
+ float f;
+ menuDef_t *menu = (menuDef_t*)item;
+ for (i = 0; i < 4; i++) {
+ if (!PC_Float_Parse(handle, &f)) {
+ return qfalse;
+ }
+ menu->disableColor[i] = f;
+ }
+ return qtrue;
+}
+
+
+qboolean MenuParse_outlinecolor( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (!PC_Color_Parse(handle, &menu->window.outlineColor)){
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_background( itemDef_t *item, int handle ) {
+ const char *buff;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_String_Parse(handle, &buff)) {
+ return qfalse;
+ }
+ menu->window.background = DC->registerShaderNoMip(buff);
+ return qtrue;
+}
+
+qboolean MenuParse_cinematic( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_String_Parse(handle, &menu->window.cinematicName)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_ownerdrawFlag( itemDef_t *item, int handle ) {
+ int i;
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Int_Parse(handle, &i)) {
+ return qfalse;
+ }
+ menu->window.ownerDrawFlags |= i;
+ return qtrue;
+}
+
+qboolean MenuParse_ownerdraw( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Int_Parse(handle, &menu->window.ownerDraw)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+
+// decoration
+qboolean MenuParse_popup( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ menu->window.flags |= WINDOW_POPUP;
+ return qtrue;
+}
+
+
+qboolean MenuParse_outOfBounds( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ menu->window.flags |= WINDOW_OOB_CLICK;
+ return qtrue;
+}
+
+qboolean MenuParse_soundLoop( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_String_Parse(handle, &menu->soundName)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_fadeClamp( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Float_Parse(handle, &menu->fadeClamp)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+qboolean MenuParse_fadeAmount( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Float_Parse(handle, &menu->fadeAmount)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+
+qboolean MenuParse_fadeCycle( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+
+ if (!PC_Int_Parse(handle, &menu->fadeCycle)) {
+ return qfalse;
+ }
+ return qtrue;
+}
+
+
+qboolean MenuParse_itemDef( itemDef_t *item, int handle ) {
+ menuDef_t *menu = (menuDef_t*)item;
+ if (menu->itemCount < MAX_MENUITEMS) {
+ menu->items[menu->itemCount] = UI_Alloc(sizeof(itemDef_t));
+ Item_Init(menu->items[menu->itemCount]);
+ if (!Item_Parse(handle, menu->items[menu->itemCount])) {
+ return qfalse;
+ }
+ Item_InitControls(menu->items[menu->itemCount]);
+ menu->items[menu->itemCount++]->parent = menu;
+ }
+ return qtrue;
+}
+
+keywordHash_t menuParseKeywords[] = {
+ {"font", MenuParse_font, NULL},
+ {"name", MenuParse_name, NULL},
+ {"fullscreen", MenuParse_fullscreen, NULL},
+ {"rect", MenuParse_rect, NULL},
+ {"style", MenuParse_style, NULL},
+ {"visible", MenuParse_visible, NULL},
+ {"onOpen", MenuParse_onOpen, NULL},
+ {"onClose", MenuParse_onClose, NULL},
+ {"onESC", MenuParse_onESC, NULL},
+ {"border", MenuParse_border, NULL},
+ {"borderSize", MenuParse_borderSize, NULL},
+ {"backcolor", MenuParse_backcolor, NULL},
+ {"forecolor", MenuParse_forecolor, NULL},
+ {"bordercolor", MenuParse_bordercolor, NULL},
+ {"focuscolor", MenuParse_focuscolor, NULL},
+ {"disablecolor", MenuParse_disablecolor, NULL},
+ {"outlinecolor", MenuParse_outlinecolor, NULL},
+ {"background", MenuParse_background, NULL},
+ {"ownerdraw", MenuParse_ownerdraw, NULL},
+ {"ownerdrawFlag", MenuParse_ownerdrawFlag, NULL},
+ {"outOfBoundsClick", MenuParse_outOfBounds, NULL},
+ {"soundLoop", MenuParse_soundLoop, NULL},
+ {"itemDef", MenuParse_itemDef, NULL},
+ {"cinematic", MenuParse_cinematic, NULL},
+ {"popup", MenuParse_popup, NULL},
+ {"fadeClamp", MenuParse_fadeClamp, NULL},
+ {"fadeCycle", MenuParse_fadeCycle, NULL},
+ {"fadeAmount", MenuParse_fadeAmount, NULL},
+ {NULL, voidFunction2, NULL}
+};
+
+keywordHash_t *menuParseKeywordHash[KEYWORDHASH_SIZE];
+
+/*
+===============
+Menu_SetupKeywordHash
+===============
+*/
+void Menu_SetupKeywordHash( void )
+{
+ int i;
+
+ memset( menuParseKeywordHash, 0, sizeof( menuParseKeywordHash ) );
+
+ for(i = 0; menuParseKeywords[ i ].keyword; i++ )
+ KeywordHash_Add( menuParseKeywordHash, &menuParseKeywords[ i ] );
+}
+
+/*
+===============
+Menu_Parse
+===============
+*/
+qboolean Menu_Parse(int handle, menuDef_t *menu) {
+ pc_token_t token;
+ keywordHash_t *key;
+
+ if (!trap_Parse_ReadToken(handle, &token))
+ return qfalse;
+ if (*token.string != '{') {
+ return qfalse;
+ }
+
+ while ( 1 ) {
+
+ memset(&token, 0, sizeof(pc_token_t));
+ if (!trap_Parse_ReadToken(handle, &token)) {
+ PC_SourceError(handle, "end of file inside menu\n");
+ return qfalse;
+ }
+
+ if (*token.string == '}') {
+ return qtrue;
+ }
+
+ key = KeywordHash_Find(menuParseKeywordHash, token.string);
+ if (!key) {
+ PC_SourceError(handle, "unknown menu keyword %s", token.string);
+ continue;
+ }
+ if ( !key->func((itemDef_t*)menu, handle) ) {
+ PC_SourceError(handle, "couldn't parse menu keyword %s", token.string);
+ return qfalse;
+ }
+ }
+ return qfalse; // bk001205 - LCC missing return value
+}
+
+/*
+===============
+Menu_New
+===============
+*/
+void Menu_New(int handle) {
+ menuDef_t *menu = &Menus[menuCount];
+
+ if (menuCount < MAX_MENUS) {
+ Menu_Init(menu);
+ if (Menu_Parse(handle, menu)) {
+ Menu_PostParse(menu);
+ menuCount++;
+ }
+ }
+}
+
+int Menu_Count( void ) {
+ return menuCount;
+}
+
+void Menu_PaintAll( void ) {
+ int i;
+ if (captureFunc) {
+ captureFunc(captureData);
+ }
+
+ for (i = 0; i < Menu_Count(); i++) {
+ Menu_Paint(&Menus[i], qfalse);
+ }
+
+ if (debugMode) {
+ vec4_t v = {1, 1, 1, 1};
+ DC->drawText(5, 25, .5, v, va("fps: %f", DC->FPS), 0, 0, 0);
+ }
+}
+
+void Menu_Reset( void )
+{
+ menuCount = 0;
+}
+
+displayContextDef_t *Display_GetContext( void ) {
+ return DC;
+}
+
+void *Display_CaptureItem(int x, int y) {
+ int i;
+
+ for (i = 0; i < menuCount; i++) {
+ // turn off focus each item
+ // menu->items[i].window.flags &= ~WINDOW_HASFOCUS;
+ if (Rect_ContainsPoint(&Menus[i].window.rect, x, y)) {
+ return &Menus[i];
+ }
+ }
+ return NULL;
+}
+
+
+// FIXME:
+qboolean Display_MouseMove(void *p, int x, int y) {
+ int i;
+ menuDef_t *menu = p;
+
+ if (menu == NULL) {
+ menu = Menu_GetFocused();
+ if (menu) {
+ if (menu->window.flags & WINDOW_POPUP) {
+ Menu_HandleMouseMove(menu, x, y);
+ return qtrue;
+ }
+ }
+ for (i = 0; i < menuCount; i++) {
+ Menu_HandleMouseMove(&Menus[i], x, y);
+ }
+ } else {
+ menu->window.rect.x += x;
+ menu->window.rect.y += y;
+ Menu_UpdatePosition(menu);
+ }
+ return qtrue;
+
+}
+
+int Display_CursorType(int x, int y) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ rectDef_t r2;
+ r2.x = Menus[i].window.rect.x - 3;
+ r2.y = Menus[i].window.rect.y - 3;
+ r2.w = r2.h = 7;
+ if (Rect_ContainsPoint(&r2, x, y)) {
+ return CURSOR_SIZER;
+ }
+ }
+ return CURSOR_ARROW;
+}
+
+
+void Display_HandleKey(int key, qboolean down, int x, int y) {
+ menuDef_t *menu = Display_CaptureItem(x, y);
+ if (menu == NULL) {
+ menu = Menu_GetFocused();
+ }
+ if (menu) {
+ Menu_HandleKey(menu, key, down );
+ }
+}
+
+static void Window_CacheContents(windowDef_t *window) {
+ if (window) {
+ if (window->cinematicName) {
+ int cin = DC->playCinematic(window->cinematicName, 0, 0, 0, 0);
+ DC->stopCinematic(cin);
+ }
+ }
+}
+
+
+static void Item_CacheContents(itemDef_t *item) {
+ if (item) {
+ Window_CacheContents(&item->window);
+ }
+
+}
+
+static void Menu_CacheContents(menuDef_t *menu) {
+ if (menu) {
+ int i;
+ Window_CacheContents(&menu->window);
+ for (i = 0; i < menu->itemCount; i++) {
+ Item_CacheContents(menu->items[i]);
+ }
+
+ if (menu->soundName && *menu->soundName) {
+ DC->registerSound(menu->soundName, qfalse);
+ }
+ }
+
+}
+
+void Display_CacheAll( void ) {
+ int i;
+ for (i = 0; i < menuCount; i++) {
+ Menu_CacheContents(&Menus[i]);
+ }
+}
+
+
+static qboolean Menu_OverActiveItem(menuDef_t *menu, float x, float y) {
+ if (menu && menu->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED)) {
+ if (Rect_ContainsPoint(&menu->window.rect, x, y)) {
+ int i;
+ for (i = 0; i < menu->itemCount; i++) {
+ // turn off focus each item
+ // menu->items[i].window.flags &= ~WINDOW_HASFOCUS;
+
+ if (!(menu->items[i]->window.flags & (WINDOW_VISIBLE | WINDOW_FORCED))) {
+ continue;
+ }
+
+ if (menu->items[i]->window.flags & WINDOW_DECORATION) {
+ continue;
+ }
+
+ if (Rect_ContainsPoint(&menu->items[i]->window.rect, x, y)) {
+ itemDef_t *overItem = menu->items[i];
+ if (overItem->type == ITEM_TYPE_TEXT && overItem->text) {
+ if (Rect_ContainsPoint(Item_CorrectedTextRect(overItem), x, y)) {
+ return qtrue;
+ } else {
+ continue;
+ }
+ } else {
+ return qtrue;
+ }
+ }
+ }
+
+ }
+ }
+ return qfalse;
+}
+
diff --git a/src/ui/ui_shared.h b/src/ui/ui_shared.h
new file mode 100644
index 0000000..e55cc7f
--- /dev/null
+++ b/src/ui/ui_shared.h
@@ -0,0 +1,454 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#ifndef __UI_SHARED_H
+#define __UI_SHARED_H
+
+
+#include "../qcommon/q_shared.h"
+#include "../renderer/tr_types.h"
+#include "../client/keycodes.h"
+
+#include "../ui/menudef.h"
+
+#define MAX_MENUNAME 32
+#define MAX_ITEMTEXT 64
+#define MAX_ITEMACTION 64
+#define MAX_MENUDEFFILE 4096
+#define MAX_MENUFILE 32768
+#define MAX_MENUS 256
+#define MAX_MENUITEMS 128
+#define MAX_COLOR_RANGES 10
+#define MAX_OPEN_MENUS 16
+
+#define WINDOW_MOUSEOVER 0x00000001 // mouse is over it, non exclusive
+#define WINDOW_HASFOCUS 0x00000002 // has cursor focus, exclusive
+#define WINDOW_VISIBLE 0x00000004 // is visible
+#define WINDOW_GREY 0x00000008 // is visible but grey ( non-active )
+#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc..
+#define WINDOW_FADINGOUT 0x00000020 // fading out, non-active
+#define WINDOW_FADINGIN 0x00000040 // fading in
+#define WINDOW_MOUSEOVERTEXT 0x00000080 // mouse is over it, non exclusive
+#define WINDOW_INTRANSITION 0x00000100 // window is in transition
+#define WINDOW_FORECOLORSET 0x00000200 // forecolor was explicitly set ( used to color alpha images or not )
+#define WINDOW_HORIZONTAL 0x00000400 // for list boxes and sliders, vertical is default this is set of horizontal
+#define WINDOW_LB_LEFTARROW 0x00000800 // mouse is over left/up arrow
+#define WINDOW_LB_RIGHTARROW 0x00001000 // mouse is over right/down arrow
+#define WINDOW_LB_THUMB 0x00002000 // mouse is over thumb
+#define WINDOW_LB_PGUP 0x00004000 // mouse is over page up
+#define WINDOW_LB_PGDN 0x00008000 // mouse is over page down
+#define WINDOW_ORBITING 0x00010000 // item is in orbit
+#define WINDOW_OOB_CLICK 0x00020000 // close on out of bounds click
+#define WINDOW_WRAPPED 0x00040000 // manually wrap text
+#define WINDOW_AUTOWRAPPED 0x00080000 // auto wrap text
+#define WINDOW_FORCED 0x00100000 // forced open
+#define WINDOW_POPUP 0x00200000 // popup
+#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set
+#define WINDOW_TIMEDVISIBLE 0x00800000 // visibility timing ( NOT implemented )
+
+
+// CGAME cursor type bits
+#define CURSOR_NONE 0x00000001
+#define CURSOR_ARROW 0x00000002
+#define CURSOR_SIZER 0x00000004
+
+#ifdef CGAME
+#define STRING_POOL_SIZE 128*1024
+#else
+#define STRING_POOL_SIZE 384*1024
+#endif
+#define MAX_STRING_HANDLES 4096
+
+#define MAX_SCRIPT_ARGS 12
+#define MAX_EDITFIELD 256
+
+#define ART_FX_BASE "menu/art/fx_base"
+#define ART_FX_BLUE "menu/art/fx_blue"
+#define ART_FX_CYAN "menu/art/fx_cyan"
+#define ART_FX_GREEN "menu/art/fx_grn"
+#define ART_FX_RED "menu/art/fx_red"
+#define ART_FX_TEAL "menu/art/fx_teal"
+#define ART_FX_WHITE "menu/art/fx_white"
+#define ART_FX_YELLOW "menu/art/fx_yel"
+
+#define ASSET_GRADIENTBAR "ui/assets/gradientbar2.tga"
+#define ASSET_SCROLLBAR "ui/assets/scrollbar.tga"
+#define ASSET_SCROLLBAR_ARROWDOWN "ui/assets/scrollbar_arrow_dwn_a.tga"
+#define ASSET_SCROLLBAR_ARROWUP "ui/assets/scrollbar_arrow_up_a.tga"
+#define ASSET_SCROLLBAR_ARROWLEFT "ui/assets/scrollbar_arrow_left.tga"
+#define ASSET_SCROLLBAR_ARROWRIGHT "ui/assets/scrollbar_arrow_right.tga"
+#define ASSET_SCROLL_THUMB "ui/assets/scrollbar_thumb.tga"
+#define ASSET_SLIDER_BAR "ui/assets/slider2.tga"
+#define ASSET_SLIDER_THUMB "ui/assets/sliderbutt_1.tga"
+#define SCROLLBAR_SIZE 16.0
+#define SLIDER_WIDTH 96.0
+#define SLIDER_HEIGHT 16.0
+#define SLIDER_THUMB_WIDTH 12.0
+#define SLIDER_THUMB_HEIGHT 20.0
+#define NUM_CROSSHAIRS 10
+
+typedef struct {
+ const char *command;
+ const char *args[MAX_SCRIPT_ARGS];
+} scriptDef_t;
+
+
+typedef struct {
+ float x; // horiz position
+ float y; // vert position
+ float w; // width
+ float h; // height;
+} rectDef_t;
+
+typedef rectDef_t Rectangle;
+
+// FIXME: do something to separate text vs window stuff
+typedef struct {
+ Rectangle rect; // client coord rectangle
+ Rectangle rectClient; // screen coord rectangle
+ const char *name; //
+ const char *group; // if it belongs to a group
+ const char *cinematicName; // cinematic name
+ int cinematic; // cinematic handle
+ int style; //
+ int border; //
+ int ownerDraw; // ownerDraw style
+ int ownerDrawFlags; // show flags for ownerdraw items
+ float borderSize; //
+ int flags; // visible, focus, mouseover, cursor
+ Rectangle rectEffects; // for various effects
+ Rectangle rectEffects2; // for various effects
+ int offsetTime; // time based value for various effects
+ int nextTime; // time next effect should cycle
+ vec4_t foreColor; // text color
+ vec4_t backColor; // border color
+ vec4_t borderColor; // border color
+ vec4_t outlineColor; // border color
+ qhandle_t background; // background asset
+} windowDef_t;
+
+typedef windowDef_t Window;
+
+typedef struct {
+ vec4_t color;
+ float low;
+ float high;
+} colorRangeDef_t;
+
+// FIXME: combine flags into bitfields to save space
+// FIXME: consolidate all of the common stuff in one structure for menus and items
+// THINKABOUTME: is there any compelling reason not to have items contain items
+// and do away with a menu per say.. major issue is not being able to dynamically allocate
+// and destroy stuff.. Another point to consider is adding an alloc free call for vm's and have
+// the engine just allocate the pool for it based on a cvar
+// many of the vars are re-used for different item types, as such they are not always named appropriately
+// the benefits of c++ in DOOM will greatly help crap like this
+// FIXME: need to put a type ptr that points to specific type info per type
+//
+#define MAX_LB_COLUMNS 16
+
+typedef struct columnInfo_s {
+ int pos;
+ int width;
+ int maxChars;
+ int align;
+} columnInfo_t;
+
+typedef struct listBoxDef_s {
+ int startPos;
+ int endPos;
+ int drawPadding;
+ int cursorPos;
+ float elementWidth;
+ float elementHeight;
+ int elementStyle;
+ int numColumns;
+ columnInfo_t columnInfo[MAX_LB_COLUMNS];
+ const char *doubleClick;
+ qboolean notselectable;
+} listBoxDef_t;
+
+typedef struct editFieldDef_s {
+ float minVal; // edit field limits
+ float maxVal; //
+ float defVal; //
+ float range; //
+ int maxChars; // for edit fields
+ int maxPaintChars; // for edit fields
+ int paintOffset; //
+} editFieldDef_t;
+
+#define MAX_MULTI_CVARS 32
+
+typedef struct multiDef_s {
+ const char *cvarList[MAX_MULTI_CVARS];
+ const char *cvarStr[MAX_MULTI_CVARS];
+ float cvarValue[MAX_MULTI_CVARS];
+ int count;
+ qboolean strDef;
+} multiDef_t;
+
+typedef struct modelDef_s {
+ int angle;
+ vec3_t origin;
+ float fov_x;
+ float fov_y;
+ int rotationSpeed;
+} modelDef_t;
+
+#define CVAR_ENABLE 0x00000001
+#define CVAR_DISABLE 0x00000002
+#define CVAR_SHOW 0x00000004
+#define CVAR_HIDE 0x00000008
+
+typedef struct itemDef_s {
+ Window window; // common positional, border, style, layout info
+ Rectangle textRect; // rectangle the text ( if any ) consumes
+ int type; // text, button, radiobutton, checkbox, textfield, listbox, combo
+ int alignment; // left center right
+ int textalignment; // ( optional ) alignment for text within rect based on text width
+ float textalignx; // ( optional ) text alignment x coord
+ float textaligny; // ( optional ) text alignment x coord
+ float textscale; // scale percentage from 72pts
+ int textStyle; // ( optional ) style, normal and shadowed are it for now
+ const char *text; // display text
+ void *parent; // menu owner
+ qhandle_t asset; // handle to asset
+ const char *mouseEnterText; // mouse enter script
+ const char *mouseExitText; // mouse exit script
+ const char *mouseEnter; // mouse enter script
+ const char *mouseExit; // mouse exit script
+ const char *action; // select script
+ const char *onFocus; // select script
+ const char *leaveFocus; // select script
+ const char *cvar; // associated cvar
+ const char *cvarTest; // associated cvar for enable actions
+ const char *enableCvar; // enable, disable, show, or hide based on value, this can contain a list
+ int cvarFlags; // what type of action to take on cvarenables
+ sfxHandle_t focusSound;
+ int numColors; // number of color ranges
+ colorRangeDef_t colorRanges[MAX_COLOR_RANGES];
+ float special; // used for feeder id's etc.. diff per type
+ int cursorPos; // cursor position in characters
+ void *typeData; // type specific data ptr's
+} itemDef_t;
+
+typedef struct {
+ Window window;
+ const char *font; // font
+ qboolean fullScreen; // covers entire screen
+ int itemCount; // number of items;
+ int fontIndex; //
+ int cursorItem; // which item as the cursor
+ int fadeCycle; //
+ float fadeClamp; //
+ float fadeAmount; //
+ const char *onOpen; // run when the menu is first opened
+ const char *onClose; // run when the menu is closed
+ const char *onESC; // run when the menu is closed
+ const char *soundName; // background loop sound for menu
+
+ vec4_t focusColor; // focus color for items
+ vec4_t disableColor; // focus color for items
+ itemDef_t *items[MAX_MENUITEMS]; // items this menu contains
+} menuDef_t;
+
+typedef struct {
+ const char *fontStr;
+ const char *cursorStr;
+ const char *gradientStr;
+ fontInfo_t textFont;
+ fontInfo_t smallFont;
+ fontInfo_t bigFont;
+ qhandle_t cursor;
+ qhandle_t gradientBar;
+ qhandle_t scrollBarArrowUp;
+ qhandle_t scrollBarArrowDown;
+ qhandle_t scrollBarArrowLeft;
+ qhandle_t scrollBarArrowRight;
+ qhandle_t scrollBar;
+ qhandle_t scrollBarThumb;
+ qhandle_t buttonMiddle;
+ qhandle_t buttonInside;
+ qhandle_t solidBox;
+ qhandle_t sliderBar;
+ qhandle_t sliderThumb;
+ sfxHandle_t menuEnterSound;
+ sfxHandle_t menuExitSound;
+ sfxHandle_t menuBuzzSound;
+ sfxHandle_t itemFocusSound;
+ float fadeClamp;
+ int fadeCycle;
+ float fadeAmount;
+ float shadowX;
+ float shadowY;
+ vec4_t shadowColor;
+ float shadowFadeClamp;
+ qboolean fontRegistered;
+
+} cachedAssets_t;
+
+typedef struct {
+ const char *name;
+ void (*handler) (itemDef_t *item, char** args);
+} commandDef_t;
+
+typedef struct {
+ qhandle_t (*registerShaderNoMip) (const char *p);
+ void (*setColor) (const vec4_t v);
+ void (*drawHandlePic) (float x, float y, float w, float h, qhandle_t asset);
+ void (*drawStretchPic) (float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader );
+ void (*drawText) (float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style );
+ int (*textWidth) (const char *text, float scale, int limit);
+ int (*textHeight) (const char *text, float scale, int limit);
+ qhandle_t (*registerModel) (const char *p);
+ void (*modelBounds) (qhandle_t model, vec3_t min, vec3_t max);
+ void (*fillRect) ( float x, float y, float w, float h, const vec4_t color);
+ void (*drawRect) ( float x, float y, float w, float h, float size, const vec4_t color);
+ void (*drawSides) (float x, float y, float w, float h, float size);
+ void (*drawTopBottom) (float x, float y, float w, float h, float size);
+ void (*clearScene) (void);
+ void (*addRefEntityToScene) (const refEntity_t *re );
+ void (*renderScene) ( const refdef_t *fd );
+ void (*registerFont) (const char *pFontname, int pointSize, fontInfo_t *font);
+ void (*ownerDrawItem) (float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle);
+ float (*getValue) (int ownerDraw);
+ qboolean (*ownerDrawVisible) (int flags);
+ void (*runScript)(char **p);
+ void (*getTeamColor)(vec4_t *color);
+ void (*getCVarString)(const char *cvar, char *buffer, int bufsize);
+ float (*getCVarValue)(const char *cvar);
+ void (*setCVar)(const char *cvar, const char *value);
+ void (*drawTextWithCursor)(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style);
+ void (*setOverstrikeMode)(qboolean b);
+ qboolean (*getOverstrikeMode)( void );
+ void (*startLocalSound)( sfxHandle_t sfx, int channelNum );
+ qboolean (*ownerDrawHandleKey)(int ownerDraw, int flags, float *special, int key);
+ int (*feederCount)(float feederID);
+ const char *(*feederItemText)(float feederID, int index, int column, qhandle_t *handle);
+ qhandle_t (*feederItemImage)(float feederID, int index);
+ void (*feederSelection)(float feederID, int index);
+ void (*keynumToStringBuf)( int keynum, char *buf, int buflen );
+ void (*getBindingBuf)( int keynum, char *buf, int buflen );
+ void (*setBinding)( int keynum, const char *binding );
+ void (*executeText)(int exec_when, const char *text );
+ void (*Error)(int level, const char *error, ...);
+ void (*Print)(const char *msg, ...);
+ void (*Pause)(qboolean b);
+ int (*ownerDrawWidth)(int ownerDraw, float scale);
+ sfxHandle_t (*registerSound)(const char *name, qboolean compressed);
+ void (*startBackgroundTrack)( const char *intro, const char *loop);
+ void (*stopBackgroundTrack)( void );
+ int (*playCinematic)(const char *name, float x, float y, float w, float h);
+ void (*stopCinematic)(int handle);
+ void (*drawCinematic)(int handle, float x, float y, float w, float h);
+ void (*runCinematicFrame)(int handle);
+
+ float yscale;
+ float xscale;
+ float bias;
+ int realTime;
+ int frameTime;
+ int cursorx;
+ int cursory;
+ qboolean debug;
+
+ cachedAssets_t Assets;
+
+ glconfig_t glconfig;
+ qhandle_t whiteShader;
+ qhandle_t gradientImage;
+ qhandle_t cursor;
+ float FPS;
+
+} displayContextDef_t;
+
+const char *String_Alloc(const char *p);
+void String_Init( void );
+void String_Report( void );
+void Init_Display(displayContextDef_t *dc);
+void Display_ExpandMacros(char * buff);
+void Menu_Init(menuDef_t *menu);
+void Item_Init(itemDef_t *item);
+void Menu_PostParse(menuDef_t *menu);
+menuDef_t *Menu_GetFocused( void );
+void Menu_HandleKey(menuDef_t *menu, int key, qboolean down);
+void Menu_HandleMouseMove(menuDef_t *menu, float x, float y);
+void Menu_ScrollFeeder(menuDef_t *menu, int feeder, qboolean down);
+qboolean Float_Parse(char **p, float *f);
+qboolean Color_Parse(char **p, vec4_t *c);
+qboolean Int_Parse(char **p, int *i);
+qboolean Rect_Parse(char **p, rectDef_t *r);
+qboolean String_Parse(char **p, const char **out);
+qboolean Script_Parse(char **p, const char **out);
+qboolean PC_Float_Parse(int handle, float *f);
+qboolean PC_Color_Parse(int handle, vec4_t *c);
+qboolean PC_Int_Parse(int handle, int *i);
+qboolean PC_Rect_Parse(int handle, rectDef_t *r);
+qboolean PC_String_Parse(int handle, const char **out);
+qboolean PC_Script_Parse(int handle, const char **out);
+int Menu_Count( void );
+void Menu_New(int handle);
+void Menu_PaintAll( void );
+menuDef_t *Menus_ActivateByName(const char *p);
+void Menu_Reset( void );
+qboolean Menus_AnyFullScreenVisible( void );
+void Menus_Activate(menuDef_t *menu);
+
+displayContextDef_t *Display_GetContext( void );
+void *Display_CaptureItem(int x, int y);
+qboolean Display_MouseMove(void *p, int x, int y);
+int Display_CursorType(int x, int y);
+qboolean Display_KeyBindPending( void );
+void Menus_OpenByName(const char *p);
+menuDef_t *Menus_FindByName(const char *p);
+void Menus_ShowByName(const char *p);
+void Menus_CloseByName(const char *p);
+void Display_HandleKey(int key, qboolean down, int x, int y);
+void LerpColor(vec4_t a, vec4_t b, vec4_t c, float t);
+void Menus_CloseAll( void );
+void Menu_Paint(menuDef_t *menu, qboolean forcePaint);
+void Menu_SetFeederSelection(menuDef_t *menu, int feeder, int index, const char *name);
+void Display_CacheAll( void );
+
+void *UI_Alloc( int size );
+void UI_InitMemory( void );
+qboolean UI_OutOfMemory( void );
+
+void Controls_GetConfig( void );
+void Controls_SetConfig(qboolean restart);
+void Controls_SetDefaults( void );
+
+//for cg_draw.c
+void Item_Text_AutoWrapped_Paint( itemDef_t *item );
+
+int trap_Parse_AddGlobalDefine( char *define );
+int trap_Parse_LoadSource( const char *filename );
+int trap_Parse_FreeSource( int handle );
+int trap_Parse_ReadToken( int handle, pc_token_t *pc_token );
+int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line );
+
+void BindingFromName( const char *cvar );
+extern char g_nameBind1[ 32 ];
+extern char g_nameBind2[ 32 ];
+#endif
diff --git a/src/ui/ui_syscalls.asm b/src/ui/ui_syscalls.asm
new file mode 100644
index 0000000..1b236fe
--- /dev/null
+++ b/src/ui/ui_syscalls.asm
@@ -0,0 +1,98 @@
+code
+
+equ trap_Error -1
+equ trap_Print -2
+equ trap_Milliseconds -3
+equ trap_Cvar_Set -4
+equ trap_Cvar_VariableValue -5
+equ trap_Cvar_VariableStringBuffer -6
+equ trap_Cvar_SetValue -7
+equ trap_Cvar_Reset -8
+equ trap_Cvar_Create -9
+equ trap_Cvar_InfoStringBuffer -10
+equ trap_Argc -11
+equ trap_Argv -12
+equ trap_Cmd_ExecuteText -13
+equ trap_FS_FOpenFile -14
+equ trap_FS_Read -15
+equ trap_FS_Write -16
+equ trap_FS_FCloseFile -17
+equ trap_FS_GetFileList -18
+equ trap_R_RegisterModel -19
+equ trap_R_RegisterSkin -20
+equ trap_R_RegisterShaderNoMip -21
+equ trap_R_ClearScene -22
+equ trap_R_AddRefEntityToScene -23
+equ trap_R_AddPolyToScene -24
+equ trap_R_AddLightToScene -25
+equ trap_R_RenderScene -26
+equ trap_R_SetColor -27
+equ trap_R_DrawStretchPic -28
+equ trap_UpdateScreen -29
+equ trap_CM_LerpTag -30
+equ trap_CM_LoadModel -31
+equ trap_S_RegisterSound -32
+equ trap_S_StartLocalSound -33
+equ trap_Key_KeynumToStringBuf -34
+equ trap_Key_GetBindingBuf -35
+equ trap_Key_SetBinding -36
+equ trap_Key_IsDown -37
+equ trap_Key_GetOverstrikeMode -38
+equ trap_Key_SetOverstrikeMode -39
+equ trap_Key_ClearStates -40
+equ trap_Key_GetCatcher -41
+equ trap_Key_SetCatcher -42
+equ trap_GetClipboardData -43
+equ trap_GetGlconfig -44
+equ trap_GetClientState -45
+equ trap_GetConfigString -46
+equ trap_LAN_GetPingQueueCount -47
+equ trap_LAN_ClearPing -48
+equ trap_LAN_GetPing -49
+equ trap_LAN_GetPingInfo -50
+equ trap_Cvar_Register -51
+equ trap_Cvar_Update -52
+equ trap_MemoryRemaining -53
+equ trap_R_RegisterFont -54
+equ trap_R_ModelBounds -55
+equ trap_Parse_AddGlobalDefine -56
+equ trap_Parse_LoadSource -57
+equ trap_Parse_FreeSource -58
+equ trap_Parse_ReadToken -59
+equ trap_Parse_SourceFileAndLine -60
+equ trap_S_StopBackgroundTrack -61
+equ trap_S_StartBackgroundTrack -62
+equ trap_RealTime -63
+equ trap_LAN_GetServerCount -64
+equ trap_LAN_GetServerAddressString -65
+equ trap_LAN_GetServerInfo -66
+equ trap_LAN_MarkServerVisible -67
+equ trap_LAN_UpdateVisiblePings -68
+equ trap_LAN_ResetPings -69
+equ trap_LAN_LoadCachedServers -70
+equ trap_LAN_SaveCachedServers -71
+equ trap_LAN_AddServer -72
+equ trap_LAN_RemoveServer -73
+equ trap_CIN_PlayCinematic -74
+equ trap_CIN_StopCinematic -75
+equ trap_CIN_RunCinematic -76
+equ trap_CIN_DrawCinematic -77
+equ trap_CIN_SetExtents -78
+equ trap_R_RemapShader -79
+equ trap_LAN_ServerStatus -80
+equ trap_LAN_GetServerPing -81
+equ trap_LAN_ServerIsVisible -82
+equ trap_LAN_CompareServers -83
+equ trap_FS_Seek -84
+equ trap_SetPbClStatus -85
+
+equ memset -101
+equ memcpy -102
+equ strncpy -103
+equ sin -104
+equ cos -105
+equ atan2 -106
+equ sqrt -107
+equ floor -108
+equ ceil -109
+
diff --git a/src/ui/ui_syscalls.c b/src/ui/ui_syscalls.c
new file mode 100644
index 0000000..528658e
--- /dev/null
+++ b/src/ui/ui_syscalls.c
@@ -0,0 +1,387 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+
+#include "ui_local.h"
+
+// this file is only included when building a dll
+// syscalls.asm is included instead when building a qvm
+
+static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1;
+
+void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t arg,... ) ) {
+ syscall = syscallptr;
+}
+
+int PASSFLOAT( float x ) {
+ float floatTemp;
+ floatTemp = x;
+ return *(int *)&floatTemp;
+}
+
+void trap_Print( const char *string ) {
+ syscall( UI_PRINT, string );
+}
+
+void trap_Error( const char *string ) {
+ syscall( UI_ERROR, string );
+}
+
+int trap_Milliseconds( void ) {
+ return syscall( UI_MILLISECONDS );
+}
+
+void trap_Cvar_Register( vmCvar_t *cvar, const char *var_name, const char *value, int flags ) {
+ syscall( UI_CVAR_REGISTER, cvar, var_name, value, flags );
+}
+
+void trap_Cvar_Update( vmCvar_t *cvar ) {
+ syscall( UI_CVAR_UPDATE, cvar );
+}
+
+void trap_Cvar_Set( const char *var_name, const char *value ) {
+ syscall( UI_CVAR_SET, var_name, value );
+}
+
+float trap_Cvar_VariableValue( const char *var_name ) {
+ int temp;
+ temp = syscall( UI_CVAR_VARIABLEVALUE, var_name );
+ return (*(float*)&temp);
+}
+
+void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) {
+ syscall( UI_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize );
+}
+
+void trap_Cvar_SetValue( const char *var_name, float value ) {
+ syscall( UI_CVAR_SETVALUE, var_name, PASSFLOAT( value ) );
+}
+
+void trap_Cvar_Reset( const char *name ) {
+ syscall( UI_CVAR_RESET, name );
+}
+
+void trap_Cvar_Create( const char *var_name, const char *var_value, int flags ) {
+ syscall( UI_CVAR_CREATE, var_name, var_value, flags );
+}
+
+void trap_Cvar_InfoStringBuffer( int bit, char *buffer, int bufsize ) {
+ syscall( UI_CVAR_INFOSTRINGBUFFER, bit, buffer, bufsize );
+}
+
+int trap_Argc( void ) {
+ return syscall( UI_ARGC );
+}
+
+void trap_Argv( int n, char *buffer, int bufferLength ) {
+ syscall( UI_ARGV, n, buffer, bufferLength );
+}
+
+void trap_Cmd_ExecuteText( int exec_when, const char *text ) {
+ syscall( UI_CMD_EXECUTETEXT, exec_when, text );
+}
+
+int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) {
+ return syscall( UI_FS_FOPENFILE, qpath, f, mode );
+}
+
+void trap_FS_Read( void *buffer, int len, fileHandle_t f ) {
+ syscall( UI_FS_READ, buffer, len, f );
+}
+
+void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) {
+ syscall( UI_FS_WRITE, buffer, len, f );
+}
+
+void trap_FS_FCloseFile( fileHandle_t f ) {
+ syscall( UI_FS_FCLOSEFILE, f );
+}
+
+int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) {
+ return syscall( UI_FS_GETFILELIST, path, extension, listbuf, bufsize );
+}
+
+int trap_FS_Seek( fileHandle_t f, long offset, int origin ) {
+ return syscall( UI_FS_SEEK, f, offset, origin );
+}
+
+qhandle_t trap_R_RegisterModel( const char *name ) {
+ return syscall( UI_R_REGISTERMODEL, name );
+}
+
+qhandle_t trap_R_RegisterSkin( const char *name ) {
+ return syscall( UI_R_REGISTERSKIN, name );
+}
+
+void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) {
+ syscall( UI_R_REGISTERFONT, fontName, pointSize, font );
+}
+
+qhandle_t trap_R_RegisterShaderNoMip( const char *name ) {
+ return syscall( UI_R_REGISTERSHADERNOMIP, name );
+}
+
+void trap_R_ClearScene( void ) {
+ syscall( UI_R_CLEARSCENE );
+}
+
+void trap_R_AddRefEntityToScene( const refEntity_t *re ) {
+ syscall( UI_R_ADDREFENTITYTOSCENE, re );
+}
+
+void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) {
+ syscall( UI_R_ADDPOLYTOSCENE, hShader, numVerts, verts );
+}
+
+void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) {
+ syscall( UI_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) );
+}
+
+void trap_R_RenderScene( const refdef_t *fd ) {
+ syscall( UI_R_RENDERSCENE, fd );
+}
+
+void trap_R_SetColor( const float *rgba ) {
+ syscall( UI_R_SETCOLOR, rgba );
+}
+
+void trap_R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, qhandle_t hShader ) {
+ syscall( UI_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader );
+}
+
+void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) {
+ syscall( UI_R_MODELBOUNDS, model, mins, maxs );
+}
+
+void trap_UpdateScreen( void ) {
+ syscall( UI_UPDATESCREEN );
+}
+
+int trap_CM_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, float frac, const char *tagName ) {
+ return syscall( UI_CM_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName );
+}
+
+void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) {
+ syscall( UI_S_STARTLOCALSOUND, sfx, channelNum );
+}
+
+sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) {
+ return syscall( UI_S_REGISTERSOUND, sample, compressed );
+}
+
+void trap_Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) {
+ syscall( UI_KEY_KEYNUMTOSTRINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_GetBindingBuf( int keynum, char *buf, int buflen ) {
+ syscall( UI_KEY_GETBINDINGBUF, keynum, buf, buflen );
+}
+
+void trap_Key_SetBinding( int keynum, const char *binding ) {
+ syscall( UI_KEY_SETBINDING, keynum, binding );
+}
+
+qboolean trap_Key_IsDown( int keynum ) {
+ return syscall( UI_KEY_ISDOWN, keynum );
+}
+
+qboolean trap_Key_GetOverstrikeMode( void ) {
+ return syscall( UI_KEY_GETOVERSTRIKEMODE );
+}
+
+void trap_Key_SetOverstrikeMode( qboolean state ) {
+ syscall( UI_KEY_SETOVERSTRIKEMODE, state );
+}
+
+void trap_Key_ClearStates( void ) {
+ syscall( UI_KEY_CLEARSTATES );
+}
+
+int trap_Key_GetCatcher( void ) {
+ return syscall( UI_KEY_GETCATCHER );
+}
+
+void trap_Key_SetCatcher( int catcher ) {
+ syscall( UI_KEY_SETCATCHER, catcher );
+}
+
+void trap_GetClipboardData( char *buf, int bufsize ) {
+ syscall( UI_GETCLIPBOARDDATA, buf, bufsize );
+}
+
+void trap_GetClientState( uiClientState_t *state ) {
+ syscall( UI_GETCLIENTSTATE, state );
+}
+
+void trap_GetGlconfig( glconfig_t *glconfig ) {
+ syscall( UI_GETGLCONFIG, glconfig );
+}
+
+int trap_GetConfigString( int index, char* buff, int buffsize ) {
+ return syscall( UI_GETCONFIGSTRING, index, buff, buffsize );
+}
+
+int trap_LAN_GetServerCount( int source ) {
+ return syscall( UI_LAN_GETSERVERCOUNT, source );
+}
+
+void trap_LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) {
+ syscall( UI_LAN_GETSERVERADDRESSSTRING, source, n, buf, buflen );
+}
+
+void trap_LAN_GetServerInfo( int source, int n, char *buf, int buflen ) {
+ syscall( UI_LAN_GETSERVERINFO, source, n, buf, buflen );
+}
+
+int trap_LAN_GetServerPing( int source, int n ) {
+ return syscall( UI_LAN_GETSERVERPING, source, n );
+}
+
+int trap_LAN_GetPingQueueCount( void ) {
+ return syscall( UI_LAN_GETPINGQUEUECOUNT );
+}
+
+int trap_LAN_ServerStatus( const char *serverAddress, char *serverStatus, int maxLen ) {
+ return syscall( UI_LAN_SERVERSTATUS, serverAddress, serverStatus, maxLen );
+}
+
+void trap_LAN_SaveCachedServers( void ) {
+ syscall( UI_LAN_SAVECACHEDSERVERS );
+}
+
+void trap_LAN_LoadCachedServers( void ) {
+ syscall( UI_LAN_LOADCACHEDSERVERS );
+}
+
+void trap_LAN_ResetPings(int n) {
+ syscall( UI_LAN_RESETPINGS, n );
+}
+
+void trap_LAN_ClearPing( int n ) {
+ syscall( UI_LAN_CLEARPING, n );
+}
+
+void trap_LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) {
+ syscall( UI_LAN_GETPING, n, buf, buflen, pingtime );
+}
+
+void trap_LAN_GetPingInfo( int n, char *buf, int buflen ) {
+ syscall( UI_LAN_GETPINGINFO, n, buf, buflen );
+}
+
+void trap_LAN_MarkServerVisible( int source, int n, qboolean visible ) {
+ syscall( UI_LAN_MARKSERVERVISIBLE, source, n, visible );
+}
+
+int trap_LAN_ServerIsVisible( int source, int n) {
+ return syscall( UI_LAN_SERVERISVISIBLE, source, n );
+}
+
+qboolean trap_LAN_UpdateVisiblePings( int source ) {
+ return syscall( UI_LAN_UPDATEVISIBLEPINGS, source );
+}
+
+int trap_LAN_AddServer(int source, const char *name, const char *addr) {
+ return syscall( UI_LAN_ADDSERVER, source, name, addr );
+}
+
+void trap_LAN_RemoveServer(int source, const char *addr) {
+ syscall( UI_LAN_REMOVESERVER, source, addr );
+}
+
+int trap_LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) {
+ return syscall( UI_LAN_COMPARESERVERS, source, sortKey, sortDir, s1, s2 );
+}
+
+int trap_MemoryRemaining( void ) {
+ return syscall( UI_MEMORY_REMAINING );
+}
+
+int trap_Parse_AddGlobalDefine( char *define ) {
+ return syscall( UI_PARSE_ADD_GLOBAL_DEFINE, define );
+}
+
+int trap_Parse_LoadSource( const char *filename ) {
+ return syscall( UI_PARSE_LOAD_SOURCE, filename );
+}
+
+int trap_Parse_FreeSource( int handle ) {
+ return syscall( UI_PARSE_FREE_SOURCE, handle );
+}
+
+int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ) {
+ return syscall( UI_PARSE_READ_TOKEN, handle, pc_token );
+}
+
+int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ) {
+ return syscall( UI_PARSE_SOURCE_FILE_AND_LINE, handle, filename, line );
+}
+
+void trap_S_StopBackgroundTrack( void ) {
+ syscall( UI_S_STOPBACKGROUNDTRACK );
+}
+
+void trap_S_StartBackgroundTrack( const char *intro, const char *loop) {
+ syscall( UI_S_STARTBACKGROUNDTRACK, intro, loop );
+}
+
+int trap_RealTime(qtime_t *qtime) {
+ return syscall( UI_REAL_TIME, qtime );
+}
+
+// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate)
+int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) {
+ return syscall(UI_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits);
+}
+
+// stops playing the cinematic and ends it. should always return FMV_EOF
+// cinematics must be stopped in reverse order of when they are started
+e_status trap_CIN_StopCinematic(int handle) {
+ return syscall(UI_CIN_STOPCINEMATIC, handle);
+}
+
+
+// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached.
+e_status trap_CIN_RunCinematic (int handle) {
+ return syscall(UI_CIN_RUNCINEMATIC, handle);
+}
+
+
+// draws the current frame
+void trap_CIN_DrawCinematic (int handle) {
+ syscall(UI_CIN_DRAWCINEMATIC, handle);
+}
+
+
+// allows you to resize the animation dynamically
+void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) {
+ syscall(UI_CIN_SETEXTENTS, handle, x, y, w, h);
+}
+
+
+void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) {
+ syscall( UI_R_REMAP_SHADER, oldShader, newShader, timeOffset );
+}
+
+void trap_SetPbClStatus( int status ) {
+ syscall( UI_SET_PBCLSTATUS, status );
+}