502 %{ #include "Entities/StdH/StdH.h" #define BLOOD_SPILL_RED RGBAToColor(250,20,20,255) #define BLOOD_SPILL_GREEN RGBAToColor(0,250,0,255) %} uses "Entities/BasicEffects"; uses "Engine/Classes/MovableEntity"; // input parameters for bullet event EBulletInit { CEntityPointer penOwner, // who launched it FLOAT fDamage, // damage }; // hit enum enum BulletHitType { 0 BHT_NONE "", // none 1 BHT_FLESH "", // flesh 2 BHT_BRUSH_STONE "", // brush stone 3 BHT_BRUSH_SAND "", // brush sand 4 BHT_BRUSH_WATER "", // brush water 5 BHT_BRUSH_UNDER_WATER "", // brush under water 6 BHT_ACID "", // acid 7 BHT_BRUSH_RED_SAND "", // brush red sand }; %{ 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 InflictDirectDamage(crRay.cr_penHit, m_penOwner, m_EdtDamage, m_fDamage, 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]; // 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; } } else { if( iSurfaceType==SURFACE_SAND) {bhtType=BHT_BRUSH_SAND;} if( iSurfaceType==SURFACE_RED_SAND) {bhtType=BHT_BRUSH_RED_SAND;} if( iSurfaceType==SURFACE_WATER) {bhtType=BHT_BRUSH_WATER;} } // spawn hit effect BOOL bPassable = pbpo->bpo_ulFlags & (BPOF_PASSABLE|BPOF_SHOOTTHRU); if (!bPassable || iSurfaceType==SURFACE_WATER) { SpawnHitTypeEffect(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(BHT_ACID, bSound, vHitNormal, crRay.cr_vHit, vHitDirection, vDistance); } else { // spawn red blood hit spill effect SpawnHitTypeEffect(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 effect from hit type void SpawnHitTypeEffect(enum BulletHitType bhtType, BOOL bSound, FLOAT3D vHitNormal, FLOAT3D vHitPoint, FLOAT3D vDirection, FLOAT3D vDistance) { switch (bhtType) { case BHT_BRUSH_STONE: case BHT_BRUSH_SAND: case BHT_BRUSH_RED_SAND: case BHT_BRUSH_WATER: case BHT_BRUSH_UNDER_WATER: { // bullet stain ESpawnEffect ese; if( bSound) { if( bhtType == BHT_BRUSH_STONE) {ese.betType = BET_BULLETSTAINSTONE;}; if( bhtType == BHT_BRUSH_SAND) {ese.betType = BET_BULLETSTAINSAND;}; if( bhtType == BHT_BRUSH_RED_SAND) {ese.betType = BET_BULLETSTAINREDSAND;}; if( bhtType == BHT_BRUSH_WATER) {ese.betType = BET_BULLETSTAINWATER;}; if( bhtType == BHT_BRUSH_UNDER_WATER) {ese.betType = BET_BULLETSTAINUNDERWATER;}; } else { if( bhtType == BHT_BRUSH_STONE) {ese.betType = BET_BULLETSTAINSTONENOSOUND;}; if( bhtType == BHT_BRUSH_SAND) {ese.betType = BET_BULLETSTAINSANDNOSOUND;}; if( bhtType == BHT_BRUSH_RED_SAND) {ese.betType = BET_BULLETSTAINREDSANDNOSOUND;}; if( bhtType == BHT_BRUSH_WATER) {ese.betType = BET_BULLETSTAINWATERNOSOUND;}; if( bhtType == BHT_BRUSH_UNDER_WATER) {ese.betType = BET_BULLETSTAINUNDERWATERNOSOUND;}; } ese.vNormal = vHitNormal; ese.colMuliplier = C_WHITE|CT_OPAQUE; FLOAT3D vDirection = (vHitPoint-GetPlacement().pl_PositionVector).Normalize(); // reflect direction arround normal FLOAT fNx = vHitNormal(1); FLOAT fNy = vHitNormal(2); FLOAT fNz = vHitNormal(3); FLOAT fNV = fNx*vDirection(1) + fNy*vDirection(2) + fNz*vDirection(3); FLOAT fRVx = vDirection(1) - 2*fNx*fNV; FLOAT fRVy = vDirection(2) - 2*fNy*fNV; FLOAT fRVz = vDirection(3) - 2*fNz*fNV; ese.vStretch = FLOAT3D( fRVx, fRVy, fRVz); SpawnEffect(vHitPoint, ese); break; } case BHT_FLESH: case BHT_ACID: { // spawn bullet entry wound ESpawnEffect ese; ese.colMuliplier = C_WHITE|CT_OPAQUE; // if there is exit wound blood spill place FLOAT fDistance = vDistance.Length(); if( fDistance>0.01f && !(IRnd()%2) ) { // spawn bullet exit wound blood patch ese.betType = BET_BLOODSPILL; if( bhtType == BHT_ACID) { ese.colMuliplier = BLOOD_SPILL_GREEN; } else { ese.colMuliplier = BLOOD_SPILL_RED; } ese.vNormal = vHitNormal; if (fDistance<25.0f) { GetNormalComponent( vDistance/fDistance, vHitNormal, ese.vDirection); FLOAT fLength = ese.vDirection.Length(); fLength = Clamp( fLength*3, 1.0f, 3.0f); fDistance = Clamp( log10(fDistance), 0.5f, 2.0f); ese.vStretch = FLOAT3D( fDistance, fLength*fDistance, 1.0f); SpawnEffect(vHitPoint, ese); } } break; } } }; // 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(eSpawnEffect); }; 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; }; };