Page 1 of 6

Avirox's Rotation Tutorial Adapted to NetQuake

Posted: Wed Jun 23, 2010 8:58 am
by Baker
Image

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);
      }
   }

   
} 
2. world.c

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;
} 

Re: Avirox's Rotation Tutorial Adapted to NetQuake

Posted: Wed Jun 23, 2010 7:22 pm
by Ranger366
Baker, you rock.

Posted: Wed Jun 23, 2010 8:49 pm
by mh
Nice. :D

You should probably add a check to your world.c stuff for ent != sv.edicts otherwise e3m3 is going to go spectacular on you.

The condition in all 3 cases becomes:

Code: Select all

if (ent->v.solid == SOLID_BSP && (ent->v.angles[0] || ent->v.angles[1] || ent->v.angles[2]) && ent != sv.edicts)
Who would have ever thought that a map could have angles set? Those wacky modders! Oh wait, hold on, this was ID...

The current implementation of SV_PushRotate also plays hell with the spiky ball in end.bsp; haven't found a fix for this one yet. :(

Update: adding an ent->v.solid == SOLID_BSP to the SV_PushRotate condition seems to fix that, but I'm dubious.

Posted: Wed Jun 23, 2010 10:43 pm
by Spike
something that is solid_not should probably not be trying to push entities out of its non-existant way.

Posted: Wed Jun 23, 2010 11:38 pm
by Baker
mh wrote:Nice. :D

You should probably add a check to your world.c stuff for ent != sv.edicts otherwise e3m3 is going to go spectacular on you.
Thanks for that. This also explains why an old distrans map malfunctioned on me in a rotation modified engine. I haven't checked but I'm certain this fixes that too as when I tried e3m3 I got the same effect.

Tutorial updated to reflect these changes.
mh wrote:The current implementation of SV_PushRotate also plays hell with the spiky ball in end.bsp; haven't found a fix for this one yet. :(

Update: adding an ent->v.solid == SOLID_BSP to the SV_PushRotate condition seems to fix that, but I'm dubious.
Spike wrote:something that is solid_not should probably not be trying to push entities out of its non-existant way.
Tutorial updated with that too. Spiky ball problem is gone.

Posted: Thu Jun 24, 2010 12:55 am
by frag.machine
mh wrote:Nice. :D

You should probably add a check to your world.c stuff for ent != sv.edicts otherwise e3m3 is going to go spectacular on you.
Yeah, I remember this bug from when I implemented rotating brushes in Q2K4. The "wtf ?" effect when I first found it was memorable. :D

Posted: Fri Jun 25, 2010 10:51 am
by goldenboy
I'll try to implement this in RMQ engine soon. Still skeptical though.

Would also be interested to change treeqbsp or something to work with this.

So much to do, so little time.

Posted: Fri Jun 25, 2010 10:53 am
by goldenboy
Oh, we actually have the hl_doors.qc stuff already. Heh. Nice that it will finally do something.

Posted: Fri Jun 25, 2010 11:11 am
by Baker
goldenboy wrote:Oh, we actually have the hl_doors.qc stuff already. Heh. Nice that it will finally do something.
http://www.quake-1.com/docs/rotate/orig ... ersion.zip

I wrote a craptacular vb6 utility that removes origin brushes and gives the parent entity the origin field with x,y,z.

Effectively in the compile chain provides defacto origin brush support.

It's the "nomapversion.exe". Run it against a .map immediately before hmap2.exe

Also just for completeness ....

Worldcraft entity definition for func_door_rotating
Worldcraft .fgd file def wrote:func_door_rotating

Code: Select all

