601
%{
#include "StdH.h"
#include "Models/Effects/Teleport01/Teleport.h"
#include "Models/Effects/ExplosionGrenade/ExplosionGrenade.h"
#include "Models/Effects/ShockWave01/ShockWave.h"
#include "Models/Effects/BloodOnTheWall01/Blood.h"
#include "Entities/MovingBrush.h"

#define EXPLOSION_GRENADE_TEXTURE_ANIM_FAST 0
#define EXPLOSION_GRENADE_TEXTURE_ANIM_MEDIUM 1
#define EXPLOSION_GRENADE_TEXTURE_ANIM_SLOW 2

#define SHOCKWAVE_TEXTURE_ANIM_FAST 0
#define SHOCKWAVE_TEXTURE_ANIM_MEDIUM 1
#define SHOCKWAVE_TEXTURE_ANIM_SLOW 2
%}

uses "Entities/Light";

enum BasicEffectType {
  0 BET_NONE                 "",     // no effect (never spawned)
  1 BET_ROCKET               "",     // rocket explosion
  2 BET_ROCKET_PLANE         "",     // rocket explosion on plane
  3 BET_GRENADE              "",     // grenade explosion
  4 BET_GRENADE_PLANE        "",     // grenade explosion on plane
  5 BET_EXPLOSIONSTAIN       "",     // explosion stain on brush
  6 BET_SHOCKWAVE            "",     // shock wave
  7 BET_LASERWAVE            "",     // laser wave
 10 BET_BLOODSPILL           "",     // blood spill from bullet exit wound
 11 BET_BLOODSTAIN           "",     // blood stain
 19 BET_BLOODSTAINGROW       "",     // blood stain which grows bigger
 12 BET_BLOODEXPLODE         "",     // blood explosion at bullet entry wound 
 13 BET_CANNON               "",     // cannon explosion
 14 BET_CANNON_PLANE         "",     // cannon explosion on plane
 15 BET_CANNONEXPLOSIONSTAIN "",     // cannon explosion stain on brush
 16 BET_CANNONSHOCKWAVE      "",     // cannon shock wave
 17 BET_TELEPORT             "",     // teleportation
 18 BET_BOMB                 "",     // small bomb explosion
 20 BET_BULLETTRAIL          "",     // white trail where bullet has passed
 21 BET_GIZMO_SPLASH_FX      "",     // gizmo splash fx
 22 BET_GIZMOSTAIN           "",     // gizmo stain
 30 BET_BULLETSTAINSTONE     "",     // bullet stain with ricochet on stone
 31 BET_BULLETSTAINSAND      "",     // bullet stain with ricochet on sand
 32 BET_BULLETSTAINWATER     "",     // bullet stain with ricochet on water surface
 33 BET_BULLETSTAINUNDERWATER "",    // bullet stain with ricochet on underwater surface
 34 BET_BULLETSTAINSTONENOSOUND "",  // bullet stain on stone with no sound
 35 BET_BULLETSTAINSANDNOSOUND  "",  // bullet stain on sand with no sound
 36 BET_BULLETSTAINWATERNOSOUND "",  // bullet stain on water surface with no sound
 37 BET_BULLETSTAINUNDERWATERNOSOUND "", // bullet stain on under water surface with no sound
 38 BET_BULLETSTAINREDSAND      "",     // bullet stain with ricochet on red sand
 39 BET_BULLETSTAINREDSANDNOSOUND "",   // bullet stain with ricochet on red sand without sound
 40 BET_LIGHT_CANNON            "",     // cannon explosion with lo sound volume
 41 BET_CANNON_NOLIGHT          "",     // cannon explosion without light witn lo sound volume
};

enum EffectParticlesType {
  0 EPT_NONE                  "",     // no partcicles
  1 EPT_BULLET_STONE          "",    // bullet partcicles on stone
  2 EPT_BULLET_SAND           "",    // bullet partcicles on sand
  3 EPT_BULLET_WATER          "",    // bullet partcicles on water
  4 EPT_BULLET_UNDER_WATER    "",    // bullet partcicles underwater
  5 EPT_BULLET_RED_SAND       "",    // bullet partcicles on red sand
};


// input parameter for spwaning a basic effect
event ESpawnEffect {
  enum BasicEffectType betType,   // type of effect
  FLOAT3D vNormal,                // normal for orientative effects
  FLOAT3D vDirection,             // direction oriented effects
  FLOAT3D vStretch,               // stretch effect model
  COLOR colMuliplier,             // color multiplier
};

