900
%{
#include "Entities/StdH/StdH.h"
%}

uses "Entities/EnvironmentMarker";
uses "Entities/WatchPlayers";

class CEnvironmentBase: CMovableEntity {
name      "Environment Base";
thumbnail "Thumbnails\\EnvironmentBase.tbn";
features  "HasName", "IsTargetable";

properties:
  1 CTString m_strName            "Name" 'N' = "Base Environment",
  2 CTString m_strDescription = "",
  3 RANGE m_fDistance             "Range" 'R' = 100.0f,   // distance when player is seen
  4 FLOAT m_fStretch              "Stretch" 'S' = 1.0f,

  5 CEntityPointer m_penTarget    "Target" 'T',
  6 CEntityPointer m_penWatcher,
  7 FLOAT m_fWatcherFrequency     "Watcher frequency" = 2.0f,   // watcher will look every x seconds for players
  8 FLOAT3D m_vDesiredPosition = FLOAT3D(0, 0, 0),              // desired position for moving

 10 FLOAT m_fMoveSpeed            "Move speed" 'V' = 2.0f,
 11 FLOAT m_fRotateSpeed          "Rotate speed" 'B' = 60.0f,
 12 FLOAT m_fMoveFrequency        "Move frequency" = 0.5f,
 13 BOOL m_bUseWatcher            "Use watcher" = FALSE,    // use individual watcher
 14 BOOL m_bFlying                "Flying" 'F' = FALSE,     // flying model
 16 FLOAT m_fWaitTime = 0.0f,

 20 CTFileName m_fnMdl           "Model" 'M' = CTFILENAME("Models\\Editor\\Axis.mdl"),
 21 CTFileName m_fnTex           "Texture" 'X' = CTString(""),
 22 ANIMATION m_iAnim            "Animation" =0,

 25 CTFileName m_fnAtt1Mdl       "Attachment 1 Model" = CTString(""),
 26 CTFileName m_fnAtt1Tex       "Attachment 1 Texture" = CTString(""),
 27 INDEX m_iAtt1Position        "Attachment 1 position"=0,
 28 ANIMATION m_iAtt1Anim        "Attachment 1 animation"=0,

 30 CTFileName m_fnAtt2Mdl       "Attachment 2 Model" = CTString(""),
 31 CTFileName m_fnAtt2Tex       "Attachment 2 Texture" = CTString(""),
 32 INDEX m_iAtt2Position        "Attachment 2 position"=1,
 33 ANIMATION m_iAtt2Anim        "Attachment 2 animation"=0,

