403
%{
#include "StdH.h"
#include "Entities/Player.h"
#include "Entities/PlayerWeapons.h"
%}


enum ViewType {
  0 VT_PLAYERDEATH    "",   // player death
  1 VT_PLAYERREBIRTH  "",   // player rebirth (player is spawned)
  2 VT_CAMERA         "",   // camera view
  3 VT_3RDPERSONVIEW  "",   // 3rd person view
};

// input parameter for viewer
event EViewInit {
  CEntityPointer penOwner,        // who owns it
  CEntityPointer penCamera,       // first camera for camera view
  enum ViewType vtView,           // view type
  BOOL bDeathFixed,
};

%{

void CPlayerView_Precache(void) 
{
  CDLLEntityClass *pdec = &CPlayerView_DLLClass;
  pdec->PrecacheModel(MODEL_MARKER);
  pdec->PrecacheTexture(TEXTURE_MARKER);
}

%}

class export CPlayerView : CMovableEntity {
name      "Player View";
thumbnail "";
features "CanBePredictable";

properties:
  1 CEntityPointer m_penOwner,            // class which owns it
  2 INDEX m_iViewType=0,                  // view type
  3 FLOAT m_fDistance = 1.0f,             // current distance
  4 FLOAT3D m_vZLast = FLOAT3D(0,0,0), 
  5 FLOAT3D m_vTargetLast = FLOAT3D(0,0,0), 
  6 BOOL m_bFixed = FALSE,  // fixed view (player falling in abyss)

components:
  1 model   MODEL_MARKER     "Models\\Editor\\Axis.mdl",
  2 texture TEXTURE_MARKER   "Models\\Editor\\Vector.tex"

functions:
  // add to prediction any entities that this entity depends on
  void AddDependentsToPrediction(void)
  {
    m_penOwner->AddToPrediction();
  }
  void PreMoving() {};
  void DoMoving() {
    en_plLastPlacement = GetPlacement();  // remember old placement for lerping
  };
  void PostMoving() 
  {
    SetCameraPosition();
  }
  CPlacement3D GetLerpedPlacement(void) const
  {
    FLOAT fLerpFactor;
    if (IsPredictor()) {
      fLerpFactor = _pTimer->GetLerpFactor();
    } else {
      fLerpFactor = _pTimer->GetLerpFactor2();
    }
    return LerpPlacementsPrecise(en_plLastPlacement, en_plPlacement, fLerpFactor);
    //return CMovableEntity::GetLerpedPlacement();
  }

  // render particles
  void RenderParticles(void)
  {
    if (Particle_GetViewer()==this) {
      Particles_ViewerLocal(this);
    }
  }

