KillLB.sk

Created by AsuDev

Just so you know, we don't know the file format for every file. If it's just a bunch of random characters, it's probably a .zip or .jar.

# #########################################################################################

# Kill Leaderboard Examples by AsuDev
# Requirements: Skript for your version, Skript-Mirror 2.0.0+, SkQuery-Lime
# Versions: 1.8.8 - 1.14.4

# #########################################################################################

# EXTRA INFORMATION

# The sorting method used in this script can sort up to 50,000 values a second!
# This script is just a collection of examples of making a toplist!
# You may edit this script however you want and you can make whatever you want with it. 

# THIS CURRENT VERSION IS FOR SKRIPT 2.3.6 AND LOWER! TO MAKE IT COMPATIBLE WITH THE HIGHER
# VERSIONS, YOU MUST CHANGE THE SEND MESSAGE TO "send formatted" instead of "send" for the
# json formatting. This will not throw an error, but the json formatting will be messed up
# when using Skript 2.3.7+

# #########################################################################################

# CREDITS

# AsuDev for making the script and all of the examples.
# EWS for making this sorting algorithm for skript variables in skript-mirror

# #########################################################################################

# List of java class imports
import:
    java.util.ArrayList
    java.util.Collections
    java.util.Map$Entry
    ch.njol.skript.variables.Variables
    java.lang.System
    org.json.simple.JSONValue

# These options are for if you are going to actually use this script instead
# of taking from it and making your own thing. You can experiment with these
# and use or change whatever you want.
options:
	updaterBreak: 15 minutes # How often to update the leaderboard automatically
	indexesPerPage: 10 # Amount of players to show per page in the toplist
	FirstPlaceFormat: &6&l # Format of first place
	SecondPlaceFormat: &b&l # Format of second place
	ThirdPlaceFormat: &2&l # Format of third place
	OtherPlaceFormat: &3 # Format of everything else
	Splitter: - # What to split the player data with when sorting players
	OutputFormat: @place-@index-@value # The output for sorting players / Make sure splitter is correct with this
	ChatBorder: &8&m----------------------------------------------------

	# @StartIndex is the value the list starts at / @EndIndex is the where the page ends / @LastUpdated is the last time the leaderboard was updated
	ChatOutputHeader: &eTop Killers &8- &7Showing &6@StartIndex-@EndIndex &8- &7Last Updated: &b@LastUpdated ago # The header of the leaderboard command
	# @place is the place the player is in / @index is the player name / @value is the amount of kills they have
	ChatOutputFormat: &8➵ @place &7@index &8- &a@value Kills # The chat output for the leaderboard command for each player

# #########################################################################################

# For null values when sorting
# Gets the name of an offline player based on UUID
function getOfflinePlayerName(uuid: text) :: string:
	replace all "-" with "" in {_uuid}
	set {_jsonInfo} to text from "https://api.mojang.com/user/profiles/%{_uuid}%/names"
	set {_nameValue} to JSONValue.parseWithException({_jsonInfo})
	set {_player} to {_nameValue}.get({_nameValue}.size()-1).toString()
	set {_nameObject} to JSONValue.parseWithException({_player})
	return {_nameObject}.get("name").toString()

# #########################################################################################

# EXPRESSIONS

# Expression used to replace multiple values within a string at once.
# Original version made by EWS
expression replace values %strings% with %strings% in %string%:
    get:
        set {_string} to expression-3
        loop expressions-1:
            add 1 to {_index}
            replace all "%loop-value%" with "%{_index}th element of expressions-2%" in {_string}
        return {_string}

