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   //classname
distance     //distance for the the rotating door to travel
speed        //shared speed of all three entities
wait         //wait value for the door_rotate before returning
accel        //optional accelaration value
decel        //optional decelaration value
fdelay       //first delay before the object triggers itself to door_rotate
sdelay       //second delay before the object triggers itself to func_rotate
fmultiply    //speed multiplier for door_rotate suggested 10
smultiply    //speed multiplier for func_rotate suggested 10
angle        //angle for the dorr rotating to travel
target       //must be the same as target name for the object to start
               the first and second rotation
targetname   //common shared target name
height       //hight for the platform to travel
spawnflags   //set to 1 so that the door rotating entity is
               start_on when trigered
fflags       //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,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for their great help and support with hosting.
Best viewed with Netscape 4