 35 CTFileName m_fnAtt3Mdl       "Attachment 3 Model" = CTString(""),
 36 CTFileName m_fnAtt3Tex       "Attachment 3 Texture" = CTString(""),
 37 INDEX m_iAtt3Position        "Attachment 3 position"=1,
 38 ANIMATION m_iAtt3Anim        "Attachment 3 animation"=0,

components:
  1 class   CLASS_WATCHPLAYERS    "Classes\\WatchPlayers.ecl",

functions:
  /* Check if entity is moved on a route set up by its targets. */
  BOOL MovesByTargetedRoute(CTString &strTargetProperty) const {
    strTargetProperty = "Target";
    return TRUE;
  };
  /* Check if entity can drop marker for making linked route. */
  BOOL DropsMarker(CTFileName &fnmMarkerClass, CTString &strTargetProperty) const {
    fnmMarkerClass = CTFILENAME("Classes\\EnvironmentMarker.ecl");
    strTargetProperty = "Target";
    return TRUE;
  };
  const CTString &GetDescription(void) const {
    ((CTString&)m_strDescription).PrintF("-><none>");
    if (m_penTarget!=NULL) {
      ((CTString&)m_strDescription).PrintF("->%s",(const char*) m_penTarget->GetName());
    }
    return m_strDescription;
  };
  /* Get anim data for given animation property - return NULL for none. */
  CAnimData *GetAnimData(SLONG slPropertyOffset) {
    if(slPropertyOffset==offsetof(CEnvironmentBase, m_iAnim)) {
      return GetModelObject()->GetData();

    } else if(slPropertyOffset==offsetof(CEnvironmentBase, m_iAtt1Anim)) {
      CAttachmentModelObject *pamo = GetModelObject()->GetAttachmentModel(m_iAtt1Position);
      if( pamo != NULL) { return pamo->amo_moModelObject.GetData(); }
      return CEntity::GetAnimData(slPropertyOffset);

    } else if(slPropertyOffset==offsetof(CEnvironmentBase, m_iAtt2Anim)) {
      CAttachmentModelObject *pamo = GetModelObject()->GetAttachmentModel(m_iAtt2Position);
      if( pamo != NULL) { return pamo->amo_moModelObject.GetData(); }
      return CEntity::GetAnimData(slPropertyOffset);

    } else if(slPropertyOffset==offsetof(CEnvironmentBase, m_iAtt3Anim)) {
      CAttachmentModelObject *pamo = GetModelObject()->GetAttachmentModel(m_iAtt3Position);
      if( pamo != NULL) { return pamo->amo_moModelObject.GetData(); }
      return CEntity::GetAnimData(slPropertyOffset);

    } else {
      return CEntity::GetAnimData(slPropertyOffset);
    }
  };



/************************************************************
 *                      MOVE FUNCTIONS                      *
 ************************************************************/
  // switch to next marker
  BOOL NextMarker(void) {
    if (m_penTarget==NULL) {
      return FALSE;
    }

    // assure valid target
    if (m_penTarget!=NULL && !IsOfClass(m_penTarget, "Environment Marker")) {
      WarningMessage("Target '%s' is not of Environment Marker class!",(const char*) m_penTarget->GetName());
      m_penTarget = NULL;
      return FALSE;
    }

    // get next marker
    CMarker *penTarget = (CMarker *)(CEntity*)m_penTarget;
    CMarker *penNextTarget = (CMarker *)(CEntity*)penTarget->m_penTarget;

    // if got to end
    if (penNextTarget==NULL) {
      return FALSE;
    }

    // remember next marker as current target
    m_penTarget = penNextTarget;

    return TRUE;
  };

  // calculate rotation
  void CalcRotation(ANGLE aWantedHeadingRelative, ANGLE3D &aRotation) {
    // normalize it to [-180,+180] degrees
    aWantedHeadingRelative = NormalizeAngle(aWantedHeadingRelative);

    // if desired position is left
    if (aWantedHeadingRelative<-m_fRotateSpeed*m_fMoveFrequency) {
      // start turning left
      aRotation(1) = -m_fRotateSpeed;
    // if desired position is right
    } else if (aWantedHeadingRelative>m_fRotateSpeed*m_fMoveFrequency) {
      // start turning right
      aRotation(1) = +m_fRotateSpeed;
    // if desired position is more-less ahead
    } else {
      aRotation(1) = aWantedHeadingRelative/m_fMoveFrequency;
    }
  };

  // stop moving
  void StopMoving(void) {
    SetDesiredRotation(ANGLE3D(0, 0, 0));
    SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f));
  };

  // move to position
  void MoveToPosition(void) {
    FLOAT3D vDesiredAngle;

    // desired angle vector
    vDesiredAngle = (m_vDesiredPosition - GetPlacement().pl_PositionVector).Normalize();
    // find relative heading towards the desired angle
    ANGLE3D aRotation(0,0,0);
    CalcRotation(GetRelativeHeading(vDesiredAngle), aRotation);

    // determine translation speed
    FLOAT3D vTranslation(0.0f, 0.0f, 0.0f);
    vTranslation(3) = -m_fMoveSpeed;

    // if flying set y axis translation speed
    if (m_bFlying) {
      vTranslation(2) = Sgn(vDesiredAngle(2)) * m_fMoveSpeed/10;
    }

    // start moving
    SetDesiredRotation(aRotation);
    SetDesiredTranslation(vTranslation);
  };

  // calc destination
  void CalcDestination() {
    // new position to walk to
    FLOAT fR = FRnd()*((CEnvironmentMarker&)*m_penTarget).m_fMarkerRange;
    FLOAT fA = FRnd()*360.0f;
    m_vDesiredPosition = m_penTarget->GetPlacement().pl_PositionVector + 
                          FLOAT3D(CosFast(fA)*fR, 0, SinFast(fA)*fR);
  };

  // marker parameters
  void MarkerParameters() {
    if (m_penTarget != NULL) {
      CEnvironmentMarker &em = (CEnvironmentMarker&)*m_penTarget;
      if (em.m_fMoveSpeed > 0.0f) {
        m_fMoveSpeed = em.m_fMoveSpeed;
      }
      if (em.m_fRotateSpeed > 0.0f) {
        m_fRotateSpeed = em.m_fRotateSpeed;
      }
    }
  };