@SolidClass base(Appearflags, Targetname, Target) = func_door_rotating : "Rotating door" 
[
	speed(integer) : "Speed" : 100
	sounds(choices) : "Sound" : 0 = 
	[
                0: "No sounds"
		1: "Stone"
		2: "Machine"
		3: "Stone Chain"
		4: "Screechy Metal"
		5: "Custom sounds"
	]
	noise2(string) : "Move sound"
 	noise1(string) : "Stop sound"
	wait(string) : "Delay before close" : "4" 
	distance(integer) : "Opening angle" : 75
	lip(integer) : "Lip" : 8
	dmg(integer) : "Damage inflicted when blocked" : 0
	message(string) : "Message if triggered"
	health(integer) : "Health (shoot open)" : 0
	spawnflags(flags) =
	[ 
		1 : "Starts Open" : 0
		2 : "Door Reverse" : 0
		4 : "Don't link" : 0
		8 : "Gold Key required" : 0
        	16: "Silver Key required" : 0
        	32: "Toggle" : 0
        	64: "Door X Axis" : 0 
               128: "Door Y Axis" : 0
	]
]
Just set the "distance" aka "Door Opening Angle" which is the angle distance the door opens

Posted: Fri Jun 25, 2010 12:14 pm
by goldenboy
:wink:

great.

Posted: Tue Jun 29, 2010 8:33 pm
by goldenboy
Implemented in RMQ, smooth fix added to PROTOCOL_RMQ, very nice rotation with Baker's example mod.

RMQ's QC code works with Baker's map, with a bug that I'll soon fix. Our hl_doors.qc might be from an earlier version of Quake-Life.

Finally.

X X X

By the way, PROTOCOL_RMQ has the number 999. I hope that isn't taken by anything.

Notes:

It works very well, and it's a heck of a lot easier to use than Hiprotate. Much better collision of course.

There is still the problem with texturing and lighting rotating entities - they aren't lit correctly (shadows don't appear etc) and textures are off, iirc because the bmodels are lit and textured at (0 0 0). I don't know how feasible a fix for this is.

Anyway, what we have here is a giant leap for mankind. No more rotating lifts made from 4 brushes and 100 func_movewalls.

I have a permanently rotating entity, Baker, if you want it... func_train_rotating might be as easy as simply giving that a velocity. I don't know, haven't tried yet.

Going to make keys work as well. Small conflict : RMQ uses spawnflags 64 and 128 on doors for additional keys - we have platinum, gold, silver, and bronze. Hence I'll probably put the axis setting into a key to free these spawnflags up.

Maybe even three keys, to eventually make things rotate around more than one axis.

I might even simply assimilate the whole thing into func_door.

Posted: Wed Jun 30, 2010 5:13 pm
by goldenboy
Severe bug encountered.

The code will link ALL func_door_rotating on the level together, instead of just ones that are touching.

The problem is around here, in door_touch:

Code: Select all


			// linking rotating doors is fubar'd.. now we do it my way ;o
			finder_ent = find (world,targetname,self.targetname);
			while (finder_ent)
			{
				if ( finder_ent != self && finder_ent.classname == "door_rotating" && finder_ent.touch != SUB_Null )
				{
					te = self;
					self = finder_ent;
					self.touch();
					self = te;
				}
				finder_ent = find (finder_ent, targetname, self.targetname);
			}

This finds all others and fires their touch functions...
I tried to use the normal method of linking doors instead, but like avirox says, that is fubar'd - they are STILL all linked together, and their touch fields are totally off. So doing it via touch seems the right idea, but the code as it is doesn't work. Setting targetname or other special keys on doors that are supposed to link isn't ideal IMO, we should find a way to do it purely via QC.

Testmap containing two func_door_rotating which don't touch (no spawnflags, just origin set):

http://www.quaketastic.com/upload/files ... rotate.zip

Shows the problem with Baker's example mod as well as my code.

BTW, I have no idea what this does:

Code: Select all

if (!(self.spawnflags & 87907))
// Magic, don't touch ?

Avirox?

Posted: Wed Jun 30, 2010 5:24 pm
by Baker
goldenboy wrote:BTW, I have no idea what this does:

Code: Select all

if (!(self.spawnflags & 87907))
// Magic, don't touch ?

