//
//      Nezu Camera V1.0    4/8/96
//
// A simple implementation of the infamous Duke3D Camera for Quake v1.01
// Made possible thanx to the DEM spec, that tell everything about
// messages from server to client.
//   
/* 
**
** Example of Camera Usage
**
** Modify file weapons.qc
**
void() ImpulseCommands =
{
  ...
//------------------------- Patch by Nezu 4/8/96 ---------------------------    
// New impulse 240 = Camera
	if (self.impulse == 240)
		NezuCamera( self);
// New impulse 242 = CameraCycle
        if (self.impulse == 242)
                NezuCameraCycle(self);
//------------------------------- End Patch -------------------------------
  ...
};
**  
**
*/
//--------------------------------------------------------
//
// Function prototypes, for dealing with camera
//
//--------------------------------------------------------
//
void(entity me, entity camera)     NezuSetViewPoint;
void(entity me, vector viewangles) NezuSetViewAngle;
void(entity me)                    NezuCamera;
//
float SVC_SETVIEWPORT = 5;    // Net.Protocol 0x05
float SVC_SETVIEWANGLES = 10; // Net.Protocol 0x0A
float SVC_UPDATEENTITY = 128; // Net.Protocol 0x80
//
//--------------------------------------------------------
// NezuSetViewPoint( me, camera);
//   me     = player entity
//   camera = entity that is playing the camera for me
// Make a player see through a given entity (the camera).
//--------------------------------------------------------
//
// Dependencies:
//      msg_entity is modified
//
void(entity me, entity camera) NezuSetViewPoint =
{
  // Set view point
  msg_entity = me;                         // target of message
  WriteByte (MSG_ONE, SVC_SETVIEWPORT);    // 5 = SVC_SETVIEWPORT;  
  WriteEntity (MSG_ONE, camera);           // view port
  // Also set angles, otherwise it feels strange
  // NezuSetViewAngle(me, camera.angles);
  WriteByte (MSG_ONE, SVC_SETVIEWANGLES);  // 10 = SVC_SETVIEWANGLES
  WriteAngle(MSG_ONE, camera.angles_x);    // tilt 
  WriteAngle(MSG_ONE, camera.angles_y);    // yaw
  WriteAngle(MSG_ONE, camera.angles_z);    // flip
};
//
//--------------------------------------------------------
// NezuSetViewAngle( me, angles);
//   me     = player entity
//   angles = new orientation angles for me
// Change the view angles of a player.
// Note: setting me.angles doesn't work for players.
//--------------------------------------------------------
//
// Dependencies:
//      msg_entity is modified
//
void(entity me, vector viewangles) NezuSetViewAngle =
{
  msg_entity = me;                         // target of message
  WriteByte(MSG_ONE, SVC_SETVIEWANGLES);   // 10 = SVC_SETVIEWANGLES
  WriteAngle(MSG_ONE, viewangles_x);       // tilt 
  WriteAngle(MSG_ONE, viewangles_y);       // yaw
  WriteAngle(MSG_ONE, viewangles_z);       // flip
};
//
//--------------------------------------------------------
// NezuUpdateCamera( me, camera);
//   me     = player entity
//   camera = entity that might be unknown to the client
// Declare an entity position to the client. That's useful because if a client
// doesn't know where an entity is, it assumes it's in '0 0 0' (usually wrong...)
//--------------------------------------------------------
//
// Dependencies: 
//      msg_entity is modified
//
void(entity me, entity camera) NezuUpdateCamera =
{
  msg_entity = me;                         // target of message
  //0x0080 = SVC_UPDATEENTITY
  // |   1 = extended mask
  // |   2 = position_x
  // |   4 = position_y
  // |   8 = position_z
  // |  10 = angles_y (yaw)
  // |  20 = new
  // |  40 = frame
  WriteByte (MSG_ONE, SVC_UPDATEENTITY|15); // 0x80|1|2|4|8
  //0x00XX =
  // |01xx = angles_x (tilt)
  // |02xx = angles_z (flip)
  // |04xx = modelindex
  // |08xx = colormap
  // |10xx = skin
  // |20xx = attack state
  // |40xx = entity as short, not byte
  WriteByte (MSG_ONE, 64); // 0x40
  // mask &0x4000:  ReadShort   
  WriteEntity(MSG_ONE,camera);
  // origin[0] = mask & 0x0002 ? ReadCoord : default
  WriteCoord(MSG_ONE,camera.origin_x);
  // origin[1] = mask & 0x0004 ? ReadCoord : default
  WriteCoord(MSG_ONE,camera.origin_y);
  // origin[2] = mask & 0x0008 ? ReadCoord : default
  WriteCoord(MSG_ONE,camera.origin_z);
};
//
//
//--------------------------------------------------------
// entity = NezuFindClosest( scenter, srange);
//   scenter = center of sphere
//   srange  = size of sphere
// Find the entity that is closest to the center of the sphere
// Returns world if none was found 
//--------------------------------------------------------
//
// Dependencies: entity.chain is modified by findradius
//
entity(vector scenter, float srange) NezuFindClosest =
{
  local float  dist;
  local float  edist;
  local entity e;
  local entity best;
  dist = srange;
  e = findradius( scenter, srange); 
  while(e)  // e != world
  { 
    edist= vlen(e.origin - scenter);
    if(edist<dist)
    { 
      if(e.classname != "path_corner") // avoid those entities
      {
        dist = edist;
        best = e;
      }
    }
    e = e.chain;
  }
  return best;
};


