/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */

101
%{
#include "StdH.h"
%}

uses "EntitiesMP/MovingBrushMarker";
uses "EntitiesMP/SoundHolder";
uses "EntitiesMP\MirrorMarker";
uses "EntitiesMP/Debris";

event EHit {
};
event EBrushDestroyed { // sent to all children of a moving brush when it is destroyed
};

enum BlockAction {
  0 BA_NONE         "None",         // continue moving
  1 BA_BOUNCE       "Bounce",       // bounce when obstructed
  2 BA_SKIPMARKER   "Skip marker",  // skip moving to next marker
};

enum TouchOrDamageEvent {
  0 TDE_TOUCHONLY   "Touch Only", 
  1 TDE_DAMAGEONLY  "Damage Only", 
  2 TDE_BOTH        "Both", 
};

%{
static const float TRANSLATION_EPSILON=0.05f;
static const float ROTATION_EPSILON=0.05f;
extern void GetDefaultForce(INDEX iForce, const FLOAT3D &vPoint, 
    CForceStrength &fsGravity, CForceStrength &fsField);
%}

class CMovingBrush : CMovableBrushEntity {
name      "Moving Brush";
thumbnail "Thumbnails\\MovingBrush.tbn";
features  "HasName", "IsTargetable";

properties:

  1 CTString m_strName            "Name" 'N' = "Moving Brush",
  2 CTString m_strDescription = "",
  
  3 CEntityPointer m_penTarget    "Target" 'T' COLOR(C_BLUE|0xFF),
  4 BOOL m_bAutoStart             "Auto start" 'A' = FALSE,
  5 FLOAT m_fSpeed                "Speed" 'S' = 1.0f,
  6 FLOAT m_fWaitTime             "Wait time" 'W' = 0.0f,
  7 BOOL m_bMoveOnTouch           "Move on touch" 'M' = FALSE,
  8 enum BlockAction m_ebaAction  "Block action" 'B' = BA_NONE,
  9 FLOAT m_fBlockDamage          "Block damage" 'D' = 10.0f,
 10 BOOL m_bPlayersOnly           "Players Only" 'P' = TRUE,
 11 BOOL m_bDynamicShadows        "Dynamic shadows" = FALSE,
 12 BOOL m_bVeryBigBrush          "Very Big Brush" = FALSE,

 // send event on touch
 13 enum EventEType m_eetTouchEvent "Touch Event - Type" 'U' = EET_IGNORE,  // type of event to send
 14 CEntityPointer m_penTouchEvent  "Touch Event - Target" 'I' COLOR(C_dCYAN|0xFF),            // target to send event to
 19 enum TouchOrDamageEvent m_tdeSendEventOnDamage "Send touch event on damage" = TDE_TOUCHONLY,

 15 CEntityPointer m_penSwitch "Switch",  // for switch relaying

 // send event on marker
 16 enum EventEType m_eetMarkerEvent = EET_IGNORE,
 17 CEntityPointer m_penMarkerEvent,

 // rotation
 18 FLOAT m_tmBankingRotation "Banking rotation speed" = 0.0f, // set if only banking rotation

 // class properties
 20 BOOL m_bMoving = FALSE,           // the brush is moving
 78 BOOL m_bRotating = FALSE,         // the brush is rotating
 79 BOOL m_bForceStop = FALSE,        // the brush should stop immediately
 80 BOOL m_bNoRotation = FALSE,       // don't rotate to marker orientation
 21 FLOAT3D m_vDesiredTranslation = FLOAT3D(0,0,0),    // desired translation
 22 ANGLE3D m_aDesiredRotation = FLOAT3D(0,0,0),       // desired rotation
 23 BOOL m_bInverseRotate = FALSE,    // use inverse rotation to target
 24 BOOL m_bStopMoving = FALSE,       // stop moving brush on next target
 25 BOOL m_bMoveToMarker = FALSE,     // PerMoving acknowledge od brush moving
 26 BOOL m_bSkipMarker = FALSE,       // when obstructed skip next marker (actually stop moving)
 27 BOOL m_bValidMarker = FALSE,      // internal for moving through valid markers
 
 // moving limits
 30 FLOAT m_fXLimitSign = 0.0f,
 31 FLOAT m_fYLimitSign = 0.0f,
 32 FLOAT m_fZLimitSign = 0.0f,
 33 ANGLE m_aHLimitSign = 0.0f,
 34 ANGLE m_aPLimitSign = 0.0f,
 35 ANGLE m_aBLimitSign = 0.0f,