Avirox?[/code]
Initial guess ... it can't possibly be important to Quake and must be a Half-Life thing.

The only field needed to add to rotating doors is really the "distance / angle" (actually isn't a new field, just a different usage of an existing field).

And such a spawn flag isn't even possible ...
Worldcraft .fgd file def wrote:func_door_rotating

Code: Select all

@SolidClass base(Appearflags, Targetname, Target) = func_door_rotating : .
....	[ 
		1 : "Starts Open" : 0
		2 : "Door Reverse" : 0
		4 : "Don't link" : 0
		8 : "Gold Key required" : 0
        	16: "Silver Key required" : 0
        	32: "Toggle" : 0
        	64: "Door X Axis" : 0 
               128: "Door Y Axis" : 0
	]
...
]
How can you have a spawnflag, for example, with the 65536 bit turned on with the above? :D Unless there is QuakeC voodoo I'm not thinking of ... which can't be ruled out since I'm no QuakeC guru.

/Disclaimer: If I am wrong, I blame it on my ignorance of QuakeC and inner knowledge of progs.dat ... still .. my imagination can't see how such a spawn flag would be possible unless something like "only in deathmatch" or so forth were set (whatever value that has).

Posted: Wed Jun 30, 2010 5:46 pm
by goldenboy
Removing that spawnflag check links the bridge in your map to the double door, it seems. Does that have any spawnflags set?

I spent most of last night trying to figure this stuff out.

The doors in my testmap do have distance set.

Edit: The linking-everything problem remains if the spawnflag check is removed.

Edit 2: I fixed this in RMQ by requiring manual linking in the map editor. Warning: Some of this code is RMQ specific; this is just posted as an example. Stuff like T_Damage is different in RMQ.

!!! We have to cheat around Worldcraft, which likes to remove "origin" keys from bmodels. Thanks, shitty editor! So we now use an info_null to mark the center of rotation. The door must target the info_null. The names were changed from func_whatever to rotate_door and rotate_continuous, because map compilers contain a hack that takes the origin from the targetted entity in such cases. This is what we're using. (thanks Urre, avirox and Lord Havoc for this info, thanks Preach for the worldcraft info)

!!! Rotating doors have to be linked manually by giving them identical "linkname" keys (in the map editor) with this code. (thanks Lardarse)

hl_doors.qc:

Code: Select all

void() rot_crush =
{
//dprint ("plat_crush\n");

	T_Damage (other, self, self, 1000, DTH_WORLD_SQUISH, FALSE, 1);
};

float DOOR_REVERSE		= 2;
float DOOR_X_AXIS		= 64;
float DOOR_Y_AXIS		= 128;

/*QUAKED rotate_door (0 .5 .8) ? START_OPEN DOOR_REVERSE DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE DOOR_X_AXIS DOOR_Y_AXIS

TOGGLE causes the door to wait in both the start and end states for a trigger event.

START_OPEN causes the door to move to its destination when spawned, and operate in reverse.  It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).

Key doors are always wait -1.

"target"	must target an info_null or info_rotate with matching targetname, which marks the center of rotation
"linkname"	rotating doors must be linked manually; give linked doors the same linkname (string)
"message"	is printed when the door is touched if it is a trigger door and it hasn't been fired yet
"distance"	amount in degrees to rotate
"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
"health"	if set, door must be shot open
"speed"		movement speed (100 default)
"wait"		wait before returning (3 default, -1 = never return)
"lip"		lip remaining at end of move (8 default)
"dmg"		damage to inflict when blocked (2 default)
"sounds"
0)	no sound
1)	stone
2)	base
3)	stone chain
4)	screechy metal
*/

//a q2 ent.

