304 %{ #include "Entities/StdH/StdH.h" %} uses "Entities/EnemyBase"; uses "Entities/BasicEffects"; enum EnemySpawnerType { 0 EST_SIMPLE "Simple", // spawns on trigger 1 EST_RESPAWNER "Respawner", // respawn after death 2 EST_DESTROYABLE "Destroyable", // spawns untill killed 3 EST_TRIGGERED "Triggered", // spawn one group on each trigger 4 EST_TELEPORTER "Teleporter", // teleport the target instead copying it - usable only once 5 EST_RESPAWNERBYONE "RespawnerbyOne", // respawn only one (not entire group) after death }; class CEnemySpawner: CRationalEntity { name "Enemy Spawner"; thumbnail "Thumbnails\\EnemySpawner.tbn"; features "HasName", "HasTarget", "IsTargetable"; properties: 1 CEntityPointer m_penTarget "Template Target" 'T' COLOR(C_BLUE|0x20), // template entity to duplicate 2 CTString m_strDescription = "", 3 CTString m_strName "Name" 'N' = "Enemy spawner", 6 RANGE m_fInnerCircle "Circle inner" 'V' = 0.0f, // inner circle for creation 7 RANGE m_fOuterCircle "Circle outer" 'B' = 0.0f, // outer circle for creation 9 FLOAT m_tmDelay "Wait delay" 'W' = 0.0f, // how long to delay before spawning 16 FLOAT m_tmSingleWait "Single delay" 'O' = 0.1f, // delay inside one group 5 FLOAT m_tmGroupWait "Group delay" 'G' = 0.1f, // delay between two groups 17 INDEX m_ctGroupSize "Group size" = 1, 8 INDEX m_ctTotal "Total count" 'C' = 1, // max. number of spawned enemies 13 CEntityPointer m_penPatrol "Patrol target" 'P' COLOR(C_lGREEN|0xFF), // for spawning patrolling 15 enum EnemySpawnerType m_estType "Type" 'Y' = EST_SIMPLE, // type of spawner 18 BOOL m_bTelefrag "Telefrag" 'F' = FALSE, // telefrag when spawning 19 BOOL m_bSpawnEffect "SpawnEffect" 'S' = TRUE, // show effect and play sound 20 BOOL m_bDoubleInSerious "Double in serious mode" = FALSE, 21 CEntityPointer m_penSeriousTarget "Template for Serious" COLOR(C_RED|0x20), 50 CSoundObject m_soSpawn, // sound channel 51 INDEX m_iInGroup=0, // in group counter for loops components: 1 model MODEL_ENEMYSPAWNER "Models\\Editor\\EnemySpawner.mdl", 2 texture TEXTURE_ENEMYSPAWNER "Models\\Editor\\EnemySpawner.tex", 3 class CLASS_BASIC_EFFECT "Classes\\BasicEffect.ecl", functions: void Precache(void) { PrecacheClass(CLASS_BASIC_EFFECT, BET_TELEPORT); } const CTString &GetDescription(void) const { ((CTString&)m_strDescription).PrintF("->"); if (m_penTarget!=NULL) { ((CTString&)m_strDescription).PrintF("->%s",(const char*) m_penTarget->GetName()); if (m_penSeriousTarget!=NULL) { ((CTString&)m_strDescription).PrintF("->%s, %s", (const char*) m_penTarget->GetName(),(const char*) m_penSeriousTarget->GetName()); } } ((CTString&)m_strDescription) = EnemySpawnerType_enum.NameForValue(INDEX(m_estType)) + m_strDescription; return m_strDescription; } // check if one template is valid for this spawner BOOL CheckTemplateValid(CEntity *pen) { if (pen==NULL || !IsDerivedFromClass(pen, "Enemy Base")) { return FALSE; } if (m_estType==EST_TELEPORTER) { return !(((CEnemyBase&)*pen).m_bTemplate); } else { return ((CEnemyBase&)*pen).m_bTemplate; } } BOOL IsTargetValid(SLONG slPropertyOffset, CEntity *penTarget) { if( slPropertyOffset == offsetof(CEnemySpawner, m_penTarget)) { return CheckTemplateValid(penTarget); } else if( slPropertyOffset == offsetof(CEnemySpawner, m_penPatrol)) { return (penTarget!=NULL && IsDerivedFromClass(penTarget, "Enemy Marker")); } else if( slPropertyOffset == offsetof(CEnemySpawner, m_penSeriousTarget)) { return CheckTemplateValid(penTarget); } return CEntity::IsTargetValid(slPropertyOffset, penTarget); } /* Fill in entity statistics - for AI purposes only */ BOOL FillEntityStatistics(EntityStats *pes) { if (m_penTarget==NULL) { return FALSE; } m_penTarget->FillEntityStatistics(pes); pes->es_ctCount = m_ctTotal; pes->es_strName += " (spawned)"; if (m_penSeriousTarget!=NULL) { pes->es_strName += " (has serious)"; } return TRUE; } // spawn new entity void SpawnEntity(BOOL bCopy) { // spawn new entity if of class basic enemy if (CheckTemplateValid(m_penTarget)) { CEntity *pen = NULL; if (bCopy) { // copy template entity pen = GetWorld()->CopyEntityInWorld( *m_penTarget, CPlacement3D(FLOAT3D(-32000.0f+FRnd()*200.0f, -32000.0f+FRnd()*200.0f, 0), ANGLE3D(0, 0, 0)) ); // change needed properties pen->End(); CEnemyBase *peb = ((CEnemyBase*)pen); peb->m_bTemplate = FALSE; if (m_estType==EST_RESPAWNER || m_estType==EST_RESPAWNERBYONE) { peb->m_penSpawnerTarget = this; } if (m_penPatrol!=NULL) { peb->m_penMarker = m_penPatrol; } pen->Initialize(); } else { pen = m_penTarget; m_penTarget = NULL; } // adjust circle radii to account for enemy size FLOAT fEntityR = 0; if (pen->en_pciCollisionInfo!=NULL) { fEntityR = pen->en_pciCollisionInfo->GetMaxFloorRadius(); } FLOAT fOuterCircle = ClampDn(m_fOuterCircle-fEntityR, 0.0f); FLOAT fInnerCircle = ClampUp(m_fInnerCircle+fEntityR, fOuterCircle); // calculate new position FLOAT fR = fInnerCircle + FRnd()*(fOuterCircle-fInnerCircle); FLOAT fA = FRnd()*360.0f; CPlacement3D pl(FLOAT3D(CosFast(fA)*fR, 0.05f, SinFast(fA)*fR), ANGLE3D(0, 0, 0)); pl.RelativeToAbsolute(GetPlacement()); // teleport back pen->Teleport(pl, m_bTelefrag); // spawn teleport effect if (m_bSpawnEffect) { ESpawnEffect ese; ese.colMuliplier = C_WHITE|CT_OPAQUE; ese.betType = BET_TELEPORT; ese.vNormal = FLOAT3D(0,1,0); FLOATaabbox3D box; pen->GetBoundingBox(box); FLOAT fEntitySize = box.Size().MaxNorm()*2; ese.vStretch = FLOAT3D(fEntitySize, fEntitySize, fEntitySize); CEntityPointer penEffect = CreateEntity(pl, CLASS_BASIC_EFFECT); penEffect->Initialize(ese); } } }; procedures: // spawn one group of entities SpawnGroup() { // no enemies in group yet m_iInGroup = 0; // repeat forever while(TRUE) { // spawn one enemy SpawnEntity(TRUE); // count total enemies spawned m_ctTotal--; // if no more left if (m_ctTotal<=0) { // finish entire spawner return EEnd(); } // count enemies in group m_iInGroup++; // if entire group spawned if (m_iInGroup>=m_ctGroupSize) { // finish return EReturn(); } // wait between two entities in group wait(m_tmSingleWait) { on (EBegin) : { resume; } on (ETimer) : { stop; } otherwise() : { pass; } } } } // simple spawner Simple() { // wait to be triggered wait() { on (EBegin) : { resume; } on (ETrigger) : { stop; }; on (EStart) : { stop; }; otherwise() : { pass; } } // if should delay if (m_tmDelay>0) { // wait delay autowait(m_tmDelay); } // repeat while(TRUE) { // spawn one group autocall SpawnGroup() EReturn; // delay between groups autowait(m_tmGroupWait); } } // teleports the template Teleporter() { // wait to be triggered wait() { on (EBegin) : { resume; } on (ETrigger) : { stop; }; on (EStart) : { stop; }; otherwise() : { pass; } } // if should delay if (m_tmDelay>0) { // wait delay autowait(m_tmDelay); } // teleport it SpawnEntity(FALSE); // end the spawner return EEnd(); } // respawn enemies when killed Respawner() { // repeat while(TRUE) { // wait to be triggered wait() { on (EBegin) : { resume; } on (ETrigger) : { stop; }; on (EStart) : { stop; }; otherwise() : { pass; } } // if should delay if (m_tmDelay>0) { // wait delay autowait(m_tmDelay); } // spawn one group autocall SpawnGroup() EReturn; // if should continue respawning by one if (m_estType==EST_RESPAWNERBYONE) { // set group size to 1 m_ctGroupSize = 1; } // wait a bit to recover autowait(0.1f); } } DestroyableInactive() { waitevent() EActivate; jump DestroyableActive(); } DestroyableActiveSpawning() { // repeat while(TRUE) { // spawn one group autocall SpawnGroup() EReturn; // delay between groups autowait(m_tmGroupWait); } } DestroyableActive() { autocall DestroyableActiveSpawning() EDeactivate; jump DestroyableInactive(); } // spawn new entities until you are stopped Destroyable() { // start in inactive state and do until stopped autocall DestroyableInactive() EStop; // finish return EEnd(); } Main() { // init as nothing InitAsEditorModel(); SetPhysicsFlags(EPF_MODEL_IMMATERIAL); SetCollisionFlags(ECF_IMMATERIAL); // set appearance SetModel(MODEL_ENEMYSPAWNER); SetModelMainTexture(TEXTURE_ENEMYSPAWNER); // set range if (m_fInnerCircle > m_fOuterCircle) { m_fInnerCircle = m_fOuterCircle; } // check target if (m_penTarget!=NULL) { if (!IsDerivedFromClass(m_penTarget, "Enemy Base")) { WarningMessage("Target '%s' is of wrong class!", (const char*)m_penTarget->GetName()); m_penTarget = NULL; } } if (m_penSeriousTarget!=NULL) { if (!IsDerivedFromClass(m_penSeriousTarget, "Enemy Base")) { WarningMessage("Target '%s' is of wrong class!", (const char*)m_penSeriousTarget->GetName()); m_penSeriousTarget = NULL; } } // never start ai in wed autowait(_pTimer->TickQuantum); if (m_bDoubleInSerious && GetSP()->sp_gdGameDifficulty==CSessionProperties::GD_EXTREME) { m_ctGroupSize*=2; m_ctTotal*=2; } if (m_penSeriousTarget!=NULL && GetSP()->sp_gdGameDifficulty==CSessionProperties::GD_EXTREME) { m_penTarget = m_penSeriousTarget; } wait() { on(EBegin) : { if(m_estType==EST_SIMPLE) { call Simple(); } else if(m_estType==EST_TELEPORTER) { call Teleporter(); } else if(m_estType==EST_RESPAWNER || m_estType==EST_RESPAWNERBYONE || m_estType==EST_TRIGGERED) { call Respawner(); } else if(m_estType==EST_DESTROYABLE) { call Destroyable(); } } on(EDeactivate) : { stop; } on(EStop) : { stop; } on(EEnd) : { stop; } } Destroy(); return; }; };