/************************************************************
 *                  INITIALIZE FUNCTIONS                    *
 ************************************************************/
  void Initialize(void) {
    // declare yourself as a model
    InitAsModel();
    SetPhysicsFlags(EPF_MODEL_WALKING&~(EPF_ORIENTEDBYGRAVITY|EPF_TRANSLATEDBYGRAVITY));
    SetCollisionFlags(ECF_MODEL);

    // set model stretch -- MUST BE DONE BEFORE SETTING MODEL!
    GetModelObject()->mo_Stretch = FLOAT3D( m_fStretch, m_fStretch, m_fStretch);

    // set appearance
    SetModel(m_fnMdl);
    GetModelObject()->PlayAnim(m_iAnim, AOF_LOOPING);
    if( m_fnTex != CTString("")) {
      GetModelObject()->mo_toTexture.SetData_t(m_fnTex);
    }

    GetModelObject()->RemoveAllAttachmentModels();
    
    AddAttachment( m_iAtt1Position, m_fnAtt1Mdl, m_fnAtt1Tex);
    CAttachmentModelObject *pamo = GetModelObject()->GetAttachmentModel( m_iAtt1Position);
    if( pamo != NULL) {
      pamo->amo_moModelObject.StartAnim( m_iAtt1Anim);
    }

    if( (m_iAtt2Position != m_iAtt1Position) && (m_fnAtt1Mdl != m_fnAtt2Mdl) ) {
      AddAttachment( m_iAtt2Position, m_fnAtt2Mdl, m_fnAtt2Tex);
      CAttachmentModelObject *pamo = GetModelObject()->GetAttachmentModel( m_iAtt2Position);
      if( pamo != NULL) {
        pamo->amo_moModelObject.StartAnim( m_iAtt2Anim);
      }
    }

    if( (m_iAtt3Position != m_iAtt1Position) && (m_fnAtt1Mdl != m_fnAtt3Mdl) && 
        (m_iAtt3Position != m_iAtt2Position) && (m_fnAtt2Mdl != m_fnAtt3Mdl) ) {
      AddAttachment( m_iAtt3Position, m_fnAtt3Mdl, m_fnAtt3Tex);
      CAttachmentModelObject *pamo = GetModelObject()->GetAttachmentModel( m_iAtt3Position);
      if( pamo != NULL) {
        pamo->amo_moModelObject.StartAnim( m_iAtt3Anim);
      }
    }

    // assure valid target
    if (m_penTarget!=NULL && !IsOfClass(m_penTarget, "Environment Marker")) {
      WarningMessage("Target '%s' is not of Environment Marker class!", (const char*)m_penTarget->GetName());
      m_penTarget = NULL;
    }
};


/************************************************************
 *                    WATCHER FUNCTIONS                     *
 ************************************************************/
  void InitializeWatcher(FLOAT fWaitTime) {
    // spawn player watcher
    m_penWatcher = CreateEntity(GetPlacement(), CLASS_WATCHPLAYERS);
    m_penWatcher->Initialize(EVoid());

    // setup player watcher
    CWatchPlayers &pw = (CWatchPlayers&)*m_penWatcher;
    pw.m_penOwner = this;
    pw.m_fWaitTime = 2.0f;
    pw.m_fDistance = m_fDistance;
    pw.m_bRangeWatcher = FALSE;
    pw.m_eetEventClose = EET_ENVIRONMENTSTART;
    pw.m_eetEventFar = EET_ENVIRONMENTSTOP;
  };



