-- Tatsuru's Prop Hunt. -- Version 1.0. -- The main culprits. freeslot("MT_TOUCHCHECK", "MT_HITCHECK", "S_ACTIVE") mobjinfo[MT_TOUCHCHECK] = { doomednum = -1, spawnstate = S_ACTIVE, radius = 128*FRACUNIT, height = 128*FRACUNIT, flags = MF_SPECIAL|MF_NOCLIPHEIGHT|MF_NOGRAVITY } mobjinfo[MT_HITCHECK] = { doomednum = -1, spawnstate = S_ACTIVE, radius = 16*FRACUNIT, height = 48*FRACUNIT, flags = MF_SPECIAL|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SHOOTABLE } states[S_ACTIVE] = { sprite = SPR_NULL, tics = -1, var1 = 0, var2 = 0, nextstate = S_ACTIVE } local props = {} local hidetime = 0 local huntstart = false local shutUpYall = false -- Nev3r saves my ass again! This function takes the player closest to the crosshair and acts as a substitute to seenames. -- This way, Hunters cannot see the names of Props in disguise. local function isInViewCone (pmo, mobj) if not mobj.player return nil end -- Obtain viewport view unitary vector. local viewx, viewy, viewz -- We use angles and cos/sin functions directly so we can assume it's an already normalized result. local a1 = pmo.angle local c1, s1 = cos(a1), sin(a1) local a2 = pmo.player.aiming local c2, s2 = cos(a2), sin(a2) viewx = FixedMul(c1, c2) viewy = FixedMul(s1, c2) viewz = s2 -- Obtain position difference unitary vector. local posx, posy, posz = 0 posx = mobj.x - pmo.x posy = mobj.y - pmo.y posz = (mobj.z + mobj.height/2) - (pmo.z + pmo.height/2) -- Normalize the vector so it becomes unitary. local posh = FixedHypot(FixedHypot(posx, posy), posz) if posh == 0 return nil end posx = FixedDiv($, posh) posy = FixedDiv($, posh) posz = FixedDiv($, posh) -- Perform dot product to obtain cosine of angle formed by the vectors. local rcos = FixedMul(viewx, posx) + FixedMul(viewy, posy) + FixedMul(viewz, posz) -- Obtain the cosine for the "minimum" threshold cone angle to consider a mobj valid. local compcos = cos(ANG10) if rcos > compcos and mobj.player pmo.player.lookingat = mobj.player else pmo.player.lookingat = nil end return nil end -- And for my next trick, local function becomeProp(player, prop) if player.prop != nil P_RemoveMobj(player.prop) end player.prop = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, prop.origtype) end -- Don't you just hate when ThinkFrame gets this big? addHook("ThinkFrame", function() if gametype == GT_TEAMMATCH local propPlayers = 0 local hunterPlayers = 0 if hidetime > 0 hidetime = $ - 1 end -- Count the players of each team. for player in players.iterate if player.ctfteam == 1 propPlayers = $ + 1 elseif player.ctfteam == 2 hunterPlayers = $ + 1 end end -- Don't start the game if there isn't at least 1 player of each team. if propPlayers > 0 and hunterPlayers > 0 if not huntstart hidetime = 59*TICRATE huntstart = true end else -- Stop the game if that's not true anymore. hidetime = 0 huntstart = false end for player in players.iterate COM_BufInsertText(player, "seenames 0") -- Oops, sorry! -- Player belongs to the prop team. (Red) if player.ctfteam == 1 and player.playerstate == PST_LIVE player.weapondelay = 105 player.exiting = 0 if player.prop player.mo.flags2 = $ | MF2_DONTDRAW player.powers[pw_underwater] = 30*TICRATE P_TeleportMove(player.prop, player.mo.x, player.mo.y, player.mo.z) if huntstart and hidetime == 0 if player.speed > 0 player.propmove = $ + 1 else player.propmove = 1 end if player.propmove % TICRATE == 0 P_AddPlayerScore(player, 1) end end -- Let's make the prop flash too if the Prop player is. if player.powers[pw_flashing] > 0 if leveltime % 2 == 0 player.prop.flags2 = $ | MF2_DONTDRAW else player.prop.flags2 = $ & ~MF2_DONTDRAW end else player.prop.flags2 = $ & ~MF2_DONTDRAW end -- Hack #1: PLEASE don't destroy the monitor props if player.prop.flags & MF_MONITOR player.prop.ImAMonitorProp = true else player.prop.ImAMonitorProp = false end -- Spin! if (player.cmd.buttons & BT_WEAPONNEXT) player.prop.angle = $ + FixedAngle(15*FRACUNIT) elseif (player.cmd.buttons & BT_WEAPONPREV) player.prop.angle = $ - FixedAngle(15*FRACUNIT) end -- Detransform! if (player.cmd.buttons & BT_ATTACK) and player.touchprop != 1 and player.propdelay == 0 P_RemoveMobj(player.prop) player.prop = nil player.mo.flags2 = $ & ~MF2_DONTDRAW player.propdelay = TICRATE end end if not player.prop player.mo.flags2 = $ & ~MF2_DONTDRAW end if player.propdelay > 0 player.propdelay = $ - 1 end end -- Player belongs to the hunter team. (Blue) if player.ctfteam == 2 and player.playerstate == PST_LIVE if player.prop P_RemoveMobj(player.prop) end player.prop = nil player.mo.flags2 = $ & ~MF2_DONTDRAW -- We will we will lock you player.lock.x = player.mo.x player.lock.y = player.mo.y player.lock.z = player.mo.z player.lock.momx = player.mo.momx player.lock.momy = player.mo.momy player.lock.momz = player.mo.momz -- Invincible! Also immovable. if hidetime > 0 if leveltime > 1 P_TeleportMove(player.mo, player.lock.x, player.lock.y, player.lock.z) end player.exiting = 1 player.mo.momx = player.lock.momx player.mo.momy = player.lock.momy player.mo.momz = player.lock.momz player.mo.frame = $ | FF_TRANS50 else player.exiting = 0 player.mo.frame = $ & ~FF_TRANS30 end end if player.mo -- My bootleg version of seenames. searchBlockmap("objects", isInViewCone, player.mo, player.mo.x - 512*FRACUNIT, player.mo.x + 512*FRACUNIT, player.mo.y - 512*FRACUNIT, player.mo.y + 512*FRACUNIT) end end end end) -- Ain't nobody collecting rings addHook("TouchSpecial", function(touched, toucher) if toucher.player.prop return true end end, MT_RING) addHook("TouchSpecial", function(touched, toucher) if toucher.player.ctfteam == 1 and touched.type >= 312 and touched.type <= 324 return true end end) -- Never collide with the prop you're disguising as. addHook("MobjMoveCollide", function(pmo, prop) if pmo.player.prop and prop == pmo.player.prop return false end end, MT_PLAYER) -- Wrong prop, idiot. -- Award 10 points to the closest Prop player to the prop that was hit in a radius of 500 fracunits. addHook("ShouldDamage", function(dummy, inflictor, source) if huntstart if (inflictor.type == MT_REDRING or inflictor.type == MT_THROWNBOUNCE or inflictor.type == MT_THROWNINFINITY or inflictor.type == MT_THROWNAUTOMATIC or inflictor.type == MT_THROWNSCATTER or inflictor.type == MT_THROWNEXPLOSION or inflictor.type == MT_THROWNGRENADE) and source.type == MT_PLAYER if source.player.powers[pw_flashing] == 0 P_DamageMobj(source, nil, dummy) local closest searchBlockmap("objects", function(reference, candidate) local maxdistance = 500*FRACUNIT local distance = maxdistance if candidate.type == MT_PLAYER and candidate.player.ctfteam == 1 distance = P_AproxDistance(reference.x - candidate.x, reference.y - candidate.y) if distance < maxdistance maxdistance = distance closest = candidate.player end end return nil end, dummy, dummy.x - 500*FRACUNIT, dummy.x + 500*FRACUNIT, dummy.y - 500*FRACUNIT, dummy.y + 500*FRACUNIT) if closest P_AddPlayerScore(closest, 10) end return false end end end end, MT_HITCHECK) -- I hate this algorithm but at least it works? Kinda -- touchprop = 0 - not touching prop (can't change) -- touchprop = 1 - touching a prop of a different type -- touchprop = 2 - touching a prop of the same type (can't change) addHook("TouchSpecial", function(dummy, toucher) if toucher.player.prop if toucher.player.prop.type == dummy.origtype toucher.player.touchprop = 2 else toucher.player.touchprop = 1 end else toucher.player.touchprop = 1 end if P_AproxDistance(dummy.x - toucher.x, dummy.y - toucher.y) >= dummy.radius - 64*FRACUNIT toucher.player.touchprop = 0 end if (toucher.player.cmd.buttons & BT_ATTACK) and toucher.player.touchprop == 1 and toucher.player.propdelay == 0 becomeProp(toucher.player, dummy) toucher.player.propdelay = TICRATE end return true end, MT_TOUCHCHECK) --Props are impervious to all damage when in disguise, except rings from the enemy team. addHook("ShouldDamage", function(pmo, inflictor, source) if pmo.player.prop if inflictor or source if (inflictor.type == MT_REDRING or inflictor.type == MT_THROWNBOUNCE or inflictor.type == MT_THROWNINFINITY or inflictor.type == MT_THROWNAUTOMATIC or inflictor.type == MT_THROWNSCATTER or inflictor.type == MT_THROWNEXPLOSION or inflictor.type == MT_THROWNGRENADE) and source.type != MT_PLAYER return false end end end end, MT_PLAYER) --Disguised Prop players cannot break monitors. addHook("ShouldDamage", function(target, inflictor) if target.flags & MF_MONITOR and not (maptol & TOL_MARIO) if (inflictor.player and inflictor.player.prop) or target.ImAMonitorProp local horizontalAngle = R_PointToAngle2(target.x, target.y, inflictor.x, inflictor.y) local verticalAngle = R_PointToAngle2(FixedHypot(target.x, target.y), target.z, FixedHypot(inflictor.x, inflictor.y), inflictor.z) P_SetObjectMomZ(inflictor, 8*sin(verticalAngle), false) P_InstaThrust(inflictor, horizontalAngle, 8*abs(cos(verticalAngle))) return false end end end) -- Set up the stage for a new game. Almost very object in the map gets a prop dummy. addHook("MapLoad", function() huntstart = false hidetime = 0 if gametype == GT_TEAMMATCH for spawner in mapthings.iterate if spawner.mobj if (spawner.mobj.flags & MF_ENEMY) continue elseif (spawner.mobj.flags & MF_NOSECTOR) continue elseif (spawner.mobj.flags & MF_SPECIAL) continue elseif (spawner.mobj.flags & MF_MISSILE) continue elseif (spawner.mobj.flags & MF_PAIN) and not spawner.mobj.type == MT_FLAME continue elseif (spawner.mobj.flags & MF_BOSS) continue elseif (spawner.mobj.flags & MF_SHOOTABLE) and not (spawner.mobj.flags & MF_MONITOR) continue elseif spawner.mobj.type == MT_METALSONIC_RACE or spawner.mobj.type == MT_WATERDRIP or spawner.mobj.type == MT_WATERDROP continue elseif spawner.mobj.type == MT_BUBBLES or spawner.mobj.type == MT_SMALLBUBBLE or spawner.mobj.type == MT_MEDIUMBUBBLE or spawner.mobj.type == MT_EXTRALARGEBUBBLE continue else spawner.mobj.dummy = P_SpawnMobj(spawner.mobj.x, spawner.mobj.y, spawner.mobj.z, MT_TOUCHCHECK) spawner.mobj.hitbox = P_SpawnMobj(spawner.mobj.x, spawner.mobj.y, spawner.mobj.z, MT_HITCHECK) spawner.mobj.dummy.origtype = spawner.mobj.type spawner.mobj.dummy.angle = spawner.mobj.angle if spawner.mobj.radius > spawner.mobj.hitbox.radius spawner.mobj.hitbox.radius = spawner.mobj.radius end if spawner.mobj.height > spawner.mobj.hitbox.height spawner.mobj.hitbox.height = spawner.mobj.height end end end end end for player in players.iterate if player.prop player.prop = nil end end end) -- Don't take the prop with you. addHook("MobjDeath", function(pmo) if pmo.player.prop P_RemoveMobj(pmo.player.prop) pmo.player.prop = nil end end, MT_PLAYER) -- I said don't take the prop with you. addHook("PlayerQuit", function(player) if player.prop P_RemoveMobj(player.prop) player.prop = nil end end) -- Screen magic. hud.add(function(v, player, camera) if gametype == GT_TEAMMATCH if player.ctfteam == 1 -- Prop Team HUD. hud.disable("weaponrings") if player.touchprop == 1 v.drawString(160, 152, "Press \x85Ring Toss", V_ALLOWLOWERCASE|V_30TRANS, "center") v.drawString(160, 160, "to disguise as this object!", V_ALLOWLOWERCASE|V_30TRANS, "center") end if player.prop if player.touchprop != 1 v.drawString(160, 152, "Fire again to remove your disguise.", V_ALLOWLOWERCASE|V_30TRANS, "center") end end if hidetime > 0 v.drawString(160, 56, "Approach an object and blend in", V_ALLOWLOWERCASE|V_30TRANS, "center") v.drawString(160, 64, "with the scenery before", V_ALLOWLOWERCASE|V_30TRANS, "center") v.drawString(160, 72, "the time runs out!", V_ALLOWLOWERCASE|V_30TRANS, "center") v.drawString(160, 88, G_TicsToSeconds(hidetime)+1, V_ALLOWLOWERCASE|V_30TRANS, "center") end -- Bootleg seenames, part 3. if player.lookingat and player.lookingat.valid if player.lookingat.ctfteam == 2 v.drawString(160, 120, player.lookingat.name, V_50TRANS|V_REDMAP, "center") elseif player.lookingat.ctfteam == 1 v.drawString(160, 120, player.lookingat.name, V_50TRANS|V_GREENMAP, "center") end end end if player.ctfteam == 2 -- Hunter Team HUD. hud.enable("weaponrings") local patch = v.cachePatch("CREDIT04") -- This screen is so cute you might just want to look at it forever! if hidetime > 0 v.drawFill(8, 8, 304, 184, 31) v.drawScaled(20*FRACUNIT, (200 - patch.height*4/5)/2*FRACUNIT, FRACUNIT*4/5, patch) v.drawString(210, 32, "You're in the", V_ALLOWLOWERCASE, "center") v.drawString(210, 40, "\x84Hunter Team\x80!", V_ALLOWLOWERCASE, "center") v.drawString(210, 56, "You won't be able to", V_ALLOWLOWERCASE, "center") v.drawString(210, 64, "move or shoot for", V_ALLOWLOWERCASE, "center") v.drawString(210, 72, "a short while.", V_ALLOWLOWERCASE, "center") v.drawString(210, 88, "Members of the \x85Prop Team", V_ALLOWLOWERCASE, "center") v.drawString(210, 96, "will be disguised as", V_ALLOWLOWERCASE, "center") v.drawString(210, 104, "objects around the map.", V_ALLOWLOWERCASE, "center") v.drawString(210, 120, "But be careful!", V_ALLOWLOWERCASE, "center") v.drawString(210, 128, "Hit the wrong object", V_ALLOWLOWERCASE, "center") v.drawString(210, 136, "and you're in for", V_ALLOWLOWERCASE, "center") v.drawString(210, 144, "a world of pain!", V_ALLOWLOWERCASE, "center") v.drawString(210, 160, "Deploying Hunters in", V_ALLOWLOWERCASE, "center") v.drawString(210, 168, G_TicsToSeconds(hidetime)+1, V_ALLOWLOWERCASE, "center") end -- Bootleg seenames, part 3. if player.lookingat and player.lookingat.valid if player.lookingat.ctfteam == 2 v.drawString(160, 116, player.lookingat.name, V_50TRANS|V_GREENMAP, "center") elseif player.lookingat.ctfteam == 1 and not player.lookingat.prop v.drawString(160, 116, player.lookingat.name, V_50TRANS|V_REDMAP, "center") end end end end end, "game") -- Restart anew. addHook("PlayerSpawn", function(player) if gametype == GT_TEAMMATCH P_RemoveShield(player) player.prop = nil player.touchprop = 0 player.propmove = 1 player.propdelay = TICRATE player.lock = {} if player.autocamera == nil player.autocamera = true end if player.autocamera if player.ctfteam == 1 COM_BufInsertText(player, "chasecam 1") end if player.ctfteam == 2 COM_BufInsertText(player, "chasecam 0") end end end end) -- Customizing damage/death messages. addHook("HurtMsg", function(target, inflictor, source) if not source return end if source.type == MT_HITCHECK print("\x84"+target.name+"\x80 mistakenly hit a real prop!") return true end if target.prop and inflictor.type == MT_REDRING and source.type == MT_PLAYER print("\x85"+target.name+"\x80's disguise was busted by \x84"+source.player.name+"\x80!") return true end end) -- Don't reveal the position of the props, dumbass addHook("PlayerMsg", function(sender, type, target) if sender.ctfteam == 0 and shutUpYall and gametype == GT_TEAMMATCH if type == 1 return elseif not target or target.ctfteam != 0 CONS_Printf(sender, "Spectators are currently muted to players in the game.") CONS_Printf(sender, "Join the game or use the sayto command to talk to other spectators.") return true end end end) -- net(owo) addHook("NetVars", function(net) hidetime = net($) huntstart = net($) shutUpYall = net($) end) -- Silence, spectator COM_AddCommand("SILENCE", function(player) if not shutUpYall shutUpYall = true CONS_Printf(player, "Spectators have been muted.") else shutUpYall = false CONS_Printf(player, "Spectators can now talk to players in the game.") end end, 1) -- Silence, spectator COM_AddCommand("AUTOPOV", function(player) if not player.autocamera player.autocamera = true CONS_Printf(player, "Your point of view will now change with your current team.") else player.autocamera = false CONS_Printf(player, "Your point of view will no longer change with your current team.") end end, 1) -- No Emeralds in our game. for _,i in ipairs({ MT_EMERALD1, MT_EMERALD2, MT_EMERALD3, MT_EMERALD4, MT_EMERALD5, MT_EMERALD6, MT_EMERALD7, MT_EMMY, }) do mobjinfo[i].flags = MF_NOBLOCKMAP|MF_NOSECTOR end