// playerthink / deaththink extend Class Demolitionist { void CheckDefaceTexture() { if ( player.usedown ) return; FLineTraceData d; LineTrace(angle,DEFMELEERANGE*2,pitch,TRF_THRUACTORS,player.viewheight,data:d); if ( d.HitType == TRACE_HitNone ) return; let [remove, replacewith] = SWWMUtility.DefaceTexture(d.HitTexture); if ( !remove ) return; if ( (d.HitType != TRACE_HitWall) || !d.HitLine.special || !(d.HitLine.activation&SPAC_Use) ) player.usedown = true; A_StartSound("bestsound",CHAN_ITEMEXTRA,CHANF_OVERLAP); lastbump *= .97; int scr = (TexMan.GetName(d.HitTexture).Left(6)~=="ZZWOLF")?200:20; if ( scr == 20 ) SWWMUtility.AchievementProgressInc("doodle",1,player); SWWMCredits.Give(player,scr); if ( player == players[consoleplayer] ) SWWMScoreObj.Spawn(scr,d.HitLocation); if ( d.HitType == TRACE_HitWall ) { if ( d.Hit3DFloor ) { // TODO connected textures for upper/lower if ( d.Hit3DFloor.flags&F3DFloor.FF_UPPERTEXTURE ) d.HitLine.sidedef[d.LineSide].SetTexture(0,replacewith); else if ( d.Hit3DFloor.flags&F3DFloor.FF_LOWERTEXTURE ) d.HitLine.sidedef[d.LineSide].SetTexture(2,replacewith); else d.Hit3DFloor.master.sidedef[0].SetTexture(1,replacewith); } else { // find connected sidedefs with the same texture Array con; con.Clear(); con.Push(d.HitLine); Sector s = d.LineSide?d.HitLine.backsector:d.HitLine.frontsector; int found = 0; do { found = 0; foreach ( l:s.Lines ) { if ( !l.sidedef[d.LineSide] || (l.sidedef[d.LineSide].GetTexture(d.LinePart) != d.HitTexture) ) continue; if ( con.Find(l) < con.Size() ) continue; bool notmatched = true; foreach ( c:con ) { if ( (l.v1 != c.v1) && (l.v2 != c.v2) && (l.v1 != c.v2) && (l.v2 != c.v1) ) continue; notmatched = false; break; } if ( notmatched ) continue; con.Push(l); found++; } } while ( found > 0 ); foreach ( c:con ) c.sidedef[d.LineSide].SetTexture(d.LinePart,replacewith); } } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) { if ( d.Hit3DFloor.flags&F3DFloor.FF_INVERTSECTOR ) d.Hit3DFloor.model.SetTexture(1,replacewith); else d.Hit3DFloor.model.SetTexture(0,replacewith); } else { // find connected sectors with the same ceiling texture (THIS IS VERY UGLY CODE) Array con; con.Clear(); con.Push(d.HitSector); int found; do { found = 0; foreach ( s:con ) { foreach ( l:s.Lines ) { // only check two-sided if ( !l.sidedef[1] ) continue; // don't check if there's a height difference if ( (l.frontsector.ceilingplane.ZAtPoint(l.v1.p) != l.backsector.ceilingplane.ZAtPoint(l.v1.p)) || (l.frontsector.ceilingplane.ZAtPoint(l.v2.p) != l.backsector.ceilingplane.ZAtPoint(l.v2.p)) ) continue; if ( (l.frontsector.GetTexture(1) == d.HitTexture) && (con.Find(l.frontsector) >= con.Size()) ) { found++; con.Push(l.frontsector); } if ( (l.backsector.GetTexture(1) == d.HitTexture) && (con.Find(l.backsector) >= con.Size()) ) { found++; con.Push(l.backsector); } } } } while ( found > 0 ); foreach ( s:con ) s.SetTexture(1,replacewith); } } else if ( d.HitType == TRACE_HitFloor ) { if ( d.Hit3DFloor ) { if ( d.Hit3DFloor.flags&F3DFloor.FF_INVERTSECTOR ) d.Hit3DFloor.model.SetTexture(0,replacewith); else d.Hit3DFloor.model.SetTexture(1,replacewith); } else { // find connected sectors with the same floor texture (THIS IS VERY UGLY CODE) Array con; con.Clear(); con.Push(d.HitSector); int found; do { found = 0; foreach ( s:con ) { foreach ( l:s.Lines ) { // only check two-sided if ( !l.sidedef[1] ) continue; // don't check if there's a height difference if ( (l.frontsector.floorplane.ZAtPoint(l.v1.p) != l.backsector.floorplane.ZAtPoint(l.v1.p)) || (l.frontsector.floorplane.ZAtPoint(l.v2.p) != l.backsector.floorplane.ZAtPoint(l.v2.p)) ) continue; if ( (l.frontsector.GetTexture(0) == d.HitTexture) && (con.Find(l.frontsector) >= con.Size()) ) { found++; con.Push(l.frontsector); } if ( (l.backsector.GetTexture(0) == d.HitTexture) && (con.Find(l.backsector) >= con.Size()) ) { found++; con.Push(l.backsector); } } } } while ( found > 0 ); foreach ( s:con ) s.SetTexture(0,replacewith); } } } void CheckItemUsePickup() { if ( player.usedown ) return; if ( !itrace ) itrace = new('SWWMItemTracer'); Vector3 dir; let [x, y, z] = SWWMUtility.GetPlayerAxes(self); Vector3 origin = SWWMUtility.GetPlayerEye(self); Sector os = level.PointInSector(origin.xy); int rings = 1; Array ignoreme; ignoreme.Clear(); for ( double i=0; i<.2; i+=.02 ) { for ( int j=0; j<360; j+=(360/rings) ) { dir = SWWMUtility.ConeSpread(x,y,z,j,i); itrace.Trace(origin,os,dir,UseRange,0,ignore:self); if ( itrace.Results.HitType != TRACE_HitActor ) continue; if ( ignoreme.Find(itrace.Results.HitActor) < ignoreme.Size() ) continue; player.usedown = true; // we found an item, ignore further uses if ( itrace.Results.HitActor.Used(self) ) return; ignoreme.Push(itrace.Results.HitActor); } rings += 2; } } bool CheckMirrorUse() { if ( !mtrace ) mtrace = new('SWWMMirrorTracer'); Vector3 dir = SWWMUtility.GetPlayerAimDir(self); Vector3 origin = SWWMUtility.GetPlayerEye(self); mtrace.Trace(origin,level.PointInSector(origin.xy),dir,UseRange,0,ignoreallactors:true); if ( mtrace.Results.HitType != TRACE_HitWall ) return false; // there's a mirror here if ( (gametic > mirrorcooldown) && (swwm_mutevoice < 2) ) mirrorcooldown = SWWMHandler.AddOneliner("mirror",2,10)+70; // mute fail use A_StopSound(CHAN_VOICE); return true; } override void PlayerThink() { if ( !player || (player.playerstate != PST_DEAD) ) deadtimer = 0; oldangles = (angle,pitch,roll); if ( player && (player.mo == self) && (player.playerstate != PST_DEAD) && (player.cmd.buttons&BT_USE) ) { if ( !player.usedown ) lastuse = gametic; CheckDefaceTexture(); if ( !player.usedown && froggy ) player.usedown = froggy.Used(self); // try to "use" the item closest to the crosshair CheckItemUsePickup(); } Super.PlayerThink(); if ( (gametic == lastuse) && IsActorPlayingSound(CHAN_VOICE,"*usefail") && !CheckMirrorUse() ) // don't play fail use if we're checking out a mirror { failcounter++; if ( (failcounter > 8) && !Random[DemoLines](0,max(0,12-failcounter/3)) && (gametic > failcooldown) && (swwm_mutevoice < 2) ) { failcooldown = SWWMHandler.AddOneliner("usefail",2,20); failcounter = max(4,failcounter-10); } else if ( (failcounter > 2) && Random[DemoLines](0,1) && (gametic > failcooldown) && (swwm_mutevoice < 4) ) { String myvoice = CVar.GetCVar('swwm_voicetype',player).GetString(); int loudlv = swwm_voiceamp; int maxusegrunt = StringTable.Localize("$SWWM_"..myvoice.."_NUSEGRUNT").ToInt(); int idx = (maxusegrunt<=1)?1:Random[DemoLines](1,maxusegrunt); A_StartSound(String.Format("voice/%s/usegrunt",myvoice,idx),CHAN_DEMOVOICE,CHANF_OVERLAP); if ( loudlv > 1 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice,idx),CHAN_DEMOVOICEAUX,CHANF_OVERLAP); if ( loudlv > 2 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice,idx),CHAN_DEMOVOICEAUX2,CHANF_OVERLAP); if ( loudlv > 3 ) A_StartSound(String.Format("voice/%s/usegrunt",myvoice,idx),CHAN_DEMOVOICEAUX3,CHANF_OVERLAP); failcooldown = int(S_GetLength(String.Format("voice/%s/usegrunt",myvoice,idx))*GameTicRate); failcounter = max(2,failcounter-1); } } else if ( gametic > lastuse+50 ) failcounter = 0; oldlagangles = lagangles; lagangles = lagangles*.8+(angle,pitch,roll)*.2; oldlagdangles = lagdangles; lagdangles = lagdangles*.8+(deltaangle(oldangles.x,angle),deltaangle(oldangles.y,pitch),deltaangle(oldangles.z,roll))*.2; if ( !player || (player.mo != self) ) return; if ( (player.playerstate != PST_DEAD) && (player.jumptics != 0) ) { // faster falloff player.jumptics -= 5; if ( player.onground && (player.jumptics < -18) ) player.jumptics = 0; } if ( (player.playerstate != PST_DEAD) && !ReactionTime ) { // quick grenade if ( player.cmd.buttons&BT_USER4 ) SWWMGesture.SetGesture(self,GS_Grenade); // emergency melee with no weapon else if ( !player.ReadyWeapon && (player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK|BT_USER1)) ) SWWMGesture.SetGesture(self,GS_EmptyMelee); } } override void DeathThink() { oldangles = (angle,pitch,roll); player.Uncrouch(); TickPSprites(); player.onground = (pos.Z<=floorz); // ded (demo-chan falls faster tho) player.deltaviewheight = 0; if ( player.viewheight > 6 ) player.viewheight -= 3; if ( player.viewheight < 6 ) player.viewheight = 6; // center pitch double dpitch = clamp(deltaangle(pitch,0),-6,6); if ( abs(dpitch) < 3. ) pitch = 0.; else A_SetPitch(pitch+dpitch,SPF_INTERPOLATE); // add roll double droll = clamp(deltaangle(roll,50)*.5,-5,5); if ( abs(droll) < 2. ) roll = 50.; else A_SetRoll(roll+droll,SPF_INTERPOLATE); // bob interpolation still active oldlagangles = lagangles; lagangles = lagangles*.8+(angle,pitch,roll)*.2; oldlagdangles = lagdangles; lagdangles = lagdangles*.8+(deltaangle(oldangles.x,angle),deltaangle(oldangles.y,pitch),deltaangle(oldangles.z,roll))*.2; player.mo.CalcHeight(); if ( player.damagecount ) player.damagecount--; if ( player.poisoncount ) player.poisoncount--; // solid unless we can respawn, for safety if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) ) bSolid = false; else bSolid = true; deadtimer++; if ( (deadtimer == 60) && (player == players[consoleplayer]) ) A_StartSound("demolitionist/youdied",CHAN_DEMOVOICE,CHANF_OVERLAP|CHANF_UI); if ( !hnd ) hnd = SWWMHandler(EventHandler.Find('SWWMHandler')); if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) ) { // standard behaviour, respawn normally if ( (((player.cmd.buttons&BT_USE) || ((deathmatch || alwaysapplydmflags) && sv_forcerespawn)) && !sv_norespawn) && ((Level.maptime >= player.respawn_time) || ((player.cmd.buttons&BT_USE) && !player.Bot)) ) { player.cls = null; player.playerstate = PST_REBORN; if ( special1 > 2 ) special1 = 0; } } else if ( (player.cmd.buttons&BT_USE) && (deadtimer > 120) ) { // reload save player.cls = null; player.playerstate = PST_ENTER; if ( special1 > 2 ) special1 = 0; } else if ( (player.cmd.buttons&BT_ATTACK) && (deadtimer > 120) && (!hnd || !hnd.gdat.disablerevive) && swwm_revive ) { // reboot (if possible) if ( !FindInventory('ReviveCooldown') && (((swwm_revivecooldown >= 0) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4)) || !hasrevived) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 6) ) { if ( hasrevived ) SWWMUtility.MarkAchievement("sekiro",player); hasrevived = true; player.Resurrect(); if ( player == players[consoleplayer] ) SWWMScoreObj.SpawnAtActorBunch(player.health,self,Font.CR_BLUE); player.damagecount = 0; player.bonuscount = 0; player.poisoncount = 0; blinktime = 30; SetState(FindState('Spawn')+1); // skip tweening roll = 0; let s = Spawn('DemolitionistShockwave',pos); s.target = self; s.special1 = 30; ReactionTime = 17; A_Stop(); A_AlertMonsters(swwm_uncapalert?0:2500); if ( player == players[consoleplayer] ) { A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP); A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7); A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4); } lastbump *= 1.5; SWWMHandler.DoFlash(self,Color(255,255,255,255),10); SWWMHandler.DoFlash(self,Color(255,128,192,255),30); if ( special1 > 2 ) special1 = 0; if ( (swwm_revivecooldown > 0) && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4) ) GiveInventory('ReviveCooldown',1); } else if ( level.maptime > revivefail ) { if ( player == players[consoleplayer] ) A_StartSound("menu/fail",CHAN_ITEM,CHANF_UI); revivefail = level.maptime+120; } } } }