%{
void CBasicEffect_OnPrecache(CDLLEntityClass *pdec, INDEX iUser) 
{
  switch ((BasicEffectType)iUser) {
  case BET_ROCKET:
  case BET_ROCKET_PLANE:
    pdec->PrecacheSound(SOUND_EXPLOSION);
    pdec->PrecacheModel(MDL_ROCKET_EXPLOSION);
    pdec->PrecacheTexture(TXT_ROCKET_EXPLOSION);
    pdec->PrecacheModel(MDL_PARTICLES_EXPLOSION);
    pdec->PrecacheTexture(TXT_PARTICLES_EXPLOSION);
    pdec->PrecacheModel(MDL_ROCKET3D_EXPLOSION);
    pdec->PrecacheTexture(TXT_ROCKET_EXPLOSION);
    pdec->PrecacheModel(MDL_PARTICLES3D_EXPLOSION);
    pdec->PrecacheTexture(TXT_PARTICLES_EXPLOSION);
    break;
  case BET_BOMB:
  case BET_GRENADE:
  case BET_GRENADE_PLANE:
    pdec->PrecacheSound(SOUND_EXPLOSION);
    pdec->PrecacheModel(MDL_GRENADE_EXPLOSION);
    pdec->PrecacheTexture(TXT_GRENADE_EXPLOSION);
    pdec->PrecacheModel(MDL_PARTICLES_EXPLOSION);
    pdec->PrecacheTexture(TXT_PARTICLES_EXPLOSION);
    pdec->PrecacheModel(MDL_GRENADE3D_EXPLOSION);
    pdec->PrecacheTexture(TXT_GRENADE_EXPLOSION);
    pdec->PrecacheModel(MDL_PARTICLES3D_EXPLOSION);
    pdec->PrecacheTexture(TXT_PARTICLES_EXPLOSION);
    break;
  case BET_CANNON:
  case BET_CANNON_NOLIGHT:
  case BET_LIGHT_CANNON:
  case BET_CANNON_PLANE:
  case BET_CANNONSHOCKWAVE:
    pdec->PrecacheSound(SOUND_EXPLOSION);
    pdec->PrecacheModel(MDL_CANNON_EXPLOSION);
    pdec->PrecacheTexture(TXT_CANNON_EXPLOSION);
    pdec->PrecacheModel(MDL_CANNON3D_EXPLOSION);
    pdec->PrecacheTexture(TXT_CANNON_EXPLOSION);
    pdec->PrecacheModel(MODEL_CANNONSHOCKWAVE);
    pdec->PrecacheTexture(TEXTURE_CANNONSHOCKWAVE);
    break;
  case BET_EXPLOSIONSTAIN:
    pdec->PrecacheModel(MODEL_EXPLOSION_STAIN);
    pdec->PrecacheTexture(TEXTURE_EXPLOSION_STAIN);
    break;
  case BET_CANNONEXPLOSIONSTAIN:
    pdec->PrecacheModel(MODEL_CANNON_EXPLOSION_STAIN);
    pdec->PrecacheTexture(TEXTURE_CANNON_EXPLOSION_STAIN);
    break;
  case BET_SHOCKWAVE:
    pdec->PrecacheModel(MODEL_SHOCKWAVE);
    pdec->PrecacheTexture(TEXTURE_SHOCKWAVE);
    break;
  case BET_LASERWAVE:
    pdec->PrecacheModel(MODEL_LASERWAVE);
    pdec->PrecacheTexture(TEXTURE_LASERWAVE);
    break;
  case BET_BULLETSTAINSTONE:
  case BET_BULLETSTAINSAND:
  case BET_BULLETSTAINREDSAND:
  case BET_BULLETSTAINWATER:
  case BET_BULLETSTAINUNDERWATER:
  case BET_BULLETSTAINSTONENOSOUND:
  case BET_BULLETSTAINSANDNOSOUND:
  case BET_BULLETSTAINREDSANDNOSOUND:
  case BET_BULLETSTAINWATERNOSOUND:
  case BET_BULLETSTAINUNDERWATERNOSOUND:
    pdec->PrecacheModel(MODEL_BULLET_HIT);
    pdec->PrecacheTexture(TEXTURE_BULLET_HIT);
    pdec->PrecacheTexture(TEXTURE_BULLET_SAND);
    pdec->PrecacheModel(MODEL_SHOCKWAVE);
    pdec->PrecacheTexture(TEXTURE_WATER_WAVE);
    pdec->PrecacheSound(SOUND_BULLET_STONE);
    pdec->PrecacheSound(SOUND_BULLET_SAND);
    pdec->PrecacheSound(SOUND_BULLET_WATER);
    pdec->PrecacheModel(MODEL_BULLET_STAIN);
    pdec->PrecacheTexture(TEXTURE_BULLET_STAIN);
    break;
  case BET_BULLETTRAIL:
    pdec->PrecacheModel(MODEL_BULLET_TRAIL);
    pdec->PrecacheTexture(TEXTURE_BULLET_TRAIL);
    break;
  case BET_GIZMO_SPLASH_FX:
    pdec->PrecacheModel(MODEL_BULLET_HIT);
    pdec->PrecacheTexture(TEXTURE_BULLET_HIT);
    pdec->PrecacheSound(SOUND_GIZMO_SPLASH);
    break;
  case BET_BLOODEXPLODE:
    pdec->PrecacheModel(MODEL_BLOOD_EXPLODE);
    pdec->PrecacheTexture(TEXTURE_BLOOD_EXPLODE);
    pdec->PrecacheSound(SOUND_BULLET_FLESH);
    break;
  case BET_BLOODSTAIN:
  case BET_BLOODSTAINGROW:
  case BET_BLOODSPILL:
  case BET_GIZMOSTAIN:
    pdec->PrecacheModel(MODEL_BLOOD_STAIN);
    pdec->PrecacheTexture(TEXTURE_BLOOD_STAIN1);
    pdec->PrecacheTexture(TEXTURE_BLOOD_STAIN2);
    pdec->PrecacheTexture(TEXTURE_BLOOD_STAIN3);
    pdec->PrecacheTexture(TEXTURE_BLOOD_STAIN4);
    pdec->PrecacheTexture(TEXTURE_BLOOD_SPILL1);
    pdec->PrecacheTexture(TEXTURE_BLOOD_SPILL2);
    pdec->PrecacheTexture(TEXTURE_BLOOD_SPILL3);
    pdec->PrecacheTexture(TEXTURE_BLOOD_FLOWER1);
    pdec->PrecacheTexture(TEXTURE_BLOOD_FLOWER2);
    pdec->PrecacheTexture(TEXTURE_BLOOD_FLOWER3);
    break;
  case BET_TELEPORT:
    pdec->PrecacheModel(MODEL_TELEPORT_EFFECT);
    pdec->PrecacheTexture(TEXTURE_TELEPORT_EFFECT);
    pdec->PrecacheSound(SOUND_TELEPORT);
    break;
  default:
    ASSERT(FALSE);
  }
}
%}

class CBasicEffect : CRationalEntity {
name      "BasicEffect";
thumbnail "";
features "ImplementsOnPrecache", "CanBePredictable";

properties:
  1 enum BasicEffectType m_betType = BET_NONE, // type of effect
  2 FLOAT m_fWaitTime = 0.0f,       // wait time
  3 FLOAT m_fFadeTime = 0.0f,       // fade away time
  4 BOOL  m_bFade = FALSE,          // fade is enabled
  5 FLOAT m_fFadeStartTime  = 0.0f,        // fade away start time
  9 FLOAT m_fFadeStartAlpha = 1.0f,        // alpha value 
  6 FLOAT3D m_vNormal    = FLOAT3D(0,0,0), // normal for orientative effects
  7 FLOAT3D m_vStretch   = FLOAT3D(0,0,0), // stretch effect
  8 FLOAT3D m_vDirection = FLOAT3D(0,0,0), // direction oriented effects
  10 FLOAT m_fDepthSortOffset = 0.0f,
  11 FLOAT m_fFadeInSpeed = 0.0f,
  12 FLOAT m_tmSpawn = 0.0f,  // when it was spawned
  13 FLOAT m_tmWaitAfterDeath = 0.0f,       // after death wait time

 20 BOOL m_bLightSource = FALSE,    // effect is also light source
 21 CAnimObject m_aoLightAnimation, // light animation object
 22 INDEX m_iLightAnimation = -1,   // lignt animation index
 23 COLOR m_colMultiplyColor = 0xFFFFFFFF, // color multiplier

 30 CSoundObject m_soEffect,        // sound channel
 31 FLOAT m_fSoundTime = 0.0f,      // wait for sound to end