void() rotate_door =
{
/*	local entity ent;

	ent = find( world, targetname, self.target);
  */ 
	if (!self.target)
		objerror ("rotate_door without target\n");

	if (world.worldtype == 0)
	{
		precache_sound ("doors/medtry.wav");
		precache_sound ("doors/meduse.wav");
		self.noise3 = "doors/medtry.wav";
		self.noise4 = "doors/meduse.wav";
	}
	else if (world.worldtype == 1)
	{
		precache_sound ("doors/runetry.wav");
		precache_sound ("doors/runeuse.wav");
		self.noise3 = "doors/runetry.wav";
		self.noise4 = "doors/runeuse.wav";
	}
	else if (world.worldtype == 2)
	{
		precache_sound ("doors/basetry.wav");
		precache_sound ("doors/baseuse.wav");
		self.noise3 = "doors/basetry.wav";
		self.noise4 = "doors/baseuse.wav";
	}
	else
	{
		dprint ("no worldtype set!\n");
	}
	if (self.sounds == 0)
	{
		precache_sound ("misc/null.wav");
		precache_sound ("misc/null.wav");
		self.noise1 = "misc/null.wav";
		self.noise2 = "misc/null.wav";
	}
	if (self.sounds == 1)
	{
		precache_sound ("doors/drclos4.wav");
		precache_sound ("doors/doormv1.wav");
		self.noise1 = "doors/drclos4.wav";
		self.noise2 = "doors/doormv1.wav";
	}
	if (self.sounds == 2)
	{
		precache_sound ("doors/hydro1.wav");
		precache_sound ("doors/hydro2.wav");
		self.noise2 = "doors/hydro1.wav";
		self.noise1 = "doors/hydro2.wav";
	}
	if (self.sounds == 3)
	{
		precache_sound ("doors/stndr1.wav");
		precache_sound ("doors/stndr2.wav");
		self.noise2 = "doors/stndr1.wav";
		self.noise1 = "doors/stndr2.wav";
	}
	if (self.sounds == 4)
	{
		precache_sound ("doors/ddoor1.wav");
		precache_sound ("doors/ddoor2.wav");
		self.noise1 = "doors/ddoor2.wav";
		self.noise2 = "doors/ddoor1.wav";
	}


	if (self.spawnflags & DOOR_X_AXIS)
		self.movedir_z = 1.0;
	else if (self.spawnflags & DOOR_Y_AXIS)
		self.movedir_x = 1.0;
	else // Z_AXIS
		self.movedir_y = 1.0;

	// check for reverse rotation
	if (self.spawnflags & DOOR_REVERSE)
		self.movedir = '0 0 0' - self.movedir;



	self.max_health = self.health;
	self.solid = SOLID_BSP;
	self.movetype = 7.000;	// MOVETYPE_PUSH, gb
	
	setorigin (self, self.origin);  // self.origin
	
	setmodel (self, self.model);
	
	self.classname = "door_rotating";
	
	if (!self.linkname)
		self.linkname = string_null;

	self.blocked = door_blocked;
	self.use = door_use;
	
	if (self.spawnflags & DOOR_SILVER_KEY)
		self.items = IT_KEY1;
	if (self.spawnflags & DOOR_GOLD_KEY)
		self.items = IT_KEY2;
	
	if (!self.speed)
		self.speed = 100;
	if (!self.wait)
		self.wait = 3;
	if (!self.lip)
		self.lip = 8;
	if (!self.dmg)
		self.dmg = 2;

//	self.pos1 = self.origin;
//	self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);

	self.pos1 = self.angles;
	self.pos2 = self.angles + self.movedir * self.distance;

// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
// but spawn in the open position
	if (self.spawnflags & DOOR_START_OPEN)
	{
//		setorigin (self, self.pos2);
		self.pos1 = self.pos2;
		self.pos1 = self.angles;
	}

	self.state = STATE_BOTTOM;

	if (self.health)
	{
		self.takedamage = DAMAGE_YES;
		self.th_die = door_killed;
	}
	
	if (self.items)
		self.wait = -1;
		
	//if (!self.spawnflags & 256)	// use only - but use is touch in quake anyways lol
		self.touch = door_touch;
	
	if (self.targetname) {		// Door is locked if it must be triggered
		self.touch = SUB_Null;
		self.th_die = SUB_Null;
	}	
		
// LinkDoors can't be done until all of the doors have been spawned, so
// the sizes can be detected properly.
	if (!self.spawnflags & 4)
		self.spawnflags = self.spawnflags | 4;		// so we DONT link doors
	
	self.think = LinkDoors;
	self.nextthink = self.ltime + 0.1;
};

