/* 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. */ 504 %{ #include "EntitiesMP/StdH/StdH.h" #define TM_APPLY_DAMAGE_QUANTUM 0.25f #define TM_APPLY_WHOLE_DAMAGE 7.5f #define DAMAGE_AMMOUNT 30.0f #define MIN_DAMAGE_QUANTUM (DAMAGE_AMMOUNT/TM_APPLY_WHOLE_DAMAGE*TM_APPLY_DAMAGE_QUANTUM) #define MAX_DAMAGE_QUANTUM (MIN_DAMAGE_QUANTUM*10.0f) #define DEATH_BURN_TIME 4.0f #include "EntitiesMP/MovingBrush.h" %} uses "EntitiesMP/Light"; // input parameter for flame event EFlame { CEntityPointer penOwner, // entity which owns it CEntityPointer penAttach, // entity on which flame is attached (his parent) }; // event for stop burning event EStopFlaming { BOOL m_bNow, }; %{ void CFlame_OnPrecache(CDLLEntityClass *pdec, INDEX iUser) { pdec->PrecacheModel(MODEL_FLAME); pdec->PrecacheTexture(TEXTURE_FLAME); pdec->PrecacheSound(SOUND_FLAME); } %} class CFlame : CMovableModelEntity { name "Flame"; thumbnail ""; features "ImplementsOnPrecache", "CanBePredictable"; properties: 1 CEntityPointer m_penOwner, // entity which owns it 2 CEntityPointer m_penAttach, // entity on which flame is attached (his parent) 5 BOOL m_bLoop = FALSE, // internal for loops 8 FLOAT3D m_vHitPoint = FLOAT3D(0.0f, 0.0f, 0.0f), // where the flame hit the entity 10 CSoundObject m_soEffect, // sound channel 20 FLOAT m_tmStart = 0.0f, 21 FLOAT m_fDamageToApply = 0.0f, 22 FLOAT m_fDamageStep = 0.0f, 23 FLOAT m_fAppliedDamage=0.0f, 24 FLOAT m_tmFirstStart = 0.0f, // when the burning started 29 INDEX m_ctFlames=0, 30 FLOAT3D m_vPos01=FLOAT3D(0,0,0), 31 FLOAT3D m_vPos02=FLOAT3D(0,0,0), 32 FLOAT3D m_vPos03=FLOAT3D(0,0,0), 33 FLOAT3D m_vPos04=FLOAT3D(0,0,0), 34 FLOAT3D m_vPos05=FLOAT3D(0,0,0), 35 FLOAT3D m_vPos06=FLOAT3D(0,0,0), 36 FLOAT3D m_vPos07=FLOAT3D(0,0,0), 37 FLOAT3D m_vPos08=FLOAT3D(0,0,0), 38 FLOAT3D m_vPos09=FLOAT3D(0,0,0), 39 FLOAT3D m_vPos10=FLOAT3D(0,0,0), 40 FLOAT3D m_vPlaneNormal=FLOAT3D(0,0,0), 51 BOOL m_bBurningBrush=FALSE, 52 FLOAT m_tmDeathParticlesStart=1e6, { CLightSource m_lsLightSource; } components: 1 class CLASS_LIGHT "Classes\\Light.ecl", // ********* FLAME ********* 10 model MODEL_FLAME "ModelsMP\\Effects\\Flame\\Flame.mdl", 11 texture TEXTURE_FLAME "ModelsMP\\Effects\\Flame\\Flame.tex", 12 sound SOUND_FLAME "SoundsMP\\Fire\\Burning.wav", functions: // add to prediction any entities that this entity depends on void AddDependentsToPrediction(void) { m_penOwner->AddToPrediction(); } // postmoving void PostMoving(void) { CMovableModelEntity::PostMoving(); // if no air CContentType &ctDn = GetWorld()->wo_actContentTypes[en_iDnContent]; // stop existing if (!(ctDn.ct_ulFlags&CTF_BREATHABLE_LUNGS)) { EStopFlaming esf; esf.m_bNow = TRUE; SendEvent(esf); } // never remove from list of movers en_ulFlags &= ~ENF_INRENDERING; // not moving in fact, only moving with its parent en_plLastPlacement = en_plPlacement; }; /* Read from stream. */ void Read_t( CTStream *istr) // throw char * { CMovableModelEntity::Read_t(istr); SetupLightSource(); } BOOL IsPointInsidePolygon(const FLOAT3D &vPos, CBrushPolygon *pbpo) { FLOATplane3D &plPlane=pbpo->bpo_pbplPlane->bpl_plAbsolute; // find major axes of the polygon plane INDEX iMajorAxis1, iMajorAxis2; GetMajorAxesForPlane(plPlane, iMajorAxis1, iMajorAxis2); // create an intersector CIntersector isIntersector(vPos(iMajorAxis1), vPos(iMajorAxis2)); // for all edges in the polygon FOREACHINSTATICARRAY(pbpo->bpo_abpePolygonEdges, CBrushPolygonEdge, itbpePolygonEdge) { // get edge vertices (edge direction is irrelevant here!) const FLOAT3D &vVertex0 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex0->bvx_vAbsolute; const FLOAT3D &vVertex1 = itbpePolygonEdge->bpe_pbedEdge->bed_pbvxVertex1->bvx_vAbsolute; // pass the edge to the intersector isIntersector.AddEdge( vVertex0(iMajorAxis1), vVertex0(iMajorAxis2), vVertex1(iMajorAxis1), vVertex1(iMajorAxis2)); } // return result of polygon intersection return isIntersector.IsIntersecting(); } /* Get static light source information. */ CLightSource *GetLightSource(void) { if (!IsPredictor()) { return &m_lsLightSource; } else { return NULL; } } // render particles void RenderParticles(void) { FLOAT fTimeFactor=CalculateRatio(_pTimer->CurrentTick(), m_tmFirstStart, m_tmStart+TM_APPLY_WHOLE_DAMAGE, 0.05f, 0.2f); FLOAT fDeathFactor=1.0f; if( _pTimer->CurrentTick()>m_tmDeathParticlesStart) { fDeathFactor=1.0f-Clamp((_pTimer->CurrentTick()-m_tmDeathParticlesStart)/DEATH_BURN_TIME, 0.0f, 1.0f); } CEntity *penParent= GetParent(); FLOAT fPower=ClampUp(m_fDamageStep-MIN_DAMAGE_QUANTUM, MAX_DAMAGE_QUANTUM)/MAX_DAMAGE_QUANTUM; if( penParent!= NULL) { if( (penParent->en_RenderType==CEntity::RT_MODEL || penParent->en_RenderType==CEntity::RT_EDITORMODEL || penParent->en_RenderType==CEntity::RT_SKAMODEL || penParent->en_RenderType==CEntity::RT_SKAEDITORMODEL) && (Particle_GetViewer()!=penParent) ) { Particles_Burning(penParent, fPower, fTimeFactor*fDeathFactor); } else { Particles_BrushBurning(this, &m_vPos01, m_ctFlames, m_vPlaneNormal, fPower, fTimeFactor*fDeathFactor); } } } // Setup light source void SetupLightSource(void) { // setup light source CLightSource lsNew; lsNew.ls_ulFlags = LSF_NONPERSISTENT|LSF_DYNAMIC; if(m_bBurningBrush) { UBYTE ubRndH = UBYTE( 25+(FLOAT(rand())/RAND_MAX-0.5f)*28); UBYTE ubRndS = 166; UBYTE ubRndV = 48; lsNew.ls_colColor = HSVToColor(ubRndH, ubRndS, ubRndV); //lsNew.ls_colColor = 0x3F3F1600; lsNew.ls_rFallOff = 4.0f; lsNew.ls_rHotSpot = 0.2f; } else { lsNew.ls_colColor = 0x8F8F5000; lsNew.ls_rFallOff = 6.0f; lsNew.ls_rHotSpot = 0.50f; } lsNew.ls_plftLensFlare = NULL; lsNew.ls_ubPolygonalMask = 0; lsNew.ls_paoLightAnimation = NULL; m_lsLightSource.ls_penEntity = this; m_lsLightSource.SetLightSource(lsNew); } /************************************************************ * P R O C E D U R E S * ************************************************************/ procedures: // --->>> MAIN Main(EFlame ef) { // attach to parent (another entity) ASSERT(ef.penOwner!=NULL); ASSERT(ef.penAttach!=NULL); m_penOwner = ef.penOwner; m_penAttach = ef.penAttach; m_tmStart = _pTimer->CurrentTick(); m_tmFirstStart=m_tmStart; SetParent(ef.penAttach); // initialization InitAsEditorModel(); SetPhysicsFlags(EPF_MODEL_FLYING); SetCollisionFlags(ECF_FLAME); SetFlags(GetFlags() | ENF_SEETHROUGH); SetModel(MODEL_FLAME); SetModelMainTexture(TEXTURE_FLAME); ModelChangeNotify(); // play the burning sound m_soEffect.Set3DParameters(10.0f, 1.0f, 1.0f, 1.0f); PlaySound(m_soEffect, SOUND_FLAME, SOF_3D|SOF_LOOP); // must always be in movers, to find sector content type AddToMovers(); m_bBurningBrush=FALSE; BOOL bAllowFlame=TRUE; if( !(ef.penAttach->en_RenderType==CEntity::RT_MODEL || ef.penAttach->en_RenderType==CEntity::RT_EDITORMODEL || ef.penAttach->en_RenderType==CEntity::RT_SKAMODEL || ef.penAttach->en_RenderType==CEntity::RT_SKAEDITORMODEL )) { m_bBurningBrush=TRUE; FLOAT3D vPos=GetPlacement().pl_PositionVector; FLOATplane3D plPlane; FLOAT fDistanceToEdge; FindSectorsAroundEntity(); CBrushPolygon *pbpo=NULL; pbpo=GetNearestPolygon(vPos, plPlane, fDistanceToEdge); FLOAT3D vBrushPos = ef.penAttach->GetPlacement().pl_PositionVector; FLOATmatrix3D mBrushRotInv = !ef.penAttach->GetRotationMatrix(); if( pbpo!=NULL && pbpo->bpo_pbscSector->bsc_pbmBrushMip->bm_pbrBrush->br_penEntity==ef.penAttach) { plPlane = pbpo->bpo_pbplPlane->bpl_plAbsolute; m_vPlaneNormal=(FLOAT3D &)plPlane; m_vPlaneNormal.Normalize(); // ------ Calculate plane-paralel normal vectors FLOAT3D vU, vV; //CPrintF("plPlane %g\n", plPlane(2)); if(plPlane(2)<-0.1f) { bAllowFlame=FALSE; } // if the plane is mostly horizontal if (Abs(plPlane(2))>0.5f) { // use cross product of +x axis and plane normal as +s axis vU = FLOAT3D(1.0f, 0.0f, 0.0f)*m_vPlaneNormal; // if the plane is mostly vertical } else { // use cross product of +y axis and plane normal as +s axis vU = FLOAT3D(0.0f, 1.0f, 0.0f)*m_vPlaneNormal; } // make +s axis normalized vU.Normalize(); // use cross product of plane normal and +s axis as +t axis vV = vU*m_vPlaneNormal; vV.Normalize(); // counter of valid flames m_ctFlames=0; for(INDEX iTest=0; iTest<20; iTest++) { FLOAT fA=FRnd()*360.0f; FLOAT fR=FRnd()*2.0f; FLOAT3D vRndV=vV*fR*SinFast(fA); FLOAT3D vRndU=vU*fR*CosFast(fA); FLOAT3D vRndPos=vPos; if( iTest!=0) { vRndPos+=vRndV+vRndU; } // project point to a plane FLOAT3D vProjectedRndPos=plPlane.ProjectPoint(vRndPos); if( IsPointInsidePolygon(vProjectedRndPos, pbpo)) { (&m_vPos01)[ m_ctFlames]=(vProjectedRndPos-vBrushPos)*mBrushRotInv; m_ctFlames++; if( m_ctFlames==6) { break; }; } } } else { bAllowFlame=FALSE; } } // setup light source if( bAllowFlame) { SetupLightSource(); } m_bLoop = bAllowFlame; while(m_bLoop) { wait(TM_APPLY_DAMAGE_QUANTUM) { // damage to parent on (EBegin) : { // if parent does not exist anymore if (m_penAttach==NULL || (m_penAttach->GetFlags()&ENF_DELETED)) { // stop existing m_bLoop = FALSE; stop; } // inflict damage to parent const FLOAT fDamageMul = GetSeriousDamageMultiplier(m_penOwner); FLOAT fDamageToApply=fDamageMul*(m_fDamageToApply/TM_APPLY_WHOLE_DAMAGE*TM_APPLY_DAMAGE_QUANTUM)*m_fDamageStep; m_penAttach->InflictDirectDamage( m_penAttach, m_penOwner, DMT_BURNING, fDamageToApply, GetPlacement().pl_PositionVector, -en_vGravityDir); m_fAppliedDamage+=fDamageToApply; resume; } on (EFlame ef) : { m_penOwner = ef.penOwner; FLOAT fTimeLeft=m_tmStart+TM_APPLY_WHOLE_DAMAGE-_pTimer->CurrentTick(); FLOAT fDamageLeft=(fTimeLeft/TM_APPLY_DAMAGE_QUANTUM)*m_fDamageStep; m_fDamageToApply=ClampUp(fDamageLeft+DAMAGE_AMMOUNT, 80.0f); m_tmStart=_pTimer->CurrentTick(); m_fDamageStep=m_fDamageToApply/(TM_APPLY_WHOLE_DAMAGE/TM_APPLY_DAMAGE_QUANTUM); resume; }; on (EStopFlaming esf) : { if( !esf.m_bNow) { m_tmDeathParticlesStart=_pTimer->CurrentTick(); resume; } else { m_bLoop = FALSE; stop; } }; on (EBrushDestroyed) : { m_bLoop = FALSE; stop; }; on (ETimer) : { stop; } } if(_pTimer->CurrentTick()>m_tmStart+TM_APPLY_WHOLE_DAMAGE) { m_bLoop = FALSE; } } // cease to exist Destroy(); return; } };