  void SetCameraPosition() 
  {
    // 3rd person view
    FLOAT fDistance = 1.0f;
    CPlacement3D pl = ((CPlayerEntity&) *m_penOwner).en_plViewpoint;
    BOOL bFollowCrossHair;

    if (m_iViewType == VT_3RDPERSONVIEW) {
      // little above player eyes so it can be seen where he is fireing
      pl.pl_OrientationAngle(2) -= 10.0f;
      pl.pl_PositionVector(2) += 1.0f;
      fDistance = 5.75f;
      bFollowCrossHair = TRUE;
    // death
    } else if (m_iViewType == VT_PLAYERDEATH) {
      fDistance = 3.5f;
      bFollowCrossHair = FALSE;
    }

    pl.pl_OrientationAngle(3) = 0.0f;

    // transform rotation angle
    pl.RelativeToAbsolute(m_penOwner->GetPlacement());
    // make base placement to back out from
    FLOAT3D vBase;
    EntityInfo *pei= (EntityInfo*) (m_penOwner->GetEntityInfo());
    GetEntityInfoPosition(m_penOwner, pei->vSourceCenter, vBase);

    // create a set of rays to test
    FLOATmatrix3D m;
    MakeRotationMatrixFast(m, pl.pl_OrientationAngle);
    FLOAT3D vRight = m.GetColumn(1);
    FLOAT3D vUp    = m.GetColumn(2);
    FLOAT3D vFront = m.GetColumn(3);

    FLOAT3D vDest[5];
    vDest[0] = vBase+vFront*fDistance+vUp*1.0f;
    vDest[1] = vBase+vFront*fDistance-vUp*1.0f;
    vDest[2] = vBase+vFront*fDistance+vRight*1.0f;
    vDest[3] = vBase+vFront*fDistance-vRight*1.0f;
    vDest[4] = vBase+vFront*fDistance;

    FLOAT fBack = 0;
    // for each ray
    for (INDEX i=0; i<5; i++) {
      // cast a ray to find if any brush is hit
      CCastRay crRay( m_penOwner, vBase, vDest[i]);
      crRay.cr_bHitTranslucentPortals = FALSE;
      crRay.cr_ttHitModels = CCastRay::TT_COLLISIONBOX;
      GetWorld()->CastRay(crRay);

      // if hit something
      if (crRay.cr_penHit!=NULL) {
        // clamp distance
        fDistance = Min(fDistance, crRay.cr_fHitDistance-0.5f);
        // if hit polygon
        if (crRay.cr_pbpoBrushPolygon!=NULL) {
          // back off
          FLOAT3D vDir = (vDest[i]-vBase).Normalize();
          FLOAT fD = Abs(FLOAT3D(crRay.cr_pbpoBrushPolygon->bpo_pbplPlane->bpl_plAbsolute)%vDir)*0.25f;
          fBack = Max(fBack, fD);
        }
      }

    }
    fDistance = ClampDn(fDistance-fBack, 0.0f);
    m_fDistance = fDistance;
    vBase += vFront*fDistance;

    CPlayerWeapons *ppw = ((CPlayer&) *m_penOwner).GetPlayerWeapons();
    if (bFollowCrossHair) {
      FLOAT3D vTarget = vBase-ppw->m_vRayHit;
      FLOAT fLen = vTarget.Length();
      if (fLen>0.01) {
        vTarget/=fLen;
      } else {
        vTarget = FLOAT3D(0,1,0);
      }

      FLOAT3D vX;
      FLOAT3D vY = m.GetColumn(2);
      FLOAT3D vZ = vTarget;
      vZ.Normalize();

      if (Abs(vY%vZ)>0.9f) {
        vY = -m.GetColumn(3);
      }

      vX = vY*vZ;
      vX.Normalize();
      vY = vZ*vX;
      vY.Normalize();
      m_vZLast = vZ;

      m(1,1) = vX(1); m(1,2) = vY(1); m(1,3) = vZ(1);
      m(2,1) = vX(2); m(2,2) = vY(2); m(2,3) = vZ(2);
      m(3,1) = vX(3); m(3,2) = vY(3); m(3,3) = vZ(3);
      DecomposeRotationMatrixNoSnap(pl.pl_OrientationAngle, m);
    }

    if (m_bFixed) {
      pl.pl_PositionVector = GetPlacement().pl_PositionVector;
      pl.pl_OrientationAngle = ANGLE3D(0,-90, 0);
      m_fDistance = (pl.pl_PositionVector-m_penOwner->GetPlacement().pl_PositionVector).Length();
      MakeRotationMatrixFast(m, pl.pl_OrientationAngle);
    } else {
      pl.pl_PositionVector = vBase;
    }

    // set camera placement
    SetPlacement_internal(pl, m, TRUE /* try to optimize for small movements */);
  };

procedures:

  Main(EViewInit eInit) {
    // remember the initial parameters
    ASSERT(eInit.penOwner!=NULL);
    m_penOwner = eInit.penOwner;
    m_iViewType = eInit.vtView;
    m_bFixed = eInit.bDeathFixed;
    ASSERT(IsOfClass(m_penOwner, "Player"));

    // init as model
    InitAsEditorModel();
    SetFlags(GetFlags()|ENF_CROSSESLEVELS);
    SetPhysicsFlags(EPF_MODEL_IMMATERIAL|EPF_MOVABLE);
    SetCollisionFlags(ECF_IMMATERIAL);
    // set appearance
    SetModel(MODEL_MARKER);
    SetModelMainTexture(TEXTURE_MARKER);

    // add to movers list if needed
    if (m_iViewType == VT_PLAYERDEATH) {
      AddToMovers();
    }

    SendEvent(EStart());
    wait() {
      on (EBegin) : { resume; }
      on (EStart) : {  
        SetCameraPosition();
        en_plLastPlacement = GetPlacement();  // remember old placement for lerping
        m_vTargetLast = ((CPlayer&) *m_penOwner).GetPlayerWeapons()->m_vRayHit;
        resume;
      };
      on (EEnd) : { stop; }
      otherwise() : { resume; }
    }
    // cease to exist
    Destroy();

    return;
  };
};

