// precise crosshair (now fully independent from weapons) const MAX_CROSSHAIRS = 25; Class SWWMCrosshairTracer : LineTracer { override ETraceStatus TraceCallback() { if ( Results.HitType == TRACE_HitActor ) { if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop; return TRACE_Skip; } else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) ) { if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) ) return TRACE_Stop; return TRACE_Skip; } return TRACE_Stop; } } extend Class SWWMHandler { transient ui SWWMCrosshairTracer ctr; // very simple crosshair tracer transient ui int numcrosshairs; // how many crosshairs the current weapon has transient ui Vector3 tpos[MAX_CROSSHAIRS]; // current trace positions in world space transient ui Color tcol[MAX_CROSSHAIRS]; // current crosshair colors transient ui SWWMProjectionData tprojdata; // cached Gutamatics projection data transient ui Vector3 lagtndc[MAX_CROSSHAIRS]; // "lagged" NDC for crosshairs transient ui bool tactive[MAX_CROSSHAIRS]; // denotes that the crosshair is "active" for drawing transient ui double prevframe; // previous frame timestamp private ui void TraceCrosshairs( RenderEvent e ) { // trace precise crosshair(s) Vector3 traceofs[MAX_CROSSHAIRS]; let sw = SWWMWeapon(players[consoleplayer].ReadyWeapon); if ( !sw ) { numcrosshairs = 0; for ( int i=0; i= 2 ) { int hp = Clamp(mo.Health,0,200); double sat = (hp<150)?1.:(1.-(hp-150)/100.); Vector3 rgb = SWWMUtility.HSVtoRGB((hp/300.,sat,1.)); col = Color(int(rgb.x*255),int(rgb.y*255),int(rgb.z*255)); } else if ( chp == 1 ) { double hp = Clamp(mo.Health,0,100)/100.; if ( hp <= 0 ) col = Color(255,0,0); else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0); else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0); else col = Color(0,255,0); } else if ( (ctr.Results.HitType == TRACE_HitActor) && ctr.Results.HitActor.bSHOOTABLE ) { // show target health, rather than our own double hp = ctr.Results.HitActor.Health/double(ctr.Results.HitActor.GetSpawnHealth()); if ( hp <= 0 ) col = Color(255,0,0); else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0); else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0); else col = Color(0,255,0); } else col = crosshaircolor; tpos[i] = e.ViewPos+ofs+x*ctr.Results.Distance; tcol[i] = col; } // copy over used slots to unused slots, so transition between weapons is smoother int j = 0; for ( int i=numcrosshairs; i 0. ) sz = Screen.GetHeight()*cs/200.; bool bGrowMe = crosshairgrow; if ( bGrowMe ) sz *= StatusBar.CrosshairSize*StatusBar.CrosshairSize; sz = max(1.,round(sz)); // ensure integer scaling of crosshair texture Vector2 oddfix = (0,0); // offset for odd-sized textures when scaling by an even number if ( int(ts.x)%2 ) oddfix.x = -floor(sz/2.)+1.; if ( int(ts.y)%2 ) oddfix.y = -floor(sz/2.)+1.; SWWMUtility.PrepareProjData(tprojdata,e.ViewPos,e.ViewAngle,e.ViewPitch,e.ViewRoll,players[consoleplayer].fov); Vector2 actpos[MAX_CROSSHAIRS]; for ( int i=0; i= 1. ) continue; Vector2 vpos = SWWMUtility.NDCToViewport(tprojdata,ndc); if ( !prevframe ) lagtndc[i] = ndc; if ( lagtndc[i].z >= 1. ) continue; Vector2 oldvpos = SWWMUtility.NDCToViewport(tprojdata,lagtndc[i]); lagtndc[i] = SWWMUtility.LerpVector3(lagtndc[i],ndc,theta); if ( lagtndc[i].z >= 1. ) continue; Vector2 lagvpos = SWWMUtility.NDCToViewport(tprojdata,lagtndc[i]); if ( !tactive[i] ) continue; // draw int streak = int(max(abs(oldvpos.x-lagvpos.x),abs(oldvpos.y-lagvpos.y))); double alph = 1.; if ( i < numcrosshairs ) actpos[i] = lagvpos; else { // unused crosshairs must "linger" until they merge with the ones that are drawn int j = (i-numcrosshairs)%numcrosshairs; double dist = (lagvpos-actpos[j]).length(); if ( (streak <= 0) && (dist < 1.) ) tactive[i] = false; alph = clamp(dist/max(2,streak+2),0.,1.); // this should make the merge less jarring } for ( int i=0; i