 // continuous speed change
 40 FLOAT3D m_vStartTranslation = FLOAT3D(0,0,0),  // start translation
 41 ANGLE3D m_aStartRotation = ANGLE3D(0,0,0),     // start rotation
 42 FLOAT m_fCourseLength = 0.0f,        // course length
 43 ANGLE m_aHeadLenght = 0.0f,          // head lenght  
 44 ANGLE m_aPitchLenght = 0.0f,         // pitch lenght
 45 ANGLE m_aBankLenght = 0.0f,          // bank lenght

 // sound target
 50 CEntityPointer m_penSoundStart    "Sound start entity" 'Q',   // sound start entity
 51 CEntityPointer m_penSoundStop     "Sound stop entity" 'Z',    // sound stop entity
 52 CEntityPointer m_penSoundFollow   "Sound follow entity" 'F',  // sound follow entity
 53 CSoundObject m_soStart,
 54 CSoundObject m_soStop,
 55 CSoundObject m_soFollow,


 60 CEntityPointer m_penMirror0 "Mirror 0" 'M',
 61 CEntityPointer m_penMirror1 "Mirror 1",
 62 CEntityPointer m_penMirror2 "Mirror 2",
 63 CEntityPointer m_penMirror3 "Mirror 3",
 64 CEntityPointer m_penMirror4 "Mirror 4",

 65 FLOAT m_fHealth             "Health" 'H' = -1.0f,
 66 BOOL m_bBlowupByBull        "Blowup by Bull" = FALSE,   // special feature for bull crushing doors
 // send event on touch
 67 enum EventEType m_eetBlowupEvent "Blowup Event - Type" = EET_IGNORE,  // type of event to send
 68 CEntityPointer m_penBlowupEvent  "Blowup Event - Target" COLOR(C_BLACK|0xFF),            // target to send event to
 69 BOOL m_bZoning              "Zoning"     'Z' =FALSE,
 70 BOOL m_bMoveOnDamage "Move on damage" = FALSE,    // move when recive damage
 71 FLOAT m_fTouchDamage        "Touch damage" = 0.0f,
 72 COLOR m_colDebrises         "Color of debrises" = C_WHITE,
 74 INDEX m_ctDebrises          "Debris count" = 12,
 75 FLOAT m_fCandyEffect        "Debris blow power" = 0.0f,
 76 FLOAT m_fCubeFactor         "Cube factor" = 1.0f,
 77 BOOL m_bBlowupByDamager     "Blowup by Damager" = FALSE,   // if only damager can destroy brush

 81 flags ClasificationBits m_cbClassificationBits "Clasification bits" 'C' = 0,
 82 flags VisibilityBits m_vbVisibilityBits "Visibility bits" 'V' = 0,


components:

// ************** STONE PARTS **************
 14 model     MODEL_STONE        "Models\\Effects\\Debris\\Stone\\Stone.mdl",
 15 texture   TEXTURE_STONE      "Models\\Effects\\Debris\\Stone\\Stone.tex",
 16 class     CLASS_DEBRIS       "Classes\\Debris.ecl",
  4 class     CLASS_BASIC_EFFECT "Classes\\BasicEffect.ecl",


functions:

 // get visibility tweaking bits
  ULONG GetVisTweaks(void)
  {
    return m_cbClassificationBits|m_vbVisibilityBits;
  }
 
