Forum

Avirox's Rotation Tutorial Adapted to NetQuake

Post tutorials on how to do certain tasks within game or engine code here.

Moderator: InsideQC Admins

Avirox's Rotation Tutorial Adapted to NetQuake

Postby Baker » Wed Jun 23, 2010 8:58 am

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[i] = ent->v.origin[i] - max;

ent->v.absmax[i] = ent->v.origin[i] + 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;
}
Last edited by Baker on Wed Jun 23, 2010 11:55 pm, edited 2 times in total.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Re: Avirox's Rotation Tutorial Adapted to NetQuake

Postby Ranger366 » Wed Jun 23, 2010 7:22 pm

Baker, you rock.
User avatar
Ranger366
 
Posts: 203
Joined: Thu Mar 18, 2010 5:51 pm

Postby mh » Wed Jun 23, 2010 8:49 pm

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.
We had the power, we had the space, we had a sense of time and place
We knew the words, we knew the score, we knew what we were fighting for
User avatar
mh
 
Posts: 2292
Joined: Sat Jan 12, 2008 1:38 am

Postby Spike » Wed Jun 23, 2010 10:43 pm

something that is solid_not should probably not be trying to push entities out of its non-existant way.
Spike
 
Posts: 2883
Joined: Fri Nov 05, 2004 3:12 am
Location: UK

Postby Baker » Wed Jun 23, 2010 11:38 pm

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.
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby frag.machine » Thu Jun 24, 2010 12:55 am

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
I know FrikaC made a cgi-bin version of the quakec interpreter once and wrote part of his website in QuakeC :) (LordHavoc)
User avatar
frag.machine
 
Posts: 2077
Joined: Sat Nov 25, 2006 1:49 pm

Postby goldenboy » Fri Jun 25, 2010 10:51 am

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.
User avatar
goldenboy
 
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel

Postby goldenboy » Fri Jun 25, 2010 10:53 am

Oh, we actually have the hl_doors.qc stuff already. Heh. Nice that it will finally do something.
User avatar
goldenboy
 
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel

Postby Baker » Fri Jun 25, 2010 11:11 am

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
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby goldenboy » Fri Jun 25, 2010 12:14 pm

:wink:

great.
User avatar
goldenboy
 
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel

Postby goldenboy » Tue Jun 29, 2010 8:33 pm

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.
User avatar
goldenboy
 
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel

Postby goldenboy » Wed Jun 30, 2010 5:13 pm

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?
User avatar
goldenboy
 
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel

Postby Baker » Wed Jun 30, 2010 5:24 pm

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).
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Postby goldenboy » Wed Jun 30, 2010 5:46 pm

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 ;-)
User avatar
goldenboy
 
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel

Postby Baker » Thu Jul 01, 2010 12:46 pm

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!
The night is young. How else can I annoy the world before sunsrise? 8) Inquisitive minds want to know ! And if they don't -- well like that ever has stopped me before ..
User avatar
Baker
 
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Next

Return to Programming Tutorials

Who is online

Users browsing this forum: No registered users and 1 guest