 40 enum EffectParticlesType m_eptType = EPT_NONE, // type of particle effect
 41 FLOAT m_tmWhenShot = 0.0f, // when entity was shot
 42 FLOAT3D m_vGravity = FLOAT3D(0,0,0), // simulated direction of gravity

{
  CLightSource m_lsLightSource;
}

components:

// ********** PROJECTILE EXPLOSIONS **********
  1 model   MDL_ROCKET_EXPLOSION      "Models\\Effects\\ExplosionRocket\\ExplosionRocket.mdl",
  2 model   MDL_ROCKET3D_EXPLOSION    "Models\\Effects\\ExplosionRocket\\ExplosionRocket3D.mdl",
  3 texture TXT_ROCKET_EXPLOSION      "Models\\Effects\\Explosionrocket\\Texture.tex",
  4 model   MDL_GRENADE_EXPLOSION     "Models\\Effects\\ExplosionGrenade\\ExplosionGrenade.mdl",
  5 model   MDL_GRENADE3D_EXPLOSION   "Models\\Effects\\ExplosionGrenade\\ExplosionGrenade3D.mdl",
  6 texture TXT_GRENADE_EXPLOSION     "Models\\Effects\\ExplosionGrenade\\Texture.tex",
  7 model   MDL_PARTICLES_EXPLOSION   "Models\\Effects\\ExplosionParticles\\Particles.mdl",
  8 model   MDL_PARTICLES3D_EXPLOSION "Models\\Effects\\ExplosionParticles\\Particles3D.mdl",
  9 texture TXT_PARTICLES_EXPLOSION   "Models\\Effects\\ExplosionParticles\\Texture.tex",
 10 sound   SOUND_EXPLOSION           "Sounds\\Weapons\\_Explosion02.wav",
 11 model   MDL_CANNON_EXPLOSION     "Models\\Effects\\ExplosionGrenade\\ExplosionGrenade.mdl",
 12 model   MDL_CANNON3D_EXPLOSION   "Models\\Effects\\ExplosionGrenade\\ExplosionGrenade3D.mdl",
 13 texture TXT_CANNON_EXPLOSION     "Models\\Effects\\ExplosionGrenade\\Texture.tex",

// ********** BULLET HIT **********
 15 model   MODEL_BULLET_HIT      "Models\\Effects\\BulletParticles\\BulletParticles.mdl",
 16 texture TEXTURE_BULLET_HIT    "Models\\Effects\\BulletParticles\\BulletParticles.tex",
 18 model   MODEL_BULLET_STAIN    "Models\\Effects\\BulletOnTheWall\\Bullet.mdl",
 19 texture TEXTURE_BULLET_STAIN  "Models\\Effects\\BulletOnTheWall\\Bullet.tex",
 70 texture TEXTURE_BULLET_TRAIL  "Models\\Effects\\BulletTrail\\BulletTrail.tex",
 71 model   MODEL_BULLET_TRAIL    "Models\\Effects\\BulletTrail\\BulletTrail.mdl",

 90 sound   SOUND_BULLET_STONE    "Sounds\\Weapons\\_BulletHitWall.wav",
 91 sound   SOUND_BULLET_SAND     "Sounds\\Weapons\\BulletHitSand.wav",
 92 sound   SOUND_BULLET_WATER    "Sounds\\Weapons\\BulletHitWater.wav",
 93 sound   SOUND_BULLET_FLESH    "Sounds\\Weapons\\_BulletHitFlesh.wav",
 94 texture TEXTURE_BULLET_SAND   "Models\\Effects\\BulletOnTheWall\\BulletSand.tex",

// ********** BLOOD **********
 21 model   MODEL_BLOOD_EXPLODE   "Models\\Effects\\BloodCloud\\BloodCloud.mdl",
 22 texture TEXTURE_BLOOD_EXPLODE "Models\\Effects\\BloodCloud\\BloodCloud.tex",
 23 model   MODEL_BLOOD_STAIN     "Models\\Effects\\BloodOnTheWall01\\Blood.mdl",
 24 texture TEXTURE_BLOOD_STAIN1  "Models\\Effects\\BloodOnTheWall01\\BloodStain01.tex",
 25 texture TEXTURE_BLOOD_STAIN2  "Models\\Effects\\BloodOnTheWall01\\BloodStain02.tex",
 26 texture TEXTURE_BLOOD_STAIN3  "Models\\Effects\\BloodOnTheWall01\\BloodStain03.tex",
 27 texture TEXTURE_BLOOD_STAIN4  "Models\\Effects\\BloodOnTheWall01\\BloodStain04.tex",
 28 texture TEXTURE_BLOOD_SPILL1  "Models\\Effects\\BloodOnTheWall01\\BloodSpill02.tex",
 29 texture TEXTURE_BLOOD_SPILL2  "Models\\Effects\\BloodOnTheWall01\\BloodSpill05.tex",
 30 texture TEXTURE_BLOOD_SPILL3  "Models\\Effects\\BloodOnTheWall01\\BloodSpill06.tex",
 31 texture TEXTURE_BLOOD_FLOWER1 "Models\\Effects\\Flowers\\Flowers1s.tex",
 32 texture TEXTURE_BLOOD_FLOWER2 "Models\\Effects\\Flowers\\Flowers2s.tex",
 33 texture TEXTURE_BLOOD_FLOWER3 "Models\\Effects\\Flowers\\Flowers3s.tex",
 
// ********** SHOCK WAVE **********
 40 model   MODEL_SHOCKWAVE       "Models\\Effects\\ShockWave01\\ShockWave.mdl",
 41 texture TEXTURE_SHOCKWAVE     "Models\\Effects\\ShockWave01\\Textures\\ShockWave.tex",
 
 42 model   MODEL_CANNONSHOCKWAVE   "Models\\Effects\\ShockWave01\\ShockWave.mdl",
 43 texture TEXTURE_CANNONSHOCKWAVE "Models\\Effects\\ShockWave01\\Textures\\ShockWave.tex",

// ********** EXPLOSION STAIN **********
 45 model   MODEL_EXPLOSION_STAIN     "Models\\Effects\\BurnedStainOnTheWall\\BurnedStainOnTheWall.mdl",
 46 texture TEXTURE_EXPLOSION_STAIN   "Models\\Effects\\BurnedStainOnTheWall\\BurnedStainOnTheWall.tex",

 47 model   MODEL_CANNON_EXPLOSION_STAIN     "Models\\Effects\\BurnedStainOnTheWall\\BurnedStainOnTheWall.mdl",
 48 texture TEXTURE_CANNON_EXPLOSION_STAIN   "Models\\Effects\\BurnedStainOnTheWall\\BurnedStainOnTheWall.tex",

// ********** LASER WAVE **********
 50 model   MODEL_LASERWAVE       "Models\\Effects\\ShockWaveGreen\\ShockWaveGreen.mdl",
 51 texture TEXTURE_LASERWAVE     "Models\\Effects\\ShockWaveGreen\\ShockWaveGreen.tex",

// ********** TELEPORT **********
 61 model   MODEL_TELEPORT_EFFECT        "Models\\Effects\\Teleport01\\Teleport.mdl",
 62 texture TEXTURE_TELEPORT_EFFECT      "Textures\\Effects\\Effect01\\Effect.tex",
 63 sound   SOUND_TELEPORT               "Sounds\\Misc\\Teleport.wav",

// ********** GIZMO SPLASH FX **********
 80 sound   SOUND_GIZMO_SPLASH           "Models\\Enemies\\Gizmo\\Sounds\\Death.wav",

// ********** Water shockwave texture **********
 100 texture TEXTURE_WATER_WAVE          "Models\\Effects\\ShockWave01\\Textures\\WaterWave.tex",

functions:

  // dump sync data to text file
  export void DumpSync_t(CTStream &strm, INDEX iExtensiveSyncCheck)  // throw char *
  {
    CRationalEntity::DumpSync_t(strm, iExtensiveSyncCheck);
    strm.FPrintF_t("Type: %d\n", m_betType);
  }

  /* Read from stream. */
  void Read_t( CTStream *istr) // throw char *
  {
    CRationalEntity::Read_t(istr);
    // setup light source
    if( m_bLightSource) {
      SetupLightSource();
    }
  }

  /* Get static light source information. */
  CLightSource *GetLightSource(void)
  {
    if( m_bLightSource && !IsPredictor()) {
      return &m_lsLightSource;
    } else {
      return NULL;
    }
  }

