/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */ 502 %{ #include "EntitiesMP/StdH/StdH.h" %} uses "EntitiesMP/BasicEffects"; uses "Engine/Classes/MovableEntity"; // input parameters for bullet event EBulletInit { CEntityPointer penOwner, // who launched it FLOAT fDamage, // damage }; %{ void CBullet_OnPrecache(CDLLEntityClass *pdec, INDEX iUser) { pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BULLETSTAINSTONE); pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BULLETSTAINSAND); pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BULLETSTAINREDSAND); pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BULLETSTAINWATER); pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BULLETSTAINSTONENOSOUND); pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BULLETSTAINSANDNOSOUND); pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BULLETSTAINREDSANDNOSOUND); pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BULLETSTAINWATERNOSOUND); pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BLOODSPILL); pdec->PrecacheClass(CLASS_BASIC_EFFECT, BET_BULLETTRAIL); } %} class export CBullet : CEntity { name "Bullet"; thumbnail ""; features "ImplementsOnPrecache"; properties: 1 CEntityPointer m_penOwner, // entity which owns it 2 FLOAT m_fDamage = 0.0f, // damage 3 FLOAT3D m_vTarget = FLOAT3D(0,0,0), // bullet target point in space 4 FLOAT3D m_vTargetCopy = FLOAT3D(0,0,0), // copy of bullet target point in space for jitter 6 FLOAT3D m_vHitPoint = FLOAT3D(0,0,0), // hit point 8 INDEX m_iBullet = 0, // bullet for lerped launch 9 enum DamageType m_EdtDamage = DMT_BULLET, // damage type 10 FLOAT m_fBulletSize = 0.0f, // bullet can have radius, for hitting models only components: 1 class CLASS_BASIC_EFFECT "Classes\\BasicEffect.ecl" functions: /************************************************************ * BULLET LAUNCH * ************************************************************/ // set bullet damage void SetDamage(FLOAT fDamage) { m_fDamage = fDamage; }; // calc jitter target void CalcTarget(FLOAT fRange) { // destination in bullet direction AnglesToDirectionVector(GetPlacement().pl_OrientationAngle, m_vTarget); m_vTarget *= fRange; m_vTarget += GetPlacement().pl_PositionVector; m_vTargetCopy = m_vTarget; }; void CalcTarget(CEntity *pen, FLOAT fRange) { FLOAT3D vTarget; // target body EntityInfo *peiTarget = (EntityInfo*) (pen->GetEntityInfo()); GetEntityInfoPosition(pen, peiTarget->vTargetCenter, vTarget); // calculate m_vTarget = (vTarget - GetPlacement().pl_PositionVector).Normalize(); m_vTarget *= fRange; m_vTarget += GetPlacement().pl_PositionVector; m_vTargetCopy = m_vTarget; }; // calc jitter target - !!! must call CalcTarget first !!! void CalcJitterTarget(FLOAT fR) { FLOAT3D vJitter; /* My Sphere FLOAT fXZ = FRnd()*360.0f; FLOAT fXY = FRnd()*360.0f; // sphere fR *= FRnd(); vJitter(1) = CosFast(fXZ)*CosFast(fXY)*fR; vJitter(2) = CosFast(fXZ)*SinFast(fXY)*fR; vJitter(3) = SinFast(fXZ)*fR;*/ // comp graphics algorithms sphere FLOAT fZ = FRnd()*2.0f - 1.0f; FLOAT fA = FRnd()*360.0f; FLOAT fT = Sqrt(1-(fZ*fZ)); vJitter(1) = fT * CosFast(fA); vJitter(2) = fT * SinFast(fA); vJitter(3) = fZ; vJitter = vJitter*fR*FRnd(); // target m_vTarget = m_vTargetCopy + vJitter; }; // calc jitter target asymetric - !!! must call CalcTarget first !!! void CalcJitterTargetFixed(FLOAT fX, FLOAT fY, FLOAT fJitter) { FLOAT fRndX = FRnd()*2.0f - 1.0f; FLOAT fRndY = FRnd()*2.0f - 1.0f; FLOAT3D vX, vY; const FLOATmatrix3D &m=GetRotationMatrix(); vX(1) = m(1,1); vX(2) = m(2,1); vX(3) = m(3,1); vY(1) = m(1,2); vY(2) = m(2,2); vY(3) = m(3,2); // target m_vTarget = m_vTargetCopy + (vX*(fX+fRndX*fJitter)) + (vY*(fY+fRndY*fJitter)); }; // launch one bullet void LaunchBullet(BOOL bSound, BOOL bTrail, BOOL bHitFX) { // cast a ray to find bullet target CCastRay crRay( m_penOwner, GetPlacement().pl_PositionVector, m_vTarget); crRay.cr_bHitPortals = TRUE; crRay.cr_bHitTranslucentPortals = TRUE; crRay.cr_ttHitModels = CCastRay::TT_COLLISIONBOX; crRay.cr_bPhysical = FALSE; crRay.cr_fTestR = m_fBulletSize; FLOAT3D vHitDirection; AnglesToDirectionVector(GetPlacement().pl_OrientationAngle, vHitDirection); INDEX ctCasts = 0; while( ctCasts<10) { if(ctCasts == 0) { // perform first cast GetWorld()->CastRay(crRay); } else { // next casts GetWorld()->ContinueCast(crRay); } ctCasts++; // stop casting if nothing hit if (crRay.cr_penHit==NULL) { break; } // apply damage const FLOAT fDamageMul = GetSeriousDamageMultiplier(m_penOwner); InflictDirectDamage(crRay.cr_penHit, m_penOwner, m_EdtDamage, m_fDamage*fDamageMul, crRay.cr_vHit, vHitDirection); m_vHitPoint = crRay.cr_vHit; // if brush hitted if (crRay.cr_penHit->GetRenderType()==RT_BRUSH && crRay.cr_pbpoBrushPolygon!=NULL) { CBrushPolygon *pbpo = crRay.cr_pbpoBrushPolygon; FLOAT3D vHitNormal = FLOAT3D(pbpo->bpo_pbplPlane->bpl_plAbsolute); // obtain surface type INDEX iSurfaceType = pbpo->bpo_bppProperties.bpp_ubSurfaceType; BulletHitType bhtType = BHT_BRUSH_STONE; // get content type INDEX iContent = pbpo->bpo_pbscSector->GetContentType(); CContentType &ct = GetWorld()->wo_actContentTypes[iContent]; bhtType=(BulletHitType) GetBulletHitTypeForSurface(iSurfaceType); // if this is under water polygon if( ct.ct_ulFlags&CTF_BREATHABLE_GILLS) { // if we hit water surface if( iSurfaceType==SURFACE_WATER) { vHitNormal = -vHitNormal; bhtType=BHT_BRUSH_WATER; } // if we hit stone under water else { bhtType=BHT_BRUSH_UNDER_WATER; } } // spawn hit effect BOOL bPassable = pbpo->bpo_ulFlags & (BPOF_PASSABLE|BPOF_SHOOTTHRU); if (!bPassable || iSurfaceType==SURFACE_WATER) { SpawnHitTypeEffect(this, bhtType, bSound, vHitNormal, crRay.cr_vHit, vHitDirection, FLOAT3D(0.0f, 0.0f, 0.0f)); } if(!bPassable) { break; } // if not brush } else { // if flesh entity if (crRay.cr_penHit->GetEntityInfo()!=NULL) { if( ((EntityInfo*)crRay.cr_penHit->GetEntityInfo())->Eeibt == EIBT_FLESH) { CEntity *penOfFlesh = crRay.cr_penHit; FLOAT3D vHitNormal = (GetPlacement().pl_PositionVector-m_vTarget).Normalize(); FLOAT3D vOldHitPos = crRay.cr_vHit; FLOAT3D vDistance; // look behind the entity (for back-stains) GetWorld()->ContinueCast(crRay); if( crRay.cr_penHit!=NULL && crRay.cr_pbpoBrushPolygon!=NULL && crRay.cr_penHit->GetRenderType()==RT_BRUSH) { vDistance = crRay.cr_vHit-vOldHitPos; vHitNormal = FLOAT3D(crRay.cr_pbpoBrushPolygon->bpo_pbplPlane->bpl_plAbsolute); } else { vDistance = FLOAT3D(0.0f, 0.0f, 0.0f); vHitNormal = FLOAT3D(0,0,0); } if(IsOfClass(penOfFlesh, "Gizmo") || IsOfClass(penOfFlesh, "Beast")) { // spawn green blood hit spill effect SpawnHitTypeEffect(this, BHT_ACID, bSound, vHitNormal, crRay.cr_vHit, vHitDirection, vDistance); } else { // spawn red blood hit spill effect SpawnHitTypeEffect(this, BHT_FLESH, bSound, vHitNormal, crRay.cr_vHit, vHitDirection, vDistance); } break; } } // stop casting ray if not brush break; } } if( bTrail) { SpawnTrail(); } }; // destroy yourself void DestroyBullet(void) { Destroy(); }; /************************************************************ * EFFECTS * ************************************************************/ // spawn trail of this bullet void SpawnTrail(void) { // get bullet path positions const FLOAT3D &v0 = GetPlacement().pl_PositionVector; const FLOAT3D &v1 = m_vHitPoint; // calculate distance FLOAT3D vD = v1-v0; FLOAT fD = vD.Length(); // if too short if (fD<1.0f) { // no trail return; } // length must be such that it doesn't get out of path FLOAT fLen = Min(20.0f, fD); // position is random, but it must not make trail go out of path FLOAT3D vPos; if (fLenInitialize(ese); } procedures: Main(EBulletInit eInit) { // remember the initial parameters ASSERT(eInit.penOwner!=NULL); m_penOwner = eInit.penOwner; m_fDamage = eInit.fDamage; InitAsVoid(); SetPhysicsFlags(EPF_MODEL_IMMATERIAL); SetCollisionFlags(ECF_IMMATERIAL); // for lerped launch m_iBullet = 0; return; }; };