# Expression used for sorting. Uses java collections for sorting and is very fast.
# Original version made by EWS, edited by AsuDev.
plural expression [the] (1¦(highest|top)|2¦(lowest|last)) %integer% values of %objects% [formatted] as %string%:
    get:
        set {_list} to new ArrayList(Variables.getVariable(raw expressions-2.getName().toString(event), event, raw expressions-2.isLocal()).entrySet())
        {_list}.sort(Entry.comparingByValue())
        if parse mark = 1:
            Collections.reverse({_list})
        loop ...{_list}:
         #   "%loop-value.getValue()%" is not "0"
            add 1 to {_index}
            if "%(loop-value.getKey()) parsed as offline player%" is not "null":
                set {_sorted::%{_index}%} to replace values "@place", "@index" and "@value" with "%{_index}%", "%(loop-value.getKey()) parsed as offline player%" and "%loop-value.getValue()%" in expression-3
            else:
                loop {CachedOfflinePlayers::*}:
                    if loop-value-2 contains "^%loop-value-1.getKey()%":
                        set {_v::*} to loop-value-2 split by "^"
                        set {_name} to {_v::1}
                        set {_sorted::%{_index}%} to replace values "@place", "@index" and "@value" with "%{_index}%", {_name} and "%loop-value-1.getValue()%" in expression-3
                        set {_continue} to false
                if {_continue} is not set:
                    set {_name} to getOfflinePlayerName(loop-value.getKey())
                    set {_sorted::%{_index}%} to replace values "@place", "@index" and "@value" with "%{_index}%", {_name} and "%loop-value.getValue()%" in expression-3
                    message "&cError when getting name from uuid '%loop-value.getKey()%'. Retrieving name from Mojangs database may severely slow down the sorting time! This should not occur with this uuid again as data is now stored for this uuid." to console
                    add "%{_name}%^%loop-value-1.getKey()%" to {CachedOfflinePlayers::*}
                delete {_continue} and {_name} and {_v::*}
            {_index} = expression-1
            stop loop
        return {_sorted::*}

# #########################################################################################

# EXAMPLE EVENTS / FUNCTIONS

# Updates the leaderboard
function updateKillsLeaderboard(senders: objects):
	delete {KillLB::*}
	set {_timeNow} to ceil(System.currentTimeMillis())
	loop {_senders::*}:
		send "&7Updating the Kill leaderboard..." to loop-value
	set {KillLB::*} to the highest (size of {Kills::*}) values of {Kills::*} as "{@OutputFormat}"
	set {_timeAfter} to (ceil(System.currentTimeMillis()) - {_timeNow})
	loop {_senders::*}:
		send "&7Update complete. The update took &b%{_timeAfter}%ms&7." to loop-value
	set {LastLeaderboardUpdate} to ceil(System.currentTimeMillis())

# Gets the ordinal string of an integer
function ordinalNumber(i: integer) :: string:
	set {_lastDigit} to mod({_i}, 10)
	set {_lastTwoDigits} to mod({_i}, 100)
	if {_lastTwoDigits} is between 10 and 20:
		return "%{_i}%th"
	else if {_lastDigit} is 1:
		return "%{_i}%st"
	else if {_lastDigit} is 2:
		return "%{_i}%nd"
	else if {_lastDigit} is 3:
		return "%{_i}%rd"
	else:
		return "%{_i}%th"		

# Gets a player's place on the leaderboard and formats it into an ordinal ranking format
function getKillPlacing(p: offline player) :: string:
	loop {KillLB::*}:
		if loop-value contains "-%{_p}%-":
			set {_placing} to loop-index parsed as integer
			if {_placing} is 1:
				return "{@FirstPlaceFormat}%ordinalNumber(1)%."
			else if {_placing} is 2:
				return "{@SecondPlaceFormat}%ordinalNumber(2)%."
			else if {_placing} is 3:
				return "{@ThirdPlaceFormat}%ordinalNumber(3)%."
			else:
				return "{@OtherPlaceFormat}%ordinalNumber({_placing})%."
			stop

# Converts an integer into an ordinal ranking format by integer
function ordinalNumberRankFormat(placing: integer) :: string:
	if {_placing} is 1:
		return "{@FirstPlaceFormat}%ordinalNumber(1)%."
	else if {_placing} is 2:
		return "{@SecondPlaceFormat}%ordinalNumber(2)%."
	else if {_placing} is 3:
		return "{@ThirdPlaceFormat}%ordinalNumber(3)%."
	else:
		return "{@OtherPlaceFormat}%ordinalNumber({_placing})%."

# Example calculation of KDR of a player
function calculateKDR(p: offline player) :: string:
	set {_uuid} to uuid of {_p}
	set {_kills} to {Kills::%{_uuid}%}
	set {_deaths} to {Deaths::%{_uuid}%}
	if {_kills} and {_deaths} is 0:
		return "&60"
	if {_kills} is not 0:
		if {_deaths} is 0:
			add 1 to {_deaths}
	return "&6%{_kills}/{_deaths}%"