  // Setup light source
  void SetupLightSource(void)
  {
    if( m_iLightAnimation>=0)
    { // set light animation if available
      try {
        m_aoLightAnimation.SetData_t(CTFILENAME("Animations\\BasicEffects.ani"));
      } catch (char *strError) {
        WarningMessage(TRANS("Cannot load Animations\\BasicEffects.ani: %s"), strError);
      }
      // play light animation
      if (m_aoLightAnimation.GetData()!=NULL) {
        m_aoLightAnimation.PlayAnim(m_iLightAnimation, 0);
      }
    }

    // setup light source
    CLightSource lsNew;
    lsNew.ls_ulFlags = LSF_NONPERSISTENT|LSF_DYNAMIC;
    lsNew.ls_rHotSpot = 0.0f;
    switch (m_betType) {
      case BET_ROCKET:
        lsNew.ls_colColor = RGBToColor(100, 100, 100);
        lsNew.ls_rHotSpot = 3.0f;
        lsNew.ls_rFallOff = 12.5f;
        lsNew.ls_plftLensFlare = NULL;
        break;
      case BET_GRENADE:
        lsNew.ls_colColor = RGBToColor(200, 200, 200);
        lsNew.ls_rFallOff = 12.5f;
        lsNew.ls_plftLensFlare = NULL;
        break;
      case BET_CANNON:
      case BET_LIGHT_CANNON:
        lsNew.ls_colColor = RGBToColor(200, 200, 200);
        lsNew.ls_rFallOff = 12.5f;
        lsNew.ls_plftLensFlare = NULL;
        break;
      case BET_LASERWAVE:
        lsNew.ls_colColor = RGBToColor(0, 64, 0);
        lsNew.ls_rFallOff = 1.5f;
        lsNew.ls_plftLensFlare = NULL;
        break;
      case BET_BOMB:
        lsNew.ls_colColor = RGBToColor(100, 100, 100);
        lsNew.ls_rFallOff = 8.0f;
        lsNew.ls_plftLensFlare = NULL;
        break;
      default:
        ASSERTALWAYS("Unknown light source");
    }
    lsNew.ls_ubPolygonalMask = 0;
    lsNew.ls_paoLightAnimation = NULL;

    // setup light animation
    lsNew.ls_paoLightAnimation = NULL;
    if (m_aoLightAnimation.GetData()!=NULL) {
      lsNew.ls_paoLightAnimation = &m_aoLightAnimation;
    }

    m_lsLightSource.ls_penEntity = this;
    m_lsLightSource.SetLightSource(lsNew);
  }


/* RENDER PARTICLES */


  void RenderParticles(void)
  {
    if( m_eptType != EPT_NONE)
    {
      Particles_BulletSpray(this, m_vGravity, m_eptType, m_tmSpawn, m_vStretch);
    }
  }



/************************************************************
 *                        FADE OUT                          *
 ************************************************************/

  BOOL AdjustShadingParameters(FLOAT3D &vLightDirection, COLOR &colLight, COLOR &colAmbient)
  {
    if( m_bFade) {
      FLOAT m_fTimeRemain = m_fFadeStartTime + m_fFadeTime - _pTimer->CurrentTick();
      if (m_fTimeRemain < 0.0f) { m_fTimeRemain = 0.0f; }
      COLOR col = GetModelColor() & ~CT_AMASK;
      col |= (ULONG)(m_fFadeStartAlpha* m_fTimeRemain/m_fFadeTime *255.0f);
      SetModelColor(col);
    } else if (m_fFadeInSpeed>0) {
      TIME tmAge = _pTimer->GetLerpedCurrentTick()-m_tmSpawn;
      COLOR col = GetModelColor() ;
      col = (col &~CT_AMASK) |
        (ULONG)((255)*Clamp(tmAge*m_fFadeInSpeed/m_fWaitTime, 0.0f, 1.0f));
      SetModelColor(col);
    }

    return FALSE;
  };

  // get offset for depth-sorting of alpha models (in meters, positive is nearer)
  FLOAT GetDepthSortOffset(void)
  {
    return m_fDepthSortOffset;
  }



/************************************************************
 *                GLOBAL SUPPORT FUNCTIONS                  *
 ************************************************************/

  void SetNonLoopingTexAnims(void)
  {
    CModelObject *pmo = GetModelObject();
    pmo->mo_toTexture.PlayAnim(0, 0);
    FOREACHINLIST(CAttachmentModelObject, amo_lnInMain, pmo->mo_lhAttachments, itamo) {
      CModelObject *pmoAtt = &itamo->amo_moModelObject;
      pmoAtt->mo_toTexture.PlayAnim(0, 0);
    }
  }

  void SetNormalForHalfFaceForward(void)
  {
    CPlacement3D pl = GetPlacement();
    UpVectorToAngles(m_vNormal, pl.pl_OrientationAngle);
    SetPlacement(pl);
  };

  void SetNormal(void)
  {
    CPlacement3D pl = GetPlacement();
    DirectionVectorToAngles(m_vNormal, pl.pl_OrientationAngle);
    SetPlacement(pl);
  };

  void SetNormalWithRandomBanking(void)
  {
    CPlacement3D pl = GetPlacement();
    DirectionVectorToAngles(m_vNormal, pl.pl_OrientationAngle);
    pl.pl_OrientationAngle(3) = FRnd()*360.0f;
    SetPlacement(pl);
  };

  void FindGravityVectorFromSector(void)
  {
    CBrushSector *pbscContent = NULL;
    {FOREACHSRCOFDST(en_rdSectors, CBrushSector, bsc_rsEntities, pbsc)
      pbscContent = &*pbsc;
      break;
    ENDFOR;}

    if( pbscContent == NULL)
    {
      return;
    }
    INDEX iForceType = pbscContent->GetForceType();
    CEntity *penBrush = pbscContent->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
    CForceStrength fsGravity;
    CForceStrength fsField;
    penBrush->GetForce( iForceType, en_plPlacement.pl_PositionVector, fsGravity, fsField);
    // remember gravity vector
    m_vGravity = fsGravity.fs_vDirection;
  };

  void SetNormalAndDirection(void)
  {
    // special case for stains without sliding
    if( m_vDirection.Length() < 0.01f) {
      SetNormalWithRandomBanking();
      return;
    }

    FLOAT3D vX;
    FLOAT3D vY = -m_vDirection;
    FLOAT3D vZ = -m_vNormal;
    vZ.Normalize();
    vX = vY*vZ;
    vX.Normalize();
    vY = vZ*vX;
    vY.Normalize();

    FLOATmatrix3D m;
    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);

