Index | FAQ | Search | Memberlist | Usergroups | Profile | Log in to check your private messages | Log in | Register

Avirox's Rotation Tutorial Adapted to NetQuake Goto page 1, 2, 3, 4, 5 Next

| Inside3d Forums Forum Index -> Programming Tutorials

View previous topic :: View next topic
Author | Message
--- | ---
Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Wed Jun 23, 2010 8:58 am Post subject: Avirox's Rotation Tutorial Adapted to NetQuake |

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:

Quote:
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:
/* ============ 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:

Quote:
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:
/* ================== 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;
}

_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...
Last edited by Baker on Wed Jun 23, 2010 11:55 pm; edited 2 times in total

Back to top |

Ranger366 Joined: 18 Mar 2010 Posts: 72 Location: Berlin (Germany) |

Posted: Wed Jun 23, 2010 7:22 pm Post subject: Re: Avirox's Rotation Tutorial Adapted to NetQuake |

Baker, you rock. _________________

Back to top |

mh Joined: 12 Jan 2008 Posts: 909 |

Posted: Wed Jun 23, 2010 8:49 pm Post subject: |

Nice. 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:
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.
_________________
DirectQ Engine - New release 1.8.666a, 9th August 2010
MHQuake Blog (General)
Direct3D 8 Quake Engines

Back to top |

Spike Joined: 05 Nov 2004 Posts: 944 Location: UK |

Posted: Wed Jun 23, 2010 10:43 pm Post subject: |

something that is solid_not should probably not be trying to push entities out of its non-existant way. _________________ What's a signature?

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Wed Jun 23, 2010 11:38 pm Post subject: |

mh wrote:
Nice. 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.
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

frag.machine Joined: 25 Nov 2006 Posts: 728 |

Posted: Thu Jun 24, 2010 12:55 am Post subject: |

mh wrote:
Nice. 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.
_________________
frag.machine - Q2K4 Project
http://fragmachine.quakedev.com/

Back to top |

goldenboy Joined: 05 Sep 2008 Posts: 310 Location: Kiel |

Posted: Fri Jun 25, 2010 10:51 am Post subject: |

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. _________________ ReMakeQuake
The Realm of Blog Magic

Back to top |

goldenboy Joined: 05 Sep 2008 Posts: 310 Location: Kiel |

Posted: Fri Jun 25, 2010 10:53 am Post subject: |

Oh, we actually have the hl_doors.qc stuff already. Heh. Nice that it will finally do something. _________________ ReMakeQuake The Realm of Blog Magic

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Fri Jun 25, 2010 11:11 am Post subject: |

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/origin_nomapversion.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:
@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

_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

goldenboy Joined: 05 Sep 2008 Posts: 310 Location: Kiel |

Posted: Fri Jun 25, 2010 12:14 pm Post subject: |

great. _________________ ReMakeQuake The Realm of Blog Magic

Back to top |

goldenboy Joined: 05 Sep 2008 Posts: 310 Location: Kiel |

Posted: Tue Jun 29, 2010 8:33 pm Post subject: |

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.
_________________
ReMakeQuake
The Realm of Blog Magic

Back to top |

goldenboy Joined: 05 Sep 2008 Posts: 310 Location: Kiel |

Posted: Wed Jun 30, 2010 5:13 pm Post subject: |

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:
// 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/single_player/maps/gbrotate.zip
Shows the problem with Baker's example mod as well as my code.
BTW, I have no idea what this does:

Code:
if (!(self.spawnflags & 87907))

// Magic, don't touch ?
Avirox?
_________________
ReMakeQuake
The Realm of Blog Magic

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Wed Jun 30, 2010 5:24 pm Post subject: |

goldenboy wrote:
BTW, I have no idea what this does:

Code:
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:
@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? 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).
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

goldenboy Joined: 05 Sep 2008 Posts: 310 Location: Kiel |

Posted: Wed Jun 30, 2010 5:46 pm Post subject: |

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:
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:
.string linkname; // rotating loors linking...

Part of doors.qc:

Code:
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
_________________
ReMakeQuake
The Realm of Blog Magic

Back to top |

Baker Joined: 14 Mar 2006 Posts: 1538 |

Posted: Thu Jul 01, 2010 12:46 pm Post subject: |

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
Which could be VERY, VERY cool!
_________________
Tomorrow Never Dies. I feel this Tomorrow knocking on the door ...

Back to top |

Display posts from previous:

| Inside3d Forums Forum Index -> Programming Tutorials | All times are GMT Goto page 1, 2, 3, 4, 5 Next
Page 1 of 5

Jump to:

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

Powered by phpBB © 2004 phpBB Group