# Formats seconds into a nice, more readable time.
function formatTime(i: number) :: string:
    set {_s} to "s"
    set {_days} to floor({_i} / 86400)
    set {_hours} to floor({_i} / 3600)
    set {_minutes} to floor((mod({_i}, 3600)) / 60)
    set {_seconds} to floor(mod({_i}, 3600))
    set {_seconds} to floor(mod({_seconds}, 60))
    set {_format} to ""
    if {_days} is not 0:
        set {_format} to "%{_days}%d "
    if {_hours} is not 0:
        set {_format} to "%{_format}%%{_hours}%h "
    if {_minutes} is not 0:
        set {_format} to "%{_format}%%{_minutes}%m "
    return "%{_format}%%{_seconds}%%{_s}%"

# Example scheduler to update the leaderboard
every {@updaterBreak}:
	updateKillsLeaderboard(console)
	loop all players:
		if loop-player has permission "boardupdator":
			message "&7The Kills leaderboard has just been updated." to loop-player

# Example, set the players stats when they join
on join:
	{Kills::%uuid of player%} is not set
	set {Kills::%uuid of player%} to 0
	set {Deaths::%uuid of player%} to 0

# Example event to add kills/deaths to a player
on death of player:
	add 1 to {Deaths::%uuid of victim%}
	attacker is a player
	add 1 to {Kills::%uuid of attacker%}

# #########################################################################################

# EXAMPLE COMMANDS

# Example, manual command for updating the kills leaderboard
command /updateKillsLeaderboard:
	permission: leaderboard.update
	permission message: &cYou do not have permission to execute this command.
	trigger:
		updateKillsLeaderboard(player and console)

# A cache for offline players in case sorting returns a null value
command /cachedofflineplayers:
	aliases: coplayers
	permission: cachedview.admin
	trigger:
		message "&6Cached Offline Players"
		loop {CachedOfflinePlayers::*}:
			set {_values::*} to loop-value split by "^"
			message "&8- &b%{_values::1}% &8(&e%{_values::2}%&8)"

# Example, K/D stats for a specified player
command /kills [<offline player=%player%>]:
	aliases: deaths, kdr
	trigger:
		if {Kills::%uuid of arg 1%} and {Deaths::%uuid of arg 1%} is set:
			message "&e%arg 1%'s K/D Stats"
			message "&7Kills: &a%{Kills::%uuid of arg 1%}%"
			message "&7Deaths: &c%{Deaths::%uuid of arg 1%}%"
			message "&7KDR: &a%{Kills::%uuid of arg 1%}%&7/&c%{Deaths::%uuid of arg 1%}% &7= %calculateKDR(arg 1)%"
			if getKillPlacing(arg 1) is not set:
				message "&7Leaderboard: &3Not on leaderboard."
			else:
				message "&7Leaderboard: %getKillPlacing(arg 1)%"
		else:
			message "&cThat player has no stats logged for kills or deaths."

# Example, Get a player's placing index
command /placing [<offline player=%player%>]:
	trigger:
		if {Kills::%uuid of arg 1%} and {Deaths::%uuid of arg 1%} is set:
			if getKillPlacing(arg 1) is not set:
				message "&e%arg 1% &7is currently not on the kill leaderboard."
			else:
				message "&e%arg 1% &7is currently %getKillPlacing(arg 1)% &7on the kill leaderboard."
		else:
			message "&cThat player has no stats logged for kills or deaths."

# Example, Get a player at a specified placing
command /place [<integer=1>]:
	trigger:
		if arg 1 is less than or equal to 0:
			message "&cInvalid placing. Please specify a number greater than 0."
			stop
		if size of {KillLB::*} is less than arg 1:
			message "&cInvalid placing. The number you specified exceeded the total amount of players in the leaderboard. The max is currently %size of {KillLB::*}%."
			stop
		set {_placing::*} to {KillLB::%arg 1%} split by "{@Splitter}"
		set {_p} to {_placing::2} parsed as offline player
		set {_placing} to ordinalNumberRankFormat({_placing::1} parsed as integer)
		message "&7The player in %{_placing}% &7place on the kill leaderboard is &e%{_p}%&7."