/************************************************************
 *                    ANIMATION FUCNTIONS                   *
 ************************************************************/
  // play default anim
  void PlayDefaultAnim(void) {
    GetModelObject()->PlayAnim(m_iAnim, AOF_LOOPING|AOF_NORESTART);
  };

  // play marker animation
  void PlayMarkerAnimation(void) {
    if (m_penTarget != NULL) {
      GetModelObject()->PlayAnim(((CEnvironmentMarker&)*m_penTarget).m_iAnim, AOF_LOOPING|AOF_NORESTART);
    }
  };

  // change default anim
  void ChangeDefaultAnim(void) {
    if (m_penTarget != NULL && ((CEnvironmentMarker&)*m_penTarget).m_bChangeDefaultAnim) {
      m_iAnim = ((CEnvironmentMarker&)*m_penTarget).m_iAnim;
    }
  };

  // wait on marker
  void WaitOnMarker(void) {
    if (m_penTarget != NULL) {
      CEnvironmentMarker &em = (CEnvironmentMarker&)*m_penTarget;
      m_fWaitTime = em.m_fWaitTime;               // wait time
      m_fWaitTime += FRnd() * em.m_fRandomTime;   // random wait time
      // fixed anim length
      if (em.m_bFixedAnimLength) {
        m_fWaitTime = floor(m_fWaitTime + 0.5f);
      }
    }
  };



procedures:
/************************************************************
 *                    SUPPORT PROCEDURES                    *
 ************************************************************/
  // move to marker
  MoveToMarker(EVoid) {
    // if next marker exist
    if (NextMarker()) {
      // destination
      CalcDestination();
      // move to marker
      while ((m_vDesiredPosition-GetPlacement().pl_PositionVector).Length() > 5.0f) {
        wait(m_fMoveFrequency) {
          on (EBegin) : {
            MoveToPosition();
            resume;
          }
          on (ETimer) : { stop; }
        }
      }
    }
    // stop moving
    StopMoving();
    return EEnd();
  };



/************************************************************
 *                    A C T I O N S                         *
 ************************************************************/
  // activate
  Activate(EVoid) {
    wait() {
      on (EBegin) : { call DoAction(); }
      on (EEnvironmentStop) : { jump Stop(); }
    }
  };

  // just wait
  Stop(EVoid) {
    StopMoving();
    wait() {
      on (EBegin) : { resume; }
      on (EEnvironmentStart) : { jump Activate(); }
    }
  };

  // do actions
  DoAction(EVoid) {
    while (TRUE) {
      WaitOnMarker();
      if (m_fWaitTime > 0.0f) {
        PlayMarkerAnimation();
        autowait(m_fWaitTime);
      }
      ChangeDefaultAnim();

      MarkerParameters();
      PlayDefaultAnim();
      autocall MoveToMarker() EEnd;

      // if no more targets wait forever in last anim (if marker anim exist otherwise in default anim)
      if (m_penTarget==NULL || ((CEnvironmentMarker&)*m_penTarget).m_penTarget==NULL) {
        autowait();
      }
    }
  };



/************************************************************
 *                M  A  I  N    L  O  O  P                  *
 ************************************************************/
  // main loop
  MainLoop(EVoid) {
    autocall Stop() EEnd;

    // destroy player watcher
    m_penWatcher->SendEvent(EEnd());

    // cease to exist
    Destroy();

    return;
  };

  Main() {
    // initialize
    Initialize();

    // wait until game starts
    autowait(FRnd()*2.0f+1.0f);

    // initialize watcher
    if (m_bUseWatcher) {
      InitializeWatcher(m_fWatcherFrequency);
    }

    m_strDescription = "Environment base";

    jump MainLoop();
  };
};
