After doing the tutorial below, you can test out a [rough] sample map with supporting QuakeC to try out rotating doors.
1. Sample map ... Download | Thread about the QuakeC
2. With the standard NQ protocol using bytes for angles, movement especially on a rotating platform will feel jerky. DarkPlaces uses floats instead of bytes which feels really smooth, but a short would probably feel smooth too. Thread offering one quicky solution to this (read thread).
3. To create a map supporting rotating entities the hmap2 compiler is the only one that offers rotating brush support for Quake that is proper. Ordinarily, a moving brush's "handle point" is the lower bottom left corner and with hmap2 rotating entities can have this be the true center for applicable entities.
Engine Instructions
Avirox has commentary in the Quakeworld version tutorial (read thread), I'm just posting code.
2 files altered: sv_phys.c and world.c
1. sv_phys.c
1a. Find void SV_Physics_Pusher (edict_t *ent) function
Add the yellow:
if (movetime)
{
//ROTATE START
if ((ent->v.avelocity[0] || ent->v.avelocity[1] || ent->v.avelocity[2]) && ent->v.solid == SOLID_BSP)
SV_PushRotate (ent, host_frametime);
else
//ROTATE END
SV_PushMove (ent, movetime); // advances ent->v.ltime if not blocked
}
1b. Add SV_PushRotate immediately *above* the SV_Physics_Pusher function.
Code: Select all
/*
============
SV_PushRotate
============
*/
void SV_PushRotate (edict_t *pusher, float movetime)
{
int i, e;
edict_t *check, *block;
vec3_t move, a, amove;
vec3_t entorig, pushorig;
int num_moved;
edict_t *moved_edict[MAX_EDICTS];
vec3_t moved_from[MAX_EDICTS];
vec3_t org, org2;
vec3_t forward, right, up;
if (!pusher->v.avelocity[0] && !pusher->v.avelocity[1] && !pusher->v.avelocity[2])
{
pusher->v.ltime += movetime;
return;
}
for (i=0 ; i<3 ; i++)
amove[i] = pusher->v.avelocity[i] * movetime;
VectorSubtract (vec3_origin, amove, a);
AngleVectors (a, forward, right, up);
VectorCopy (pusher->v.angles, pushorig);
// move the pusher to it's final position
VectorAdd (pusher->v.angles, amove, pusher->v.angles);
pusher->v.ltime += movetime;
SV_LinkEdict (pusher, false);
// see if any solid entities are inside the final position
num_moved = 0;
check = NEXT_EDICT(sv.edicts);
for (e=1 ; e<sv.num_edicts ; e++, check = NEXT_EDICT(check))
{
if (check->free)
continue;
if (check->v.movetype == MOVETYPE_PUSH
|| check->v.movetype == MOVETYPE_NONE
// || check->v.movetype == MOVETYPE_FOLLOW
|| check->v.movetype == MOVETYPE_NOCLIP)
continue;
// if the entity is standing on the pusher, it will definately be moved
if ( ! ( ((int)check->v.flags & FL_ONGROUND)
&& PROG_TO_EDICT(check->v.groundentity) == pusher) )
{
if ( check->v.absmin[0] >= pusher->v.absmax[0]
|| check->v.absmin[1] >= pusher->v.absmax[1]
|| check->v.absmin[2] >= pusher->v.absmax[2]
|| check->v.absmax[0] <= pusher->v.absmin[0]
|| check->v.absmax[1] <= pusher->v.absmin[1]
|| check->v.absmax[2] <= pusher->v.absmin[2] )
continue;
// see if the ent's bbox is inside the pusher's final position
if (!SV_TestEntityPosition (check))
continue;
}
// remove the onground flag for non-players
if (check->v.movetype != MOVETYPE_WALK)
check->v.flags = (int)check->v.flags & ~FL_ONGROUND;
VectorCopy (check->v.origin, entorig);
VectorCopy (check->v.origin, moved_from[num_moved]);
moved_edict[num_moved] = check;
num_moved++;
// calculate destination position
VectorSubtract (check->v.origin, pusher->v.origin, org);
org2[0] = DotProduct (org, forward);
org2[1] = -DotProduct (org, right);
org2[2] = DotProduct (org, up);
VectorSubtract (org2, org, move);
// try moving the contacted entity
pusher->v.solid = SOLID_NOT;
SV_PushEntity (check, move);
pusher->v.solid = SOLID_BSP;
// if it is still inside the pusher, block
block = SV_TestEntityPosition (check);
if (block)
{ // fail the move
if (check->v.mins[0] == check->v.maxs[0])
continue;
if (check->v.solid == SOLID_NOT || check->v.solid == SOLID_TRIGGER)
{ // corpse
check->v.mins[0] = check->v.mins[1] = 0;
VectorCopy (check->v.mins, check->v.maxs);
continue;
}
VectorCopy (entorig, check->v.origin);
SV_LinkEdict (check, true);
VectorCopy (pushorig, pusher->v.angles);
SV_LinkEdict (pusher, false);
pusher->v.ltime -= movetime;
// if the pusher has a "blocked" function, call it
// otherwise, just stay in place until the obstacle is gone
if (pusher->v.blocked)
{
pr_global_struct->self = EDICT_TO_PROG(pusher);
pr_global_struct->other = EDICT_TO_PROG(check);
PR_ExecuteProgram (pusher->v.blocked);
}
// move back any entities we already moved
for (i=0 ; i<num_moved ; i++)
{
VectorCopy (moved_from[i], moved_edict[i]->v.origin);
VectorSubtract (moved_edict[i]->v.angles, amove, moved_edict[i]->v.angles);
SV_LinkEdict (moved_edict[i], false);
}
return;
}
else
{
VectorAdd (check->v.angles, amove, check->v.angles);
}
}
}
2a. Find SV_LinkEdict function and add the yellow:
if (ent->free)
return;
// set the abs box
// ROTATE START
if (ent->v.solid == SOLID_BSP &&
(ent->v.angles[0] || ent->v.angles[1] || ent->v.angles[2]) && ent != sv.edicts)
{ // expand for rotation
float max, v;
int i;
max = DotProduct(ent->v.mins, ent->v.mins);
v = DotProduct(ent->v.maxs, ent->v.maxs);
if (max < v)
max = v;
max = sqrt(max);
for (i=0 ; i<3 ; i++)
{
ent->v.absmin = ent->v.origin - max;
ent->v.absmax = ent->v.origin + max;
}
}
else
// ROTATE END
{
VectorAdd (ent->v.origin, ent->v.mins, ent->v.absmin);
VectorAdd (ent->v.origin, ent->v.maxs, ent->v.absmax);
}
//
// to make items easier to pick up and allow them to be grabbed off
// of shelves, the abs sizes are expanded
//
if ((int)ent->v.flags & FL_ITEM)
2b. Replace SV_ClipMoveToEntity with this
Code: Select all
/*
==================
SV_ClipMoveToEntity
Handles selection or creation of a clipping hull, and offseting (and
eventually rotation) of the end points
==================
*/
trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end)
{
trace_t trace;
vec3_t offset;
vec3_t start_l, end_l;
hull_t *hull;
// fill in a default trace
memset (&trace, 0, sizeof(trace_t));
trace.fraction = 1;
trace.allsolid = true;
VectorCopy (end, trace.endpos);
// get the clipping hull
hull = SV_HullForEntity (ent, mins, maxs, offset);
VectorSubtract (start, offset, start_l);
VectorSubtract (end, offset, end_l);
// ROTATE START
// rotate start and end into the models frame of reference
if (ent->v.solid == SOLID_BSP &&
(ent->v.angles[0] || ent->v.angles[1] || ent->v.angles[2]) && ent != sv.edicts)
{
vec3_t a;
vec3_t forward, right, up;
vec3_t temp;
AngleVectors (ent->v.angles, forward, right, up);
VectorCopy (start_l, temp);
start_l[0] = DotProduct (temp, forward);
start_l[1] = -DotProduct (temp, right);
start_l[2] = DotProduct (temp, up);
VectorCopy (end_l, temp);
end_l[0] = DotProduct (temp, forward);
end_l[1] = -DotProduct (temp, right);
end_l[2] = DotProduct (temp, up);
}
// ROTATE END
// trace a line through the apropriate clipping hull
SV_RecursiveHullCheck (hull, hull->firstclipnode, 0, 1, start_l, end_l, &trace);
// ROTATE START
// rotate endpos back to world frame of reference
if (ent->v.solid == SOLID_BSP &&
(ent->v.angles[0] || ent->v.angles[1] || ent->v.angles[2]) && ent != sv.edicts)
{
vec3_t a;
vec3_t forward, right, up;
vec3_t temp;
if (trace.fraction != 1)
{
VectorSubtract (vec3_origin, ent->v.angles, a);
AngleVectors (a, forward, right, up);
VectorCopy (trace.endpos, temp);
trace.endpos[0] = DotProduct (temp, forward);
trace.endpos[1] = -DotProduct (temp, right);
trace.endpos[2] = DotProduct (temp, up);
VectorCopy (trace.plane.normal, temp);
trace.plane.normal[0] = DotProduct (temp, forward);
trace.plane.normal[1] = -DotProduct (temp, right);
trace.plane.normal[2] = DotProduct (temp, up);
}
// fix trace up by the offset
VectorAdd (trace.endpos, offset, trace.endpos);
}
#if 1 // Baker addition
// Cases where not Solid BSP or no avelocity
// Otherwise backpacks from dead monsters and such can fall through the floor
else {
if (trace.fraction != 1)
VectorAdd (trace.endpos, offset, trace.endpos);
}
#endif
// ROTATE END
// did we clip the move?
if (trace.fraction < 1 || trace.startsolid )
trace.ent = ent;
return trace;
}