 void Precache(void)
  {
    PrecacheClass(CLASS_DEBRIS);
    PrecacheModel(MODEL_STONE);
    PrecacheTexture(TEXTURE_STONE);
  }
  /* Get force in given point. */
  void GetForce(INDEX iForce, const FLOAT3D &vPoint, 
    CForceStrength &fsGravity, CForceStrength &fsField)
  {
    GetDefaultForce(iForce, vPoint, fsGravity, fsField);
  }
  /* Receive damage */
  void ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType,
    FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection) 
  {
    if( m_bMoveOnDamage)
    {
      EHit eHit;
      SendEvent( eHit);
      return;
    }

    // send event on damage
    if(m_tdeSendEventOnDamage!=TDE_TOUCHONLY && CanReactOnEntity(penInflictor)) {
      SendToTarget(m_penTouchEvent, m_eetTouchEvent, penInflictor);
    }

    // if not destroyable
    if(m_fHealth<0) {
      // ignore damages
      return;
    }

    // if special feature for bull crushing doors
    if (m_bBlowupByBull)
    {
      // if impact by bull
      if( dmtType == DMT_IMPACT && IsOfClass(penInflictor, "Werebull"))
      {
        // receive the damage so large to blowup
        CMovableBrushEntity::ReceiveDamage(penInflictor, dmtType, m_fHealth*2, vHitPoint, vDirection);
        // kill the bull in place, but make sure it doesn't blow up
        ((CLiveEntity*)penInflictor)->SetHealth(0.0f);
        InflictDirectDamage(penInflictor, this, DMT_IMPACT, 1.0f, 
          GetPlacement().pl_PositionVector, FLOAT3D(0,1,0));
      }
    }
    else if(m_bBlowupByDamager)
    {
      if( dmtType == DMT_DAMAGER)
      {
        CMovableBrushEntity::ReceiveDamage(penInflictor, dmtType, fDamageAmmount, vHitPoint, vDirection);
      }
    }
    else
    {
      // react only on explosions
      if( (dmtType == DMT_EXPLOSION) ||
          (dmtType == DMT_PROJECTILE) ||
          (dmtType == DMT_CANNONBALL) )
      {
        CMovableBrushEntity::ReceiveDamage(penInflictor, dmtType, fDamageAmmount, vHitPoint, vDirection);
      }
    }
  };

  // adjust angle
  void AdjustAngle(ANGLE &a) {
    if (m_bInverseRotate) {
      if (a>0) { a = a - 360; }
      else if (a<0) { a = 360 + a; }
    }
  };


  /* 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\\MovingBrushMarker.ecl");
    strTargetProperty = "Target";
    return TRUE;
  }
  const CTString &GetDescription(void) const {
    ((CTString&)m_strDescription).PrintF("-><none>");
    if (m_penTarget!=NULL) {
      ((CTString&)m_strDescription).PrintF("->%s", m_penTarget->GetName());
    }
    return m_strDescription;
  }
  /* Get mirror type name, return empty string if not used. */
  const CTString &GetMirrorName(INDEX iMirror)
  {
    static const CTString strDummyName("");
    static const CTString strMarkerUnused("Marker not set");
    if (iMirror==0) {
      return strDummyName;
    }

    switch (iMirror) {
    case 1: { static const CTString str("std mirror 1"); return str; }; break;
    case 2: { static const CTString str("std mirror 2"); return str; }; break;
    case 3: { static const CTString str("std mirror 3"); return str; }; break;
    case 4: { static const CTString str("std mirror 4"); return str; }; break;
    case 5: { static const CTString str("std mirror 5"); return str; }; break;
    case 6: { static const CTString str("std mirror 6"); return str; }; break;
    case 7: { static const CTString str("std mirror 7"); return str; }; break;
    case 8: { static const CTString str("std mirror 8"); return str; }; break;
    default: {
      iMirror-=9;
      INDEX ctMirrorMarkers = &m_penMirror4-&m_penMirror0;
      if (iMirror<ctMirrorMarkers){
        CMirrorMarker *pfm = (CMirrorMarker *)&*(&m_penMirror0)[iMirror];
        if (pfm != NULL) {
          return pfm->GetMirrorName();
        } else {
          return strMarkerUnused;
        }
      }
             }
    }
    return strDummyName;
  }

  /* Get mirror, return FALSE for none. */
  BOOL GetMirror(INDEX iMirror, class CMirrorParameters &mpMirror)
  {
    if (iMirror==0) {
      return FALSE;
    }
    if (iMirror>=1 && iMirror<=8) {
      mpMirror.mp_ulFlags = 0;
      return TRUE;
    }
    iMirror-=9;
    INDEX ctMirrorMarkers = &m_penMirror4-&m_penMirror0;
    if (iMirror<ctMirrorMarkers){
      CMirrorMarker *pmm = (CMirrorMarker *)&*(&m_penMirror0)[iMirror];
      if (pmm != NULL) {
        pmm->GetMirror(mpMirror);
        return TRUE;
      }
    }
    return FALSE;
  }

  // pre moving
  void PreMoving() {
    if (m_bMoveToMarker) {
      const FLOAT3D &vTarget = m_penTarget->GetPlacement().pl_PositionVector;
      const ANGLE3D &aTarget = m_penTarget->GetPlacement().pl_OrientationAngle;
      const FLOAT3D &vSource = GetPlacement().pl_PositionVector;
      const ANGLE3D &aSource = GetPlacement().pl_OrientationAngle;

      // translation
      FLOAT3D vSpeed = (vTarget-vSource)/_pTimer->TickQuantum;
      // X axis
      if (Abs(vSpeed(1))<TRANSLATION_EPSILON) {
        vSpeed(1) = 0.0f;
      } else if ((vSpeed(1)-m_vDesiredTranslation(1))*m_fXLimitSign>0) {
        vSpeed(1) = m_vDesiredTranslation(1);
      }
      // Y axis
      if (Abs(vSpeed(2))<TRANSLATION_EPSILON) {
        vSpeed(2) = 0.0f;
      } else if ((vSpeed(2)-m_vDesiredTranslation(2))*m_fYLimitSign>0) {
        vSpeed(2) = m_vDesiredTranslation(2);
      }
      // Z axis
      if (Abs(vSpeed(3))<TRANSLATION_EPSILON) {
        vSpeed(3) = 0.0f;
      } else if ((vSpeed(3)-m_vDesiredTranslation(3))*m_fZLimitSign>0) {
        vSpeed(3) = m_vDesiredTranslation(3);
      }

      // rotation
      ANGLE3D aSpeed;
      aSpeed(1) = NormalizeAngle(aTarget(1)-aSource(1));      // normalize angle
      AdjustAngle(aSpeed(1));                                 // adjust angle (inverse rotation)
      aSpeed(1) = Abs(aSpeed(1)) * m_aHLimitSign;             // set sign (direction)
      aSpeed(1) /= _pTimer->TickQuantum;                      // transform to tick speed
      aSpeed(2) = NormalizeAngle(aTarget(2)-aSource(2));
      AdjustAngle(aSpeed(2));
      aSpeed(2) = Abs(aSpeed(2)) * m_aPLimitSign;
      aSpeed(2) /= _pTimer->TickQuantum;
      aSpeed(3) = NormalizeAngle(aTarget(3)-aSource(3));
      AdjustAngle(aSpeed(3));
      aSpeed(3) = Abs(aSpeed(3)) * m_aBLimitSign;
      aSpeed(3) /= _pTimer->TickQuantum;
      // Heading
      if (Abs(aSpeed(1))<ROTATION_EPSILON) {
        aSpeed(1) = 0.0f;
      } else if ((aSpeed(1)-m_aDesiredRotation(1))*m_aHLimitSign>0) {
        aSpeed(1) = m_aDesiredRotation(1);
      }
      // Pitch
      if (Abs(aSpeed(2))<ROTATION_EPSILON) {
        aSpeed(2) = 0.0f;
      } else if ((aSpeed(2)-m_aDesiredRotation(2))*m_aPLimitSign>0) {
        aSpeed(2) = m_aDesiredRotation(2);
      }
      // Banking
      if (Abs(aSpeed(3))<ROTATION_EPSILON) {
        aSpeed(3) = 0.0f;
      } else if ((aSpeed(3)-m_aDesiredRotation(3))*m_aBLimitSign>0) {
        aSpeed(3) = m_aDesiredRotation(3);
      }

      // stop moving ?
      if (vSpeed(1)==0.0f && vSpeed(2)==0.0f && vSpeed(3)==0.0f
       && ((m_tmBankingRotation!=0 || m_bNoRotation)||(aSpeed(1)==0.0f && aSpeed(2)==0.0f && aSpeed(3)==0.0f)) ) 
      {
        // stop brush
        ForceFullStop();
        // stop PreMoving check
        m_bMoveToMarker = FALSE;
        // this EEnd event will end MoveToMarker autowait() and return to MoveBrush
        SendEvent(EEnd());

      // move brush
      } else {
        SetDesiredTranslation(vSpeed);
        if (m_bRotating) {
          MaybeActivateRotation();
        } else if (!m_tmBankingRotation && !m_bNoRotation) {
          SetDesiredRotation(aSpeed);
        } else {
          SetDesiredRotation(ANGLE3D(0,0,0));
        }
      }

    }
    CMovableBrushEntity::PreMoving();
  };


  // load marker parameters
  BOOL LoadMarkerParameters() {
    if (m_penTarget==NULL) {
      return FALSE;
    }

    if (!IsOfClass(m_penTarget, "Moving Brush Marker")) {
      WarningMessage("Entity '%s' is not of Moving Brush Marker class!", m_penTarget->GetName());
      return FALSE;
    }

    CMovingBrushMarker &mbm = (CMovingBrushMarker&) *m_penTarget;
    if (mbm.m_penTarget==NULL) {
      return FALSE;
    }

    // speed
    if (mbm.m_fSpeed > 0.0f) { m_fSpeed = mbm.m_fSpeed; }

    // wait time
    if (mbm.m_fWaitTime >= 0.0f) { m_fWaitTime = mbm.m_fWaitTime; }

    // inverse rotate
    m_bInverseRotate = mbm.m_bInverseRotate;
    
    // move on touch
    SetBoolFromBoolEType(m_bMoveOnTouch, mbm.m_betMoveOnTouch);

    // stop moving
    m_bStopMoving = mbm.m_bStopMoving;

    // block damage
    if (mbm.m_fBlockDamage >= 0.0f) {
      m_fBlockDamage = mbm.m_fBlockDamage;
    }

    // touch event
    if (mbm.m_penTouchEvent != NULL) {
      m_penTouchEvent = mbm.m_penTouchEvent;
      m_eetTouchEvent = mbm.m_eetTouchEvent;
    }

    // marker event -> SEND ALWAYS (if target is valid) !!!
    SendToTarget(mbm.m_penMarkerEvent, mbm.m_eetMarkerEvent);

    // sound entity
    if (mbm.m_penSoundStart!=NULL) {
      m_penSoundStart = mbm.m_penSoundStart;
    }
    if (mbm.m_penSoundStop!=NULL) {
      m_penSoundStop = mbm.m_penSoundStop;
    }
    if (mbm.m_penSoundFollow!=NULL) {
      m_penSoundFollow = mbm.m_penSoundFollow;
    }

    m_bNoRotation = mbm.m_bNoRotation;

    if (mbm.m_tmBankingRotation>=0.0f) {
      m_tmBankingRotation = mbm.m_tmBankingRotation;
      if (!mbm.m_bBankingClockwise) {
        m_tmBankingRotation *= -1;
      }
    }

    return TRUE;
  };


  // test if this door reacts on this entity
  BOOL CanReactOnEntity(CEntity *pen)
  {
    if (pen==NULL) {
      return FALSE;
    }
    // never react on non-live or dead entities
    if (!(pen->GetFlags()&ENF_ALIVE)) {
      return FALSE;
    }

    if (m_bPlayersOnly && !IsDerivedFromClass(pen, "Player")) {
      return FALSE;
    }

    return TRUE;
  }

  // play start sound
  void PlayStartSound(void) {
    // if sound entity exists
    if (m_penSoundStart!=NULL) {
      CSoundHolder &sh = (CSoundHolder&)*m_penSoundStart;
      m_soStart.Set3DParameters(FLOAT(sh.m_rFallOffRange), FLOAT(sh.m_rHotSpotRange), sh.m_fVolume, 1.0f);
      PlaySound(m_soStart, sh.m_fnSound, sh.m_iPlayType);
    }
  };

  // play stop sound
  void PlayStopSound(void) {
    // if sound entity exists
    if (m_penSoundStop!=NULL) {
      CSoundHolder &sh = (CSoundHolder&)*m_penSoundStop;
      m_soStop.Set3DParameters(FLOAT(sh.m_rFallOffRange), FLOAT(sh.m_rHotSpotRange), sh.m_fVolume, 1.0f);
      PlaySound(m_soStop, sh.m_fnSound, sh.m_iPlayType);
    }
  };

  // play follow sound
  void PlayFollowSound(void) {
    // if sound entity exists
    if (m_penSoundFollow!=NULL) {
      CSoundHolder &sh = (CSoundHolder&)*m_penSoundFollow;
      m_soFollow.Set3DParameters(FLOAT(sh.m_rFallOffRange), FLOAT(sh.m_rHotSpotRange), sh.m_fVolume, 1.0f);
      PlaySound(m_soFollow, sh.m_fnSound, sh.m_iPlayType);
    }
  };

  // stop follow sound
  void StopFollowSound(void) {
    m_soFollow.Stop();
  };


  void MovingOn(void)
  {
    if (m_bMoving) {
      return;
    }
    if (m_bVeryBigBrush) {
      SetCollisionFlags(ECF_BRUSH|ECF_IGNOREMODELS);
    }
    m_bMoving = TRUE;
  }
  void MovingOff(void)
  {
    if (!m_bMoving) {
      return;
    }
    if (m_bVeryBigBrush) {
      SetCollisionFlags(ECF_BRUSH);
    }
    m_bMoving = FALSE;
  }

  void MaybeActivateRotation(void)
  {
    if (m_tmBankingRotation!=0) {
      m_bRotating = TRUE;
      SetDesiredRotation(ANGLE3D(0.0f,0.0f,360.0f/m_tmBankingRotation));  
    }
    else {
      m_bRotating = FALSE;
    }
  }

  void DeactivateRotation(void)
  {
    m_bRotating = FALSE;
    SetDesiredRotation(ANGLE3D(0.0f,0.0f,0.0f));  
  }

  void SetCombinedRotation(ANGLE3D aRotAngle, ANGLE3D aAddAngle)
  {
    aRotAngle(1) += aAddAngle(1);
    aRotAngle(2) += aAddAngle(2);
    aRotAngle(3) += aAddAngle(3);
  }


  // returns bytes of memory used by this object
  SLONG GetUsedMemory(void)
  {
    // initial
    SLONG slUsedMemory = sizeof(CMovingBrush) - sizeof(CMovableBrushEntity) + CMovableBrushEntity::GetUsedMemory();
    // add some more
    slUsedMemory += m_strName.Length();
    slUsedMemory += m_strDescription.Length();
    slUsedMemory += 3* sizeof(CSoundObject); // 3 of them
    return slUsedMemory;
  }



