
Quake DeveLS - RPLAT map entity
Author: James Mario
Difficulty: Medium
What's it
all about ?
Quake2 maps come with a
standard set of map entities. For example, func_door gives you a door that
slides open, and func_train gives you a platform that moves around.
Unfortunately, the platforms cannot rotate... you cannot have (say) a
spacepod that starts facing one direction, and then rotates 90 degrees before
firing down a launch tube into space. Well this tutorial should help you out !
Refer to the comments in the code for an explanation of the entity name (func_rplat)
and fields you should use in your map editor. (Aint used a map editor ? Search
planetquake... try Worldcraft, QERadiant, BSP or Qoole for starters...)
[/SumFuka]
On with
the show
This code was written for
the 3.05 codebase but should work with 3.13...
The first thing to do is
to add the SP_func_rplat in g_spawn.c. The code should look like this.( + means
added lines)
{"func_plat", SP_func_plat},+ {"func_rplat", SP_func_drplat}, {"func_button", SP_func_button},
This is in
the spawn_t, right after all the voids.
After this you will have
to make all the fields the RPLAT will use when you are using it in a map. This
is done by adding the following lines in g_save.c in the fields_t( + means
lines addes)
{"spawnflags", FOFS(spawnflags), F_INT},+ {"myflags", FOFS(myflags), F_INT},+ {"fflags", FOFS(fflags), F_INT},+ {"sflags", FOFS(sflags), F_INT},+ {"fmultiply", FOFS(fmultiply), F_INT},+ {"smultiply", FOFS(smultiply), F_INT},+ {"fdelay", FOFS(fdelay), F_INT},+ {"sdelay", FOFS(sdelay), F_INT}, {"speed", FOFS(speed), F_FLOAT},
Also you
have define these fields in g_local.h. To do this you have to add the following
lines in g_local.h in struc edict_s. The code will be like this ( + means added
lines)
char *classname;+ int spawnflags;+ int myflags;+ int drdistance;+ int fmultiply;+ int smultiply;+ int fdelay;+ int sdelay;+ int rflags;+ char *dtargetname;+ int fflags;+ int sflags; float timestamp;
OK when
you done with that you will add the following code in the very end of g_func.c.
The problem with this code is that I have yet to find a way to minimize this
code.
void Rplat_rot_use (edict_t *self, edict_t *other, edict_t *activator){ self->speed *= self->smultiply; self->moveinfo.speed=self->speed; if (!VectorCompare (self->avelocity, vec3_origin)) { self->s.sound = 0; VectorClear (self->avelocity); self->touch = NULL; } else { self->s.sound = self->moveinfo.sound_middle; VectorScale (self->movedir, self->speed, self->avelocity); if (self->spawnflags & 16) self->touch = rotating_touch; }} void Rplat_Rot (edict_t *ent){ ent->solid = SOLID_BSP; if (ent->sflags & 32) ent->movetype = MOVETYPE_STOP; else ent->movetype = MOVETYPE_PUSH; // set the axis of rotation VectorClear(ent->movedir); if (ent->sflags & 4) ent->movedir[2] = 1.0; else if (ent->sflags & 8) ent->movedir[0] = 1.0; else // Z_AXIS ent->movedir[1] = 1.0; // check for reverse rotation if (ent->sflags & 2) VectorNegate (ent->movedir, ent->movedir); if (!ent->speed) ent->speed = 100; if (!ent->dmg) ent->dmg = 2; // ent->moveinfo.sound_middle = "doors/hydro1.wav"; ent->use = Rplat_rot_use; if (ent->dmg) ent->blocked = rotating_blocked; if (ent->sflags & 1) ent->use (ent, NULL, NULL); if (ent->sflags & 64) ent->s.effects |= EF_ANIM_ALL; if (ent->sflags & 128) ent->s.effects |= EF_ANIM_ALLFAST; gi.setmodel (ent, ent->model); gi.linkentity (ent);} void Rdoor_use (edict_t *self, edict_t *other, edict_t *activator){ edict_t *ent; self->moveinfo.speed *= self->fmultiply; if (self->flags & FL_TEAMSLAVE) return; if (self->fflags & DOOR_TOGGLE) { if (self->moveinfo.state == STATE_UP || self->moveinfo.state == STATE_TOP) { // trigger all paired doors for (ent = self ; ent ; ent = ent->teamchain) { ent->message = NULL; ent->touch = NULL; door_go_down (ent); } return; } } // trigger all paired doors for (ent = self ; ent ; ent = ent->teamchain) { ent->message = NULL; ent->touch = NULL; door_go_up (ent, activator); }}; void Rplat_dr (edict_t *ent){ VectorClear (ent->s.angles); // set the axis of rotation VectorClear(ent->movedir); if (ent->fflags & 64) ent->movedir[2] = 1.0; else if (ent->fflags & 128) ent->movedir[0] = 1.0; else // Z_AXIS ent->movedir[1] = 1.0; // check for reverse rotation if (ent->fflags & 2) VectorNegate (ent->movedir, ent->movedir); if (!st.distance) { gi.dprintf("%s at %s with no distance set\n", ent->classname, vtos(ent->s.origin)); st.distance = 90; } VectorCopy (ent->s.angles, ent->pos1); VectorMA (ent->s.angles, st.distance, ent->movedir, ent->pos2); ent->moveinfo.distance = st.distance; ent->movetype = MOVETYPE_PUSH; ent->solid = SOLID_BSP; gi.setmodel (ent, ent->model); ent->blocked = door_blocked; ent->use = Rdoor_use; if (!ent->speed) ent->speed = 100; if (!ent->accel) ent->accel = ent->speed; if (!ent->decel) ent->decel = ent->speed; if (!ent->wait) ent->wait = 3; if (!ent->dmg) ent->dmg = 2; if (ent->sounds != 1) { ent->moveinfo.sound_start = gi.soundindex ("doors/dr1_strt.wav"); ent->moveinfo.sound_middle = gi.soundindex ("doors/dr1_mid.wav"); ent->moveinfo.sound_end = gi.soundindex ("doors/dr1_end.wav"); } // if it starts open, switch the positions if (ent->fflags & 1) { VectorCopy (ent->pos2, ent->s.angles); VectorCopy (ent->pos1, ent->pos2); VectorCopy (ent->s.angles, ent->pos1); VectorNegate (ent->movedir, ent->movedir); } if (ent->health) { ent->takedamage = DAMAGE_YES; ent->die = door_killed; ent->max_health = ent->health; } if (ent->targetname && ent->message) { gi.soundindex ("misc/talk.wav"); ent->touch = door_touch; } ent->moveinfo.state = STATE_BOTTOM; ent->moveinfo.speed = ent->speed; ent->moveinfo.accel = ent->accel; ent->moveinfo.decel = ent->decel; ent->moveinfo.wait = ent->wait; VectorCopy (ent->s.origin, ent->moveinfo.start_origin); VectorCopy (ent->pos1, ent->moveinfo.start_angles); VectorCopy (ent->s.origin, ent->moveinfo.end_origin); VectorCopy (ent->pos2, ent->moveinfo.end_angles); if (ent->fflags & 16) ent->s.effects |= EF_ANIM_ALL; if (ent->rflags==16) { ent->delay=ent->fdelay; G_UseTargets (ent,ent); ent->rflags +=1; } else if (ent->rflags==17) { ent->delay=ent->sdelay; G_UseTargets (ent,ent); ent->rflags +=1; } gi.linkentity (ent); ent->nextthink = level.time + FRAMETIME; if (ent->health || ent->targetname) ent->think = Think_CalcMoveSpeed; else ent->think = Think_SpawnDoorTrigger;} void Use_RPlat (edict_t *ent, edict_t *other, edict_t *activator){ if (ent->think) return; // already down plat_go_down (ent);} void Rplat_Plat (edict_t *ent){ VectorClear (ent->s.angles); ent->solid = SOLID_BSP; ent->movetype = MOVETYPE_PUSH; gi.setmodel (ent, ent->model); ent->blocked = plat_blocked; if (!ent->speed) ent->speed = 20; else ent->speed *= 0.1; if (!ent->accel) ent->accel = 5; else ent->accel *= 0.1; if (!ent->decel) ent->decel = 5; else ent->decel *= 0.1; if (!ent->dmg) ent->dmg = 1; if (!st.lip) st.lip = 8; // pos1 is the top position, pos2 is the bottom VectorCopy (ent->s.origin, ent->pos1); VectorCopy (ent->s.origin, ent->pos2); if (st.height) ent->pos2[2] -= st.height; else ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; ent->use = Use_RPlat; plat_spawn_inside_trigger (ent); // the "start moving" trigger if (ent->targetname) { ent->moveinfo.state = STATE_UP; } else { VectorCopy (ent->pos2, ent->s.origin); gi.linkentity (ent); ent->moveinfo.state = STATE_BOTTOM; } ent->moveinfo.speed = ent->speed; ent->moveinfo.accel = ent->accel; ent->moveinfo.decel = ent->decel; ent->moveinfo.wait = ent->wait; VectorCopy (ent->pos1, ent->moveinfo.start_origin); VectorCopy (ent->s.angles, ent->moveinfo.start_angles); VectorCopy (ent->pos2, ent->moveinfo.end_origin); VectorCopy (ent->s.angles, ent->moveinfo.end_angles); ent->moveinfo.sound_start = gi.soundindex ("plats/pt1_strt.wav"); ent->moveinfo.sound_middle = gi.soundindex ("plats/pt1_mid.wav"); ent->moveinfo.sound_end = gi.soundindex ("plats/pt1_end.wav");} void SP_func_drplat(edict_t *ent){ ent->rflags=16; ent->drdistance=st.distance; ent->dtargetname=ent->targetname; if (!ent->sdelay) ent->sdelay=3; if (!ent->fdelay) ent->fdelay=3; if (!ent->fmultiply) ent->fmultiply=10; if (!ent->smultiply) ent->smultiply=10; if (!ent->drdistance) ent->drdistance=90; if (!ent->myflags) ent->myflags=1; Rplat_Plat(ent);}
Now this
is a prety long code so i would suggest you just paste it over.
There are more additions
in g_func.c. These are:
Add the following line in
the void door_go_up. ( + means lines added)
else if (strcmp(self->classname, "func_door_rotating") == 0) AngleMove_Calc (self, door_hit_top); + // added for dr_plat+ else if (strcmp(self->classname, "func_rplat") == 0)+ AngleMove_Calc (self, door_hit_top); G_UseTargets (self, activator); door_use_areaportals (self, true);}
Also add
almost the same thing to void dorr_go_down. ( + means lines added)
else if (strcmp(self->classname, "func_door_rotating") == 0) AngleMove_Calc (self, door_hit_bottom); + // added for dr_plat+ else if (strcmp(self->classname, "func_rplat") == 0)+ AngleMove_Calc (self, door_hit_bottom);}
OK now add
the following code right after the defines in g_func.c. ( + means lines added)
//// Support routines for movement (changes in origin using velocity)// +void RplatMove_Done (edict_t *ent)+{+ if (ent->myflags==1)+ {+ st.distance=ent->drdistance;+ ent->targetname=ent->dtargetname;+ ent->myflags=2;+ Rplat_dr(ent);+ }+ VectorClear (ent->velocity);+ ent->moveinfo.endfunc (ent);+} void Move_Done (edict_t *ent)
Now add
the following lines at the end the void Move_Final. ( + means lines added)
VectorScale (ent->moveinfo.dir, ent->moveinfo.remaining_distance / FRAMETIME, ent->velocity); + if (ent->myflags==1)+ {+ RplatMove_Done(ent);+ }+ else+ { ent->think = Move_Done; ent->nextthink = level.time + FRAMETIME;+ }}
You also
have to add the code into the support structure for angle moving objects. This
is done by adding the following code right before AngleMove_Done. ( + means
lines added)
//// Support routines for angular movement (changes in angle using avelocity)// +void RplatAngleMove_Done (edict_t *ent)+{+ if(ent->myflags==2)+ {+ ent->myflags=3;+ Rplat_Rot(ent);+ }+ VectorClear (ent->avelocity);+ ent->moveinfo.endfunc (ent);+} void AngleMove_Done (edict_t *ent)
Now you've
got to add this into the AngleMove_Final. ( + means lines added)
VectorScale (move, 1.0/FRAMETIME, ent->avelocity);+ if (ent->myflags==2)+ {+ RplatAngleMove_Done(ent);+ }+ else+ { ent->think = AngleMove_Done; ent->nextthink = level.time + FRAMETIME;+ }}
I belive
thats about it The following is the usage of fields when using RPLAT.
==========================func_rplat==========================func_rplat //classnamedistance //distance for the the rotating door to travelspeed //shared speed of all three entitieswait //wait value for the door_rotate before returningaccel //optional accelaration valuedecel //optional decelaration valuefdelay //first delay before the object triggers itself to door_rotatesdelay //second delay before the object triggers itself to func_rotatefmultiply //speed multiplier for door_rotate suggested 10smultiply //speed multiplier for func_rotate suggested 10angle //angle for the dorr rotating to traveltarget //must be the same as target name for the object to start the first and second rotationtargetname //common shared target nameheight //hight for the platform to travelspawnflags //set to 1 so that the door rotating entity is start_on when trigeredfflags //flags used for func_door_rotate START_OPEN = 1 REVERSE = 2 CRUSHER = 4 NOMONSTER = 8 ANIMATED = 16 TOGGLE = 32 X-AXIS = 64 Y-AXIS = 128 For any combination of these just add them together.sflags //flags used for func_rotating START_ON = 1 REVERSE = 2 X-AXIS = 4 Y-AXIS = 8 TOUCH_PAIN = 16 STOP = 32 ANIMATED = 64 ANIMATED_FAST = 128 For any combination of these just add them together.myflags //controll flag for the whole movement leave 1 or set 1 for default If set to anything else the combined funtion will not work
Now this
is how this thing works:
The platform will first
move then the door_rotate will move and only after that the func_rotating will
move. There is no way to control the seqence without editing the code and since
I had no use for different seqences I left this as is. The multipliers are
there because when the door_rotate or the func_rotating move the computer still
goes through the plat function, this means the platform will stop moving but
the platform code will still be executed with each cycle. To counter act this i
suggest you use func_areaportal to seperate the brush which uses the RPLAT.
The RPLAT is also self
triggering. This means that when RPLAT is triggered it will triger the
door_rotating and then the func rotating. Now the brush cannot move up or down
and rotate at the same time( at least in this code ) so if RPLAT triggers
itself twice while it is still moving up or down, the rotating movement will
never begin. To counter act this I have implementer the FDELAY and the SDELAY
fields. These are the amount of time the first(door_rotate) and the
second(func_rotate) movements are triggered. Both values are from the first
firings, this means that if you set FDELAY to 3 the door_rotate movement will
begin 3 second after the RPLAT is first triggered and is you set the SDELAY to
5 the func_rotate movement will begin in 5 seconds after the RPLAT is
triggered.
Now onto the bugs, the
once I know are:
When the door_rotate moves
it takes the origin brush with it so that the axis are switched when it moves.
Also there is no way to make a brush rotate on the Y-AXIS with the door_rotate
and then use func_rotating on the X-AXIS( all other combinations should work)
This is it have fun!
Tutorial by James Mario.
|
This site, and all
content and graphics displayed on it, |