-- Tatsuru's Post-exit Movement (ExitMove) Script. -- You're being watched. -- Version 1.6.2 -- Exit system revamped -- Removed the stupid monitor breaking toggle -- Goal orb design updated -- Added warping delay -- Added freeze command -- ExitMove exposed to foreign scripts -- Slowdown on exit sectors fixed rawset(_G, "exitmove", {}) -- Wow! freeslot("MT_PLRGOAL", "S_PLAYERFIN1", "S_PLAYERFIN2", "S_PLAYERFIN3", "S_PLAYERFIN4") -- Let there be light! mobjinfo[MT_PLRGOAL] = { doomednum = -1, spawnstate = S_PLAYERFIN1, radius = 10*FRACUNIT, height = 8*FRACUNIT, speed = 4*FRACUNIT, flags = MF_NOGRAVITY|MF_FLOAT|MF_NOCLIPHEIGHT } states[S_PLAYERFIN1] = { sprite = SPR_GOAL, frame = A|TR_TRANS40, tics = 35, nextstate = S_PLAYERFIN2 } states[S_PLAYERFIN2] = { sprite = SPR_GOAL, frame = B|TR_TRANS40, tics = 35, nextstate = S_PLAYERFIN3 } states[S_PLAYERFIN3] = { sprite = SPR_GOAL, frame = C|TR_TRANS40, tics = 35, nextstate = S_PLAYERFIN4 } states[S_PLAYERFIN4] = { sprite = SPR_GOAL, frame = D|TR_TRANS40, tics = 35, nextstate = S_PLAYERFIN1 } local finishcount local endlevel local starposts = {} local exitplayers local freeze local mapnum local warp = true local gameovertimer = 10*TICRATE local ThisIsABossLevel = false local NoMoreExits = false local dummy local camera -- Is the sector you're in an exit sector or an "exit" sector? local function findExit(player, sector) if not player.finished -- Only the first to finish actually does it. if sector.special / 4096 == 2 player.finished = true elseif sector.special >= -12288 and sector.special < -8192 -- The others are only fake finish. player.finished = true player.exiting = 0 print(player.name+" has completed the level.") -- Let's search for custom exits. local line = P_FindSpecialLineFromTag(2, sector.tag) if line >= 0 if lines[line].flags & ML_BLOCKMONSTERS if All7Emeralds(emeralds) G_SetCustomExitVars(lines[line].frontsector.ceilingheight/FRACUNIT) else G_SetCustomExitVars(lines[line].frontsector.floorheight/FRACUNIT) end else G_SetCustomExitVars(lines[line].frontsector.floorheight/FRACUNIT) end end end end end addHook("ThinkFrame", function() if gametype == GT_COOP and not (maptol & TOL_NIGHTS) -- Failsafe for bosses and Egg Capsules. If everyone living has the same exiting value at the same tic, it means a boss got defeated. local able = false for player in players.iterate if not endlevel -- Just to make sure this does NOT run on levels with exit sectors if player.playerstate == PST_LIVE and player.exiting == 0 and not player.finished able = true end end end if not able and not endlevel ThisIsABossLevel = true else ThisIsABossLevel = false end -- Get rid of the exit sectors so the dumbasses can move for player in players.iterate if not endlevel if player.finished if player.mo.subsector.sector.special / 4096 == 2 player.mo.subsector.sector.special = $ + (4096*11) end for fof in player.mo.subsector.sector.ffloors() if player.mo.z <= fof.topheight and player.mo.z + player.mo.height >= fof.bottomheight if player.mo.subsector.sector.special / 4096 == 2 player.mo.subsector.sector.special = $ + (4096*11) end end end -- Leave no exit sector unturned if not NoMoreExits for sector in sectors.iterate if sector.special / 4096 == 2 sector.special = $ + (4096*11) end end NoMoreExits = true end end -- DON'T GET STUCK ANYWHERE EVER THERE'S NOT EVEN ANY EXIT SECTORS ANYMORE OH MY GOD if not ThisIsABossLevel if utility and player.utility if not player.utility.afk player.exiting = 0 end else player.exiting = 0 end end end -- Is the sector you're in an exit sector? findExit(player, player.mo.subsector.sector) for fof in player.mo.subsector.sector.ffloors() if player.mo.z <= fof.topheight and player.mo.z + player.mo.height >= fof.bottomheight findExit(player, fof.sector) end end -- If the finished player is alive and in the server, give them the light. if player.finished and player.playerstate == PST_LIVE and not player.light player.light = {} for i = 1, 3 local light = P_SpawnMobj(player.mo.x, player.mo.y, player.mo.z, MT_PLRGOAL) light.target = player.mo light.scale = FRACUNIT/2 light.angle = FixedAngle(((360/3)*i)*FRACUNIT) table.insert(player.light, light) end end -- For stopping the timer properly. if not (player.finished or player.lives <= 0) player.finishtime = player.realtime end -- Game-overed players can't finish the level. if player.lives <= 0 and player.finished player.finished = false end -- Finished players cannot drown, and time stops for them. if player.finished player.realtime = player.finishtime player.powers[pw_underwater] = 20*TICRATE player.powers[pw_spacetime] = 20*TICRATE end -- So people stop spamming warp. if not player.starpostdelay player.starpostdelay = 0 elseif player.starpostdelay > 0 player.starpostdelay = $ - 1 end -- If a smart guy happens to join when everyone's finished if endlevel and player.exiting == 0 player.exiting = 99 end end local playercount = 0 local nonplayer = 0 finishcount = 0 -- Compare if the number of players that have finished matches the number of active and alive players in the server. if not endlevel for player in players.iterate playercount = $1 + 1 if player.finished finishcount = $1 + 1 end if player.lives <= 0 nonplayer = $1 + 1 elseif utility and player.utility if player.utility.afk and not player.finished nonplayer = $1 + 1 end end end end -- Cut the number of players according to the command determined by the server. if exitplayers == "One" --Cut the number of players according to the command determined by the server. playercount = 1 elseif exitplayers == "Half" if nonplayer > playercount / 2 playercount = $1 - nonplayer else playercount = $1 / 2 end elseif exitplayers == "3/4" if nonplayer > playercount / 4 * 3 playercount = $1 - nonplayer else playercount = $1 / 4 * 3 end else exitplayers = "All" playercount = $1 - nonplayer end -- If the number of finished players corresponds to the number of players who can finish the level, end it. if not endlevel and leveltime > 0 and finishcount > 0 and playercount > 0 and finishcount >= playercount and not freeze if exitplayers == "All" and nonplayer == 0 print ("All players have completed the level.") else print ("Enough players have completed the level.") end endlevel = true for player in players.iterate player.exiting = 99 end elseif playercount == 0 and nonplayer > 1 and gameovertimer > 0 -- All our players are game-overed or AFK gameovertimer = $ - 1 else gameovertimer = 10*TICRATE end -- Game over. Restart the map. if gameovertimer == 0 and not freeze G_ExitLevel(mapnum, true) gameovertimer = 10*TICRATE end if dummy and dummy.valid and dummy.light if not camera for _, light in ipairs(dummy.light) if light.valid and not (light.flags2 & MF2_DONTDRAW) light.flags2 = $ | MF2_DONTDRAW end end else for _, light in ipairs(dummy.light) if light.valid and light.flags2 & MF2_DONTDRAW light.flags2 = $ & ~MF2_DONTDRAW end end end end end end) addHook("MapLoad", function(map) if gametype == GT_COOP and not (maptol & TOL_NIGHTS) mapnum = map ThisIsABossLevel = false NoMoreExits = false -- Because maps don't actually reset for sector in sectors.iterate if sector.special >= -12288 and sector.special < -8192 sector.special = $ - (4096*11) end end -- Store all Star Posts. starposts = {} for thing in mapthings.iterate if thing.type == 502 table.insert(starposts, thing.mobj) end end -- Reset the detectors at every map load. endlevel = false for player in players.iterate player.exiting = 0 player.powers[pw_underwater] = 20*TICRATE player.starpost = #starposts player.finished = false player.light = nil player.finishtime = 0 end end end) -- Warping magic! local function toStarpost(player) if player.starpostdelay > 0 return end if not P_IsObjectOnGround(player.mo) or player.speed > 0 CONS_Printf(player, "You can't warp while moving.") return end if #starposts == 0 CONS_Printf(player, "There are no Star Posts in this level.") return end if not player.starpost or player.starpost < 1 player.starpost = #starposts end player.mo.momx = 0 player.mo.momy = 0 player.mo.momz = 0 P_TeleportMove(player.mo, starposts[player.starpost].x, starposts[player.starpost].y, starposts[player.starpost].z) player.starpostdelay = TICRATE player.starpost = $1 - 1 player.exiting = 0 end -- The goal icon now spins around the player. addHook("MobjThinker", function(mo) if mo.target and mo.target.player.finished A_RotateSpikeBall(mo) mo.z = $ - mo.target.height/3 else P_RemoveMobj(mo) end end, MT_PLRGOAL) -- Finished players cannot lose rings when hurt. addHook("ShouldDamage", function(targetmo, _, _, damage) if gametype == GT_COOP and not (maptol & TOL_NIGHTS) if damage == 10000 and targetmo.player.exiting == 0 if not utility or (utility and targetmo.player.utility and not targetmo.player.utility.afk) -- Suicide return true end end if targetmo.player != nil and targetmo.player.finished and targetmo.player.powers[pw_flashing] == 0 and not endlevel if not utility or (utility and targetmo.player.utility and not targetmo.player.utility.afk) S_StartSound(targetmo, sfx_shldls) P_RemoveShield(targetmo.player) P_DoPlayerPain(targetmo.player) return false end end end end, MT_PLAYER) -- Finished players that die won't lose lives. addHook("MobjDeath", function(mo) if gametype == GT_COOP and not (maptol & TOL_NIGHTS) if mo.player.finished mo.player.lives = $1 + 1 mo.player.light = nil end end end, MT_PLAYER) COM_AddCommand("FREEZE", function(player) if not freeze freeze = true CONS_Printf(player, "Freeze has been activated.") CONS_Printf(player, "The current level will not end until it's deactivated again.") else CONS_Printf(player, "Freeze has been deactivated.") freeze = false end end, 1) COM_AddCommand("REMAINING", function(player) if server == nil CONS_Printf(player, "You must be in a game to use this command.") end if gametype != GT_COOP or (maptol & TOL_NIGHTS) CONS_Printf(player, "This command cannot be used in this gametype.") end CONS_Printf(player, "Players who haven't finished the level:") for plr in players.iterate if not plr.finished and plr.lives > 0 -- Don't count game-overed players as players. if not utility or (utility and plr.utility and not plr.utility.afk) CONS_Printf(player, " "..plr.name) end end end end, 0) --This allows the server to control which fraction of the players is necessary to finish the level. COM_AddCommand("CUSTOMEXIT", function(player, arg) if server == nil CONS_Printf(player, "You must be in a game to use this command.") return false end if not arg CONS_Printf(player, "Sets how many players are needed to finish the level.") CONS_Printf(player, "Available options are One, Half, 3/4 and All.") CONS_Printf(player, "Currently set to "..exitplayers..", default is All.") elseif string.lower(arg) == "one" or string.lower(arg) == "1" exitplayers = "One" elseif string.lower(arg) == "half" or string.lower(arg) == "1/2" exitplayers = "Half" elseif string.lower(arg) == "3/4" exitplayers = "3/4" elseif string.lower(arg) == "all" exitplayers = "All" else CONS_Printf(player, "Invalid option. Currently set to "..exitplayers..".") end end, 1) COM_AddCommand("WARP", function(player, arg) if arg if server and player != server and not IsPlayerAdmin(player) or (utility and not utility.IsAdmin(player)) CONS_Printf(player, "Only the server can toggle this feature.") elseif string.lower(arg) == "on" if not warp warp = true CONS_Printf(player, "Warping has been enabled.") end elseif string.lower(arg) == "off" if warp warp = false CONS_Printf(player, "Warping has been disabled.") end else if warp CONS_Printf(player, "Invalid option. Warping is currently enabled.") else CONS_Printf(player, "Invalid option. Warping is currently disabled.") end end else if server == nil CONS_Printf(player, "You must be in a game to use this command.") elseif not warp CONS_Printf(player, "Warping is currently disabled by the server.") elseif player.lives <= 0 or player.playerstate == PST_DEAD CONS_Printf(player, "You can't use this command while dead.") elseif not player.finished CONS_Printf(player, "You haven't finished the level yet.") elseif gametype != GT_COOP or (maptol & TOL_NIGHTS) CONS_Printf(player, "This command cannot be used in this gametype.") elseif utility and player.utility and player.utility.afk CONS_Printf(player, "You cannot warp while AFK.") else toStarpost(player) end end end, 0) addHook("NetVars", function(net) finishcount = net($) endlevel = net($) exitplayers = net($) starposts = net($) freeze = net($) mapnum = net($) gameovertimer = net($) ThisIsABossLevel = net($) NoMoreExits = net($) warp = net($) end) hud.add(function(v, player, cam) if (not dummy or not dummy.valid) and (leveltime == 0 or player.jointime < leveltime) dummy = player end camera = cam.chase if not camera and player.finished local patch = v.cachePatch(sprnames[SPR_GOAL] + "A0") v.draw(298, 203, patch, V_SNAPTOBOTTOM|V_SNAPTORIGHT) end end, "game")