procedures:


  MoveToMarker() {
    // move to target
    const FLOAT3D &vTarget = m_penTarget->GetPlacement().pl_PositionVector;
    const ANGLE3D &aTarget = m_penTarget->GetPlacement().pl_OrientationAngle;
    const FLOAT3D &vSource = GetPlacement().pl_PositionVector;
    const ANGLE3D &aSource = GetPlacement().pl_OrientationAngle;

    // set new translation
    m_vDesiredTranslation = (vTarget-vSource)/m_fSpeed;
    m_fXLimitSign = Sgn(vTarget(1)-vSource(1));
    m_fYLimitSign = Sgn(vTarget(2)-vSource(2));
    m_fZLimitSign = Sgn(vTarget(3)-vSource(3));

    // set new rotation
    // heading
    ANGLE aDelta = NormalizeAngle(aTarget(1)-aSource(1));
    AdjustAngle(aDelta);
    m_aDesiredRotation(1) = aDelta/m_fSpeed;
    m_aHLimitSign = Sgn(aDelta);
    // pitch
    aDelta = NormalizeAngle(aTarget(2)-aSource(2));
    AdjustAngle(aDelta);
    m_aDesiredRotation(2) = aDelta/m_fSpeed;
    m_aPLimitSign = Sgn(aDelta);
    // banking
    aDelta = NormalizeAngle(aTarget(3)-aSource(3));
    AdjustAngle(aDelta);
    m_aDesiredRotation(3) = aDelta/m_fSpeed;
    m_aBLimitSign = Sgn(aDelta);

    // start moving
    m_bMoveToMarker = TRUE;
    SetDesiredTranslation(m_vDesiredTranslation);
    if (m_bRotating) {
      MaybeActivateRotation();
    } else if (!m_tmBankingRotation) {
      SetDesiredRotation(m_aDesiredRotation);
    }

    // PreMoving will send EEnd event to end
    wait() {
      on (EBegin) : { resume; }
      on (EStop) : {
        //SetCollisionFlags(ECF_IMMATERIAL);
        SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f));
        if (m_tmBankingRotation==0) {
          SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 0.0f));
        }
        m_bForceStop = TRUE;
        // stop PreMoving check
        m_bMoveToMarker = FALSE;
        m_bStopMoving = TRUE;

        return EEnd();
      }
      // move is obstructed
      on (EBlock eBlock) : {
        // inflict damage to entity that block brush
        InflictDirectDamage(eBlock.penOther, this, DMT_BRUSH, m_fBlockDamage,
          FLOAT3D(0.0f,0.0f,0.0f), (FLOAT3D &)eBlock.plCollision);
        if (m_ebaAction == BA_BOUNCE) {
          // change direction for two ticks
          SetDesiredTranslation(-m_vDesiredTranslation);
          if (m_bRotating) {
              MaybeActivateRotation();
            } else if (!m_tmBankingRotation) {
              SetDesiredRotation(-m_aDesiredRotation);
          }
          // wait for two ticks and reset direction
          call BounceObstructed();
        } else if (m_ebaAction == BA_SKIPMARKER) {
          // stop moving brush
          ForceFullStop();
          // stop PreMoving check
          m_bMoveToMarker = FALSE;
          // skip this marker and move to next one
          m_bSkipMarker = TRUE;
          return EEnd();
        }
        resume;
      }
    }
  }

  BounceObstructed() {
    autowait(0.2f);
    // return to standard direction
    SetDesiredTranslation(m_vDesiredTranslation);
    if (m_bRotating) {
      SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 360.0f/m_tmBankingRotation));
    } else if (!m_tmBankingRotation) {
      SetDesiredRotation(m_aDesiredRotation);
    }
    return;
  }

  /*Rotating()
  {
    if (m_bAutoStart) {
      jump RotActive();
    } else {
      jump RotInactive();
    }
  }

  RotInactive()
  {
    SetDesiredRotation(ANGLE3D(0,0,0));
    wait() {
      on (EActivate) : {
        jump RotActive();
      }
      otherwise() : {
        resume;
      }
    };
  }

  RotActive()
  {
    SetDesiredRotation(ANGLE3D(0,0,360.0f/m_tmBankingRotation));

    wait() {
      on (EDeactivate) : {
        jump RotInactive();
      }
      otherwise() : {
        resume;
      }
    };
  }*/

  // move brush
  MoveBrush() 
  {
    if (m_penTarget==NULL) {
      MovingOff();
      return;
    }

    MovingOn();

    // move through markers
    do {
      
      if (m_bForceStop==FALSE)
      {
        // new moving target
        m_penTarget = m_penTarget->GetTarget();
      }
      else {
        m_bForceStop=FALSE;
      }

      if (m_penTarget==NULL) {
        MovingOff();
        return EVoid();
      }

      // skip this marker / ignore wait time
      if (m_bSkipMarker) {
        m_bSkipMarker = FALSE;
      // wait for a while
      } else if (m_fWaitTime > 0.0f) { 
        //autowait(m_fWaitTime);
        wait(m_fWaitTime) {
          on (EBegin) : { resume; }
          on (EStop) : {
            //SetCollisionFlags(ECF_IMMATERIAL);
            SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f));
            if (m_tmBankingRotation==0) {
              SetDesiredRotation(ANGLE3D(0.0f, 0.0f, 0.0f));
            }
            m_bForceStop = TRUE;
            // stop PreMoving check
            m_bMoveToMarker = FALSE;
            m_bStopMoving = TRUE;
            
            resume; }//return; }
          on (ETimer) : { stop; }
        }          
      }

      if (!m_bForceStop) {
      MaybeActivateRotation();

      PlayStartSound();
      PlayFollowSound();
      autocall MoveToMarker() EEnd;
      StopFollowSound();
      PlayStopSound();
      }

      // load marker parameters or stop moving if there is no marker
      if (!m_bForceStop) {
        m_bValidMarker = LoadMarkerParameters();
      }
      
      // skip this marker / ignore stop moving
      if (m_bSkipMarker) {
        m_bStopMoving = FALSE;
      }
    } while (!m_bStopMoving && m_bValidMarker && !m_bForceStop);
    MovingOff();
    return;
  }

  TeleportToStopMarker()
  {
    MovingOn();

    INDEX ctMarkers=0;
    // new moving target
    CMovingBrushMarker *pmbm = (CMovingBrushMarker *) &*m_penTarget;
    while( pmbm!=NULL && IsOfClass(pmbm->m_penTarget, "Moving Brush Marker") && !pmbm->m_bStopMoving && ctMarkers<50)
    {      
      pmbm = (CMovingBrushMarker *) &*pmbm->m_penTarget;
      ctMarkers++;
    }

    if( pmbm!=NULL && IsOfClass(pmbm, "Moving Brush Marker") && ctMarkers<50)
    {
      SetPlacement(pmbm->GetPlacement());
      en_plLastPlacement=pmbm->GetPlacement();
      ForceFullStop();
      m_soStart.Stop();
      m_soStop.Stop();
      m_soFollow.Stop();
    }

    // stop PreMoving check
    m_bMoveToMarker = FALSE;
    MovingOff();
    return EReturn();
  }

  Main() {
    // declare yourself as a brush
    InitAsBrush();
    SetPhysicsFlags(EPF_BRUSH_MOVING);
    SetCollisionFlags(ECF_BRUSH);
    SetHealth(m_fHealth);

    // set zoning flag
    if (m_bZoning) {
      SetFlags(GetFlags()|ENF_ZONING);
    } else {
      SetFlags(GetFlags()&~ENF_ZONING);
    }


    // set dynamic shadows as needed
    if (m_bDynamicShadows) {
      SetFlags(GetFlags()|ENF_DYNAMICSHADOWS);
    } else {
      SetFlags(GetFlags()&~ENF_DYNAMICSHADOWS);
    }

    // stop moving brush
    ForceFullStop();

    autowait(0.1f);

    // load marker parameters
    m_bValidMarker = LoadMarkerParameters();

    /*if (m_tmBankingRotation!=0) {
      jump Rotating();
    }*/

    // start moving
    wait() {
      on (EBegin) : {
        if (m_bAutoStart) {
          // if not already moving and have target
          MaybeActivateRotation();
          if(!m_bMoving && m_bValidMarker) {
            call MoveBrush();
          }
        }
        resume;
      }
      on (EHit eHit) : {
        if (!m_bMoving) {
          MaybeActivateRotation();
          call MoveBrush();
        }
        resume;
      }
      // move on touch
      on (ETouch eTouch) : {
        // inflict damage if required
        if( m_fTouchDamage != 0.0f)
        {
          InflictDirectDamage( eTouch.penOther, this, DMT_SPIKESTAB, m_fTouchDamage,
                     eTouch.penOther->GetPlacement().pl_PositionVector, eTouch.plCollision);
        }
        // send event on touch
        if(m_tdeSendEventOnDamage!=TDE_DAMAGEONLY && CanReactOnEntity(eTouch.penOther)) {
          SendToTarget(m_penTouchEvent, m_eetTouchEvent);
        }
        // if not already moving
        if(!m_bMoving) {
          // move brush
          if (m_bMoveOnTouch && CanReactOnEntity(eTouch.penOther) && m_bValidMarker) {
            MaybeActivateRotation();
            call MoveBrush();
          }
        }
        // if special feature for bull crushing doors
        if (m_bBlowupByBull) {
          // if hit by bull
          if (IsOfClass(eTouch.penOther, "Werebull")) {
            // calculate speed along impact normal
            FLOAT fImpactSpeed = 
              ((CMovableEntity&)*eTouch.penOther).en_vCurrentTranslationAbsolute%
              -(FLOAT3D&)eTouch.plCollision;

            // if strong collision
            if (fImpactSpeed>m_fHealth) {
              // receive artificial impact damage
              ReceiveDamage(eTouch.penOther, DMT_IMPACT, m_fHealth*2, 
                FLOAT3D(0,0,0), FLOAT3D(0,0,0));
            }
          }
        }
        resume;
      }
      on (EBlock eBlock) : {
        // inflict damage to entity that block brush
        InflictDirectDamage(eBlock.penOther, this, DMT_BRUSH, m_fBlockDamage,
          FLOAT3D(0.0f,0.0f,0.0f), (FLOAT3D &)eBlock.plCollision);
        if (m_ebaAction == BA_BOUNCE) {
          // change direction for two ticks
          SetDesiredTranslation(-m_vDesiredTranslation);
          if (m_bRotating) {
              SetDesiredRotation(-ANGLE3D(0.0f, 0.0f, 360.0f/m_tmBankingRotation));
            } else if (!m_tmBankingRotation) {
              SetDesiredRotation(-m_aDesiredRotation);
          }

          // wait for two ticks and reset direction
          call BounceObstructed();
        }
        resume;
      }
      // move on start (usually trigger)
      on (EStart) : {
        // if not already moving and have target
        if(!m_bMoving && m_bValidMarker) {
          call MoveBrush();
        }
        resume;
      }
      on (EStop) : {
        //SetCollisionFlags(ECF_IMMATERIAL);
        resume;
      }
      on (ETeleportMovingBrush) : {
        call TeleportToStopMarker();
        resume;
      }
      on (ETrigger) : {
        // if not already moving and have target
        if(!m_bMoving && m_bValidMarker) {
          call MoveBrush();
        }
        resume;
      }
      on (EActivate) : {
        if (!m_bRotating) {
          MaybeActivateRotation();
        }
        resume;
      }
      on (EDeactivate) : {
        DeactivateRotation();
        resume;
      }
      on (EDeath eDeath) : {
        // get your size
        FLOATaabbox3D box;
        GetSize(box);
        if( m_ctDebrises>0)
        {
          FLOAT fEntitySize = pow(box.Size()(1)*box.Size()(2)*box.Size()(3)/m_ctDebrises, 1.0f/3.0f)*m_fCubeFactor;
          
          Debris_Begin(EIBT_ROCK, DPT_NONE, BET_NONE, fEntitySize, FLOAT3D(1.0f,2.0f,3.0f),
            FLOAT3D(0,0,0), 1.0f+m_fCandyEffect/2.0f, m_fCandyEffect, m_colDebrises);
          for(INDEX iDebris = 0; iDebris<m_ctDebrises; iDebris++) {
            Debris_Spawn(this, this, MODEL_STONE, TEXTURE_STONE, 0, 0, 0, IRnd()%4, 1.0f,
              FLOAT3D(FRnd()*0.8f+0.1f, FRnd()*0.8f+0.1f, FRnd()*0.8f+0.1f));
          }
        }

        // notify children
        FOREACHINLIST( CEntity, en_lnInParent, en_lhChildren, iten) {
          iten->SendEvent( EBrushDestroyed());
        }
        // send event to blowup target
        SendToTarget(m_penBlowupEvent, m_eetBlowupEvent, eDeath.eLastDamage.penInflictor);

        // make sure it doesn't loop with destroying itself
        m_tdeSendEventOnDamage = TDE_TOUCHONLY;
        m_fHealth = -1;
        m_bMoveOnDamage = FALSE;
        ForceFullStop();
        SetDefaultProperties();

        // notify engine to kickstart entities that are cached in stationary position,
        // before we turn off, so they can fall
        NotifyCollisionChanged();

        SetFlags( GetFlags()|ENF_HIDDEN);
        SetCollisionFlags(ECF_IMMATERIAL);

        // for each child of this entity
        {FOREACHINLIST(CEntity, en_lnInParent, en_lhChildren, itenChild) {
          // send it destruction event
          itenChild->SendEvent(ERangeModelDestruction());
        }}

        stop;
      }
    }
    return;
  }
};