    CPlacement3D pl = GetPlacement();
    DecomposeRotationMatrixNoSnap(pl.pl_OrientationAngle, m);
    SetPlacement(pl);
  };

  void RandomBanking(void)
  {
    CPlacement3D pl = GetPlacement();
    pl.pl_OrientationAngle(3) = FRnd()*360.0f;
    SetPlacement(pl);
  };

  void Stretch(void) {
    ASSERT(m_vStretch(1)>0.01f && m_vStretch(3)>0.01f && m_vStretch(3)>0.01f);
    GetModelObject()->mo_Stretch = m_vStretch;
  };

  // parent the effect if needed and adjust size not to get out of the polygon
  void ParentToNearestPolygonAndStretch(void) 
  {
    // find nearest polygon
    FLOAT3D vPoint; 
    FLOATplane3D plPlaneNormal;
    FLOAT fDistanceToEdge;
    CBrushPolygon *pbpoNearBrush = GetNearestPolygon( vPoint, plPlaneNormal, fDistanceToEdge);

    if( m_betType>=BET_BULLETSTAINSTONE && m_betType<=BET_BULLETSTAINREDSANDNOSOUND)
    {
      if( pbpoNearBrush != NULL)
      {
        CBrushSector *pbscContent = pbpoNearBrush->bpo_pbscSector;
        INDEX iForceType = pbscContent->GetForceType();
        CEntity *penNearBrush = pbscContent->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
        CForceStrength fsGravity;
        CForceStrength fsField;
        penNearBrush->GetForce( iForceType, en_plPlacement.pl_PositionVector, fsGravity, fsField);
        // remember gravity vector
        m_vGravity = fsGravity.fs_vDirection;
      }
    }

    // if there is none, or if it is portal, or it is not near enough
    if (pbpoNearBrush==NULL || (pbpoNearBrush->bpo_ulFlags&BPOF_PORTAL) 
      || (vPoint-GetPlacement().pl_PositionVector).ManhattanNorm()>0.1f*3) {
      // dissapear
      SwitchToEditorModel();
    // if polygon is found
    } else {
      CEntity *penNearBrush = pbpoNearBrush->bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity;
      FLOATaabbox3D box;
      en_pmoModelObject->GetCurrentFrameBBox( box);
      box.StretchByVector( en_pmoModelObject->mo_Stretch);
      FLOAT fOrgSize = box.Size().MaxNorm();
      FLOAT fMaxSize = fDistanceToEdge*2.0f;
      // if minimum distance from polygon edges is too small
      if( fMaxSize<fOrgSize*0.25f) {
        // dissapear
        SwitchToEditorModel();
      // if the distance is acceptable
      } else {
        // set your size to not get out of it
        FLOAT fStretch = fMaxSize/fOrgSize;
        fStretch = ClampUp( fStretch, 1.0f);
        m_vStretch = en_pmoModelObject->mo_Stretch*fStretch;
        Stretch();
        ModelChangeNotify();
        // set parent to that brush
        SetParent( penNearBrush);
      }
    }
  }


/************************************************************
 *         PROJECTILE/GRENADE EXPLOSION,  STAIN             *
 ************************************************************/

  void ProjectileExplosion(void)
  {
    SetPredictable(TRUE);
    Stretch();
    SetModel(MDL_ROCKET_EXPLOSION);
    SetModelMainTexture(TXT_ROCKET_EXPLOSION);
    AddAttachment(0, MDL_PARTICLES_EXPLOSION, TXT_PARTICLES_EXPLOSION);
    RandomBanking();
    SetNonLoopingTexAnims();
    m_soEffect.Set3DParameters(150.0f, 3.0f, 1.0f, 1.0f);
    PlaySound(m_soEffect, SOUND_EXPLOSION, SOF_3D);
    m_fSoundTime = GetSoundLength(SOUND_EXPLOSION);
    m_fWaitTime = 0.95f;
    m_bLightSource = TRUE;
    m_iLightAnimation = 0;
  };

  void ProjectilePlaneExplosion(void) {
    SetPredictable(TRUE);
    Stretch();
    SetModel(MDL_ROCKET3D_EXPLOSION);
    SetModelMainTexture(TXT_ROCKET_EXPLOSION);
    AddAttachment(0, MDL_PARTICLES3D_EXPLOSION, TXT_PARTICLES_EXPLOSION);
    SetNormalWithRandomBanking();
    SetNonLoopingTexAnims();
    m_fWaitTime = 0.95f;
    m_bLightSource = FALSE;
  };

  void BombExplosion(void) {
    SetPredictable(TRUE);
    Stretch();
    SetModel(MDL_GRENADE_EXPLOSION);
    SetModelMainTexture(TXT_GRENADE_EXPLOSION);
    SetNonLoopingTexAnims();
    FLOAT fSizeFactor = m_vStretch.MaxNorm();
    m_soEffect.Set3DParameters(50.0f*fSizeFactor, 10.0f*fSizeFactor, 1.0f*fSizeFactor, 1.0f);
    PlaySound(m_soEffect, SOUND_EXPLOSION, SOF_3D);
    m_fSoundTime = GetSoundLength(SOUND_EXPLOSION);
    m_fWaitTime = 0.95f;
    m_bLightSource = TRUE;
    m_iLightAnimation = 1;
  };
  
  void GizmoSplashFX(void)
  {
    SetPredictable(TRUE);
    SetModel(MODEL_BULLET_HIT);
    SetModelMainTexture(TEXTURE_BULLET_HIT);
    m_soEffect.Set3DParameters(50.0f, 10.0f, 1.0f, 1.0f);
    PlaySound(m_soEffect, SOUND_GIZMO_SPLASH, SOF_3D);
    m_fSoundTime = GetSoundLength(SOUND_GIZMO_SPLASH);
    m_fWaitTime = 0.95f;
    m_bLightSource = FALSE;
  };

  void GrenadeExplosion(void) {
    SetPredictable(TRUE);
    Stretch();
    SetModel(MDL_GRENADE_EXPLOSION);
    SetModelMainTexture(TXT_GRENADE_EXPLOSION);
    AddAttachment(0, MDL_PARTICLES_EXPLOSION, TXT_PARTICLES_EXPLOSION);
    RandomBanking();
    SetNonLoopingTexAnims();
    m_soEffect.Set3DParameters(150.0f, 3.0f, 1.0f, 1.0f);
    PlaySound(m_soEffect, SOUND_EXPLOSION, SOF_3D);
    m_fSoundTime = GetSoundLength(SOUND_EXPLOSION);
    m_fWaitTime = 0.95f;
    m_bLightSource = TRUE;
    m_iLightAnimation = 1;
  };

  void GrenadePlaneExplosion(void) {
    SetPredictable(TRUE);
    Stretch();
    SetModel(MDL_GRENADE3D_EXPLOSION);
    SetModelMainTexture(TXT_GRENADE_EXPLOSION);
    AddAttachment(0, MDL_PARTICLES3D_EXPLOSION, TXT_PARTICLES_EXPLOSION);
    SetNonLoopingTexAnims();
    SetNormalWithRandomBanking();
    m_fWaitTime = 0.95f;
    m_bLightSource = FALSE;
  };

  void CannonExplosion(BOOL bLoVolume, BOOL bNoLight) {
    SetPredictable(TRUE);
    Stretch();
    SetModel(MDL_CANNON_EXPLOSION);
    CModelObject &moExplosion = *GetModelObject();
    SetModelMainTexture(TXT_CANNON_EXPLOSION);
    moExplosion.mo_colBlendColor = m_colMultiplyColor;
    moExplosion.mo_toTexture.PlayAnim(EXPLOSION_GRENADE_TEXTURE_ANIM_FAST, 0);
    RandomBanking();
    if( bLoVolume)
    {
      m_soEffect.Set3DParameters(150.0f, 3.0f, 0.5f, 1.0f);
    }
    else
    {
      m_soEffect.Set3DParameters(150.0f, 3.0f, 1.0f, 1.0f);
    }

    PlaySound(m_soEffect, SOUND_EXPLOSION, SOF_3D);
    m_fSoundTime = GetSoundLength(SOUND_EXPLOSION);
    m_fWaitTime = 0.8f;
    if( bNoLight)
    {
      m_bLightSource = FALSE;
    }
    else
    {
      m_bLightSource = TRUE;
      m_iLightAnimation = 1;
    }
  };

  void CannonPlaneExplosion(void) {
    SetPredictable(TRUE);
    Stretch();
    SetModel(MDL_CANNON3D_EXPLOSION);
    CModelObject &moExplosion = *GetModelObject();
    SetModelMainTexture(TXT_CANNON_EXPLOSION);
    moExplosion.mo_toTexture.PlayAnim(EXPLOSION_GRENADE_TEXTURE_ANIM_FAST, 0);
    SetNormalWithRandomBanking();
    m_fWaitTime = 1.2f;
    m_bLightSource = FALSE;
  };

  void Stain(void) {
    SetModel(MODEL_EXPLOSION_STAIN);
    SetModelMainTexture(TEXTURE_EXPLOSION_STAIN);
    SetNormalWithRandomBanking();
    m_fWaitTime = 5.0f;
    m_fFadeTime = 2.5f;
    m_bLightSource = FALSE;
    ParentToNearestPolygonAndStretch();
  };

  void CannonStain(void) {
    Stretch();
    SetModel(MODEL_CANNON_EXPLOSION_STAIN);
    SetModelMainTexture(TEXTURE_CANNON_EXPLOSION_STAIN);
    SetNormalWithRandomBanking();
    m_fWaitTime = 5.0f;
    m_fFadeTime = 2.5f;
    m_bLightSource = FALSE;
    ParentToNearestPolygonAndStretch();
  };