/*
 * gb - another rotating bmodel using origin brushes / origin vector - for testing purposes
 * this is from the quakesrc.org avelocity engine / qc tutorial
 * and should work in conjunction with avirox' engine avelocity tutorial
 * or any engine that supports avelocity (angle velocity)
 *
 */

/*QUAKED rotate_continuous (0 .5 .8) ? x REVERSE Z_AXIS X_AXIS x x NONSOLID 
RMQ Origin-rotating bmodel - experimental

"speed"		rotation speed (100 default)
"target"	must target an info_null, which marks the center of rotation
*/

void() rotate_continuous =
{
	if (!self.target)
		objerror ("rotate_continuous without target\n");

	self.solid = SOLID_BSP;
	self.movetype = MOVETYPE_PUSH;
	//self.think = SUB_Null;
	setorigin (self, self.origin);
	setmodel (self, self.model);
	self.classname = "rotate_continuous";
	setsize (self, self.mins, self.maxs);
	
	if (!self.speed)
		self.speed = 100;

	if (self.spawnflags & 2) // reverse direction
		self.speed = 0 - self.speed;

	if (self.spawnflags & 64) // not solid
		self.solid = SOLID_NOT;

	if (self.spawnflags & 4)
	{
		self.avelocity_z = self.speed;
		self.avelocity_x = 0;
		self.avelocity_y = 0;
	}
	else if (self.spawnflags & 8)
	{
		self.avelocity_z = 0;
		self.avelocity_x = self.speed;
		self.avelocity_y = 0;
	}
	else
	{
		self.avelocity_z = 0;
		self.avelocity_x = 0;
		self.avelocity_y = self.speed;
	}

	self.think = SUB_Null;
	self.nextthink = self.ltime + 9999999; 
	self.blocked = rot_crush;
};
defs.qc:

Code: Select all

.string linkname;	// rotating loors linking...
Part of doors.qc:

Code: Select all

float DOOR_START_OPEN = 1;
float DOOR_DONT_LINK = 4;
float DOOR_GOLD_KEY = 8;
float DOOR_SILVER_KEY = 16;
float DOOR_TOGGLE = 32;
float DOOR_BRONZE_KEY = 64;
float DOOR_PLATINUM_KEY = 128;

/*

Doors are similar to buttons, but can spawn a fat trigger field around them
to open without a touch, and they link together to form simultanious
double/quad doors.

Door.owner is the master door.  If there is only one door, it points to itself.
If multiple doors, all will point to a single one.

Door.enemy chains from the master door through all doors linked in the chain.

*/

/*
=============================================================================

THINK FUNCTIONS

=============================================================================
*/

void() door_go_down;
void() door_go_up;

void() door_blocked =
{
//	other.deathtype = "squish";	// QIP, Supa
	T_Damage (other, self, self.goalentity, self.dmg, DTH_WORLD_SQUISH, FALSE, 1);	// QIP, Supa

	if (!self.model)
		return;

// if a door has a negative wait, it would never come back if blocked,
// so let it just squash the object to death real fast
	if (self.wait >= 0)
	{
		if (self.state == STATE_DOWN)
			door_go_up ();
		else
			door_go_down ();
	}
};

void() door_hit_top =
{
	if (self.continue_movesound)
		sound (self, CHAN_AUTO, self.noise1, 1, ATTN_NORM);	// gb, was CHAN_VOICE
	else
		sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
	
	self.state = STATE_TOP;

	if (self.spawnflags & DOOR_TOGGLE)
	{
#ifdef HALFLIFE
		if (self.classname == "door_rotating") {
			self.touch = SUB_Null;
			self.th_die = SUB_Null;
		}
#endif
		return;		// don't come down automatically
	}
	self.think = door_go_down;
	self.nextthink = self.ltime + self.wait;
#ifdef HALFLIFE
	if (self.classname == "door_rotating") {
		if (self.health)
		{
			self.takedamage = 1;
			self.th_die = door_killed;
		}
	}
#endif
};

