/* Copyright (c) 2002-2012 Croteam Ltd. This program is free software; you can redistribute it and/or modify it under the terms of version 2 of the GNU General Public License as published by the Free Software Foundation This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ 345 %{ #include "EntitiesMP/StdH/StdH.h" #include "ModelsMP/Enemies/CannonRotating/Turret.h" #include "ModelsMP/Enemies/CannonRotating/RotatingMechanism.h" %} uses "EntitiesMP/ModelHolder2"; uses "EntitiesMP/Projectile"; uses "EntitiesMP/SoundHolder"; uses "EntitiesMP/BloodSpray"; uses "EntitiesMP/CannonBall"; %{ #define CANNONR_SIZE 2.0f // info structure static EntityInfo eiCannonRotating = { EIBT_WOOD, 10000.0f, 0.0f, 1.5f*CANNONR_SIZE, 0.0f, // source (eyes) 0.0f, 0.5f*CANNONR_SIZE, 0.0f, // target (body) }; #define FIRING_POSITION_MUZZLE FLOAT3D(0.0f, 0.0f, -1.0f) #define MUZZLE_ROTATION_SPEED 45.0f //deg/sec %} class CCannonRotating: CEnemyBase { name "CannonRotating"; thumbnail "Thumbnails\\CannonRotating.tbn"; properties: 1 FLOAT m_fHealth "Cannon Health" = 100.0f, 2 RANGE m_fFiringRangeClose "Cannon Firing Close Range" = 50.0f, 3 RANGE m_fFiringRangeFar "Cannon Firing Far Range" = 150.0f, 4 FLOAT m_fWaitAfterFire "Cannon Wait After Fire" = 3.0f, 5 FLOAT m_fSize = CANNONR_SIZE, 6 FLOAT m_fMaxPitch "Cannon Max Pitch" = 20.0f, 7 FLOAT m_fViewAngle "Cannon View Angle" = 2.5f, 8 FLOAT m_fScanAngle "Cannon Scanning Angle" = 60.0f, 9 FLOAT m_fRotationSpeed "Cannon Rotation Speed" = 20.0f, 10 BOOL m_bActive "Cannon Active" = TRUE, 20 FLOAT3D m_fRotSpeedMuzzle = ANGLE3D(0.0f, 0.0f, 0.0f), 21 FLOAT3D m_fRotSpeedRotator = ANGLE3D(0.0f, 0.0f, 0.0f), 25 FLOAT m_fDistanceToPlayer = 0.0f, 26 FLOAT m_fDesiredMuzzlePitch = 0.0f, 27 FLOAT m_iMuzzleDir = 1.0f, 28 FLOAT3D m_vFiringPos = FLOAT3D(0.0f, 0.0f, 0.0f), 29 FLOAT3D m_vTarget = FLOAT3D(0.0f, 0.0f, 0.0f), 30 FLOAT m_tmLastFireTime = -1000.0f, 40 FLOAT3D m_aBeginMuzzleRotation = ANGLE3D(0.0f, 0.0f, 0.0f), 41 FLOAT3D m_aEndMuzzleRotation = ANGLE3D(0.0f, 0.0f, 0.0f), 42 FLOAT3D m_aBeginRotatorRotation = ANGLE3D(0.0f, 0.0f, 0.0f), 43 FLOAT3D m_aEndRotatorRotation = ANGLE3D(0.0f, 0.0f, 0.0f), components: 1 class CLASS_BASE "Classes\\EnemyBase.ecl", 2 class CLASS_BASIC_EFFECT "Classes\\BasicEffect.ecl", 3 class CLASS_PROJECTILE "Classes\\Projectile.ecl", 4 class CLASS_CANNONBALL "Classes\\CannonBall.ecl", // ************** CANNON MODEL ************** 10 model MODEL_TURRET "ModelsMP\\Enemies\\CannonRotating\\Turret.mdl", 11 model MODEL_ROTATOR "ModelsMP\\Enemies\\CannonRotating\\RotatingMechanism.mdl", 12 model MODEL_CANNON "ModelsMP\\Enemies\\CannonStatic\\Cannon.mdl", 20 texture TEXTURE_TURRET "ModelsMP\\Enemies\\CannonRotating\\Turret.tex", 21 texture TEXTURE_ROTATOR "ModelsMP\\Enemies\\CannonRotating\\RotatingMechanism.tex", 22 texture TEXTURE_CANNON "Models\\Weapons\\Cannon\\Body.tex", // ************** CANNON PARTS ************** 30 model MODEL_DEBRIS_MUZZLE "ModelsMP\\Enemies\\CannonRotating\\Debris\\Cannon.mdl", 31 model MODEL_DEBRIS_ROTATOR "ModelsMP\\Enemies\\CannonRotating\\Debris\\RotatingMechanism.mdl", 32 model MODEL_DEBRIS_BASE "ModelsMP\\Enemies\\CannonRotating\\Debris\\Turret.mdl", 35 model MODEL_BALL "Models\\Weapons\\Cannon\\Projectile\\CannonBall.mdl", 36 texture TEXTURE_BALL "Models\\Weapons\\Cannon\\Projectile\\IronBall.tex", // ************** SOUNDS ************** 50 sound SOUND_FIRE "ModelsMP\\Enemies\\CannonRotating\\Sounds\\Fire.wav", functions: virtual CTString GetPlayerKillDescription(const CTString &strPlayerName, const EDeath &eDeath) { CTString str; str.PrintF(TRANSV("A Cannon killed %s"), (const char *) strPlayerName); return str; } /* Entity info */ void *GetEntityInfo(void) { return &eiCannonRotating; }; virtual const CTFileName &GetComputerMessageName(void) const { static DECLARE_CTFILENAME(fnmCannon, "DataMP\\Messages\\Enemies\\CannonRotating.txt"); return fnmCannon; }; void Precache(void) { CEnemyBase::Precache(); PrecacheModel(MODEL_DEBRIS_MUZZLE); PrecacheModel(MODEL_DEBRIS_ROTATOR); PrecacheModel(MODEL_DEBRIS_BASE); PrecacheModel(MODEL_BALL); PrecacheTexture(TEXTURE_BALL); PrecacheSound(SOUND_FIRE); PrecacheClass(CLASS_CANNONBALL); }; void ReceiveDamage(CEntity *penInflictor, enum DamageType dmtType, FLOAT fDamageAmmount, const FLOAT3D &vHitPoint, const FLOAT3D &vDirection) { // take less damage from heavy bullets (e.g. sniper) if(dmtType==DMT_BULLET && fDamageAmmount>100.0f) { fDamageAmmount*=0.5f; } CEnemyBase::ReceiveDamage(penInflictor, dmtType, fDamageAmmount, vHitPoint, vDirection); }; // damage anim INDEX AnimForDamage(FLOAT fDamage) { return 0; }; // death INDEX AnimForDeath(void) { return 0; }; // cast a ray to entity checking only for brushes BOOL IsVisible(CEntity *penEntity) { ASSERT(penEntity!=NULL); // get ray source and target FLOAT3D vSource, vTarget; GetPositionCastRay(this, penEntity, vSource, vTarget); // 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); }; BOOL IsInTheLineOfFire(CEntity *penEntity, FLOAT fAngle) { ASSERT(penEntity!=NULL); FLOAT fCosAngle; FLOAT3D vHeading; FLOAT3D vToPlayer; FLOAT3D vSide = FLOAT3D(1.0f, 0.0f, 0.0f)*GetRotationMatrix(); FLOAT3D vFront = FLOAT3D(0.0f, 0.0f, -1.0f)*GetRotationMatrix(); FLOATmatrix3D m; MakeRotationMatrixFast(m, m_aBeginRotatorRotation); vSide = vSide*m; vFront = vFront*m; vToPlayer = penEntity->GetPlacement().pl_PositionVector - GetPlacement().pl_PositionVector; vToPlayer.Normalize(); fCosAngle = vToPlayer%vSide; // if on the firing plane if (Abs(fCosAngle)0.0f) { return TRUE; } } return FALSE; } CPlayer *AcquireTarget() { // find actual number of players INDEX ctMaxPlayers = GetMaxPlayers(); CEntity *penPlayer; for(INDEX i=0; iInitialize(eSpawnEffect); // spawn shockwave plExplosion = GetPlacement(); penExplosion = CreateEntity(plExplosion, CLASS_BASIC_EFFECT); eSpawnEffect.colMuliplier = C_WHITE|CT_OPAQUE; eSpawnEffect.betType = BET_CANNONSHOCKWAVE; fSize = m_fBlowUpSize*1.0f; eSpawnEffect.vStretch = FLOAT3D(fSize,fSize,fSize); penExplosion->Initialize(eSpawnEffect); // hide yourself (must do this after spawning debris) SwitchToEditorModel(); SetPhysicsFlags(EPF_MODEL_IMMATERIAL); SetCollisionFlags(ECF_IMMATERIAL); } void PreMoving() { // manually update rotation of the attachments UpdateAttachmentRotations(); CEnemyBase::PreMoving(); } void PostMoving() { CEnemyBase::PostMoving(); // make sure this entity stays in the moving list SetFlags(GetFlags()&~ENF_INRENDERING); } // rotate to between-tick position BOOL AdjustShadingParameters(FLOAT3D &vLightDirection, COLOR &colLight, COLOR &colAmbient) { // rotator CAttachmentModelObject &amo0 = *GetModelObject()->GetAttachmentModel(TURRET_ATTACHMENT_ROTATORHEADING); amo0.amo_plRelative.pl_OrientationAngle = Lerp(m_aBeginRotatorRotation, m_aEndRotatorRotation, _pTimer->GetLerpFactor()); // muzzle CAttachmentModelObject &amo1 = *amo0.amo_moModelObject.GetAttachmentModel(ROTATINGMECHANISM_ATTACHMENT_CANNON); amo1.amo_plRelative.pl_OrientationAngle = Lerp(m_aBeginMuzzleRotation, m_aEndMuzzleRotation, _pTimer->GetLerpFactor()); // finish by calling the default 'AdjustShadingParameters' return CEnemyBase::AdjustShadingParameters(vLightDirection, colLight, colAmbient); }; void UpdateAttachmentRotations( void ) { // rotator m_aBeginRotatorRotation = m_aEndRotatorRotation; m_aEndRotatorRotation += m_fRotSpeedRotator*_pTimer->TickQuantum; // muzzle m_aBeginMuzzleRotation = m_aEndMuzzleRotation; m_aEndMuzzleRotation += m_fRotSpeedMuzzle*_pTimer->TickQuantum; } void UpdateFiringPos() { FLOATmatrix3D m; // initial position m_vFiringPos = FIRING_POSITION_MUZZLE*m_fSize; // heading rotation MakeRotationMatrixFast(m, m_aBeginRotatorRotation); m_vFiringPos = m_vFiringPos*m; // pitch rotation MakeRotationMatrixFast(m, m_aBeginMuzzleRotation); m_vFiringPos = m_vFiringPos*m; // add translations CAttachmentModelObject &amo0 = *GetModelObject()->GetAttachmentModel(TURRET_ATTACHMENT_ROTATORHEADING); CAttachmentModelObject &amo1 = *amo0.amo_moModelObject.GetAttachmentModel(ROTATINGMECHANISM_ATTACHMENT_CANNON); m_vFiringPos += amo0.amo_plRelative.pl_PositionVector + amo1.amo_plRelative.pl_PositionVector; } procedures: MainLoop() { wait() { on (EBegin) : { call Scan(); resume; } on (EDeactivate) : { jump Inactive(); } on (EDeath eDeath) : { // die jump Die(eDeath); } }; return; }; Scan() { // this is a kind of 'sleep' mode - check to see if any player entered // the attack radius every once in a while, and change rotation when needed while(TRUE) { autowait(0.20f); // adjust rotations BOOL bPause = FALSE; if ( m_aBeginRotatorRotation(1)>(m_fScanAngle/2.0f) ) { m_fRotSpeedRotator = FLOAT3D(-m_fRotationSpeed, 0.0f, 0.0f); if (m_iMuzzleDir!=-1.0f) { m_iMuzzleDir= -1.0f; bPause = TRUE; } } else if ( m_aBeginRotatorRotation(1)<(-m_fScanAngle/2.0f) ) { m_fRotSpeedRotator = FLOAT3D(m_fRotationSpeed, 0.0f, 0.0f); if (m_iMuzzleDir!=1.0f) { m_iMuzzleDir= 1.0f; bPause = TRUE; } } else if (TRUE) { m_fRotSpeedRotator = FLOAT3D(m_iMuzzleDir*m_fRotationSpeed, 0.0f, 0.0f); } if (bPause) { m_fRotSpeedRotator = FLOAT3D(0.0f, 0.0f, 0.0f); autowait(0.5f); } // check for players CPlayer *pTarget = AcquireTarget(); if (pTarget) { if ((pTarget->GetFlags()&ENF_ALIVE) && !(pTarget->GetFlags()&ENF_DELETED)) { // stop rotations m_fRotSpeedRotator = FLOAT3D(0.0f, 0.0f, 0.0f); m_penEnemy = pTarget; m_fDistanceToPlayer = DistanceTo(this, pTarget); // if it's time to fire, do so if (m_tmLastFireTime+m_fWaitAfterFire<_pTimer->CurrentTick()) { autocall FireCannon() EReturn; } } } } } Die(EDeath eDeath) { // not alive anymore SetFlags(GetFlags()&~ENF_ALIVE); // find the one who killed, or other best suitable player CEntityPointer penKiller = eDeath.eLastDamage.penInflictor; if (penKiller==NULL || !IsOfClass(penKiller, "Player")) { penKiller = m_penEnemy; } if (penKiller==NULL || !IsOfClass(penKiller, "Player")) { penKiller = FixupCausedToPlayer(this, penKiller, /*bWarning=*/FALSE); } // if killed by someone if (penKiller!=NULL) { // give him score EReceiveScore eScore; eScore.iPoints = (INDEX) m_iScore; penKiller->SendEvent(eScore); if( CountAsKill()) { penKiller->SendEvent(EKilledEnemy()); } // send computer message EComputerMessage eMsg; eMsg.fnmMessage = GetComputerMessageName(); if (eMsg.fnmMessage!="") { penKiller->SendEvent(eMsg); } } // send event to death target SendToTarget(m_penDeathTarget, m_eetDeathType, penKiller); // send event to spawner if any // NOTE: trigger's penCaused has been changed from penKiller to THIS; if (m_penSpawnerTarget) { SendToTarget(m_penSpawnerTarget, EET_TRIGGER, this); } // spawn debris CannonBlowUp(); Destroy(); return; }; RotateMuzzle() { FLOAT fDeltaP = m_fDesiredMuzzlePitch - m_aBeginMuzzleRotation(2); // if close enough to desired rotation, don't rotate if (Abs(fDeltaP)<5.0f) { return EReturn(); } m_fRotSpeedMuzzle = ANGLE3D(0.0f, MUZZLE_ROTATION_SPEED*Sgn(fDeltaP), 0.0f); //CPrintF("wait %f; beg:%f, end:%f at %f\n", Abs(fDeltaP/MUZZLE_ROTATION_SPEED), m_aBeginMuzzleRotation, m_aEndMuzzleRotation, _pTimer->CurrentTick()); autowait(Abs(fDeltaP/MUZZLE_ROTATION_SPEED)); m_fRotSpeedMuzzle = ANGLE3D(0.0f, 0.0f, 0.0f); return EReturn(); }; FireCannon() { UpdateFiringPos(); FLOAT3D vToTarget = m_penEnemy->GetPlacement().pl_PositionVector - GetPlacement().pl_PositionVector + m_vFiringPos; vToTarget.Normalize(); // get vector pointing in heading direction of the muzzle FLOAT3D vCannonFront = FLOAT3D(0.0f, 0.0f, -1.0f)*GetRotationMatrix(); FLOATmatrix3D m; MakeRotationMatrixFast(m, m_aBeginRotatorRotation); vCannonFront = vCannonFront*m; ANGLE aToPlayer = ACos(vToTarget%vCannonFront); FLOAT fPitch = aToPlayer + 5.0f; FLOAT3D vCannonUp = FLOAT3D(0.0, 1.0f, 0.0f)*GetRotationMatrix(); // if too far, do not fire if (m_fDistanceToPlayer>m_fFiringRangeFar) { return EReturn(); // if player under cannon, minimize pitch } else if (vToTarget%vCannonUp<0.0f) { fPitch = 5.0f; // if in far range } else if (m_fDistanceToPlayer>m_fFiringRangeClose) { if (aToPlayerGetPlacement().pl_PositionVector; m_fDesiredMuzzlePitch = fPitch; autocall RotateMuzzle() EReturn; FLOAT3D vShooting = GetPlacement().pl_PositionVector + m_vFiringPos; FLOAT3D vSpeedDest = FLOAT3D(0.0f, 0.0f, 0.0f); FLOAT fLaunchSpeed; FLOAT fRelativeHdg; // calculate parameters for predicted angular launch curve EntityInfo *peiTarget = (EntityInfo*) (m_penEnemy->GetEntityInfo()); CalculateAngularLaunchParams( vShooting, peiTarget->vTargetCenter[1]-6.0f/3.0f, m_vTarget, vSpeedDest, m_fDesiredMuzzlePitch , fLaunchSpeed, fRelativeHdg); // target enemy body FLOAT3D vShootTarget; GetEntityInfoPosition(m_penEnemy, peiTarget->vTargetCenter, vShootTarget); // launch CPlacement3D pl; PrepareFreeFlyingProjectile(pl, vShootTarget, m_vFiringPos, ANGLE3D( fRelativeHdg, m_fDesiredMuzzlePitch, 0)); CEntityPointer penBall = CreateEntity(pl, CLASS_CANNONBALL); ELaunchCannonBall eLaunch; eLaunch.penLauncher = this; eLaunch.fLaunchPower = fLaunchSpeed; eLaunch.cbtType = CBT_IRON; eLaunch.fSize = 1.0f; penBall->Initialize(eLaunch); m_tmLastFireTime = _pTimer->CurrentTick(); return EReturn(); }; Inactive() { m_fRotSpeedMuzzle = ANGLE3D(0.0f, 0.0f, 0.0f); m_fRotSpeedRotator = ANGLE3D(0.0f, 0.0f, 0.0f); wait() { on (EBegin) : { resume; } on (EActivate) : { jump MainLoop(); } on (EDeath eDeath) : { jump Die(eDeath); } otherwise (): { resume; } } } Main(EVoid) { // declare yourself as a model InitAsModel(); SetPhysicsFlags(EPF_MODEL_WALKING|EPF_HASLUNGS); SetCollisionFlags(ECF_MODEL); SetFlags(GetFlags()|ENF_ALIVE); en_fDensity = 2000.0f; // set your appearance SetModel(MODEL_TURRET); SetModelMainTexture(TEXTURE_TURRET); AddAttachment(TURRET_ATTACHMENT_ROTATORHEADING, MODEL_ROTATOR, TEXTURE_ROTATOR); CModelObject &amo0 = GetModelObject()->GetAttachmentModel(TURRET_ATTACHMENT_ROTATORHEADING)->amo_moModelObject; AddAttachmentToModel(this, amo0, ROTATINGMECHANISM_ATTACHMENT_CANNON, MODEL_CANNON, TEXTURE_CANNON, 0, 0, 0); // setup moving speed m_fWalkSpeed = 0.0f; m_aWalkRotateSpeed = 0.0f; m_fAttackRunSpeed = 0.0f; m_aAttackRotateSpeed = 0.0f; m_fCloseRunSpeed = 0.0f; m_aCloseRotateSpeed = 0.0f; // setup attack distances m_fStopDistance = 100.0f; //m_fBlowUpAmount = 65.0f; m_fBlowUpAmount = 100.0f; m_fBodyParts = 4; m_fDamageWounded = 0.0f; m_iScore = 1000; m_sptType = SPT_WOOD; // properties that modify EnemyBase properties if (m_fHealth<=0.0f) { m_fHealth=1.0f; } m_fCloseFireTime = m_fAttackFireTime = m_fWaitAfterFire; SetHealth(m_fHealth); m_fMaxHealth = m_fHealth; if (m_fFiringRangeFarStretchModel(FLOAT3D(m_fSize, m_fSize, m_fSize)); ModelChangeNotify(); StandingAnim(); // don't continue behavior in base class! - this enemy is derived // from CEnemyBase only because of its properties autowait(0.05f); SetDesiredTranslation(FLOAT3D(0.0f, 0.0f, 0.0f)); UpdateFiringPos(); if (!m_bActive) { jump Inactive(); } jump MainLoop(); }; };