/************************************************************
 *                   SHOCK / LASER WAVE                     *
 ************************************************************/
  void ShockWave(void) {
    SetPredictable(TRUE);
    SetModel(MODEL_SHOCKWAVE);
    CModelObject &moShockwave = *GetModelObject();
    moShockwave.PlayAnim(SHOCKWAVE_ANIM_FAST, 0);
    SetModelMainTexture(TEXTURE_SHOCKWAVE);
    SetNormal();
    SetNonLoopingTexAnims();
    m_fWaitTime = 0.4f;
    m_fFadeTime = 0.1f;
    m_bLightSource = FALSE;
  };

  void CannonShockWave(void) {
    SetPredictable(TRUE);
    Stretch();
    SetModel(MODEL_CANNONSHOCKWAVE);
    CModelObject &moShockwave = *GetModelObject();
    moShockwave.PlayAnim(SHOCKWAVE_ANIM_SLOW, 0);
    SetModelMainTexture(TEXTURE_CANNONSHOCKWAVE);
    moShockwave.mo_toTexture.PlayAnim(SHOCKWAVE_TEXTURE_ANIM_SLOW, 0);
    SetNormal();
    m_fWaitTime = 1.25f;
    m_fFadeTime = 0.25f;
    m_bLightSource = FALSE;
  };

  void LaserWave(void) {
    SetModel(MODEL_LASERWAVE);
    GetModelObject()->StretchModel(FLOAT3D(0.75f, 0.75f, 0.75f));
    ModelChangeNotify();
    SetModelMainTexture(TEXTURE_LASERWAVE);
    SetNormalWithRandomBanking();
    SetNonLoopingTexAnims();
    m_fWaitTime = 0.05f;
    m_fFadeTime = 0.25f;
    m_bLightSource = TRUE;
    ParentToNearestPolygonAndStretch();
  };



  /************************************************************
   *                   TELEPORT                               *
   ************************************************************/
  void TeleportEffect(void)
  {
    SetPredictable(TRUE);
    Stretch();
    SetModel(MODEL_TELEPORT_EFFECT);
    CModelObject &mo = *GetModelObject();
    SetModelMainTexture(TEXTURE_TELEPORT_EFFECT);
    mo.PlayAnim(TELEPORT_ANIM_ACTIVATE, 0);
    RandomBanking();
    FLOAT fSize = m_vStretch.MaxNorm();
    m_soEffect.Set3DParameters(40.0f*fSize, 10.0f*fSize, 1.0f, 1.0f);
    PlaySound(m_soEffect, SOUND_TELEPORT, SOF_3D);
    m_fSoundTime = GetSoundLength(SOUND_TELEPORT);
    m_fWaitTime = 0.8f;
    m_bLightSource = FALSE;
  };

/************************************************************
 *                   BULLET HIT / STAIN                     *
 ************************************************************/
  void BulletStainSand(BOOL bSound)
  {
    if( bSound)
    {
      m_soEffect.Set3DParameters(20.0f, 10.0f, 1.0f, 1.0f+FRnd()*0.2f);
      PlaySound(m_soEffect, SOUND_BULLET_SAND, SOF_3D);
      m_fSoundTime = GetSoundLength(SOUND_BULLET_SAND);
    }
    
    SetModel(MODEL_BULLET_STAIN);
    SetModelMainTexture(TEXTURE_BULLET_SAND);
    CModelObject &moHole = *GetModelObject();
    moHole.StretchModel(FLOAT3D(1.5f, 1.5f, 1.5f));
    ModelChangeNotify();

    SetNormalWithRandomBanking();
    m_fWaitTime = 2.0f;
    m_fFadeTime = 2.0f;
    m_bLightSource = FALSE;
    m_eptType = EPT_BULLET_SAND;
    FLOAT3D vTemp = m_vStretch;
    ParentToNearestPolygonAndStretch();
    m_vStretch = vTemp;
  }

  void BulletStainRedSand(BOOL bSound)
  {
    if( bSound)
    {
      m_soEffect.Set3DParameters(20.0f, 10.0f, 1.0f, 1.0f+FRnd()*0.2f);
      PlaySound(m_soEffect, SOUND_BULLET_SAND, SOF_3D);
      m_fSoundTime = GetSoundLength(SOUND_BULLET_SAND);
    }
    
    SetModel(MODEL_BULLET_STAIN);
    SetModelMainTexture(TEXTURE_BULLET_SAND);
    CModelObject &moHole = *GetModelObject();
    moHole.StretchModel(FLOAT3D(1.5f, 1.5f, 1.5f));
    ModelChangeNotify();
    moHole.mo_colBlendColor = 0x805030FF;

    SetNormalWithRandomBanking();
    m_fWaitTime = 2.0f;
    m_fFadeTime = 2.0f;
    m_bLightSource = FALSE;
    m_eptType = EPT_BULLET_RED_SAND;
    FLOAT3D vTemp = m_vStretch;
    ParentToNearestPolygonAndStretch();
    m_vStretch = vTemp;
  }

  void BulletStainStone(BOOL bSound, BOOL bSmoke)
  {
    if( bSound)
    {
      m_soEffect.Set3DParameters(20.0f, 10.0f, 1.0f, 1.0f+FRnd()*0.2f);
      PlaySound(m_soEffect, SOUND_BULLET_STONE, SOF_3D);
      m_fSoundTime = GetSoundLength(SOUND_BULLET_STONE);
    }
    SetModel(MODEL_BULLET_STAIN);
    SetModelMainTexture(TEXTURE_BULLET_STAIN);
    SetNormalWithRandomBanking();
    m_fWaitTime = 2.0f;
    m_fFadeTime = 2.0f;
    m_bLightSource = FALSE;
    if( bSmoke)
    {
      m_eptType = EPT_BULLET_STONE;
    }
    else
    {
      m_eptType = EPT_BULLET_UNDER_WATER;
    }
    FLOAT3D vTemp = m_vStretch;
    ParentToNearestPolygonAndStretch();
    m_vStretch = vTemp;
  }

  void BulletStainWater(BOOL bSound)
  {
    if( bSound)
    {
      m_soEffect.Set3DParameters(20.0f, 10.0f, 1.0f, 1.0f+FRnd()*0.2f);
      PlaySound(m_soEffect, SOUND_BULLET_WATER, SOF_3D);
      m_fSoundTime = GetSoundLength(SOUND_BULLET_WATER);
    }

    SetModel(MODEL_SHOCKWAVE);
    SetModelMainTexture(TEXTURE_WATER_WAVE);
    CModelObject &moShockwave = *GetModelObject();
    moShockwave.PlayAnim(SHOCKWAVE_ANIM_MEDIUM, 0);
    moShockwave.StretchModel(FLOAT3D(0.25f, 0.25f, 0.25f));
    ModelChangeNotify();

    SetNormalWithRandomBanking();
    FindGravityVectorFromSector();
    m_fWaitTime = 0.5f;
    m_fFadeTime = 0.5f;
    m_bLightSource = FALSE;
    m_tmWaitAfterDeath = 1.0f;
    m_eptType = EPT_BULLET_WATER;
  }

  void BulletTrail(void) {
    Stretch();
    SetModel(MODEL_BULLET_TRAIL);
    SetModelMainTexture(TEXTURE_BULLET_TRAIL);
    CModelObject &mo = *GetModelObject();
    mo.mo_colBlendColor = m_colMultiplyColor;
    SetNormalForHalfFaceForward();
    m_fWaitTime = 0.1f;
    m_bLightSource = FALSE;
  };