void() door_hit_bottom =
{
	if (self.continue_movesound)
		sound (self, CHAN_AUTO, self.noise1, 1, ATTN_NORM);	// gb, plays the stop sound *and* the move sound 
	else
		sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM); // gb, cuts the movesound
	
	self.state = STATE_BOTTOM;

#ifdef HALFLIFE
	if (self.classname == "door_rotating") {
		self.touch = door_touch;
		self.solid = SOLID_BSP;
		if (self.health)
		{
			self.takedamage = 1;
			self.th_die = door_killed;
		}
	}
#endif
};

void() door_go_down =
{
	sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
	if (self.max_health)
	{
		self.takedamage = DAMAGE_YES;
		self.health = self.max_health;
	}

	self.state = STATE_DOWN;

#ifdef HALFLIFE
	if (self.classname == "door_rotating") {
		SUB_CalcAngleMove (self.pos1, self.speed, door_hit_bottom);
		SUB_UseTargets();
		self.takedamage = 0;
	}
	else
#endif
	SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
};

void() door_go_up =
{
	if (self.state == STATE_UP)
		return;		// allready going up

	if (self.state == STATE_TOP)
	{	// reset top wait time
		self.nextthink = self.ltime + self.wait;
		return;
	}

	sound (self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
	self.state = STATE_UP;

#ifdef HALFLIFE
	if (self.classname == "door_rotating") {
		SUB_CalcAngleMove (self.pos2, self.speed, door_hit_top);
		self.takedamage = 0;
	}
	else
#endif
	SUB_CalcMove (self.pos2, self.speed, door_hit_top);

	SUB_UseTargets();
};


/*
=============================================================================

ACTIVATION FUNCTIONS

=============================================================================
*/

void() door_fire =
{
	local entity 	oself;
	local entity	starte;

	if (self.owner != self)
		objerror ("door_fire: self.owner != self");

// play use key sound

	if ((self.items) || (self.items2))
		sound (self, CHAN_ITEM, self.noise4, 1, ATTN_NORM);

	self.message = string_null;		// no more message
	oself = self;

	if (self.spawnflags & DOOR_TOGGLE)
	{
		if (self.state == STATE_UP || self.state == STATE_TOP)
		{
			starte = self;
			do
			{
				door_go_down ();
				self = self.enemy;
			} while ( (self != starte) && (self != world) );
			self = oself;
			return;
		}
	}

// trigger all paired doors
	starte = self;
	do
	{
		self.goalentity = activator;	// QIP
		door_go_up ();
		self = self.enemy;
	} while ( (self != starte) && (self != world) );
	self = oself;
};


void() door_use =
{
	local entity oself;
	
	self.message = "";			// door message are for touch only
	self.owner.message = "";
	self.enemy.message = "";
	oself = self;
	self = self.owner;
	door_fire ();
	self = oself;
};


void() door_trigger_touch =
{
	if (other.health <= 0)
		return;

	if (time < self.attack_finished)
		return;
	self.attack_finished = time + 1;

	activator = other;

	self = self.owner;
	door_use ();
};


void() door_killed =
{
	local entity oself;

	oself = self;
	self = self.owner;
	self.health = self.max_health;
	self.takedamage = DAMAGE_NO;	// wil be reset upon return
	door_use ();
	self = oself;
};


float (entity e1, entity e2) EntitiesTouching;
/*
================
door_touch

Prints messages and opens key doors
================
*/
void() door_touch =
{
#ifdef HALFLIFE
	local entity finder_ent, te;
#endif
	if (other.classname != "player")
		return;
	if (self.owner.attack_finished > time)
		return;

	if (other.classname == "player" && (self.health > 0) )	// gb, any door with health set must be shot open
		return;

	self.owner.attack_finished = time + 2;

	if (self.owner.message != "")
	{
		centerprint (other, self.owner.message);
		sound (other, CHAN_VOICE, "misc/talk.wav", 1, ATTN_NORM);
	}

// key door stuff
	if ((!self.items) && (!self.items2) && (!self.classname == "door_rotating"))	// gb, the latter must touch
		return;

// FIXME: blink key on player's status bar
	if ( ((self.items & other.items) != self.items)
	|| ((self.items2 & other.items2) != self.items2) )
	{
		if (self.owner.items == IT_KEY1)
		{
			if (world.worldtype == 2)
			{
				centerprint (other, "You need the silver keycard");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
			else if (world.worldtype == 1)
			{
				centerprint (other, "You need the silver runekey");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
			else if (world.worldtype == 0)
			{
				centerprint (other, "You need the silver key");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
		}
		else if (self.owner.items == IT_KEY2)
		{
			if (world.worldtype == 2)
			{
				centerprint (other, "You need the gold keycard");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
			else if (world.worldtype == 1)
			{
				centerprint (other, "You need the gold runekey");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
			else if (world.worldtype == 0)
			{
				centerprint (other, "You need the gold key");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
		}
		else if (self.owner.items2 == IT_KEY3)
		{
			if (world.worldtype == 2)
			{
				centerprint (other, "You need the bronze keycard");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
			else if (world.worldtype == 1)
			{
				centerprint (other, "You need the bronze runekey");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
			else if (world.worldtype == 0)
			{
				centerprint (other, "You need the bronze key");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
		}
		else
		{
			if (world.worldtype == 2)
			{
				centerprint (other, "You need the platinum keycard");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
			else if (world.worldtype == 1)
			{
				centerprint (other, "You need the platinum runekey");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
			else if (world.worldtype == 0)
			{
				centerprint (other, "You need the platinum key");
				sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
			}
		}
		return;
	}
	
	other.items = other.items - self.items;
	other.items2 = other.items2 - self.items2;
	
	self.touch = SUB_Null;
	
	if (self.enemy)
		self.enemy.touch = SUB_Null;	// get paired door
	
	door_use ();

#ifdef HALFLIFE
	if (self.classname == "door_rotating") {
		// linking rotating doors is fubar'd... hence we do it manually, and this time it even works
		finder_ent = find (world,linkname,self.linkname);
		
		while (finder_ent)
		{
			if ( finder_ent != self && finder_ent.linkname != string_null && finder_ent.linkname == self.linkname && finder_ent.touch != SUB_Null )
			{
				te = self;
				self = finder_ent;
				self.touch();
				self = te;
			}
			finder_ent = find (finder_ent, linkname, self.linkname);
		}
	}
#endif

};


This is not ready to be plugged into any mod, you would have to remove the RMQ-isms first (extra keys, items2, weird T_Damage etc.) but in principle, it works. Keys work, toggle / triggering / shooting / linking etc. work.

What's needed is an updated example mod where this is plugged into a clean progs.dat.

Remember to #define HALFLIFE in progs.src and use FTEQCC to compile.

hmap2 (only the qbsp stage) is still needed to compile maps that use this, but that's not really a problem.

It works. Using info_nulls in place of origin brushes also has the benefit that they're crossplatform and cross-map compiler.

I have an example map, but that requires rmq atm.

Also, who finds errors, may keep them ;-)

Posted: Thu Jul 01, 2010 12:46 pm
by Baker
Thanks for the refinements.

I'm not sure why doors would need to be linked manually, but if both Avirox and you weren't able to make them link the traditional way then my lower level QuakeC understanding probably isn't understanding the issue right.

Anyways, manually linked doors isn't too big a deal and -- hey! -- it will allow unusual door linking :P

Which could be VERY, VERY cool!