/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */ 306 %{ #include "StdH.h" #include "Models/Enemies/Scorpman/Scorpman.h" #include "Models/Enemies/Scorpman/Gun.h" %} uses "EntitiesMP/EnemyBase"; uses "EntitiesMP/Bullet"; uses "EntitiesMP/Reminder"; enum ScorpmanType { 0 SMT_SOLDIER "Soldier", 1 SMT_GENERAL "General", 2 SMT_MONSTER "Obsolete", }; %{ #define GUN_X 0.375f #define GUN_Y 0.6f #define GUN_Z -1.85f #define STRETCH_SOLDIER 2 #define STRETCH_GENERAL 3 #define STRETCH_MONSTER 4 // info structure static EntityInfo eiScorpman = { EIBT_FLESH, 1000.0f, 0, 1.6f*STRETCH_SOLDIER, 0, // source (eyes) 0.0f, 1.0f*STRETCH_SOLDIER, 0.0f, // target (body) }; static EntityInfo eiScorpmanGeneral = { EIBT_FLESH, 1500.0f, 0, 1.6f*STRETCH_GENERAL, 0, // source (eyes) 0.0f, 1.0f*STRETCH_GENERAL, 0.0f, // target (body) }; static EntityInfo eiScorpmanMonster = { EIBT_FLESH, 2000.0f, 0, 1.6f*STRETCH_MONSTER, 0, // source (eyes) 0.0f, 1.0f*STRETCH_MONSTER, 0.0f, // target (body) }; #define LIGHT_ANIM_FIRE 3 #define LIGHT_ANIM_NONE 5 %} class CScorpman : CEnemyBase { name "Scorpman"; thumbnail "Thumbnails\\Scorpman.tbn"; properties: 1 enum ScorpmanType m_smtType "Type" 'Y' = SMT_SOLDIER, 2 INDEX m_bFireBulletCount = 0, // fire bullet binary divider 3 INDEX m_iSpawnEffect = 0, // counter for spawn effect every 'x' times 4 FLOAT m_fFireTime = 0.0f, // time to fire bullets 5 CAnimObject m_aoLightAnimation, // light animation object 6 BOOL m_bSleeping "Sleeping" 'S' = FALSE, // set to make scorpman sleep initally { CEntity *penBullet; // bullet CLightSource m_lsLightSource; } components: 0 class CLASS_BASE "Classes\\EnemyBase.ecl", 1 class CLASS_BULLET "Classes\\Bullet.ecl", 5 model MODEL_SCORPMAN "Models\\Enemies\\Scorpman\\Scorpman.mdl", 6 texture TEXTURE_SOLDIER "Models\\Enemies\\Scorpman\\Soldier.tex", 7 texture TEXTURE_GENERAL "Models\\Enemies\\Scorpman\\General.tex", // 8 texture TEXTURE_MONSTER "Models\\Enemies\\Scorpman\\Monster.tex", 12 texture TEXTURE_SPECULAR "Models\\SpecularTextures\\Medium.tex", 9 model MODEL_GUN "Models\\Enemies\\Scorpman\\Gun.mdl", 10 model MODEL_FLARE "Models\\Enemies\\Scorpman\\Flare.mdl", 11 texture TEXTURE_GUN "Models\\Enemies\\Scorpman\\Gun.tex", // ************** SOUNDS ************** 50 sound SOUND_IDLE "Models\\Enemies\\Scorpman\\Sounds\\Idle.wav", 51 sound SOUND_SIGHT "Models\\Enemies\\Scorpman\\Sounds\\Sight.wav", 52 sound SOUND_WOUND "Models\\Enemies\\Scorpman\\Sounds\\Wound.wav", 53 sound SOUND_FIRE "Models\\Enemies\\Scorpman\\Sounds\\Fire.wav", 54 sound SOUND_KICK "Models\\Enemies\\Scorpman\\Sounds\\Kick.wav", 55 sound SOUND_DEATH "Models\\Enemies\\Scorpman\\Sounds\\Death.wav", functions: // describe how this enemy killed player virtual CTString GetPlayerKillDescription(const CTString &strPlayerName, const EDeath &eDeath) { CTString str; if (eDeath.eLastDamage.dmtType==DMT_CLOSERANGE) { str.PrintF(TRANS("%s was stabbed by an Arachnoid"), strPlayerName); } else { str.PrintF(TRANS("An Arachnoid poured lead into %s"), strPlayerName); } return str; } void Precache(void) { CEnemyBase::Precache(); PrecacheModel(MODEL_FLARE); PrecacheSound(SOUND_IDLE ); PrecacheSound(SOUND_SIGHT); PrecacheSound(SOUND_WOUND); PrecacheSound(SOUND_FIRE ); PrecacheSound(SOUND_KICK ); PrecacheSound(SOUND_DEATH); }; /* Read from stream. */ void Read_t( CTStream *istr) { // throw char * CEnemyBase::Read_t(istr); // setup light source SetupLightSource(); } /* Fill in entity statistics - for AI purposes only */ BOOL FillEntityStatistics(EntityStats *pes) { CEnemyBase::FillEntityStatistics(pes); switch(m_smtType) { case SMT_MONSTER: { pes->es_strName+=" Monster"; } break; case SMT_GENERAL: { pes->es_strName+=" General"; } break; case SMT_SOLDIER: { pes->es_strName+=" Soldier"; } break; } return TRUE; } virtual const CTFileName &GetComputerMessageName(void) const { //static DECLARE_CTFILENAME(fnmMonster, "Data\\Messages\\Enemies\\ScorpmanMonster.txt"); static DECLARE_CTFILENAME(fnmGeneral, "Data\\Messages\\Enemies\\ScorpmanGeneral.txt"); static DECLARE_CTFILENAME(fnmSoldier, "Data\\Messages\\Enemies\\ScorpmanSoldier.txt"); switch(m_smtType) { default: ASSERT(FALSE); case SMT_MONSTER: //return fnmMonster; case SMT_GENERAL: return fnmGeneral; case SMT_SOLDIER: return fnmSoldier; } }; /* Get static light source information. */ CLightSource *GetLightSource(void) { if (!IsPredictor()) { return &m_lsLightSource; } else { return NULL; } } BOOL ForcesCannonballToExplode(void) { if (m_smtType!=SMT_SOLDIER) { return TRUE; } return CEnemyBase::ForcesCannonballToExplode(); } // Setup light source void SetupLightSource(void) { // setup light source CLightSource lsNew; lsNew.ls_ulFlags = LSF_NONPERSISTENT|LSF_DYNAMIC; lsNew.ls_rHotSpot = 2.0f; lsNew.ls_rFallOff = 8.0f; lsNew.ls_colColor = RGBToColor(128, 128, 128); lsNew.ls_plftLensFlare = NULL; lsNew.ls_ubPolygonalMask = 0; lsNew.ls_paoLightAnimation = &m_aoLightAnimation; m_lsLightSource.ls_penEntity = this; m_lsLightSource.SetLightSource(lsNew); } // play light animation void PlayLightAnim(INDEX iAnim, ULONG ulFlags) { if (m_aoLightAnimation.GetData()!=NULL) { m_aoLightAnimation.PlayAnim(iAnim, ulFlags); } }; // fire minigun on/off void MinigunOn(void) { PlayLightAnim(LIGHT_ANIM_FIRE, AOF_LOOPING); CModelObject *pmoGun = &GetModelObject()->GetAttachmentModel(SCORPMAN_ATTACHMENT_MINIGUN)-> amo_moModelObject; pmoGun->PlayAnim(GUN_ANIM_FIRE, AOF_LOOPING); AddAttachmentToModel(this, *pmoGun, GUN_ATTACHMENT_FLAME, MODEL_FLARE, TEXTURE_GUN, 0, 0, 0); switch (m_smtType) { case SMT_SOLDIER: pmoGun->StretchModel(FLOAT3D(2.0f, 2.0f, 2.0f)); break; case SMT_GENERAL: pmoGun->StretchModel(FLOAT3D(3.0f, 3.0f, 3.0f)); break; case SMT_MONSTER: pmoGun->StretchModel(FLOAT3D(4.0f, 4.0f, 4.0f)); break; } } void MinigunOff(void) { PlayLightAnim(LIGHT_ANIM_NONE, 0); CModelObject *pmoGun = &GetModelObject()->GetAttachmentModel(SCORPMAN_ATTACHMENT_MINIGUN)-> amo_moModelObject; pmoGun->PlayAnim(GUN_ANIM_IDLE, AOF_LOOPING); pmoGun->RemoveAttachmentModel(GUN_ATTACHMENT_FLAME); } /* Entity info */ void *GetEntityInfo(void) { if (m_smtType == SMT_MONSTER) { return &eiScorpmanMonster; } else if (m_smtType == SMT_GENERAL) { return &eiScorpmanGeneral; } else { return &eiScorpman; } }; /* Receive damage */ void ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType, FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection) { // scorpman can't harm scorpman if (!IsOfClass(penInflictor, "Scorpman")) { CEnemyBase::ReceiveDamage(penInflictor, dmtType, fDamageAmmount, vHitPoint, vDirection); } }; // damage anim INDEX AnimForDamage(FLOAT fDamage) { INDEX iAnim; switch (IRnd()%3) { case 0: iAnim = SCORPMAN_ANIM_WOUND01; break; case 1: iAnim = SCORPMAN_ANIM_WOUND02; break; case 2: iAnim = SCORPMAN_ANIM_WOUND03; break; default: ASSERTALWAYS("Scorpman unknown damage"); } StartModelAnim(iAnim, 0); MinigunOff(); return iAnim; }; // death INDEX AnimForDeath(void) { StartModelAnim(SCORPMAN_ANIM_DEATH, 0); MinigunOff(); return SCORPMAN_ANIM_DEATH; }; FLOAT WaitForDust(FLOAT3D &vStretch) { if(GetModelObject()->GetAnim()==SCORPMAN_ANIM_DEATH) { vStretch=FLOAT3D(1,1,1)*1.5f; return 1.3f; } return -1.0f; }; void DeathNotify(void) { ChangeCollisionBoxIndexWhenPossible(SCORPMAN_COLLISION_BOX_DEATH); SetCollisionFlags(ECF_MODEL); }; // virtual anim functions void StandingAnim(void) { StartModelAnim(SCORPMAN_ANIM_IDLE, AOF_LOOPING|AOF_NORESTART); }; void WalkingAnim(void) { StartModelAnim(SCORPMAN_ANIM_WALK, AOF_LOOPING|AOF_NORESTART); }; void RunningAnim(void) { StartModelAnim(SCORPMAN_ANIM_WALK, AOF_LOOPING|AOF_NORESTART); }; void RotatingAnim(void) { StartModelAnim(SCORPMAN_ANIM_WALK, AOF_LOOPING|AOF_NORESTART); }; // virtual sound functions void IdleSound(void) { PlaySound(m_soSound, SOUND_IDLE, SOF_3D); }; void SightSound(void) { PlaySound(m_soSound, SOUND_SIGHT, SOF_3D); }; void WoundSound(void) { PlaySound(m_soSound, SOUND_WOUND, SOF_3D); }; void DeathSound(void) { PlaySound(m_soSound, SOUND_DEATH, SOF_3D); }; /************************************************************ * FIRE BULLET / RAIL * ************************************************************/ BOOL CanFireAtPlayer(void) { // get ray source and target FLOAT3D vSource, vTarget; GetPositionCastRay(this, m_penEnemy, vSource, vTarget); // bullet start position CPlacement3D plBullet; plBullet.pl_OrientationAngle = ANGLE3D(0,0,0); plBullet.pl_PositionVector = FLOAT3D(GUN_X, GUN_Y, 0); // offset are changed according to stretch factor if (m_smtType == SMT_MONSTER) { plBullet.pl_PositionVector*=STRETCH_MONSTER; } else if (m_smtType == SMT_GENERAL) { plBullet.pl_PositionVector*=STRETCH_GENERAL; } else { plBullet.pl_PositionVector*=STRETCH_SOLDIER; } plBullet.RelativeToAbsolute(GetPlacement()); vSource = plBullet.pl_PositionVector; // cast the ray CCastRay crRay(this, vSource, vTarget); crRay.cr_ttHitModels = CCastRay::TT_NONE; // check for brushes only crRay.cr_bHitTranslucentPortals = FALSE; en_pwoWorld->CastRay(crRay); // if hit nothing (no brush) the entity can be seen return (crRay.cr_penHit==NULL); } void PrepareBullet(FLOAT fDamage) { // bullet start position CPlacement3D plBullet; plBullet.pl_OrientationAngle = ANGLE3D(0,0,0); plBullet.pl_PositionVector = FLOAT3D(GUN_X, GUN_Y, 0); // offset are changed according to stretch factor if (m_smtType == SMT_MONSTER) { plBullet.pl_PositionVector*=STRETCH_MONSTER; } else if (m_smtType == SMT_GENERAL) { plBullet.pl_PositionVector*=STRETCH_GENERAL; } else { plBullet.pl_PositionVector*=STRETCH_SOLDIER; } plBullet.RelativeToAbsolute(GetPlacement()); // create bullet penBullet = CreateEntity(plBullet, CLASS_BULLET); // init bullet EBulletInit eInit; eInit.penOwner = this; eInit.fDamage = fDamage; penBullet->Initialize(eInit); }; // fire bullet void FireBullet(void) { // binary divide counter m_bFireBulletCount++; if (m_bFireBulletCount>1) { m_bFireBulletCount = 0; } if (m_bFireBulletCount==1) { return; } // bullet PrepareBullet(3.0f); ((CBullet&)*penBullet).CalcTarget(m_penEnemy, 250); ((CBullet&)*penBullet).CalcJitterTarget(10); ((CBullet&)*penBullet).LaunchBullet( TRUE, TRUE, TRUE); ((CBullet&)*penBullet).DestroyBullet(); }; // adjust sound and watcher parameters here if needed void EnemyPostInit(void) { // set sound default parameters m_soSound.Set3DParameters(160.0f, 50.0f, 1.0f, 1.0f); }; procedures: /************************************************************ * A T T A C K E N E M Y * ************************************************************/ // shoot Fire(EVoid) : CEnemyBase::Fire{ if (!CanFireAtPlayer()) { return EReturn(); } // confused amount switch (m_smtType) { case SMT_MONSTER: m_fDamageConfused = 200; m_fFireTime = 8.0f; break; case SMT_GENERAL: m_fDamageConfused = 100; m_fFireTime = 4.0f; break; case SMT_SOLDIER: m_fDamageConfused = 50; m_fFireTime = 2.0f; break; } if (GetSP()->sp_gdGameDifficulty<=CSessionProperties::GD_EASY) { m_fFireTime *= 0.5f; } // to fire StartModelAnim(SCORPMAN_ANIM_STANDTOFIRE, 0); m_fLockOnEnemyTime = GetModelObject()->GetAnimLength(SCORPMAN_ANIM_STANDTOFIRE) + 0.5f + FRnd()/3; autocall CEnemyBase::LockOnEnemy() EReturn; // fire m_iSpawnEffect = 0; // effect every 'x' frames m_fFireTime += _pTimer->CurrentTick(); m_bFireBulletCount = 0; PlaySound(m_soSound, SOUND_FIRE, SOF_3D|SOF_LOOP); MinigunOn(); while (m_fFireTime > _pTimer->CurrentTick()) { m_fMoveFrequency = 0.1f; wait(m_fMoveFrequency) { on (EBegin) : { // make fuss AddToFuss(); // fire bullet FireBullet(); m_vDesiredPosition = m_penEnemy->GetPlacement().pl_PositionVector; // rotate to enemy if (!IsInPlaneFrustum(m_penEnemy, CosFast(5.0f))) { m_fMoveSpeed = 0.0f; m_aRotateSpeed = 4000.0f; StartModelAnim(SCORPMAN_ANIM_WALK_AND_FIRE, AOF_LOOPING|AOF_NORESTART); // stand in place } else { m_fMoveSpeed = 0.0f; m_aRotateSpeed = 0.0f; StartModelAnim(SCORPMAN_ANIM_FIRE_MINIGUN, AOF_LOOPING|AOF_NORESTART); } // adjust direction and speed SetDesiredMovement(); resume; } on (ETimer) : { stop; } } } m_soSound.Stop(); MinigunOff(); // set next shoot time m_fShootTime = _pTimer->CurrentTick() + m_fAttackFireTime*(1.0f + FRnd()/3.0f); // from fire StartModelAnim(SCORPMAN_ANIM_FIRETOSTAND, 0); autowait(GetModelObject()->GetAnimLength(SCORPMAN_ANIM_FIRETOSTAND)); MaybeSwitchToAnotherPlayer(); // shoot completed return EReturn(); }; // hit enemy Hit(EVoid) : CEnemyBase::Hit { // close attack StartModelAnim(SCORPMAN_ANIM_SPIKEHIT, 0); autowait(0.5f); PlaySound(m_soSound, SOUND_KICK, SOF_3D); if (CalcDist(m_penEnemy) < m_fCloseDistance) { FLOAT3D vDirection = m_penEnemy->GetPlacement().pl_PositionVector-GetPlacement().pl_PositionVector; vDirection.Normalize(); if (m_smtType == SMT_MONSTER) { InflictDirectDamage(m_penEnemy, this, DMT_CLOSERANGE, 80.0f, FLOAT3D(0, 0, 0), vDirection); } else if (m_smtType == SMT_GENERAL) { InflictDirectDamage(m_penEnemy, this, DMT_CLOSERANGE, 40.0f, FLOAT3D(0, 0, 0), vDirection); } else { InflictDirectDamage(m_penEnemy, this, DMT_CLOSERANGE, 20.0f, FLOAT3D(0, 0, 0), vDirection); } } autowait(0.3f); MaybeSwitchToAnotherPlayer(); return EReturn(); }; Sleep(EVoid) { // start sleeping anim StartModelAnim(SCORPMAN_ANIM_SLEEP, AOF_LOOPING); // repeat wait() { // if triggered on(ETrigger eTrigger) : { // remember enemy SetTargetSoft(eTrigger.penCaused); // wake up jump WakeUp(); } // if damaged on(EDamage eDamage) : { // wake up jump WakeUp(); } otherwise() : { resume; } } } WakeUp(EVoid) { // wakeup anim SightSound(); StartModelAnim(SCORPMAN_ANIM_WAKEUP, 0); autowait(GetModelObject()->GetCurrentAnimLength()); // trigger your target SendToTarget(m_penDeathTarget, m_eetDeathType); // proceed with normal functioning return EReturn(); } // overridable called before main enemy loop actually begins PreMainLoop(EVoid) : CEnemyBase::PreMainLoop { // if sleeping if (m_bSleeping) { m_bSleeping = FALSE; // go to sleep until waken up wait() { on (EBegin) : { call Sleep(); } on (EReturn) : { stop; }; // if dead on(EDeath eDeath) : { // die jump CEnemyBase::Die(eDeath); } } } return EReturn(); } /************************************************************ * M A I N * ************************************************************/ Main(EVoid) { if (m_smtType==SMT_MONSTER) { m_smtType=SMT_GENERAL; } // declare yourself as a model InitAsModel(); SetPhysicsFlags(EPF_MODEL_WALKING|EPF_HASLUNGS); SetCollisionFlags(ECF_MODEL); SetFlags(GetFlags()|ENF_ALIVE); en_tmMaxHoldBreath = 25.0f; en_fDensity = 3000.0f; // set your appearance SetModel(MODEL_SCORPMAN); switch (m_smtType) { case SMT_SOLDIER: // set your texture SetModelMainTexture(TEXTURE_SOLDIER); SetModelSpecularTexture(TEXTURE_SPECULAR); SetHealth(300.0f); m_fMaxHealth = 300.0f; // damage/explode properties m_fDamageWounded = 200.0f; m_fBlowUpAmount = 1E10f; m_fBodyParts = 30; // setup attack distances m_fAttackDistance = 200.0f; m_fCloseDistance = 5.0f; m_fStopDistance = 4.5f; m_fAttackFireTime = 0.5f; m_fCloseFireTime = 1.0f; m_fIgnoreRange = 350.0f; m_iScore = 1000; break; case SMT_GENERAL: // set your texture SetModelMainTexture(TEXTURE_GENERAL); SetModelSpecularTexture(TEXTURE_SPECULAR); SetHealth(600.0f); m_fMaxHealth = 600.0f; // damage/explode properties m_fDamageWounded = 400.0f; m_fBlowUpAmount = 1E10f; m_fBodyParts = 30; // setup attack distances m_fAttackDistance = 200.0f; m_fCloseDistance = 5.0f; m_fStopDistance = 4.5f; m_fAttackFireTime = 2.0f; m_fCloseFireTime = 1.0f; m_fIgnoreRange = 350.0f; m_iScore = 5000; break; case SMT_MONSTER: // set your texture SetModelMainTexture(TEXTURE_GENERAL); SetModelSpecularTexture(TEXTURE_SPECULAR); SetHealth(1200.0f); m_fMaxHealth = 1200.0f; // damage/explode properties m_fDamageWounded = 800.0f; m_fBlowUpAmount = 1E10f; m_fBodyParts = 60; // setup attack distances m_fAttackDistance = 250.0f; m_fCloseDistance = 11.0f; m_fStopDistance = 9.0f; m_fAttackFireTime = 2.0f; m_fCloseFireTime = 1.0f; m_fIgnoreRange = 500.0f; m_iScore = 10000; break; } AddAttachment(SCORPMAN_ATTACHMENT_MINIGUN, MODEL_GUN, TEXTURE_GUN); // set stretch factors for height and width - MUST BE DONE BEFORE SETTING MODEL! switch (m_smtType) { case SMT_SOLDIER: GetModelObject()->StretchModel(FLOAT3D(1.0f, 1.0f, 1.0f)*STRETCH_SOLDIER); break; case SMT_GENERAL: GetModelObject()->StretchModel(FLOAT3D(1.0f, 1.0f, 1.0f)*STRETCH_GENERAL); break; case SMT_MONSTER: GetModelObject()->StretchModel(FLOAT3D(1.0f, 1.0f, 1.0f)*STRETCH_MONSTER); break; } ModelChangeNotify(); // setup moving speed m_fWalkSpeed = FRnd() + 1.5f; m_aWalkRotateSpeed = AngleDeg(FRnd()*20.0f + 550.0f); m_fAttackRunSpeed = FRnd()*1.5f + 4.5f; m_aAttackRotateSpeed = AngleDeg(FRnd()*50.0f + 275.0f); m_fCloseRunSpeed = FRnd()*1.5f + 4.5f; m_aCloseRotateSpeed = AngleDeg(FRnd()*50.0f + 275.0f); // set stretch factors for height and width CEnemyBase::SizeModel(); // setup light source SetupLightSource(); // 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); } MinigunOff(); // continue behavior in base class jump CEnemyBase::MainLoop(); }; };