/************************************************************
 *                  BLOOD SPILL / STAIN                     *
 ************************************************************/


  // bullet hitpoint wound
  void BloodExplode(void)
  {
    // readout blood type
    const INDEX iBloodType = GetSP()->sp_iBlood;
    if( iBloodType<1) { return; }

    SetPredictable(TRUE);
    Stretch();
    SetModel(MODEL_BLOOD_EXPLODE);
    if( iBloodType==3) {
      // flower mode! :)
      SetModelColor( RGBAToColor( 255,255,255,255));
      switch( IRnd()&3) {
      case 1:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      case 2:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER2);  break; }
      case 3:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER3);  break; }
      default: { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      }
    } else {
      SetModelMainTexture(TEXTURE_BLOOD_EXPLODE);
      if( iBloodType==2) { SetModelColor( RGBAToColor( 250,20,20,255)); }
      else               { SetModelColor( RGBAToColor( 0,250,0,255)); }
    }
    //RandomBanking();
    m_soEffect.Set3DParameters(7.5f, 5.0f, 1.0f, 1.0f);
    PlaySound(m_soEffect, SOUND_BULLET_FLESH, SOF_3D);
    m_fSoundTime = GetSoundLength(SOUND_BULLET_FLESH);
    m_fWaitTime = 0.25f;
    m_fFadeTime = 0.75f;
    m_bLightSource = FALSE;
  }


  // blood stain on wall/floor
  void BloodStain(void)
  {
    // readout blood type
    const INDEX iBloodType = GetSP()->sp_iBlood;
    if( iBloodType<1) { return; }

    Stretch();
    SetModel(MODEL_BLOOD_STAIN);
    if( iBloodType==3) {
      // flower mode! :)
      SetModelColor( RGBAToColor( 255,255,255,255));
      switch( IRnd()&3) {
      case 1:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      case 2:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER2);  break; }
      case 3:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER3);  break; }
      default: { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      }
    } else {
      switch( IRnd()&3) {
      case 1:  { SetModelMainTexture(TEXTURE_BLOOD_STAIN1);   break; }
      case 2:  { SetModelMainTexture(TEXTURE_BLOOD_STAIN2);   break; }
      case 3:  { SetModelMainTexture(TEXTURE_BLOOD_STAIN3);   break; }
      default: { SetModelMainTexture(TEXTURE_BLOOD_STAIN4);   break; }
      }
      if( iBloodType==2) { SetModelColor( RGBAToColor( 250,20,20,255)); }
      else               { SetModelColor( RGBAToColor( 0,250,0,255)); }
    }

    SetNormalAndDirection();
    m_fWaitTime = 12.0f + FRnd()*3.0f;
    m_fFadeTime = 3.0f;
    m_bLightSource = FALSE;
    m_fDepthSortOffset = -0.1f;
    ParentToNearestPolygonAndStretch();
  }


  // blood stain on wall/floor that grows
  void BloodStainGrow(void)
  {
    // readout blood type
    const INDEX iBloodType = GetSP()->sp_iBlood;
    if( iBloodType<1) { return; }

    SetPredictable(TRUE);
    Stretch();
    SetModel(MODEL_BLOOD_STAIN);
    if( iBloodType==3) {
      // flower mode! :)
      SetModelColor( RGBAToColor( 255,255,255,255));
      switch( IRnd()&3) {
      case 1:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      case 2:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER2);  break; }
      case 3:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER3);  break; }
      default: { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      }
    } else {
      SetModelMainTexture(TEXTURE_BLOOD_STAIN4);
      if( iBloodType==2) { SetModelColor( RGBAToColor( 250,20,20,255)); }
      else               { SetModelColor( RGBAToColor( 0,250,0,255)); }
    }
    SetNormalAndDirection();
    m_bLightSource = FALSE;
    m_fDepthSortOffset = -0.1f;
    ParentToNearestPolygonAndStretch();

    m_fWaitTime = 15.0f + FRnd()*2.0f;
    m_fFadeTime = 2.0f;
    m_fFadeInSpeed = 4.0f;
    CModelObject &mo = *GetModelObject();
    mo.PlayAnim(BLOOD_ANIM_GROW, 0);
  }


  // gizmo stain on wall/floor
  void GizmoStain(void)
  {
    // readout blood type
    const INDEX iBloodType = GetSP()->sp_iBlood;
    if( iBloodType<1) { return; }

    Stretch();
    SetModel(MODEL_BLOOD_STAIN);
    if( iBloodType==3) {
      // flower mode! :)
      SetModelColor( RGBAToColor( 255,255,255,255));
      switch( IRnd()&3) {
      case 1:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      case 2:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER2);  break; }
      case 3:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER3);  break; }
      default: { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      }
    } else {
      SetModelColor( RGBAToColor( 0,250,0,255));
      switch( IRnd()&3) {
      case 1:  { SetModelMainTexture(TEXTURE_BLOOD_STAIN1);   break; }
      case 2:  { SetModelMainTexture(TEXTURE_BLOOD_STAIN2);   break; }
      case 3:  { SetModelMainTexture(TEXTURE_BLOOD_STAIN3);   break; }
      default: { SetModelMainTexture(TEXTURE_BLOOD_STAIN4);   break; }
      }
    }
    SetNormalAndDirection();
    m_fWaitTime = 15.0f + FRnd()*2.0f;
    m_fFadeTime = 2.0f;
    m_bLightSource = FALSE;
    m_fDepthSortOffset = -0.1f;
    ParentToNearestPolygonAndStretch();
  }


  // bullet exit wound blood on wall/floor
  void BloodSpill(COLOR colBloodSpillColor)
  {
    // readout blood type
    const INDEX iBloodType = GetSP()->sp_iBlood;
    if( iBloodType<1) { return; }

    Stretch();
    SetModel(MODEL_BLOOD_STAIN);
    if( iBloodType==3) {
      // flower mode! :)
      SetModelColor( RGBAToColor( 255,255,255,255));
      switch( IRnd()&3) {
      case 1:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      case 2:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER2);  break; }
      case 3:  { SetModelMainTexture(TEXTURE_BLOOD_FLOWER3);  break; }
      default: { SetModelMainTexture(TEXTURE_BLOOD_FLOWER1);  break; }
      }
    } else {
      switch( IRnd()%5) {
      case 1:  { SetModelMainTexture(TEXTURE_BLOOD_SPILL1); break; }
      case 2:  { SetModelMainTexture(TEXTURE_BLOOD_SPILL2); break; }
      case 3:  { SetModelMainTexture(TEXTURE_BLOOD_SPILL1); break; }
      case 4:  { SetModelMainTexture(TEXTURE_BLOOD_SPILL2); break; }
      default: { SetModelMainTexture(TEXTURE_BLOOD_SPILL3); break; }
      }
      if( iBloodType==2)
      {
        SetModelColor( colBloodSpillColor);
      }
      else               { SetModelColor( RGBAToColor( 0,250,0,255)); }
    }
    SetNormalAndDirection();
    m_fWaitTime = 15.0f + FRnd()*2.0f;
    m_fFadeTime = 2.0f;
    m_bLightSource = FALSE;
    ParentToNearestPolygonAndStretch();
  }