//
//--------------------------------------------------------
// NezuCamera( me);
//   me     = player entity
//   me.aflag = camera status (0=off 666=on 777=ready)
//              this is a hack: self.aflag is used by doors only.
//  Fire a line ahead. If an entity is touched, it becomes the camera.
//  Next time this function is called, the view is restored to the player
//--------------------------------------------------------
// 
// Dependencies:
//   entity.trigger_field is used for the current camera
//   entity.aflag   is used to toggle the camera status for each player
//   trace_fraction, trace_ent  are modified by traceline()
//
// Rule:  me.aflag==666  means the camera is active.
//
void(entity me) NezuCamera = 
{
  local entity camera;
  local	vector me_src;
  local vector me_targ;
  local vector me_fire;
  if(me.aflag!=666)                // camera is on, set it off
  { 
    camera=me;
    NezuSetViewPoint(me, me);      // restore player position
    sprint(me,"Camera off\n");
    me.aflag=666;                  // camera is ready
    //do not change me.trigger_field  for the NezuCameraCycle
  }
  else                             // camera is off, try to set it on
  { 
    me_fire = aim(me, 200);        // slow missile speed
    // makevectors(me.angles);     // set v_forward  
    me_src = me.origin + '0 0 16';
    me_targ = me_src  + me_fire*10000;
    //
    // Fire a line 10000 ahead. 
    //
    traceline (me_src, me_targ , FALSE, me);
    //
    // Find the entity hit
    // 
    if (trace_fraction >= 1.0) return;  // Nothing was hit
    camera = trace_ent;
    if (camera == world)                // hit the walls or floors
    { 
      camera = NezuFindClosest(trace_endpos, 100);
      if(camera == world) return;          // found nothing
    } 
    //
    // Set camera viewpoint
    //    
    NezuSetViewPoint(me, camera);       // Restore player position
    sprint(me,"Camera set to ");      // Message for player
    sprint(me,camera.classname);      // Bastard string concatenation from hell
    sprint(me,"\n");                  // End the message,and print it
    me.aflag=0;                       // Camera is on
    me.trigger_field = camera;        // for the NezuCameraCycle
  }
};
//
//--------------------------------------------------------
// NezuCameraCycle( me);
//   me     = player entity
//   me.aflag = camera status (0=off 666=on 777=ready)
//              this is a hack: self.aflag is used by doors only.
//   me.trigger_field = current camera
//              this is a hack: self.trigger_field is used by doors only.
//  Cycle player through all entities of a specific kind
//  Next time this function is called, the view is restored to the player
//--------------------------------------------------------
// 
//
// Dependencies:
//   entity.trigger_field is used for the current camera
//   entity.aflag   is used to toggle the camera status for each player
//   trace_fraction, trace_ent  are modified by traceline()
//
void(entity me) NezuCameraCycle = 
{
  local entity camera;
  local string stufftofind;
  
  //
  // cycle through entities, never come back to me
  //
  camera=me.trigger_field; 
  do
  {
    camera = nextent(camera);
    if(me.aflag & 1) // might not exist in level     
      stufftofind = "item_shells"; 
    else // must exist in level!
      stufftofind = "info_player_deathmatch"; 
    camera = find(camera, classname, stufftofind);
    if(!camera) // cycle of 2 steps
      me.aflag = (me.aflag + 1) & 1;
  }while(!camera);
  //
  // Send to client an update of camera position
  // Otherwise the camera is beleived to be in the void
  //
  NezuUpdateCamera(me, camera);
  //
  // Set camera viewpoint
  //    
  NezuSetViewPoint(me, camera);       // Restore player position
// Removed the printing of coordinates, because fairly annoying 
//  stufftofind= vtos(camera.origin);
//  sprint(me,"Camera set to ");        // Message for player
//  sprint(me, stufftofind);            // Bastard string concatenation from hell
//  sprint(me,"\n");                    // End the message,and print it
  me.trigger_field = camera;          // Current Camera
};
//
// That's all there is to it. Enjoy!
//