# Example, Leaderboard command
command /topkills [<integer=1>]:
	aliases: topk, topkiller, topkillers, toplb, topleaderboard
	trigger:
		if arg 1 is less than or equal to 0:
			message "&cYou must specify a page over 0."
			stop
		set {_lastUpdated} to formatTime((ceil(System.currentTimeMillis()) - {LastLeaderboardUpdate}) / 1000)
		set {_showPerPage} to {@indexesPerPage}
		set {_indexesToShow} to arg 1 * {_showPerPage}
		set {_startingIndex} to {_indexesToShow} - ({_showPerPage} - 1)
		set {_maxpage} to ceil(size of {KillLB::*} / {_showPerPage})
		if arg 1 is {_maxpage}:
			set {_showingMax} to size of {KillLB::*}
		else:
			set {_showingMax} to {_indexesToShow}
		if {KillLB::%{_startingIndex}%} is not set:
			message "&cThe page you specified wasn't found. Valid pages are 1-%{_maxpage}%."
			stop
		message "{@ChatBorder}"
		message replace values "@StartIndex", "@EndIndex" and "@LastUpdated" with "%{_indexesToShow} - ({_showPerPage} - 1)%", "%{_showingMax}%" and "%{_lastUpdated}%" in "{@ChatOutputHeader}"
		message "{@ChatBorder}"
		#message ""
		loop {KillLB::*}:
			if (loop-index parsed as integer) is between {_startingIndex} and {_indexesToShow}:
				set {_info::*} to loop-value split by "{@Splitter}"
				set {_placing} to ordinalNumberRankFormat({_info::1} parsed as integer)
				set {_p} to {_info::2} parsed as offline player
				set {_uuid} to uuid of {_p}
				set {_format} to replace values "@place", "@index", "@value" with "%{_placing}%", "%{_info::2}%", "%{_info::3}%" in "{@ChatOutputFormat}"
				send " <tooltip:&e%{_p}%'s K/D Stats%nl%&7Kills: &a%{Kills::%{_uuid}%}%%nl%&7Deaths: &c%{Deaths::%{_uuid}%}%%nl%&7KDR: &a%{Kills::%{_uuid}%}%&7/&c%{Deaths::%{_uuid}%}% &7= %calculateKDR({_p})%>%{_format}%&r" to player																																														
				delete {_info::*} and {_placing} and {_p} and {_uuid} and {_format}
		#message ""
		message "   <tooltip:&eClick to go back a page.><cmd:/topkills %arg 1 - 1%>&7←&r                                                                    <tooltip:&eClick to go forward one page.><cmd:/topkills %arg 1 + 1%>&7→&r"
		message "{@ChatBorder}"		

# Example, Admin command for manipulating stats of players
command /killsadmin [<text>] [<text>] [<offline player>] [<integer>]:
	permission: killsadmin
	permission message: &cYou do not have permission to use this command.
	aliases: kadmin, killadmin
	trigger:
		if arg 1 is not set:
			message "&fSets the amount of kills or deaths a player has."
			message "&f/killsadmin set kills,deaths <player> <amount>"
			message "&fAdds to the kills or deaths a player has."
			message "&f/killsadmin add kills,deaths <player> <amount>"
			message "&fRemoves from the amount of kills or deaths a player has."
			message "&f/killsadmin remove kills,deaths <player> <amount>"
			stop
		if arg 1 and arg 2 and arg 3 and arg 4 is set:
			if arg 2 is "kills" or "deaths":
				if arg 4 is greater than or equal to 0:
					if arg 1 is "set":
						if {%arg 2%::%uuid of arg 3%} is not set:
							set {Kills::%uuid of arg 3%} to 0
							set {Deaths::%uuid of arg 3%} to 0
						set {%arg 2%::%uuid of arg 3%} to arg 4
						message "&7You set the &c%arg 2% &7value of &b%arg 3% &7to &e%arg 4%&7."
					else if arg 1 is "add":
						if {%arg 2%::%uuid of arg 3%} is not set:
							set {Kills::%uuid of arg 3%} to 0
							set {Deaths::%uuid of arg 3%} to 0
						add arg 4 to {%arg 2%::%uuid of arg 3%}
						message "&7You added &e%arg 4% &7to the &c%arg 2% &7value of &b%arg 3%&7."
					else if arg 1 is "remove":
						if {%arg 2%::%uuid of arg 3%} is not set:
							set {Kills::%uuid of arg 3%} to 0
							set {Deaths::%uuid of arg 3%} to 0
						remove arg 4 from {%arg 2%::%uuid of arg 3%}
						message "&7You removed &e%arg 4% &7from the &c%arg 2% &7value of &b%arg 3%&7."
						if {%arg 2%::%uuid of arg 3%} is less than 0:
							set {%arg 2%::%uuid of arg 3%} to 0
					else:
						make player execute command "killsadmin"
				else:
					message "&cYou must specify an amount above or equal to 0."
			else:
				message "&cYou must specify either kills or deaths."
		else:
			make player execute command "killsadmin"			

# #########################################################################################