## =========================================== ## ## Manhunt minigame implemented using Denizen ## ## by @seb303 ## ## v1.3.2 2022-06-03 ## ## Requires Denizen-1.2.4-b6247-DEV or newer ## https://ci.citizensnpcs.co/job/Denizen_Developmental/ ## Requires Multiverse-Core ## https://dev.bukkit.org/projects/multiverse-core ## Optional Multiverse-NetherPortals ## https://dev.bukkit.org/projects/multiverse-netherportals ## Optional Multiverse-Inventories ## https://dev.bukkit.org/projects/multiverse-inventories ## =========================================== ## ## To do: # Runner should drop items on die # Ensure spawn point is always on land # Remove dependency on Multiverse # Optional Nether Fortress tracker for runners # Optional Piglin enderpearl drop rate boost for runners # Optional hunters track each other # Requires the following noted cuboids: # hunt_runner = the portal through which the runner(s) join the game (should not be in hunt world) # hunt_hunter = the portal through which the hunter(s) join the game (should not be in hunt world) # # Requires the following noted locations: # hunt_endlobby = Location to teleport to when leaving hunt or when hunt ends # # To note a location, stand in the location and use command: # /ex note as: # To note cuboids use the Denizen Area Selector Tool # https://forum.denizenscript.com/resources/area-selector-tool.1/ with this command: # /selnote # Or if you have WorldEdit & Depenizen installed, you can use the WorldEdit wand and command: # /ex note as: ## -------- ## ## COMMANDS ## ## -------- ## # # To start the hunt # /hunt start [] # Permission: custom.hunt # # To leave the hunt and return to hunt_endlobby # /hunt leave # Permission: custom.hunt # # To join the hunt after it has already started # /hunt join # Permission: custom.hunt # # To agree to another player joining the hunt after it has already started # /hunt agree # Permission: custom.hunt # # To reset the hunt and all flags # /hunt reset # Permission: custom.hunt & custom.hunt.reset ## CONFIG STARTS ## hunt_config: type: data # Default head start time in seconds for runners # Can be overridden in the /hunt start command head_start_time: 30 # The worlds where the hunt takes places # 1 for each environment NORMAL, NETHER & THE_END worlds: NORMAL: manhunt NETHER: manhunt_nether THE_END: manhunt_the_end # Whether new worlds should be regenerated from a random seed when a hunt ends # If "false" the same world will be reused every time # If "true" the regen process can lag the server regen_worlds: true # The delay before generating each of the 3 worlds (avoids a single prolonged lag spike) regen_world_delay: 5s # Difficulty setting for regenerated worlds difficulty: NORMAL ## CONFIG ENDS ## # ---------------------------------------------------------------------------------------- # Server flags: # hunt.waiting.joining - hunt is waiting for players to join # hunt.waiting.headstart - hunt is waiting for runners to have a head start, value is countdown timer # hunt.active - hunt is active (set after head start) # hunt.regenerating - hunt worlds are regenerating # # Player flags: # hunt.runner - player is a runner # hunt.hunter - player is a hunter # hunt.spectator - player is a spectator, possible values: # "dead" (dead runner) # "runner" (joined through runner portal after hunt started) # "hunter" (joined through hunter portal after hunt started) # hunt.request - player is requesting to join an active hunt # hunt.agree - player agrees that waiting player can join hunt # hunt.joins - player is just joining (connecting to) server # hunt.tracking - runner that hunter's compass is tracking # hunt.lastportal.NORMAL - location of last portal that a runner used in the overworld # hunt.lastportal.NETHER - location of last portal that a runner used in the nether # hunt.lastportal.THE_END - location of last portal that a runner used in the end # ---------------------------------------------------------------------------------------- hunt_events: type: world debug: false events: # Secondly event to update titles, handle new players joining during active phase, etc. on delta time secondly server_flagged:hunt: - if : - stop - define runners - define hunters - define all_players <[runners].include[<[hunters]>]> # Waiting phases - if : # List names of runners and hunters - define sidebar_text <[runners].proc[hunt_build_player_list].context[Runners]> - define sidebar_text:|:<[hunters].proc[hunt_build_player_list].context[Hunters]> - if : # Joining phase - define sidebar_title "Waiting for Players" - define "sidebar_text:|:|To start the hunt:|/hunt start||To leave the hunt:|/hunt leave" - sidebar set title:<[sidebar_title]> values:<[sidebar_text]> players:<[all_players]> - else if : # Head start phase - if <= 0: # End of head start - flag server hunt: - sidebar remove players:<[all_players]> - foreach <[hunters]> as:__player: - inject hunt_events.set_hunter_active - else: # Head start phase - define sidebar_title "Runners Go!" - define less_text "The hunters will be released in s" - define "sidebar_text:|:|<[less_text]>||To leave the hunt:|/hunt leave" - sidebar set title:<[sidebar_title]> values:<[sidebar_text]> players:<[runners]> - sidebar remove players:<[hunters]> - run hunt_set_actionbar def:<[less_text]>|<[hunters]> - flag server hunt.waiting.headstart:-- # Hunt active - else: - define spectators - define waiting_to_join <[spectators].filter[has_flag[hunt.request]]> - run hunt_set_actionbar "def:Use the number keys to teleport to players (keep pressing 1)|<[spectators]>" - if <[waiting_to_join].size> == 0: # Normal active game sidebar - define sidebar_title "The hunt is on!" - define sidebar_text <[runners].proc[hunt_build_player_list].context[Runners]> - define sidebar_text:|:<[hunters].proc[hunt_build_player_list].context[Hunters]> - if <[spectators].size> > 0: - define sidebar_text:|:<[spectators].proc[hunt_build_player_list].context[Spectators]> - sidebar set title:<[sidebar_title]> values:<[sidebar_text]> players:<[all_players]> - define "sidebar_text:|:|To join the hunt:|/hunt join" - sidebar set title:<[sidebar_title]> values:<[sidebar_text]> players:<[spectators]> - else: # Player(s) waiting to join sidebar - define agreed - if <[agreed].size> == <[all_players].size>: # All players agree to new player(s) joining - foreach <[agreed]> as:__player: - flag hunt.agree:! - foreach <[waiting_to_join]> as:__player: - define which - flag hunt:=1]> - inject hunt_enters_portal.teleport # Need to wait 2t before setting gamemode as Multiverse sets it after 1t delay - wait 2t - if <[which]> == runner: - inject hunt_events.set_runner_active - else: - inject hunt_events.set_hunter_active - else: # Waiting for players to agree to join - run hunt_set_actionbar "def:Waiting for the other players to accept your join request|<[waiting_to_join]>" - if <[waiting_to_join].size> > 1: - define sidebar_title "Players waiting to join" - else: - define sidebar_title "Player waiting to join" - define sidebar_text - define waiting_to_join_runners <[waiting_to_join].filter[flag[hunt.spectator].equals[runner]]> - if <[waiting_to_join_runners].size> > 0: - define "sidebar_text:|:<[waiting_to_join_runners].proc[hunt_build_player_list].context[New Runner waiting]>" - define waiting_to_join_hunters <[waiting_to_join].filter[flag[hunt.spectator].equals[hunter]]> - if <[waiting_to_join_hunters].size> > 0: - define "sidebar_text:|:<[waiting_to_join_hunters].proc[hunt_build_player_list].context[New Hunter waiting]>" - define sidebar_text:|:<[agreed].proc[hunt_build_player_list].context[Agreed]> - define "sidebar_text:|:<[all_players].filter[has_flag[hunt.agree].not].proc[hunt_build_player_list].context[Not agreed]>" - define "sidebar_text:|:|To agree:|/hunt agree" - sidebar set title:<[sidebar_title]> values:<[sidebar_text]> players:<[all_players]> - sidebar set title:<[sidebar_title]> values:<[sidebar_text]> players:<[spectators]> # The 2 entry portals on player enters hunt_runner: - run hunt_enters_portal def:runner on player enters hunt_hunter: - run hunt_enters_portal def:hunter on player teleports flagged:hunt: # Allow a player just joining the server to first teleport to lobby - if && == lobby: - flag hunt.joins:! - stop # Only allow leaving using the "/hunt leave" command - if !]>: - narrate "<&[error]>To leave use command <&[emphasis]>/hunt leave" - determine cancelled # Enforce spectator on world change - if : # Need to wait 2t before setting gamemode as Multiverse sets it after 1t delay - wait 2t - adjust gamemode:spectator on player joins flagged:hunt bukkit_priority:LOWEST: - if && > 1: # There is another player in an active hunt, so join back # Flag that initial join teleport should be allowed - flag hunt.joins:1 # Wait a short while to allow initial teleport to lobby to occur # (otherwise /mv tp will ignore last_location) - wait 2t - execute as_server "mv tp " - stop - else: # Clear player flag - flag hunt:! on player quits flagged:hunt: # Last player quitting? - if == 1: - inject hunt_events.reset_regen on player drops item flagged:hunt.hunter: # Don't allow hunters to drop their compass - if == compass: - determine cancelled on player left clicks block flagged:hunt.hunter: # Hunters left clicks compass - if == compass: - ratelimit 3t - define onlineRunners # Currently tracking an online runner? - if && : # Get next runner to track - if <[onlineRunners].size> > 1: - define pickNext false - foreach <[onlineRunners]> as:runner: - if <[pickNext]>: - flag hunt.tracking:<[runner]> - inject hunt_events.track_runner - stop - if <[runner]> == : - define pickNext true # Pick first online runner to track - flag hunt.tracking:<[onlineRunners].first.if_null[null]> - inject hunt_events.track_runner - stop on player right clicks block flagged:hunt.hunter: # Hunters right clicks compass - if == compass: - determine passively cancelled - ratelimit 3t - inject hunt_events.track_runner on player dies flagged:hunt.runner: # If a runner dies, switch them to spectator - determine passively cancelled - adjust gamemode:spectator - flag hunt: - if > 0: - title "title:You Died!" fade_in:0 stay:5 fade_out:2 - else: - inject hunt_events.check_for_winner after player dies flagged:hunt.hunter: # Make sure hunter always has a compass - inventory set o:compass slot:1 on player enters portal flagged:hunt.runner: # Runner wins the game? - if == THE_END: - title "title:The Runner Wins!" fade_in:0 stay:5 fade_out:2 targets: - wait 5 - inject hunt_events.reset_regen # Record last used portals by runners on player uses portal flagged:hunt.runner: - flag hunt.lastportal.: after player dies flagged:hunt.runner: - flag hunt.lastportal:! # Team chat on player chats message:#* flagged:hunt: - determine passively - if : - determine passively FORMAT:hunt_team_chat_runner - determine passively RECIPIENTS: - else if : - determine passively FORMAT:hunt_team_chat_hunter - determine passively RECIPIENTS: on player damaged flagged:hunt server_flagged:hunt.waiting: - determine cancelled # Ensure hunt worlds get created if they don't exist on server start: - if !].exists>: - inject hunt_events.regen_worlds on reload scripts: - if !].exists>: - inject hunt_events.regen_worlds # At beginning of head start phase set_runner_active: - inventory clear - adjust health:20 - feed amount:20 - adjust gamemode:survival # At beginning of head start phase set_hunter_frozen: - inventory clear - adjust health:20 - feed amount:20 - inventory set o:compass slot:1 - flag hunt.tracking:null # Freeze hunter during head start phase - define blindness - define slow - define jump - adjust potion_effects:].include_single[<[slow]>].include_single[<[jump]>]> # Set world time & weather - adjust time:1000 - weather sunny # After head start phase set_hunter_active: - adjust remove_effects - inject hunt_events.set_runner_active - inventory set o:compass slot:1 # When compass clicked track_runner: # Check if runner online and still a runner (not died or left) - if ! || !: - flag hunt.tracking: - define tracking - define hunterEnv - if <[tracking]> == null: # No online runner to track - run hunt_set_actionbar "def:No runner to track" - inject hunt_events.reset_compass_location - stop - else if <[tracking].location.world.name> != : # Runner in different world # Check for runner's last used portal - if <[tracking].has_flag[hunt.lastportal.<[hunterEnv]>]>: - define trackingLocation <[tracking].flag[hunt.lastportal.<[hunterEnv]>]> - define distance ].round> - run hunt_set_actionbar "def:Distance: <[distance]>m Hunting: <[tracking].name>'s portal" - inject hunt_events.set_compass_location - stop - if <[hunterEnv]> == NORMAL: # Shouldn't ever get to here since the runner must have used a portal - run hunt_set_actionbar "def:Hmm, can't find any trace of the runner!" - inject hunt_events.reset_compass_location - stop - else: # Hunter in Nether or End, and runner didn't leave through a portal, so find another runner to track - define found false - foreach as:runner: - if <[runner].location.world.name> == : - flag hunt.tracking:<[runner]> - define tracking <[runner]> - define found true - if !<[found]>: - choose <[hunterEnv]>: - case NETHER: - run hunt_set_actionbar "def:No runner in The Nether to track" - case THE_END: - run hunt_set_actionbar "def:No runner in The End to track" - default: - run hunt_set_actionbar "def:No runner in <[hunterEnv]> to track" - inject hunt_events.reset_compass_location - stop # Runner in same world - define trackingLocation <[tracking].location> - define distance ].round> - run hunt_set_actionbar "def:Distance: <[distance]>m Hunting: <[tracking].name>" - inject hunt_events.set_compass_location # Set compass to tracking location set_compass_location: - if <[hunterEnv]> == NORMAL: - compass <[trackingLocation]> - foreach as:slot: - inventory adjust slot:<[slot]> lodestone_tracked:false - inventory adjust slot:<[slot]> lodestone_location: - else: - foreach as:slot: - inventory adjust slot:<[slot]> lodestone_tracked:false - inventory adjust slot:<[slot]> lodestone_location:<[trackingLocation]> # Reset compass reset_compass_location: - if <[hunterEnv]> == NORMAL: - compass reset - foreach as:slot: - inventory adjust slot:<[slot]> lodestone_tracked:false - inventory adjust slot:<[slot]> lodestone_location: - else: - foreach as:slot: - inventory adjust slot:<[slot]> lodestone_tracked:true check_for_winner: - if == 0: - title "title:The Hunter Wins!" fade_in:0 stay:5 fade_out:2 targets: - wait 5 - inject hunt_events.reset_regen # Reset flags, teleport players to endlobby & regenerate hunt worlds reset_regen: - foreach as:__player: - flag hunt:! - sidebar remove - teleport hunt_endlobby - define wasActive - flag server hunt: - foreach as:__player: - flag hunt:! # Regenerate worlds - if <[wasActive]> && : - inject hunt_events.regen_worlds - flag server hunt:! regen_worlds: - define seed - foreach key:env as:world: - wait - execute as_server "mv delete <[world]>" - execute as_server "mv confirm" - ~createworld <[world]> environment:<[env]> seed:<[seed]> - if <[env]> == THE_END: - define env END - execute as_server "mv import <[world]> <[env]>" - execute as_server "mv modify set respawnWorld <[world]>" - execute as_server "mv modify set difficulty <[world]>" - execute as_server "mv modify set gamemode survival <[world]>" - execute as_server "mv modify set memory false <[world]>" hunt_set_actionbar: type: task debug: false definitions: text|targets script: - if !<[targets].exists>: - define targets ]> - foreach <[targets]> as:__player: - flag hud.off expire:1s - actionbar <[text]> targets:<[targets]> hunt_enters_portal: type: task debug: false # runner or hunter definitions: which teleport: - define spawn ].spawn_location> - teleport <[spawn]> - wait 1t # Check world in case Multiverse has teleported player to last_location in another dimension - if != NORMAL: - teleport <[spawn]> script: - if : - narrate "<&[error]>The hunt world is regenerating, please try again in a few seconds" - stop - if !: # Hunt not active, so player can join at spawn - flag hunt:=1]> - inject hunt_enters_portal.teleport # Set world time & weather - adjust time:1000 - weather sunny # Need to wait 2t before setting gamemode as Multiverse sets it after 1t delay - wait 2t - adjust gamemode:adventure # Clear inventory, effects, heal & feed - inventory clear - adjust health:20 - feed amount:20 - adjust remove_effects - if !: # Start/continue joining phase - flag server hunt.waiting.joining - else: # Already in head start phase - if <[which]> == runner: - inject hunt_events.set_runner_active - else: - inject hunt_events.set_hunter_frozen - else: # Hunt active, new player must spectate - flag hunt:]> - inject hunt_enters_portal.teleport # Need to wait 2t before setting gamemode as Multiverse sets it after 1t delay - wait 2t - adjust gamemode:spectator - title "title:Hunt already started" "subtitle:You are spectating" fade_in:0 stay:5 fade_out:2 - wait 5 hunt_build_player_list: type: procedure debug: false definitions: players|heading script: - define nameList <[heading]>:]> - if <[players].size> == 0: - define "nameList:->: None!" - else: - foreach <[players]> as:__player: - define "nameList:->: " - determine <[nameList]> hunt_team_chat_runner: type: format format: [Runner Chat] <[name]> » <[text]> hunt_team_chat_hunter: type: format format: [Hunter Chat] <[name]> » <[text]> hunt_command: type: command debug: false name: hunt description: Speedrunners vs Hunters command usage: /hunt start|leave|join|agree|reset tab completions: 1: start|leave|join|agree|reset permission: custom.hunt show_docs: - narrate <&sp.repeat[60].strikethrough> - narrate "/hunt start [<<>head start time<>>] Start the hunt" - narrate "/hunt leave Leave the hunt and return to hub" - narrate "/hunt join Join an already active hunt" - narrate "/hunt agree Agree to another player joining" - if : - narrate "/hunt reset Reset the hunt" - narrate <&sp.repeat[60].strikethrough> - stop no_permission: - narrate "<&[error]>You don't have permission to use this command" script: - if == 0: - inject hunt_command.show_docs # Check that player is in one of the hunt worlds - if != reset && !]>: - narrate "<&[error]>You can only run this command when in the hunt" - stop - choose : # /hunt start - case start: # Override configured head_start_time time? - if == 2: - define head_start_time - if !<[head_start_time].matches_character_set[0123456789]>: - inject hunt_command.show_docs - if <[head_start_time]> > 300: - narrate "<&[error]>The maximum head start time is 300 seconds" - stop - else if == 1: - define head_start_time - else: - inject hunt_command.show_docs # Check that conditions are valid for command to proceed - if : - narrate "<&[error]>The hunt has already started" - stop - define runners - define hunters - if <[runners].size> == 0: - narrate "<&[error]>There must be at least 1 runner to start" - stop - if <[hunters].size> == 0: - narrate "<&[error]>There must be at least 1 hunter to start" - stop # Start head start phase of hunt - flag server hunt.waiting:]> - foreach <[runners]> as:__player: - inject hunt_events.set_runner_active - foreach <[hunters]> as:__player: - inject hunt_events.set_hunter_frozen # Display help info for hunters - if <[head_start_time]> < 6: # Min time to show help info - define head_start_time 6 - define title "Use the Compass" - define subtitle "Right click to track a runner" - if <[runners].size> == 1: - title title:<[title]> subtitle:<[subtitle]> fade_in:0 stay:<[head_start_time]> fade_out:1 targets:<[hunters]> - else: - define subtitle2 "Left click to switch runners" - repeat <[head_start_time].div[6].round_down>: - title title:<[title]> subtitle:<[subtitle]> fade_in:0 stay:3 fade_out:1 targets:<[hunters]> - wait 3 - title title:<[title]> subtitle:<[subtitle2]> fade_in:0 stay:3 fade_out:1 targets:<[hunters]> - wait 3 # In case players left - if !: - stop # /hunt leave - case leave: - if != 1: - inject hunt_command.show_docs - flag hunt:! - sidebar remove - title title:. fade_in:0 stay:0 fade_out:0 - teleport hunt_endlobby - if == 0: - inject hunt_events.reset_regen - else: - inject hunt_events.check_for_winner # /hunt join - case join: - if != 1: - inject hunt_command.show_docs - if ! || == dead: - narrate "<&[error]>You must first leave the hunt before rejoining" - else: - flag hunt.request # /hunt agree - case agree: - if != 1: - inject hunt_command.show_docs - if ! && !: - narrate "<&[error]>Only active players can agree" - else if > 0: - flag hunt.agree # /hunt reset - case reset: - if != 1: - inject hunt_command.show_docs - if !: - inject hunt_command.no_permission - stop - inject hunt_events.reset_regen - default: - inject hunt_command.show_docs