mirror of
https://github.com/w23/xash3d-fwgs
synced 2024-12-16 14:10:11 +01:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
1745 lines
44 KiB
C
1745 lines
44 KiB
C
/*
|
|
sv_world.c - world query functions
|
|
Copyright (C) 2008 Uncle Mike
|
|
|
|
This program 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 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "server.h"
|
|
#include "const.h"
|
|
#include "pm_local.h"
|
|
#include "studio.h"
|
|
|
|
typedef struct moveclip_s
|
|
{
|
|
vec3_t boxmins, boxmaxs; // enclose the test object along entire move
|
|
float *mins, *maxs; // size of the moving object
|
|
vec3_t mins2, maxs2; // size when clipping against mosnters
|
|
const float *start, *end;
|
|
edict_t *passedict;
|
|
trace_t trace;
|
|
int type; // move type
|
|
qboolean ignoretrans;
|
|
qboolean monsterclip;
|
|
} moveclip_t;
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
HULL BOXES
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
static hull_t box_hull;
|
|
static mclipnode_t box_clipnodes[6];
|
|
static mplane_t box_planes[6];
|
|
|
|
/*
|
|
===================
|
|
SV_InitBoxHull
|
|
|
|
Set up the planes and clipnodes so that the six floats of a bounding box
|
|
can just be stored out and get a proper hull_t structure.
|
|
===================
|
|
*/
|
|
void SV_InitBoxHull( void )
|
|
{
|
|
int i, side;
|
|
|
|
box_hull.clipnodes = box_clipnodes;
|
|
box_hull.planes = box_planes;
|
|
box_hull.firstclipnode = 0;
|
|
box_hull.lastclipnode = 5;
|
|
|
|
for( i = 0; i < 6; i++ )
|
|
{
|
|
box_clipnodes[i].planenum = i;
|
|
|
|
side = i & 1;
|
|
|
|
box_clipnodes[i].children[side] = CONTENTS_EMPTY;
|
|
if( i != 5 ) box_clipnodes[i].children[side^1] = i + 1;
|
|
else box_clipnodes[i].children[side^1] = CONTENTS_SOLID;
|
|
|
|
box_planes[i].type = i>>1;
|
|
box_planes[i].normal[i>>1] = 1;
|
|
box_planes[i].signbits = 0;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
====================
|
|
StudioPlayerBlend
|
|
|
|
====================
|
|
*/
|
|
void SV_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch )
|
|
{
|
|
// calc up/down pointing
|
|
*pBlend = (*pPitch * 3);
|
|
|
|
if( *pBlend < pseqdesc->blendstart[0] )
|
|
{
|
|
*pPitch -= pseqdesc->blendstart[0] / 3.0f;
|
|
*pBlend = 0;
|
|
}
|
|
else if( *pBlend > pseqdesc->blendend[0] )
|
|
{
|
|
*pPitch -= pseqdesc->blendend[0] / 3.0f;
|
|
*pBlend = 255;
|
|
}
|
|
else
|
|
{
|
|
if( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error
|
|
*pBlend = 127;
|
|
else *pBlend = 255.0f * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]);
|
|
*pPitch = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SV_CheckSphereIntersection
|
|
|
|
check clients only
|
|
====================
|
|
*/
|
|
qboolean SV_CheckSphereIntersection( edict_t *ent, const vec3_t start, const vec3_t end )
|
|
{
|
|
int i, sequence;
|
|
float radiusSquared;
|
|
vec3_t traceOrg, traceDir;
|
|
studiohdr_t *pstudiohdr;
|
|
mstudioseqdesc_t *pseqdesc;
|
|
model_t *mod;
|
|
|
|
if( !FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT ))
|
|
return true;
|
|
|
|
if(( mod = SV_ModelHandle( ent->v.modelindex )) == NULL )
|
|
return true;
|
|
|
|
if(( pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( mod )) == NULL )
|
|
return true;
|
|
|
|
sequence = ent->v.sequence;
|
|
if( sequence < 0 || sequence >= pstudiohdr->numseq )
|
|
sequence = 0;
|
|
|
|
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence;
|
|
|
|
VectorCopy( start, traceOrg );
|
|
VectorSubtract( end, start, traceDir );
|
|
radiusSquared = 0.0f;
|
|
|
|
for ( i = 0; i < 3; i++ )
|
|
radiusSquared += Q_max( fabs( pseqdesc->bbmin[i] ), fabs( pseqdesc->bbmax[i] ));
|
|
|
|
return SphereIntersect( ent->v.origin, radiusSquared, traceOrg, traceDir );
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
SV_HullForBox
|
|
|
|
To keep everything totally uniform, bounding boxes are turned into small
|
|
BSP trees instead of being compared directly.
|
|
===================
|
|
*/
|
|
hull_t *SV_HullForBox( const vec3_t mins, const vec3_t maxs )
|
|
{
|
|
box_planes[0].dist = maxs[0];
|
|
box_planes[1].dist = mins[0];
|
|
box_planes[2].dist = maxs[1];
|
|
box_planes[3].dist = mins[1];
|
|
box_planes[4].dist = maxs[2];
|
|
box_planes[5].dist = mins[2];
|
|
|
|
return &box_hull;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_HullAutoSelect
|
|
|
|
select the apropriate hull automatically
|
|
==================
|
|
*/
|
|
hull_t *SV_HullAutoSelect( model_t *model, const vec3_t mins, const vec3_t maxs, const vec3_t size, vec3_t offset )
|
|
{
|
|
float curdiff;
|
|
float lastdiff = 999;
|
|
int i, hullNumber = 0; // assume we fail
|
|
vec3_t clip_size;
|
|
hull_t *hull;
|
|
|
|
// NOTE: this is not matched with hardcoded values in some cases...
|
|
for( i = 0; i < MAX_MAP_HULLS; i++ )
|
|
{
|
|
VectorSubtract( model->hulls[i].clip_maxs, model->hulls[i].clip_mins, clip_size );
|
|
curdiff = floor( VectorAvg( size )) - floor( VectorAvg( clip_size ));
|
|
curdiff = fabs( curdiff );
|
|
|
|
if( curdiff < lastdiff )
|
|
{
|
|
hullNumber = i;
|
|
lastdiff = curdiff;
|
|
}
|
|
}
|
|
|
|
// TraceHull stuff
|
|
hull = &model->hulls[hullNumber];
|
|
|
|
// calculate an offset value to center the origin
|
|
// NOTE: never get offset of drawing hull
|
|
if( !hullNumber ) VectorCopy( hull->clip_mins, offset );
|
|
else VectorSubtract( hull->clip_mins, mins, offset );
|
|
|
|
return hull;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_HullForBsp
|
|
|
|
forcing to select BSP hull
|
|
==================
|
|
*/
|
|
hull_t *SV_HullForBsp( edict_t *ent, const vec3_t mins, const vec3_t maxs, vec3_t offset )
|
|
{
|
|
hull_t *hull;
|
|
model_t *model;
|
|
vec3_t size;
|
|
|
|
if( svgame.physFuncs.SV_HullForBsp != NULL )
|
|
{
|
|
hull = svgame.physFuncs.SV_HullForBsp( ent, mins, maxs, offset );
|
|
if( hull ) return hull;
|
|
}
|
|
|
|
// decide which clipping hull to use, based on the size
|
|
model = SV_ModelHandle( ent->v.modelindex );
|
|
|
|
if( !model || model->type != mod_brush )
|
|
Host_Error( "Entity %i (%s) SOLID_BSP with a non bsp model %s\n", NUM_FOR_EDICT( ent ), SV_ClassName( ent ), STRING( ent->v.model ));
|
|
|
|
VectorSubtract( maxs, mins, size );
|
|
|
|
#ifdef RANDOM_HULL_NULLIZATION
|
|
// author: The FiEctro
|
|
hull = &model->hulls[COM_RandomLong( 0, 0 )];
|
|
#endif
|
|
// g-cont: find a better method to detect quake-maps?
|
|
if( FBitSet( world.flags, FWORLD_SKYSPHERE ))
|
|
{
|
|
// alternate hull select for quake maps
|
|
if( size[0] < 3.0f || ent->v.solid == SOLID_PORTAL )
|
|
hull = &model->hulls[0];
|
|
else if( size[0] <= 32.0f )
|
|
hull = &model->hulls[1];
|
|
else hull = &model->hulls[2];
|
|
|
|
VectorSubtract( hull->clip_mins, mins, offset );
|
|
}
|
|
else
|
|
{
|
|
if( size[0] <= 8.0f || ent->v.solid == SOLID_PORTAL )
|
|
{
|
|
hull = &model->hulls[0];
|
|
VectorCopy( hull->clip_mins, offset );
|
|
}
|
|
else
|
|
{
|
|
if( size[0] <= 36.0f )
|
|
{
|
|
if( size[2] <= 36.0f )
|
|
hull = &model->hulls[3];
|
|
else hull = &model->hulls[1];
|
|
}
|
|
else hull = &model->hulls[2];
|
|
|
|
VectorSubtract( hull->clip_mins, mins, offset );
|
|
}
|
|
}
|
|
|
|
VectorAdd( offset, ent->v.origin, offset );
|
|
|
|
return hull;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SV_HullForEntity
|
|
|
|
Returns a hull that can be used for testing or clipping an object of mins/maxs
|
|
size.
|
|
Offset is filled in to contain the adjustment that must be added to the
|
|
testing object's origin to get a point to use with the returned hull.
|
|
================
|
|
*/
|
|
hull_t *SV_HullForEntity( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset )
|
|
{
|
|
hull_t *hull;
|
|
vec3_t hullmins, hullmaxs;
|
|
|
|
if( ent->v.solid == SOLID_BSP || ent->v.solid == SOLID_PORTAL )
|
|
{
|
|
if( ent->v.solid != SOLID_PORTAL )
|
|
{
|
|
if( ent->v.movetype != MOVETYPE_PUSH && ent->v.movetype != MOVETYPE_PUSHSTEP )
|
|
Host_Error( "'%s' has SOLID_BSP without MOVETYPE_PUSH or MOVETYPE_PUSHSTEP\n", SV_ClassName( ent ));
|
|
}
|
|
hull = SV_HullForBsp( ent, mins, maxs, offset );
|
|
}
|
|
else
|
|
{
|
|
// create a temp hull from bounding box sizes
|
|
VectorSubtract( ent->v.mins, maxs, hullmins );
|
|
VectorSubtract( ent->v.maxs, mins, hullmaxs );
|
|
hull = SV_HullForBox( hullmins, hullmaxs );
|
|
|
|
VectorCopy( ent->v.origin, offset );
|
|
}
|
|
|
|
return hull;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SV_HullForStudioModel
|
|
|
|
====================
|
|
*/
|
|
hull_t *SV_HullForStudioModel( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset, int *numhitboxes )
|
|
{
|
|
qboolean useComplexHull;
|
|
float scale = 0.5f;
|
|
hull_t *hull = NULL;
|
|
vec3_t size;
|
|
model_t *mod;
|
|
|
|
if(( mod = SV_ModelHandle( ent->v.modelindex )) == NULL )
|
|
{
|
|
*numhitboxes = 1;
|
|
return SV_HullForEntity( ent, mins, maxs, offset );
|
|
}
|
|
|
|
VectorSubtract( maxs, mins, size );
|
|
useComplexHull = false;
|
|
|
|
if( VectorIsNull( size ) && !FBitSet( svgame.globals->trace_flags, FTRACE_SIMPLEBOX ))
|
|
{
|
|
useComplexHull = true;
|
|
|
|
if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT ))
|
|
{
|
|
if( sv_clienttrace.value == 0.0f )
|
|
{
|
|
// so no way to trace studiomodels by hitboxes
|
|
// use bbox instead
|
|
useComplexHull = false;
|
|
}
|
|
else
|
|
{
|
|
scale = sv_clienttrace.value * 0.5f;
|
|
VectorSet( size, 1.0f, 1.0f, 1.0f );
|
|
}
|
|
}
|
|
}
|
|
|
|
if( FBitSet( mod->flags, STUDIO_TRACE_HITBOX ) || useComplexHull )
|
|
{
|
|
VectorScale( size, scale, size );
|
|
VectorClear( offset );
|
|
|
|
if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT ))
|
|
{
|
|
studiohdr_t *pstudio;
|
|
mstudioseqdesc_t *pseqdesc;
|
|
byte controller[4];
|
|
byte blending[2];
|
|
vec3_t angles;
|
|
int iBlend;
|
|
|
|
pstudio = Mod_StudioExtradata( mod );
|
|
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudio + pstudio->seqindex) + ent->v.sequence;
|
|
VectorCopy( ent->v.angles, angles );
|
|
|
|
SV_StudioPlayerBlend( pseqdesc, &iBlend, &angles[PITCH] );
|
|
|
|
controller[0] = controller[1] = 0x7F;
|
|
controller[2] = controller[3] = 0x7F;
|
|
blending[0] = (byte)iBlend;
|
|
blending[1] = 0;
|
|
|
|
hull = Mod_HullForStudio( mod, ent->v.frame, ent->v.sequence, angles, ent->v.origin, size, controller, blending, numhitboxes, ent );
|
|
}
|
|
else
|
|
{
|
|
hull = Mod_HullForStudio( mod, ent->v.frame, ent->v.sequence, ent->v.angles, ent->v.origin, size, ent->v.controller, ent->v.blending, numhitboxes, ent );
|
|
}
|
|
}
|
|
|
|
if( hull ) return hull;
|
|
|
|
*numhitboxes = 1;
|
|
return SV_HullForEntity( ent, mins, maxs, offset );
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
ENTITY AREA CHECKING
|
|
|
|
===============================================================================
|
|
*/
|
|
static int iTouchLinkSemaphore = 0; // prevent recursion when SV_TouchLinks is active
|
|
areanode_t sv_areanodes[AREA_NODES];
|
|
static int sv_numareanodes;
|
|
|
|
/*
|
|
===============
|
|
SV_CreateAreaNode
|
|
|
|
builds a uniformly subdivided tree for the given world size
|
|
===============
|
|
*/
|
|
areanode_t *SV_CreateAreaNode( int depth, vec3_t mins, vec3_t maxs )
|
|
{
|
|
areanode_t *anode;
|
|
vec3_t size;
|
|
vec3_t mins1, maxs1;
|
|
vec3_t mins2, maxs2;
|
|
|
|
anode = &sv_areanodes[sv_numareanodes++];
|
|
|
|
ClearLink( &anode->trigger_edicts );
|
|
ClearLink( &anode->solid_edicts );
|
|
ClearLink( &anode->portal_edicts );
|
|
|
|
if( depth == AREA_DEPTH )
|
|
{
|
|
anode->axis = -1;
|
|
anode->children[0] = anode->children[1] = NULL;
|
|
return anode;
|
|
}
|
|
|
|
VectorSubtract( maxs, mins, size );
|
|
if( size[0] > size[1] )
|
|
anode->axis = 0;
|
|
else anode->axis = 1;
|
|
|
|
anode->dist = 0.5f * ( maxs[anode->axis] + mins[anode->axis] );
|
|
VectorCopy( mins, mins1 );
|
|
VectorCopy( mins, mins2 );
|
|
VectorCopy( maxs, maxs1 );
|
|
VectorCopy( maxs, maxs2 );
|
|
|
|
maxs1[anode->axis] = mins2[anode->axis] = anode->dist;
|
|
anode->children[0] = SV_CreateAreaNode( depth+1, mins2, maxs2 );
|
|
anode->children[1] = SV_CreateAreaNode( depth+1, mins1, maxs1 );
|
|
|
|
return anode;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
SV_ClearWorld
|
|
|
|
===============
|
|
*/
|
|
void SV_ClearWorld( void )
|
|
{
|
|
int i;
|
|
|
|
SV_InitBoxHull(); // for box testing
|
|
|
|
// clear lightstyles
|
|
for( i = 0; i < MAX_LIGHTSTYLES; i++ )
|
|
{
|
|
sv.lightstyles[i].value = 256.0f;
|
|
sv.lightstyles[i].time = 0.0f;
|
|
}
|
|
|
|
memset( sv_areanodes, 0, sizeof( sv_areanodes ));
|
|
iTouchLinkSemaphore = 0;
|
|
sv_numareanodes = 0;
|
|
|
|
SV_CreateAreaNode( 0, sv.worldmodel->mins, sv.worldmodel->maxs );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
SV_UnlinkEdict
|
|
===============
|
|
*/
|
|
void SV_UnlinkEdict( edict_t *ent )
|
|
{
|
|
// not linked in anywhere
|
|
if( !ent->area.prev ) return;
|
|
|
|
RemoveLink( &ent->area );
|
|
ent->area.prev = NULL;
|
|
ent->area.next = NULL;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SV_TouchLinks
|
|
====================
|
|
*/
|
|
void SV_TouchLinks( edict_t *ent, areanode_t *node )
|
|
{
|
|
link_t *l, *next;
|
|
edict_t *touch;
|
|
hull_t *hull;
|
|
vec3_t test, offset;
|
|
model_t *mod;
|
|
|
|
// touch linked edicts
|
|
for( l = node->trigger_edicts.next; l != &node->trigger_edicts; l = next )
|
|
{
|
|
next = l->next;
|
|
touch = EDICT_FROM_AREA( l );
|
|
|
|
if( svgame.physFuncs.SV_TriggerTouch != NULL )
|
|
{
|
|
// user dll can override trigger checking (Xash3D extension)
|
|
if( !svgame.physFuncs.SV_TriggerTouch( ent, touch ))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if( touch == ent || touch->v.solid != SOLID_TRIGGER ) // disabled ?
|
|
continue;
|
|
|
|
if( touch->v.groupinfo && ent->v.groupinfo )
|
|
{
|
|
if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, ent->v.groupinfo ))
|
|
continue;
|
|
|
|
if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, ent->v.groupinfo ))
|
|
continue;
|
|
}
|
|
|
|
if( !BoundsIntersect( ent->v.absmin, ent->v.absmax, touch->v.absmin, touch->v.absmax ))
|
|
continue;
|
|
|
|
mod = SV_ModelHandle( touch->v.modelindex );
|
|
|
|
// check brush triggers accuracy
|
|
if( mod && mod->type == mod_brush )
|
|
{
|
|
// force to select bsp-hull
|
|
hull = SV_HullForBsp( touch, ent->v.mins, ent->v.maxs, offset );
|
|
|
|
// support for rotational triggers
|
|
if( FBitSet( mod->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( touch->v.angles ))
|
|
{
|
|
matrix4x4 matrix;
|
|
Matrix4x4_CreateFromEntity( matrix, touch->v.angles, offset, 1.0f );
|
|
Matrix4x4_VectorITransform( matrix, ent->v.origin, test );
|
|
}
|
|
else
|
|
{
|
|
// offset the test point appropriately for this hull.
|
|
VectorSubtract( ent->v.origin, offset, test );
|
|
}
|
|
|
|
// test hull for intersection with this model
|
|
if( PM_HullPointContents( hull, hull->firstclipnode, test ) != CONTENTS_SOLID )
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// never touch the triggers when "playersonly" is active
|
|
if( !sv.playersonly )
|
|
{
|
|
svgame.globals->time = sv.time;
|
|
svgame.dllFuncs.pfnTouch( touch, ent );
|
|
}
|
|
}
|
|
|
|
// recurse down both sides
|
|
if( node->axis == -1 ) return;
|
|
|
|
if( ent->v.absmax[node->axis] > node->dist )
|
|
SV_TouchLinks( ent, node->children[0] );
|
|
if( ent->v.absmin[node->axis] < node->dist )
|
|
SV_TouchLinks( ent, node->children[1] );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
SV_FindTouchedLeafs
|
|
|
|
===============
|
|
*/
|
|
void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode )
|
|
{
|
|
int sides;
|
|
mleaf_t *leaf;
|
|
|
|
if( node->contents == CONTENTS_SOLID )
|
|
return;
|
|
|
|
// add an efrag if the node is a leaf
|
|
if( node->contents < 0 )
|
|
{
|
|
if( ent->num_leafs > ( MAX_ENT_LEAFS - 1 ))
|
|
{
|
|
// continue counting leafs,
|
|
// so we know how many it's overrun
|
|
ent->num_leafs = (MAX_ENT_LEAFS + 1);
|
|
}
|
|
else
|
|
{
|
|
leaf = (mleaf_t *)node;
|
|
ent->leafnums[ent->num_leafs] = leaf->cluster;
|
|
ent->num_leafs++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// NODE_MIXED
|
|
sides = BOX_ON_PLANE_SIDE( ent->v.absmin, ent->v.absmax, node->plane );
|
|
|
|
if(( sides == 3 ) && ( *headnode == -1 ))
|
|
*headnode = node - sv.worldmodel->nodes;
|
|
|
|
// recurse down the contacted sides
|
|
if( sides & 1 ) SV_FindTouchedLeafs( ent, node->children[0], headnode );
|
|
if( sides & 2 ) SV_FindTouchedLeafs( ent, node->children[1], headnode );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
SV_LinkEdict
|
|
===============
|
|
*/
|
|
void SV_LinkEdict( edict_t *ent, qboolean touch_triggers )
|
|
{
|
|
areanode_t *node;
|
|
int headnode;
|
|
|
|
if( ent->area.prev ) SV_UnlinkEdict( ent ); // unlink from old position
|
|
if( ent == svgame.edicts ) return; // don't add the world
|
|
if( !SV_IsValidEdict( ent )) return; // never add freed ents
|
|
|
|
// set the abs box
|
|
svgame.dllFuncs.pfnSetAbsBox( ent );
|
|
|
|
if( ent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( ent->v.aiment ))
|
|
{
|
|
memcpy( ent->leafnums, ent->v.aiment->leafnums, sizeof( ent->leafnums ));
|
|
ent->num_leafs = ent->v.aiment->num_leafs;
|
|
ent->headnode = ent->v.aiment->headnode;
|
|
}
|
|
else
|
|
{
|
|
// link to PVS leafs
|
|
ent->num_leafs = 0;
|
|
ent->headnode = -1;
|
|
headnode = -1;
|
|
|
|
if( ent->v.modelindex )
|
|
SV_FindTouchedLeafs( ent, sv.worldmodel->nodes, &headnode );
|
|
|
|
if( ent->num_leafs > MAX_ENT_LEAFS )
|
|
{
|
|
memset( ent->leafnums, -1, sizeof( ent->leafnums ));
|
|
ent->num_leafs = 0; // so we use headnode instead
|
|
ent->headnode = headnode;
|
|
}
|
|
}
|
|
|
|
// ignore non-solid bodies
|
|
if( ent->v.solid == SOLID_NOT && ent->v.skin >= CONTENTS_EMPTY )
|
|
return;
|
|
|
|
// find the first node that the ent's box crosses
|
|
node = sv_areanodes;
|
|
|
|
while( 1 )
|
|
{
|
|
if( node->axis == -1 ) break;
|
|
if( ent->v.absmin[node->axis] > node->dist )
|
|
node = node->children[0];
|
|
else if( ent->v.absmax[node->axis] < node->dist )
|
|
node = node->children[1];
|
|
else break; // crosses the node
|
|
}
|
|
|
|
// link it in
|
|
if( ent->v.solid == SOLID_TRIGGER )
|
|
InsertLinkBefore( &ent->area, &node->trigger_edicts );
|
|
else if( ent->v.solid == SOLID_PORTAL )
|
|
InsertLinkBefore( &ent->area, &node->portal_edicts );
|
|
else InsertLinkBefore( &ent->area, &node->solid_edicts );
|
|
|
|
if( touch_triggers && !iTouchLinkSemaphore )
|
|
{
|
|
iTouchLinkSemaphore = true;
|
|
SV_TouchLinks( ent, sv_areanodes );
|
|
iTouchLinkSemaphore = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
POINT TESTING IN HULLS
|
|
|
|
===============================================================================
|
|
*/
|
|
void SV_WaterLinks( const vec3_t origin, int *pCont, areanode_t *node )
|
|
{
|
|
link_t *l, *next;
|
|
edict_t *touch;
|
|
hull_t *hull;
|
|
vec3_t test, offset;
|
|
model_t *mod;
|
|
|
|
// get water edicts
|
|
for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )
|
|
{
|
|
next = l->next;
|
|
touch = EDICT_FROM_AREA( l );
|
|
|
|
if( touch->v.solid != SOLID_NOT ) // disabled ?
|
|
continue;
|
|
|
|
if( touch->v.groupinfo )
|
|
{
|
|
if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, svs.groupmask ))
|
|
continue;
|
|
|
|
if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, svs.groupmask ))
|
|
continue;
|
|
}
|
|
|
|
mod = SV_ModelHandle( touch->v.modelindex );
|
|
|
|
// only brushes can have special contents
|
|
if( !mod || mod->type != mod_brush )
|
|
continue;
|
|
|
|
if( !BoundsIntersect( origin, origin, touch->v.absmin, touch->v.absmax ))
|
|
continue;
|
|
|
|
// check water brushes accuracy
|
|
hull = SV_HullForBsp( touch, vec3_origin, vec3_origin, offset );
|
|
|
|
// support for rotational water
|
|
if( FBitSet( mod->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( touch->v.angles ))
|
|
{
|
|
matrix4x4 matrix;
|
|
Matrix4x4_CreateFromEntity( matrix, touch->v.angles, offset, 1.0f );
|
|
Matrix4x4_VectorITransform( matrix, origin, test );
|
|
}
|
|
else
|
|
{
|
|
// offset the test point appropriately for this hull.
|
|
VectorSubtract( origin, offset, test );
|
|
}
|
|
|
|
// test hull for intersection with this model
|
|
if( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY )
|
|
continue;
|
|
|
|
// compare contents ranking
|
|
if( RankForContents( touch->v.skin ) > RankForContents( *pCont ))
|
|
*pCont = touch->v.skin; // new content has more priority
|
|
}
|
|
|
|
// recurse down both sides
|
|
if( node->axis == -1 ) return;
|
|
|
|
if( origin[node->axis] > node->dist )
|
|
SV_WaterLinks( origin, pCont, node->children[0] );
|
|
if( origin[node->axis] < node->dist )
|
|
SV_WaterLinks( origin, pCont, node->children[1] );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
SV_TruePointContents
|
|
|
|
=============
|
|
*/
|
|
int SV_TruePointContents( const vec3_t p )
|
|
{
|
|
int cont;
|
|
|
|
// sanity check
|
|
if( !p ) return CONTENTS_NONE;
|
|
|
|
// get base contents from world
|
|
cont = PM_HullPointContents( &sv.worldmodel->hulls[0], 0, p );
|
|
|
|
// check all water entities
|
|
SV_WaterLinks( p, &cont, sv_areanodes );
|
|
|
|
return cont;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
SV_PointContents
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT SV_PointContents( const vec3_t p )
|
|
{
|
|
int cont = SV_TruePointContents( p );
|
|
|
|
if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN )
|
|
cont = CONTENTS_WATER;
|
|
return cont;
|
|
}
|
|
|
|
//===========================================================================
|
|
|
|
/*
|
|
============
|
|
SV_TestEntityPosition
|
|
|
|
returns true if the entity is in solid currently
|
|
============
|
|
*/
|
|
qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker )
|
|
{
|
|
qboolean monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false;
|
|
trace_t trace;
|
|
|
|
if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT ))
|
|
{
|
|
// to avoid falling through tracktrain update client mins\maxs here
|
|
if( FBitSet( ent->v.flags, FL_DUCKING ))
|
|
SV_SetMinMaxSize( ent, svgame.pmove->player_mins[1], svgame.pmove->player_maxs[1], true );
|
|
else SV_SetMinMaxSize( ent, svgame.pmove->player_mins[0], svgame.pmove->player_maxs[0], true );
|
|
}
|
|
|
|
trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip );
|
|
|
|
if( SV_IsValidEdict( blocker ) && SV_IsValidEdict( trace.ent ))
|
|
{
|
|
if( trace.ent->v.movetype == MOVETYPE_PUSH || trace.ent == blocker )
|
|
return trace.startsolid;
|
|
return false;
|
|
}
|
|
|
|
return trace.startsolid;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
LINE TESTING IN HULLS
|
|
|
|
===============================================================================
|
|
*/
|
|
/*
|
|
==================
|
|
SV_ClipMoveToEntity
|
|
|
|
Handles selection or creation of a clipping hull, and offseting (and
|
|
eventually rotation) of the end points
|
|
==================
|
|
*/
|
|
void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace )
|
|
{
|
|
hull_t *hull;
|
|
model_t *model;
|
|
vec3_t start_l, end_l;
|
|
vec3_t offset, temp;
|
|
int last_hitgroup;
|
|
trace_t trace_hitbox;
|
|
int i, j, hullcount;
|
|
qboolean rotated, transform_bbox;
|
|
matrix4x4 matrix;
|
|
|
|
memset( trace, 0, sizeof( trace_t ));
|
|
VectorCopy( end, trace->endpos );
|
|
trace->fraction = 1.0f;
|
|
trace->allsolid = 1;
|
|
|
|
model = SV_ModelHandle( ent->v.modelindex );
|
|
|
|
if( model && model->type == mod_studio )
|
|
{
|
|
hull = SV_HullForStudioModel( ent, mins, maxs, offset, &hullcount );
|
|
}
|
|
else
|
|
{
|
|
hull = SV_HullForEntity( ent, mins, maxs, offset );
|
|
hullcount = 1;
|
|
}
|
|
|
|
// rotate start and end into the models frame of reference
|
|
if(( ent->v.solid == SOLID_BSP || ent->v.solid == SOLID_PORTAL ) && !VectorIsNull( ent->v.angles ))
|
|
rotated = true;
|
|
else rotated = false;
|
|
|
|
if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ))
|
|
{
|
|
// keep untransformed bbox less than 45 degress or train on subtransit.bsp will stop working
|
|
if(( check_angles( ent->v.angles[0] ) || check_angles( ent->v.angles[2] )) && !VectorIsNull( mins ))
|
|
transform_bbox = true;
|
|
else transform_bbox = false;
|
|
}
|
|
else transform_bbox = false;
|
|
|
|
if( rotated )
|
|
{
|
|
vec3_t out_mins, out_maxs;
|
|
|
|
if( transform_bbox )
|
|
Matrix4x4_CreateFromEntity( matrix, ent->v.angles, ent->v.origin, 1.0f );
|
|
else Matrix4x4_CreateFromEntity( matrix, ent->v.angles, offset, 1.0f );
|
|
|
|
Matrix4x4_VectorITransform( matrix, start, start_l );
|
|
Matrix4x4_VectorITransform( matrix, end, end_l );
|
|
|
|
if( transform_bbox )
|
|
{
|
|
World_TransformAABB( matrix, mins, maxs, out_mins, out_maxs );
|
|
VectorSubtract( hull->clip_mins, out_mins, offset ); // calc new local offset
|
|
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
if( start_l[j] >= 0.0f )
|
|
start_l[j] -= offset[j];
|
|
else start_l[j] += offset[j];
|
|
if( end_l[j] >= 0.0f )
|
|
end_l[j] -= offset[j];
|
|
else end_l[j] += offset[j];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorSubtract( start, offset, start_l );
|
|
VectorSubtract( end, offset, end_l );
|
|
}
|
|
|
|
if( hullcount == 1 )
|
|
{
|
|
PM_RecursiveHullCheck( hull, hull->firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)trace );
|
|
}
|
|
else
|
|
{
|
|
last_hitgroup = 0;
|
|
|
|
for( i = 0; i < hullcount; i++ )
|
|
{
|
|
memset( &trace_hitbox, 0, sizeof( trace_t ));
|
|
VectorCopy( end, trace_hitbox.endpos );
|
|
trace_hitbox.fraction = 1.0;
|
|
trace_hitbox.allsolid = 1;
|
|
|
|
PM_RecursiveHullCheck( &hull[i], hull[i].firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)&trace_hitbox );
|
|
|
|
if( i == 0 || trace_hitbox.allsolid || trace_hitbox.startsolid || trace_hitbox.fraction < trace->fraction )
|
|
{
|
|
if( trace->startsolid )
|
|
{
|
|
*trace = trace_hitbox;
|
|
trace->startsolid = true;
|
|
}
|
|
else *trace = trace_hitbox;
|
|
|
|
last_hitgroup = i;
|
|
}
|
|
}
|
|
|
|
trace->hitgroup = Mod_HitgroupForStudioHull( last_hitgroup );
|
|
}
|
|
|
|
if( trace->fraction != 1.0f )
|
|
{
|
|
// compute endpos (generic case)
|
|
VectorLerp( start, trace->fraction, end, trace->endpos );
|
|
|
|
if( rotated )
|
|
{
|
|
// transform plane
|
|
VectorCopy( trace->plane.normal, temp );
|
|
Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist );
|
|
}
|
|
else
|
|
{
|
|
trace->plane.dist = DotProduct( trace->endpos, trace->plane.normal );
|
|
}
|
|
}
|
|
|
|
if( trace->fraction < 1.0f || trace->startsolid )
|
|
trace->ent = ent;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_PortalCSG
|
|
|
|
a portal is flush with a world surface behind it. this causes problems. namely that we can't pass through the portal plane
|
|
if the bsp behind it prevents out origin from getting through. so if the trace was clipped and ended infront of the portal,
|
|
continue the trace to the edges of the portal cutout instead.
|
|
==================
|
|
*/
|
|
void SV_PortalCSG( edict_t *portal, const vec3_t trace_mins, const vec3_t trace_maxs, const vec3_t start, const vec3_t end, trace_t *trace )
|
|
{
|
|
vec4_t planes[6]; //far, near, right, left, up, down
|
|
int plane, k;
|
|
vec3_t worldpos;
|
|
float bestfrac;
|
|
int hitplane;
|
|
model_t *model;
|
|
float portalradius;
|
|
|
|
// only run this code if we impacted on the portal's parent.
|
|
if( trace->fraction == 1.0f && !trace->startsolid )
|
|
return;
|
|
|
|
// decide which clipping hull to use, based on the size
|
|
model = SV_ModelHandle( portal->v.modelindex );
|
|
|
|
if( !model || model->type != mod_brush )
|
|
return;
|
|
|
|
// make sure we use a sane valid position.
|
|
if( trace->startsolid ) VectorCopy( start, worldpos );
|
|
else VectorCopy( trace->endpos, worldpos );
|
|
|
|
// determine the csg area. normals should be facing in
|
|
AngleVectors( portal->v.angles, planes[1], planes[3], planes[5] );
|
|
VectorNegate(planes[1], planes[0]);
|
|
VectorNegate(planes[3], planes[2]);
|
|
VectorNegate(planes[5], planes[4]);
|
|
|
|
portalradius = model->radius * 0.5f;
|
|
planes[0][3] = DotProduct( portal->v.origin, planes[0] ) - (4.0f / 32.0f);
|
|
planes[1][3] = DotProduct( portal->v.origin, planes[1] ) - (4.0f / 32.0f); //an epsilon beyond the portal
|
|
planes[2][3] = DotProduct( portal->v.origin, planes[2] ) - portalradius;
|
|
planes[3][3] = DotProduct( portal->v.origin, planes[3] ) - portalradius;
|
|
planes[4][3] = DotProduct( portal->v.origin, planes[4] ) - portalradius;
|
|
planes[5][3] = DotProduct( portal->v.origin, planes[5] ) - portalradius;
|
|
|
|
// if we're actually inside the csg region
|
|
for( plane = 0; plane < 6; plane++ )
|
|
{
|
|
float d = DotProduct( worldpos, planes[plane] );
|
|
vec3_t nearest;
|
|
|
|
for( k = 0; k < 3; k++ )
|
|
nearest[k] = (planes[plane][k]>=0) ? trace_maxs[k] : trace_mins[k];
|
|
|
|
// front plane gets further away with side
|
|
if( !plane )
|
|
{
|
|
planes[plane][3] -= DotProduct( nearest, planes[plane] );
|
|
}
|
|
else if( plane > 1 )
|
|
{
|
|
// side planes get nearer with size
|
|
planes[plane][3] += 24; // DotProduct( nearest, planes[plane] );
|
|
}
|
|
|
|
if( d - planes[plane][3] >= 0 )
|
|
continue; // endpos is inside
|
|
else return; // end is already outside
|
|
}
|
|
|
|
// yup, we're inside, the trace shouldn't end where it actually did
|
|
bestfrac = 1;
|
|
hitplane = -1;
|
|
|
|
for( plane = 0; plane < 6; plane++ )
|
|
{
|
|
float ds = DotProduct( start, planes[plane] ) - planes[plane][3];
|
|
float de = DotProduct( end, planes[plane] ) - planes[plane][3];
|
|
float frac;
|
|
|
|
if( ds >= 0 && de < 0 )
|
|
{
|
|
frac = (ds) / (ds - de);
|
|
if( frac < bestfrac )
|
|
{
|
|
if( frac < 0 )
|
|
frac = 0;
|
|
bestfrac = frac;
|
|
hitplane = plane;
|
|
}
|
|
}
|
|
}
|
|
|
|
trace->startsolid = trace->allsolid = false;
|
|
|
|
// if we cross the front of the portal, don't shorten the trace,
|
|
// that will artificially clip us
|
|
if( hitplane == 0 && trace->fraction > bestfrac )
|
|
return;
|
|
|
|
// okay, elongate to clip to the portal hole properly.
|
|
VectorLerp( start, bestfrac, end, trace->endpos );
|
|
trace->fraction = bestfrac;
|
|
|
|
if( hitplane >= 0 )
|
|
{
|
|
VectorCopy( planes[hitplane], trace->plane.normal );
|
|
trace->plane.dist = planes[hitplane][3];
|
|
if( hitplane == 1 ) trace->ent = portal;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_CustomClipMoveToEntity
|
|
|
|
A part of physics engine implementation
|
|
or custom physics implementation
|
|
==================
|
|
*/
|
|
void SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace )
|
|
{
|
|
// initialize custom trace
|
|
memset( trace, 0, sizeof( trace_t ));
|
|
VectorCopy( end, trace->endpos );
|
|
trace->allsolid = true;
|
|
trace->fraction = 1.0f;
|
|
|
|
if( svgame.physFuncs.ClipMoveToEntity != NULL )
|
|
{
|
|
// do custom sweep test
|
|
svgame.physFuncs.ClipMoveToEntity( ent, start, mins, maxs, end, trace );
|
|
}
|
|
else
|
|
{
|
|
// function is missed, so we didn't hit anything
|
|
trace->allsolid = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SV_ClipToEntity
|
|
|
|
generic clip function
|
|
====================
|
|
*/
|
|
static qboolean SV_ClipToEntity( edict_t *touch, moveclip_t *clip )
|
|
{
|
|
trace_t trace;
|
|
model_t *mod;
|
|
|
|
if( touch->v.groupinfo && SV_IsValidEdict( clip->passedict ) && clip->passedict->v.groupinfo != 0 )
|
|
{
|
|
if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo ))
|
|
return true;
|
|
|
|
if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo ))
|
|
return true;
|
|
}
|
|
|
|
if( touch == clip->passedict || touch->v.solid == SOLID_NOT )
|
|
return true;
|
|
|
|
if( touch->v.solid == SOLID_TRIGGER )
|
|
Host_Error( "trigger in clipping list\n" );
|
|
|
|
// custom user filter
|
|
if( svgame.dllFuncs2.pfnShouldCollide )
|
|
{
|
|
if( !svgame.dllFuncs2.pfnShouldCollide( touch, clip->passedict ))
|
|
return true;
|
|
}
|
|
|
|
// monsterclip filter (solid custom is a static or dynamic bodies)
|
|
if( touch->v.solid == SOLID_BSP || touch->v.solid == SOLID_CUSTOM )
|
|
{
|
|
// func_monsterclip works only with monsters that have same flag!
|
|
if( FBitSet( touch->v.flags, FL_MONSTERCLIP ) && !clip->monsterclip )
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// ignore all monsters but pushables
|
|
if( clip->type == MOVE_NOMONSTERS && touch->v.movetype != MOVETYPE_PUSHSTEP )
|
|
return true;
|
|
}
|
|
|
|
mod = SV_ModelHandle( touch->v.modelindex );
|
|
|
|
if( mod && mod->type == mod_brush && clip->ignoretrans )
|
|
{
|
|
// we ignore brushes with rendermode != kRenderNormal and without FL_WORLDBRUSH set
|
|
if( touch->v.rendermode != kRenderNormal && !FBitSet( touch->v.flags, FL_WORLDBRUSH ))
|
|
return true;
|
|
}
|
|
|
|
if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax ))
|
|
return true;
|
|
|
|
// aditional check to intersects clients with sphere
|
|
if( touch->v.solid != SOLID_SLIDEBOX && !SV_CheckSphereIntersection( touch, clip->start, clip->end ))
|
|
return true;
|
|
|
|
// Xash3D extension
|
|
if( SV_IsValidEdict( clip->passedict ) && clip->passedict->v.solid == SOLID_TRIGGER )
|
|
{
|
|
// never collide items and player (because call "give" always stuck item in player
|
|
// and total trace returns fail (old half-life bug)
|
|
// items touch should be done in SV_TouchLinks not here
|
|
if( FBitSet( touch->v.flags, FL_CLIENT|FL_FAKECLIENT ))
|
|
return true;
|
|
}
|
|
|
|
// g-cont. make sure what size is really zero - check all the components
|
|
if( SV_IsValidEdict( clip->passedict ) && !VectorIsNull( clip->passedict->v.size ) && VectorIsNull( touch->v.size ))
|
|
return true; // points never interact
|
|
|
|
// might intersect, so do an exact clip
|
|
if( clip->trace.allsolid ) return false;
|
|
|
|
if( SV_IsValidEdict( clip->passedict ))
|
|
{
|
|
if( touch->v.owner == clip->passedict )
|
|
return true; // don't clip against own missiles
|
|
if( clip->passedict->v.owner == touch )
|
|
return true; // don't clip against owner
|
|
}
|
|
|
|
// make sure we don't hit the world if we're inside the portal
|
|
if( touch->v.solid == SOLID_PORTAL )
|
|
SV_PortalCSG( touch, clip->mins, clip->maxs, clip->start, clip->end, &clip->trace );
|
|
|
|
if( touch->v.solid == SOLID_CUSTOM )
|
|
SV_CustomClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace );
|
|
else if( FBitSet( touch->v.flags, FL_MONSTER ))
|
|
SV_ClipMoveToEntity( touch, clip->start, clip->mins2, clip->maxs2, clip->end, &trace );
|
|
else SV_ClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace );
|
|
|
|
clip->trace = World_CombineTraces( &clip->trace, &trace, touch );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SV_ClipToLinks
|
|
|
|
Mins and maxs enclose the entire area swept by the move
|
|
====================
|
|
*/
|
|
static void SV_ClipToLinks( areanode_t *node, moveclip_t *clip )
|
|
{
|
|
link_t *l, *next;
|
|
edict_t *touch;
|
|
|
|
// touch linked edicts
|
|
for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )
|
|
{
|
|
next = l->next;
|
|
|
|
touch = EDICT_FROM_AREA( l );
|
|
|
|
if( !SV_ClipToEntity( touch, clip ))
|
|
return; // trace.allsoild
|
|
}
|
|
|
|
// recurse down both sides
|
|
if( node->axis == -1 ) return;
|
|
|
|
if( clip->boxmaxs[node->axis] > node->dist )
|
|
SV_ClipToLinks( node->children[0], clip );
|
|
if( clip->boxmins[node->axis] < node->dist )
|
|
SV_ClipToLinks( node->children[1], clip );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SV_ClipToPortals
|
|
|
|
Mins and maxs enclose the entire area swept by the move
|
|
====================
|
|
*/
|
|
static void SV_ClipToPortals( areanode_t *node, moveclip_t *clip )
|
|
{
|
|
link_t *l, *next;
|
|
edict_t *touch;
|
|
|
|
// touch linked edicts
|
|
for( l = node->portal_edicts.next; l != &node->portal_edicts; l = next )
|
|
{
|
|
next = l->next;
|
|
|
|
touch = EDICT_FROM_AREA( l );
|
|
|
|
if( !SV_ClipToEntity( touch, clip ))
|
|
return; // trace.allsoild
|
|
}
|
|
|
|
// recurse down both sides
|
|
if( node->axis == -1 ) return;
|
|
|
|
if( clip->boxmaxs[node->axis] > node->dist )
|
|
SV_ClipToPortals( node->children[0], clip );
|
|
if( clip->boxmins[node->axis] < node->dist )
|
|
SV_ClipToPortals( node->children[1], clip );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SV_ClipToWorldBrush
|
|
|
|
Mins and maxs enclose the entire area swept by the move
|
|
====================
|
|
*/
|
|
void SV_ClipToWorldBrush( areanode_t *node, moveclip_t *clip )
|
|
{
|
|
link_t *l, *next;
|
|
edict_t *touch;
|
|
trace_t trace;
|
|
|
|
for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next )
|
|
{
|
|
next = l->next;
|
|
|
|
touch = EDICT_FROM_AREA( l );
|
|
|
|
if( touch->v.solid != SOLID_BSP || touch == clip->passedict || !( touch->v.flags & FL_WORLDBRUSH ))
|
|
continue;
|
|
|
|
if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax ))
|
|
continue;
|
|
|
|
if( clip->trace.allsolid ) return;
|
|
|
|
SV_ClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace );
|
|
|
|
clip->trace = World_CombineTraces( &clip->trace, &trace, touch );
|
|
}
|
|
|
|
// recurse down both sides
|
|
if( node->axis == -1 ) return;
|
|
|
|
if( clip->boxmaxs[node->axis] > node->dist )
|
|
SV_ClipToWorldBrush( node->children[0], clip );
|
|
|
|
if( clip->boxmins[node->axis] < node->dist )
|
|
SV_ClipToWorldBrush( node->children[1], clip );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_Move
|
|
==================
|
|
*/
|
|
trace_t SV_Move( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e, qboolean monsterclip )
|
|
{
|
|
moveclip_t clip;
|
|
vec3_t trace_endpos;
|
|
float trace_fraction;
|
|
|
|
memset( &clip, 0, sizeof( moveclip_t ));
|
|
SV_ClipMoveToEntity( EDICT_NUM( 0 ), start, mins, maxs, end, &clip.trace );
|
|
|
|
if( clip.trace.fraction != 0.0f )
|
|
{
|
|
VectorCopy( clip.trace.endpos, trace_endpos );
|
|
trace_fraction = clip.trace.fraction;
|
|
clip.trace.fraction = 1.0f;
|
|
clip.start = start;
|
|
clip.end = trace_endpos;
|
|
clip.type = (type & 0xFF);
|
|
clip.ignoretrans = type >> 8;
|
|
clip.monsterclip = false;
|
|
clip.passedict = (e) ? e : EDICT_NUM( 0 );
|
|
clip.mins = mins;
|
|
clip.maxs = maxs;
|
|
|
|
if( monsterclip && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ))
|
|
clip.monsterclip = true;
|
|
|
|
if( clip.type == MOVE_MISSILE )
|
|
{
|
|
VectorSet( clip.mins2, -15.0f, -15.0f, -15.0f );
|
|
VectorSet( clip.maxs2, 15.0f, 15.0f, 15.0f );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( mins, clip.mins2 );
|
|
VectorCopy( maxs, clip.maxs2 );
|
|
}
|
|
|
|
World_MoveBounds( start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs );
|
|
SV_ClipToLinks( sv_areanodes, &clip );
|
|
SV_ClipToPortals( sv_areanodes, &clip );
|
|
|
|
clip.trace.fraction *= trace_fraction;
|
|
svgame.globals->trace_ent = clip.trace.ent;
|
|
}
|
|
|
|
SV_CopyTraceToGlobal( &clip.trace );
|
|
|
|
return clip.trace;
|
|
}
|
|
|
|
trace_t SV_MoveNormal( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e )
|
|
{
|
|
return SV_Move( start, mins, maxs, end, type, e, false );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_MoveNoEnts
|
|
==================
|
|
*/
|
|
trace_t SV_MoveNoEnts( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e )
|
|
{
|
|
moveclip_t clip;
|
|
vec3_t trace_endpos;
|
|
float trace_fraction;
|
|
|
|
memset( &clip, 0, sizeof( moveclip_t ));
|
|
SV_ClipMoveToEntity( EDICT_NUM( 0 ), start, mins, maxs, end, &clip.trace );
|
|
|
|
if( clip.trace.fraction != 0.0f )
|
|
{
|
|
VectorCopy( clip.trace.endpos, trace_endpos );
|
|
trace_fraction = clip.trace.fraction;
|
|
clip.trace.fraction = 1.0f;
|
|
clip.start = start;
|
|
clip.end = trace_endpos;
|
|
clip.type = (type & 0xFF);
|
|
clip.ignoretrans = type >> 8;
|
|
clip.monsterclip = false;
|
|
clip.passedict = (e) ? e : EDICT_NUM( 0 );
|
|
clip.mins = mins;
|
|
clip.maxs = maxs;
|
|
|
|
VectorCopy( mins, clip.mins2 );
|
|
VectorCopy( maxs, clip.maxs2 );
|
|
|
|
World_MoveBounds( start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs );
|
|
SV_ClipToWorldBrush( sv_areanodes, &clip );
|
|
SV_ClipToPortals( sv_areanodes, &clip );
|
|
|
|
clip.trace.fraction *= trace_fraction;
|
|
svgame.globals->trace_ent = clip.trace.ent;
|
|
}
|
|
|
|
SV_CopyTraceToGlobal( &clip.trace );
|
|
|
|
return clip.trace;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_TraceSurface
|
|
|
|
find the face where the traceline hit
|
|
assume pTextureEntity is valid
|
|
==================
|
|
*/
|
|
msurface_t *SV_TraceSurface( edict_t *ent, const vec3_t start, const vec3_t end )
|
|
{
|
|
matrix4x4 matrix;
|
|
model_t *bmodel;
|
|
hull_t *hull;
|
|
vec3_t start_l, end_l;
|
|
vec3_t offset;
|
|
|
|
bmodel = SV_ModelHandle( ent->v.modelindex );
|
|
if( !bmodel || bmodel->type != mod_brush )
|
|
return NULL;
|
|
|
|
hull = SV_HullForBsp( ent, vec3_origin, vec3_origin, offset );
|
|
|
|
VectorSubtract( start, offset, start_l );
|
|
VectorSubtract( end, offset, end_l );
|
|
|
|
// rotate start and end into the models frame of reference
|
|
if( !VectorIsNull( ent->v.angles ))
|
|
{
|
|
Matrix4x4_CreateFromEntity( matrix, ent->v.angles, offset, 1.0f );
|
|
Matrix4x4_VectorITransform( matrix, start, start_l );
|
|
Matrix4x4_VectorITransform( matrix, end, end_l );
|
|
}
|
|
|
|
return PM_RecursiveSurfCheck( bmodel, &bmodel->nodes[hull->firstclipnode], start_l, end_l );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_TraceTexture
|
|
|
|
find the face where the traceline hit
|
|
assume pTextureEntity is valid
|
|
==================
|
|
*/
|
|
const char *SV_TraceTexture( edict_t *ent, const vec3_t start, const vec3_t end )
|
|
{
|
|
msurface_t *surf = SV_TraceSurface( ent, start, end );
|
|
|
|
if( !surf || !surf->texinfo || !surf->texinfo->texture )
|
|
return NULL;
|
|
|
|
return surf->texinfo->texture->name;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_MoveToss
|
|
==================
|
|
*/
|
|
trace_t SV_MoveToss( edict_t *tossent, edict_t *ignore )
|
|
{
|
|
float gravity;
|
|
vec3_t move, end;
|
|
vec3_t original_origin;
|
|
vec3_t original_velocity;
|
|
vec3_t original_angles;
|
|
vec3_t original_avelocity;
|
|
trace_t trace;
|
|
int i;
|
|
|
|
VectorCopy( tossent->v.origin, original_origin );
|
|
VectorCopy( tossent->v.velocity, original_velocity );
|
|
VectorCopy( tossent->v.angles, original_angles );
|
|
VectorCopy( tossent->v.avelocity, original_avelocity );
|
|
gravity = tossent->v.gravity * svgame.movevars.gravity * 0.05f;
|
|
|
|
for( i = 0; i < 200; i++ )
|
|
{
|
|
SV_CheckVelocity( tossent );
|
|
tossent->v.velocity[2] -= gravity;
|
|
VectorMA( tossent->v.angles, 0.05f, tossent->v.avelocity, tossent->v.angles );
|
|
VectorScale( tossent->v.velocity, 0.05f, move );
|
|
VectorAdd( tossent->v.origin, move, end );
|
|
trace = SV_Move( tossent->v.origin, tossent->v.mins, tossent->v.maxs, end, MOVE_NORMAL, tossent, false );
|
|
VectorCopy( trace.endpos, tossent->v.origin );
|
|
if( trace.fraction < 1.0f ) break;
|
|
}
|
|
|
|
VectorCopy( original_origin, tossent->v.origin );
|
|
VectorCopy( original_velocity, tossent->v.velocity );
|
|
VectorCopy( original_angles, tossent->v.angles );
|
|
VectorCopy( original_avelocity, tossent->v.avelocity );
|
|
|
|
return trace;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
LIGHTING INFO
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
static vec3_t sv_pointColor;
|
|
|
|
/*
|
|
=================
|
|
SV_RecursiveLightPoint
|
|
=================
|
|
*/
|
|
static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec3_t start, const vec3_t end )
|
|
{
|
|
float front, back, scale, frac;
|
|
int i, map, side, size;
|
|
float ds, dt, s, t;
|
|
int sample_size;
|
|
msurface_t *surf;
|
|
mtexinfo_t *tex;
|
|
mextrasurf_t *info;
|
|
color24 *lm;
|
|
vec3_t mid;
|
|
|
|
// didn't hit anything
|
|
if( !node || node->contents < 0 )
|
|
return false;
|
|
|
|
// calculate mid point
|
|
front = PlaneDiff( start, node->plane );
|
|
back = PlaneDiff( end, node->plane );
|
|
|
|
side = front < 0.0f;
|
|
if(( back < 0.0f ) == side )
|
|
return SV_RecursiveLightPoint( model, node->children[side], start, end );
|
|
|
|
frac = front / ( front - back );
|
|
|
|
VectorLerp( start, frac, end, mid );
|
|
|
|
// co down front side
|
|
if( SV_RecursiveLightPoint( model, node->children[side], start, mid ))
|
|
return true; // hit something
|
|
|
|
if(( back < 0.0f ) == side )
|
|
return false;// didn't hit anything
|
|
|
|
// check for impact on this node
|
|
surf = model->surfaces + node->firstsurface;
|
|
|
|
for( i = 0; i < node->numsurfaces; i++, surf++ )
|
|
{
|
|
int smax, tmax;
|
|
|
|
tex = surf->texinfo;
|
|
info = surf->info;
|
|
|
|
if( FBitSet( surf->flags, SURF_DRAWTILED ))
|
|
continue; // no lightmaps
|
|
|
|
s = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3];
|
|
t = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3];
|
|
|
|
if( s < info->lightmapmins[0] || t < info->lightmapmins[1] )
|
|
continue;
|
|
|
|
ds = s - info->lightmapmins[0];
|
|
dt = t - info->lightmapmins[1];
|
|
|
|
if ( ds > info->lightextents[0] || dt > info->lightextents[1] )
|
|
continue;
|
|
|
|
if( !surf->samples )
|
|
return true;
|
|
|
|
sample_size = Mod_SampleSizeForFace( surf );
|
|
smax = (info->lightextents[0] / sample_size) + 1;
|
|
tmax = (info->lightextents[1] / sample_size) + 1;
|
|
ds /= sample_size;
|
|
dt /= sample_size;
|
|
|
|
VectorClear( sv_pointColor );
|
|
|
|
lm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds );
|
|
size = smax * tmax;
|
|
|
|
for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )
|
|
{
|
|
scale = sv.lightstyles[surf->styles[map]].value;
|
|
|
|
sv_pointColor[0] += lm->r * scale;
|
|
sv_pointColor[1] += lm->g * scale;
|
|
sv_pointColor[2] += lm->b * scale;
|
|
|
|
lm += size; // skip to next lightmap
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// go down back side
|
|
return SV_RecursiveLightPoint( model, node->children[!side], mid, end );
|
|
}
|
|
|
|
void SV_RunLightStyles( void )
|
|
{
|
|
int i, ofs;
|
|
lightstyle_t *ls;
|
|
float scale;
|
|
|
|
scale = sv_lighting_modulate->value;
|
|
|
|
// run lightstyles animation
|
|
for( i = 0, ls = sv.lightstyles; i < MAX_LIGHTSTYLES; i++, ls++ )
|
|
{
|
|
ls->time += sv.frametime;
|
|
ofs = (ls->time * 10);
|
|
|
|
if( ls->length == 0 ) ls->value = scale; // disable this light
|
|
else if( ls->length == 1 ) ls->value = ( ls->map[0] / 12.0f ) * scale;
|
|
else ls->value = ( ls->map[ofs % ls->length] / 12.0f ) * scale;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_SetLightStyle
|
|
|
|
needs to get correct working SV_LightPoint
|
|
==================
|
|
*/
|
|
void SV_SetLightStyle( int style, const char* s, float f )
|
|
{
|
|
int j, k;
|
|
|
|
Q_strncpy( sv.lightstyles[style].pattern, s, sizeof( sv.lightstyles[0].pattern ));
|
|
sv.lightstyles[style].time = f;
|
|
|
|
j = Q_strlen( s );
|
|
sv.lightstyles[style].length = j;
|
|
|
|
for( k = 0; k < j; k++ )
|
|
sv.lightstyles[style].map[k] = (float)(s[k] - 'a');
|
|
|
|
if( sv.state != ss_active ) return;
|
|
|
|
// tell the clients about changed lightstyle
|
|
MSG_BeginServerCmd( &sv.reliable_datagram, svc_lightstyle );
|
|
MSG_WriteByte( &sv.reliable_datagram, style );
|
|
MSG_WriteString( &sv.reliable_datagram, sv.lightstyles[style].pattern );
|
|
MSG_WriteFloat( &sv.reliable_datagram, sv.lightstyles[style].time );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_GetLightStyle
|
|
|
|
needs to get correct working SV_LightPoint
|
|
==================
|
|
*/
|
|
const char *SV_GetLightStyle( int style )
|
|
{
|
|
if( style < 0 ) style = 0;
|
|
if( style >= MAX_LIGHTSTYLES )
|
|
Host_Error( "SV_GetLightStyle: style: %i >= %d", style, MAX_LIGHTSTYLES );
|
|
|
|
return sv.lightstyles[style].pattern;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_LightForEntity
|
|
|
|
grab the ambient lighting color for current point
|
|
==================
|
|
*/
|
|
int SV_LightForEntity( edict_t *pEdict )
|
|
{
|
|
vec3_t start, end;
|
|
|
|
if( FBitSet( pEdict->v.effects, EF_FULLBRIGHT ) || !sv.worldmodel->lightdata )
|
|
return 255;
|
|
|
|
// player has more precision light level that come from client-side
|
|
if( FBitSet( pEdict->v.flags, FL_CLIENT ))
|
|
return pEdict->v.light_level;
|
|
|
|
VectorCopy( pEdict->v.origin, start );
|
|
VectorCopy( pEdict->v.origin, end );
|
|
|
|
if( FBitSet( pEdict->v.effects, EF_INVLIGHT ))
|
|
end[2] = start[2] + world.size[2];
|
|
else end[2] = start[2] - world.size[2];
|
|
VectorSet( sv_pointColor, 1.0f, 1.0f, 1.0f );
|
|
|
|
SV_RecursiveLightPoint( sv.worldmodel, sv.worldmodel->nodes, start, end );
|
|
|
|
return VectorAvg( sv_pointColor );
|
|
}
|