procedures:


/************************************************************
 *                    M  A  I  N                            *
 ************************************************************/

  Main(ESpawnEffect eSpawn)
  {
    if(eSpawn.betType == BET_GIZMO_SPLASH_FX)
    {
      InitAsEditorModel();
    }
    else
    {
      InitAsModel();
    }
    SetPhysicsFlags(EPF_MODEL_IMMATERIAL);
    SetCollisionFlags(ECF_IMMATERIAL);
    SetFlags(GetFlags() | ENF_SEETHROUGH);

    // set appearance
    m_tmSpawn = _pTimer->CurrentTick();
    m_vNormal = eSpawn.vNormal;
    m_vDirection = eSpawn.vDirection;
    m_vStretch = eSpawn.vStretch;
    m_betType = eSpawn.betType;
    m_colMultiplyColor = eSpawn.colMuliplier;
    
    switch (m_betType) {
      case BET_ROCKET: ProjectileExplosion(); break;
      case BET_ROCKET_PLANE: ProjectilePlaneExplosion(); break;
      case BET_GRENADE: GrenadeExplosion(); break;
      case BET_GRENADE_PLANE: GrenadePlaneExplosion(); break;
      case BET_EXPLOSIONSTAIN: Stain(); break;
      case BET_SHOCKWAVE: ShockWave(); break;
      case BET_LASERWAVE: LaserWave(); break;
      case BET_BULLETTRAIL: BulletTrail(); break;
      case BET_BULLETSTAINSAND: BulletStainSand(TRUE); break;
      case BET_BULLETSTAINREDSAND: BulletStainRedSand(TRUE); break;
      case BET_BULLETSTAINSTONE: BulletStainStone(TRUE, TRUE); break;
      case BET_BULLETSTAINWATER: BulletStainWater(TRUE); break;
      case BET_BULLETSTAINUNDERWATER: BulletStainStone(TRUE, FALSE); break;
      case BET_BULLETSTAINSANDNOSOUND: BulletStainSand(FALSE); break;
      case BET_BULLETSTAINREDSANDNOSOUND: BulletStainRedSand(FALSE); break;
      case BET_BULLETSTAINSTONENOSOUND: BulletStainStone(FALSE, TRUE); break;
      case BET_BULLETSTAINWATERNOSOUND: BulletStainWater(FALSE); break;
      case BET_BULLETSTAINUNDERWATERNOSOUND: BulletStainStone(FALSE, FALSE); break;
      case BET_BLOODSPILL: BloodSpill(m_colMultiplyColor); break;
      case BET_BLOODSTAIN: BloodStain(); break;
      case BET_GIZMOSTAIN: GizmoStain(); break;
      case BET_BLOODSTAINGROW: BloodStainGrow(); break;
      case BET_BLOODEXPLODE: BloodExplode(); break;
      case BET_CANNON: CannonExplosion(FALSE, FALSE); break;
      case BET_CANNON_NOLIGHT: CannonExplosion(TRUE, TRUE); break;
      case BET_LIGHT_CANNON: CannonExplosion(TRUE, FALSE); break;
      case BET_CANNON_PLANE: CannonPlaneExplosion(); break;
      case BET_CANNONEXPLOSIONSTAIN: CannonStain(); break;
      case BET_CANNONSHOCKWAVE: CannonShockWave(); break;
      case BET_TELEPORT: TeleportEffect(); break;
      case BET_BOMB: BombExplosion(); break;
      case BET_GIZMO_SPLASH_FX: GizmoSplashFX(); break;
      default:
        ASSERTALWAYS("Unknown effect type");
    }

    // setup light source
    if (m_bLightSource) { SetupLightSource(); }

    wait() {
      on (EBegin) : { call EffectLoop(); }
      on (EBrushDestroyed) : { stop; }
      on (EReturn) : { stop; }
    }

    // cease to exist
    Destroy();
    return;
  }


  // standard effect loop
  EffectLoop() 
  {
    // wait
    if (m_fWaitTime>0.0f) {
      autowait(m_fWaitTime);
    }
    // fading
    if (m_fFadeTime>0.0f) {
      m_fFadeStartTime  = _pTimer->CurrentTick();
      m_fFadeStartAlpha = ((GetModelColor()&CT_AMASK)>>CT_ASHIFT) / 255.0f;
      m_bFade = TRUE;
      autowait(m_fFadeTime);
      m_bFade = FALSE;
    }
    
    // wait for sound to end
    if (m_fSoundTime > m_fWaitTime+m_fFadeTime) {
      SwitchToEditorModel();
      autowait(m_fSoundTime - (m_fWaitTime+m_fFadeTime));
    }

    if (m_tmWaitAfterDeath>0.0f) {
      if( en_RenderType != RT_EDITORMODEL)
      {
        SwitchToEditorModel();
      }
      autowait(m_tmWaitAfterDeath);
    }

    return EReturn();
  }

};
