#List of all players to ever join the server (including offline, and preferably `ServerPlayer[]`)
1 messages · Page 1 of 1 (latest)
Once your ticket has been resolved, please close it with </ticket close:1054771505520717835> command!
you could potentially create a leaderboard that tracks all players joining
but interfacing with that through kubeJS might be a challenge
i found a method to get a ServerPlayer object, from the server object, and a UUID
if you know how to get a list of UUIDs
you could on the player join event, grab the UUID with event.player.uuid
const $EntityHelper = Java.loadClass('ovh.corail.tombstone.helper.EntityHelper')
let offlinePlayer = $EntityHelper.getOfflinePlayer(server, uuid)
and store it in a json file
hm, i could get a list of teams from FTBTeams, and get their owners
wait,, no... what if ppl join other ppls teams
wait you're doing playtime
create a leaderboard
through vanilla
that tracks playtime
then just grab data from there
that's surely a better way of doing this
because vanilla mc supports that already
i'm too far into this shit to go that route now 
i have like 16h in creating this damn command, and im not about to quit now
just make the command pull from that leaderboard ¯_(ツ)_/¯
PlayerEvents.loggedIn(e => {
let uuid = e.player.getUUID().toString()
let players = JsonIO.read('kubejs/playerlist.json')
if (!players.includes(uuid)) {
players.push(uuid)
JsonIO.write('kubejs/playerlist.json')
}
})
this?
Kubejs has a feature known as beans which allows you to make scripts a tad more readable.
Anything getXy() can be gotten with xy, anything setXy(value) can be set with xy = value and anything isXy() can be checked with just xy.
This allows us to shorten our code! For example, to get a list of all online players you can do: event.getServer().getPlayers(). With beans this can be shortened to event.server.players!
Note that get and is beans only work if the method has no parameters. This means a method like getHeldItem(InteractionHand hand) cannot be shortened to heldItem.
For set the method needs to have a single parameter.
its just syntactic sugar yeh
just a bit easier to read
aprt from that, it should work
well I mean it'll work either way
or at least it should
it should work, and rhino, rarely mix
xD
you should have seen earlier, it was complaining that new String(str), and str.toString() wasn't a string
cannot call method includes of null...
i literally initialised playerlist.json to []... why is it null
oh.. /kubejs/server_scripts/playerlist.json
wait...
JsonIO.read('usernamecache.json')
??
yeh, it works lol
and it has a backlog list of everyone who's ever joined, i don't have to wait for them to join again to populate it
"The choice of java method is ambiguous" how do i tell it to pick one?
i think i found it
sec, reloading to test
why it no find it?
it found it on single player
but not on the server
same modpack, they both have corail tombstone
wait... i see why it may be... java.lang.String not string
it is the wrapper
wym?
Rhino/KubeJS wraps String to UUID, and it gets confused when it have both
so, there is no fix?
also I'm suspicious OFFLINE player doesn't mean offline, it means unauthenticated via Microsoft Account player 
hm... that's true...
corail is closed source, so no way to tell
unless i manage to get it to run
no
You can call the method with the method signature to resolve the ambiguous call, as long as it's not a minecraft method that should still work
yeah, i did that, but i put java.lang.string not java.lang.String, changed it and restarted, waiting to load in to see if it works
yehhh still no
const $EntityHelper = Java.loadClass('ovh.corail.tombstone.helper.EntityHelper')
const getOfflinePlayer = $EntityHelper['getOfflinePlayer(net.minecraft.server.MinecraftServer, java.lang.String)']
``` this is how u do it, right?
i believe it is a player that is actually offline, because that seems like the command to give a player who is offline, their grave back, so corail would have to be able to get a serverplayer of an offline player
source.getServer().getProfileCache().getAsync(playerName, profileOpt -> {
source.getServer().executeIfPossible(() ->
profileOpt.ifPresentOrElse(profile -> tpOffline(source, profile.getId(), level, dest),
() -> source.sendFailure(Component.literal("Unknown player: " + playerName))
)
);
});
``` this is how FTBEssentials gets an instance of a player who is offline, possible to replicate it?
oh wait, ```js
private static int tpOffline(CommandSourceStack source, UUID playerId, ServerLevel level, Coordinates dest) {
MinecraftServer server = source.getServer();
Path playerDir = server.getWorldPath(LevelResource.PLAYER_DATA_DIR);
File datFile = playerDir.resolve(playerId + ".dat").toFile();
if (server.getPlayerList().getPlayer(playerId) != null) {
source.sendFailure(Component.literal("Player is online! Use regular /tp command instead"));
return 0;
}
try {
CompoundTag tag = NbtIo.readCompressed(datFile);
Vec3 vec = dest.getPosition(source);
ListTag newPos = new ListTag();
newPos.add(DoubleTag.valueOf(vec.x));
newPos.add(DoubleTag.valueOf(vec.y));
newPos.add(DoubleTag.valueOf(vec.z));
tag.put("Pos", newPos);
tag.putString("Dimension", level.dimension().location().toString());
File tempFile = File.createTempFile(playerId + "-", ".dat", playerDir.toFile());
NbtIo.writeCompressed(tag, tempFile);
File backupFile = new File(playerDir.toFile(), playerId + ".dat_old");
Util.safeReplaceFile(datFile, tempFile, backupFile);
source.sendSuccess(() -> Component.literal(String.format("Offline player %s moved to [%.2f,%.2f,%.2f] in %s",
playerId, vec.x, vec.y, vec.z, source.getLevel().dimension().location())), false);
return 1;
} catch (IOException e) {
source.sendFailure(Component.literal("Can't update dat file: " + e.getMessage()));
return 0;
}
}
i have the UUID, and username
no filesystem access
That’s almost correct, but you don’t pass the JSON that you are writing to.
Oh I needed to scroll down further
yeah, i figured that out ages ago lmao
ah, the filesystem access is just ftbessentials, editing the .dat file to teleport the player, yeah nevermind, that's not what i need
.getAsync( I think this will hang kubejs, we are having problem when running async stuff via rhino
The hardest part about this issue, is the lack of ability to file walk the player directory
You can read any JSON file, but it assumes you know the name of the JSON file first
i do, because i get the UUIDs from usernamecache, then read /world/stats/<uuid>.json
in the end what do you need to do/check stuff on offline players?
stats.minecraft:play_time
That’s pretty simple
which is in the stats/<uuid>.json
ah, I see, I don't mess with dedicated servers enough
Just make sure to be efficient with your file IO
Cache anyone’s offline playtime, and only load files that aren’t currently cached.
uh, how do? xd
how to check if a player with a given UUID is online or not then?
If the player is within the map, you assume you have the most recent playtime
And the first thing you do when running the command is updating the playtime of all online players
server.getPlayer(UUID) if return null it is prob offline, no?
idk... will it? xd
Possibly, or it may throw an error
and try catch is broken?
Yeah
it returns null
I am happy to see someone still using KJS Offline. I spent way too god danm long on that mod 
I do use it too, at least used to use
One day I will finish reworking it
But generics are such a pain
I think I finally have a firm grasp on it because of WrapperGen, but it’s still a PITA to work with
I think I have it backwards
PlayerList may be the best bet here
Danm it, now I have the sudden urge to work on KJS Offline right before my plane flight
But I know I have no time to do that
wait
if i cache the playtime of an offline player, and i do the following:
PlayerA is offline, PlayerB uses /playtime top: gets correct playtime for PlayerA
PlayerA comes online, nobody uses /playtime top
PlayerA plays for 1 hour, goes offline
PlayerB uses /playtime top again: PlayerA's playtime is now 1 hour behind
the longer PlayerA plays, between statement 0, and 3, the worse it gets
Player A is offline.
Player B uses /playtime top.
Playtime top scans all, not currently cached, player playtimes and loads them.
It also grabs the current player’s playtime.
Then displays the top from there.
If any player logs out, you cache their playtime.
okay, gimme... some minutes lemme try to implement what i think you're saying
i have to refactor my entire codebase
Make sure to use functions
So that you can reduce the dependencies in your code on current implementation, and instead rely on functionality needed for each command
i already am
Good
but all my functions were inside ServerEvents.commandRegistry
i now need them in both ServerEvents.commandRegistry, and PlayerEvents.loggedOut
I would just define the functions outside the event hooks
so i have to move everything
No need to nest them inside the event hooks
Just add arguments for server, player, etc…
Anyways, I got a plane to ride, good luck with the playtime leaderboard
https://paste.gg/p/anonymous/483f1952d75040b9a47c45f4b5049f76 does this look right?
i do apologise for lack of comments 
im terrible at that
but atleast i (mostly) have typehinting
Why are you using commands for tellraw
I swear there's a tellraw function
I think the standard event.server.tell allows for formatting
i asked someone how to do tellraw and they told me this
cos i prefer formatting it as json, since im used to it
ah... well, i already have this 
use single quotes
thats not a template string though
unless I'm being stupid they do the same thing
there's a chance I'm getting languages confused
oh yep I am
'' != templateString
nevermind
xd
I'm getting langs confused
it okey, happens to the best of us
i typed lua if (cond) then end in my .c file in a uni workshop many a time
out of habit
server.runCommandSilent(`/tellraw ${playerName} [{"text": "${username} has played for: "}, {"text": "${timeString}", "color": "green"}]`)
can be replaced with
server.tell([{text: `${username} has played for: `}, {text: timeString, color: "green"}])
infact i did it a bunch while writing this code ur looking at lmao
is that lua syntax
yeah, i was writing stuff for computercraft literally all night before i went to uni in the morning (without sleep) xd
(lua is my least favourite language, just because of arrays etc starting at 1, which has always drived me up the wall)
yeah... but,, computercraft
I know its a stupid grudge, its just my autistic brain hates it sooo much
anyway this should work
and its more performant, cleaner and easier to read
yes, commands are laggy
not the individual player
uhh there's a way to specifiy player
wait 1 sec
and I screwed up something
it would probably just be player.tell
oh I just forgot the brackets
^
server.runCommandSilent(`/tellraw ${playerName} [{"text": "${username} has played for: "}, {"text": "${timeString}", "color": "green"}]`)
can be replaced with
player.tell([{text: `${username} has played for: `}, {text: timeString, color: "green"}])
this should be the solution
just tested it myself
server.runCommandSilent(`/tellraw ${playerName} [{"text": "You", "color": "gold"}, {"text": " have been playing for: ${timeString}", "color": "green"}]`)
replaced with
player.tell([{text: "You", color: "gold"}, {text: ` have been playing for: ${timeString}`, color: "green"}])
server.runCommandSilent(`/tellraw ${playerName} [{"text": "${targetName}", "color": "gold"}, {"text": " has been playing for: ${timeString}", "color": "green"}]`)
replaced with
player.tell([{text: targetName, color: "gold"}, {text: ` has been playing for: ${timeString}`, color: "green"}])
also any time you are doing .getUsername you can use beans and replace it with .username
ohh right gotcha
you did
player.tell([{"text": `${username} has played for: `}, {"text": timeString, "color": "green"}])
instead of
player.tell([{text: `${username} has played for: `}, {text: timeString, color: "green"}])
I don't think so but the syntax highlighting will be better
Kubejs has a feature known as beans which allows you to make scripts a tad more readable.
Anything getXy() can be gotten with xy, anything setXy(value) can be set with xy = value and anything isXy() can be checked with just xy.
This allows us to shorten our code! For example, to get a list of all online players you can do: event.getServer().getPlayers(). With beans this can be shortened to event.server.players!
Note that get and is beans only work if the method has no parameters. This means a method like getHeldItem(InteractionHand hand) cannot be shortened to heldItem.
For set the method needs to have a single parameter.
¯_(ツ)_/¯
i'm specifically looking to see if i correctly implemented what pie was talking ab here in
let playTimeCache = {}
/**
* @param {net.minecraft.server.MinecraftServer} param0
*/
let updatePlaytimeCache = (server) => {
let playerUUIDs = JsonIO.read('usernamecache.json')
let playtimes = {}
for (let uuid in playerUUIDs) {
let username = playerUUIDs[uuid]
if (server.getPlayer(uuid) === null) {
if (!playTimeCache.contains(username)) {
let stats = JsonIO.read('world/stats/' + uuid + '.json')
playTimeCache[username] = ticksToPlaytime(stats['stats']['minecraft:custom']['minecraft:play_time'])
}
}
else {
playTimeCache[username] = getPlaytime(player)
}
}
}
```and
```js
/**
* @param {com.mojang.brigadier.context.CommandContext} param0
* @return {int} return0
*/
let playtimeLeaderboard = (ctx) => { // send the player `reply` a sorted list of the playtimes of everyone in the /world/stats directory
// TODO: get stats of all players to ever join the server
let server = ctx.source.server
let player = ctx.source.player
updatePlaytimeCache(server)
for (let username in playTimeCache) {
let playtime = playTimeCache[username]
let timeString = getTimeString(playtime)
player.tell([{text: `${username} has played for: `}, {text: `${timeString}`, color: "green"}])
}
return 1
}
```and
```js
PlayerEvents.loggedOut(e => {
let username = e.player.username
if (playTimeCache.contains(username)) {
delete playTimeCache[username]
}
})
isn't that the opposite of what pie was saying?
updating the cache when they logged out
rather than deleting it when thy log out
*they
i make sure the cache is up to date whenever the command is run
and if they have logged out, delete them from the cache, so that the next time the command is ran, the cache is updated
i think its equivalent, just jigged around a bit
Pretty sure the point is that IO is slow, and should be minimized
since, when i update the cache, i only update it if its not already cached
unless I'm wrong pie was saying, grab playtime for all online players, if they are offline grab from a cache
when they log out
update the cache
not update it when the function is next called
yeah, updatePlaytimeCache only does fileIO when there is an offline player that isn't in the cache
which happens when a player logs out
getPlaytime(player) for an online player isnt doing file io
Can you not just get the stats of the player before the log out and save them?
riiight, so that i only do fileIO on the first time that the command is ran?
well I mean potentially you could only do IO on server shutdown
and log on
have an array containing logged out player's playtime
that gets updated whenever someone logs out
and checked when someone gets the leaderboard
and on shutdown, it adds everyone to it then saves that
on on startup it loads the array
that way no fileIO is done at any point except when it won't make a difference to players
and maybe also have it done every once in a while as a backup
i think, if i only ever update the cache on log out, and never delete from the cache, and initialise it on startup, then it will only do fileIO on startup
yeah, that would be my suggestion
Otherwise you might risk somebody running the command multiple times before the array is populated
i.e. the only edits should be ```js
PlayerEvents.loggedOut(e => {
let player = e.player
playTimeCache[player.username] = getPlaytime(player)
})
/**wtf is the server start event e => {
updatePlaytimeCache(e.server)
}*/
ServerEvents.loaded?
probably
PlayerEvents.loggedOut(e => {
let player = e.player
playTimeCache[player.username] = getPlaytime(player)
})
ServerEvents.loaded(e => {
updatePlaytimeCache(e.server)
})
seems good
so, all in all: https://paste.gg/p/anonymous/33485d2f24f0461e9749f4c91e68ddfe
oh also
for you if else blocks
instead of
if (condition) {
code
}
else {
code
}
do
if (condition) {
code
} else {
code
}
(sorry if this is irritating, I just like following standards)
no >:)
if (condition) {
code
}
else {
code
}
``` is a standard, its called K&R
"commonly used in C, C++ and other curly brace programming languages"
it how i've always done it, helps my brain seperate them
if (condition) {
code
} else {
code
}
```feels... *deeply wrong* to me
tbh... my coding/brace style, isn't any standard per-se... its Autism™️
I mean I'm autistic but the line break just feels wrong to me
guess its what we're used to
i write it how it feels right, and scritches the itch in my brain 
i started out in python, and js if (condition) { code } else { code } is the closest to ```py
if cond:
...
else:
...
I started out in python too
...okay now i gotta listen to that song 
...anyway, let me wait a sufficient amount of time not to annoy my players by restarting the server, and we'll see if it works, then i can close the ticket xd
if (i == 1) {
return `${sparsePlaytime.value0} ${sparsePlaytime.unit0}`
}
else if (i == 2) {
return `${sparsePlaytime.value0} ${sparsePlaytime.unit0} and ${sparsePlaytime.value1} ${sparsePlaytime.unit1}`
}
else if (i == 3) {
return `${sparsePlaytime.value0} ${sparsePlaytime.unit0}, ${sparsePlaytime.value1} ${sparsePlaytime.unit1} and ${sparsePlaytime.value2} ${sparsePlaytime.unit2}`
}
else if (i == 4) {
return `${sparsePlaytime.value0} ${sparsePlaytime.unit0}, ${sparsePlaytime.value1} ${sparsePlaytime.unit1}, ${sparsePlaytime.value2} ${sparsePlaytime.unit2} and ${sparsePlaytime.value3} ${sparsePlaytime.unit3}`
}
else if (i == 5) {
return `${sparsePlaytime.value0} ${sparsePlaytime.unit0}, ${sparsePlaytime.value1} ${sparsePlaytime.unit1}, ${sparsePlaytime.value2} ${sparsePlaytime.unit2}, ${sparsePlaytime.value3} ${sparsePlaytime.unit3} and ${sparsePlaytime.value4} ${sparsePlaytime.unit4}`
}
can be replaced with
let playtimeArray = [`${sparsePlaytime.value0} ${sparsePlaytime.unit0}`,`${sparsePlaytime.value1} ${sparsePlaytime.unit1}`,`${sparsePlaytime.value2} ${sparsePlaytime.unit2}`,`${sparsePlaytime.value3} ${sparsePlaytime.unit3}`,`${sparsePlaytime.value4} ${sparsePlaytime.unit4}`]
let outputString = playtimeArray.slice(0,i-2).join(", ")
let lastIndex = outputString.lastIndexOf(', ');
if (lastIndex != -1) {
outputString = outputString.slice(0, lastIndex) + ' and ' + str.slice(lastIndex + 1);
}
if I did that correctly
you could always make the array multiline for readability
k, sorry I didn't respond was just typing this out
last time it was an array... everything broke
all thats doing is joining the first however many elements with a comma
then replacing the last comma with and
that will playtimeArray will error, because sometimes unit4 and value4 dont exist
why do they sometimes not exist??
let sparsePlaytime = {}
let i = 0
for (let unit in playtime) {
let value = playtime[unit]
if (value > 0) {
sparsePlaytime['value'+i] = value
if (value == 1) {unit = unit.slice(0, -1)}
sparsePlaytime['unit'+i] = unit
i++
}
}
``` if any value of playtime is 0, it doesn't get copied to sparsePlaytime
so it has variable keys
thats why i check what i is at the end
surely that would cause errors with the original as well?
if its goign to cause errors with the new one
no, because the template strings only get ran when sparsePlaytime has enough values to run them
ohh I get it now
in your one, you define them all at once
I misunderstood what was being checked
hmm okay then, that just seems a bit janky and if you forced them to exist that'd make more sense
and just hide them if they are 0
but its fine as it is ig
(If you can't tell my autism makes me the sorta person to completely rewrite something if its inefficient, because it bugs me, so you might be best off tuning me out sometimes)
xD
this is just the easiest way i could think of to turn {days: int, hours: int, minutes: int, seconds: int, ticks: int} into "int days, int hours, int minutes, int seconds and int ticks", where some keys can be 0
yeah, no i get that xd
I scrapped like 15 hours of work
and rewrote it all
to make it cleaner
a few days ago
i just can't think of a way to make it actually work like that
to be honest, it is a lot nicer now and it didnt take too long to write as I knew what to do that time around, but still, coulda just left it
if you have a dict already (which in my haste I didn't check) you could probably remove the first line and modify the rest to work directly on the array
*dict
tho I don't understand why you are using a dict instead of 2 arrays
with the unit and value
or using a single dict, where the key is the unit
i was originally doing something like this with loops ```js
// in this version sparsePlaytime is a 2d array [[value1, unit1], [value2, unit2]]
let l = sparsePlaytime.length
let str = ${sparsePlaytime[0][0]} ${sparsePlaytime[0][1]}
for (let i = 1; i < l - 1; i++) {
str += , ${sparsePlaytime[i][0]} ${sparsePlaytime[i][1]}
}
str += and ${sparsePlaytime[l-1][0]} ${sparsePlaytime[l-1][1]}
return str
if it was me I'd rewrite the entire system storing all of the playtime values xD
so i just hard coded all 5 strings into a list, and used '' instead of "``", then wrote a format function
but when i passed in the string, Rhino did some black magic, and thought that it wasnt a string, and even str.toString() and new String(str) couldn't fix it so i gave up
you don't know how close I've been to attempting to rewrite the entire minecraft server in a different language that I prefer, writing a custom java crosscompiler then adding my own events because something tiny has annoyed me
lmfao
can rhino handle async?
or, wait, nevermind i don't think i need it to
or... no, i do, cos JS doesn't sleep
slightly offtopic but```js
const $permissionAPI = Java.loadClass('dev.ftb.mods.ftbranks.api.FTBRanksAPI')
/**
-
@param {net.minecraft.commands.CommandSourceStack} param0
-
@param {string} param1
-
@param {boolean | string | int} param2
-
@return {boolean} return0
*/
let hasFTBRanksPermission = (commandSourceStack, node, value) => {
let player
if (commandSourceStack.isPlayer()) {
player = commandSourceStack.getPlayer()
}
else {
return commandSourceStack.hasPermission(2)
}if (typeof value === "boolean") {
let perm = $permissionAPI.getPermissionValue(player, node).asBoolean()
if (perm.isEmpty()) {
return false
}
return perm.get() == value
}
else {
return $permissionAPI.getPermissionValue(player, node) == value
}
}
/**
- @param {int} param0
- @return {Promise} return0
/
let sleep = (s) => {
let ms = s1000
return new Promise(r => setTimeout(r, ms))
}
/**
- @param {com.mojang.brigadier.context.CommandContext} param0
- @return {int} return0
*/
async function restart(ctx) {
let server = ctx.server
server.tell("Server restarting in 5 minutes...")
await sleep(240)
server.tell("Server restarting in 1 minute...")
await sleep(30)
server.tell("Server restarting in 30 seconds...")
await sleep(15)
server.tell("Server restarting in 15 seconds...")
await sleep(10)
server.tell("Server restarting in 5...")
await sleep(1)
server.tell("Server restarting in 4...")
await sleep(1)
server.tell("Server restarting in 3...")
await sleep(1)
server.tell("Server restarting in 2...")
await sleep(1)
server.tell("Server restarting in 1...")
await sleep(1)
server.runCommandSilent("/stop")
return 1
}
ServerEvents.commandRegistry(e => {
const {commands: Commands, arguments: Arguments} = e
e.register(
Commands.literal('restart')
.requires(commandSourceStack => hasFTBRanksPermission(commandSourceStack, 'server.restart', true))
.executes(ctx => restart(ctx))
)
})
can rhino handle async await like that?
how?
have a variable set to 0 on restart
then in the tick event, increment it by 1 each tick
and if it matches the amount of ticks for a certain statement
print the statement
const $permissionAPI = Java.loadClass('dev.ftb.mods.ftbranks.api.FTBRanksAPI')
/**
* @param {net.minecraft.commands.CommandSourceStack} param0
* @param {string} param1
* @param {boolean | string | int} param2
* @return {boolean} return0
*/
let hasFTBRanksPermission = (commandSourceStack, node, value) => {
let player
if (commandSourceStack.isPlayer()) {
player = commandSourceStack.getPlayer()
}
else {
return commandSourceStack.hasPermission(2)
}
if (typeof value === "boolean") {
let perm = $permissionAPI.getPermissionValue(player, node).asBoolean()
if (perm.isEmpty()) {
return false
}
return perm.get() == value
}
else {
return $permissionAPI.getPermissionValue(player, node) == value
}
}
let i = 0
let event = 0
let times = [
[4800, "Server restarting in 1 minute..."],
[5400, "Server restarting in 30 seconds..."],
[5700, "Server restarting in 15 seconds..."],
[5900, "Server restarting in 5..."],
[5920, "Server restarting in 4..."],
[5940, "Server restarting in 3..."],
[5960, "Server restarting in 2..."],
[5980, "Server restarting in 1..."]
]
/**
* @param {com.mojang.brigadier.context.CommandContext} param0
* @return {int} return0
*/
let restart = (ctx) => {
i = 0
event = 0
ctx.server.tell("Server restarting in 5 minutes...")
return 1
}
ServerEvents.commandRegistry(e => {
const {commands: Commands, arguments: Arguments} = e
e.register(
Commands.literal('restart')
.requires(commandSourceStack => hasFTBRanksPermission(commandSourceStack, 'server.restart', true))
.executes(ctx => restart(ctx))
)
})
ServerEvents.tick(e => {
i++
let server = e.server
if (i == 6000) {
server.runCommandSilent("/stop")
}
else if (i == times[event][0]) {
server.tell(times[event][1])
event++
}
})
```like this?
wait no
const $permissionAPI = Java.loadClass('dev.ftb.mods.ftbranks.api.FTBRanksAPI')
/**
* @param {net.minecraft.commands.CommandSourceStack} param0
* @param {string} param1
* @param {boolean | string | int} param2
* @return {boolean} return0
*/
let hasFTBRanksPermission = (commandSourceStack, node, value) => {
let player
if (commandSourceStack.isPlayer()) {
player = commandSourceStack.getPlayer()
}
else {
return commandSourceStack.hasPermission(2)
}
if (typeof value === "boolean") {
let perm = $permissionAPI.getPermissionValue(player, node).asBoolean()
if (perm.isEmpty()) {
return false
}
return perm.get() == value
}
else {
return $permissionAPI.getPermissionValue(player, node) == value
}
}
let i = 0
let event = 0
let restarting = false
let times = [
[4800, "Server restarting in 1 minute..."],
[5400, "Server restarting in 30 seconds..."],
[5700, "Server restarting in 15 seconds..."],
[5900, "Server restarting in 5..."],
[5920, "Server restarting in 4..."],
[5940, "Server restarting in 3..."],
[5960, "Server restarting in 2..."],
[5980, "Server restarting in 1..."]
]
/**
* @param {com.mojang.brigadier.context.CommandContext} param0
* @return {int} return0
*/
let restart = (ctx) => {
i = 0
event = 0
restarting = true
ctx.server.tell("Server restarting in 5 minutes...")
return 1
}
ServerEvents.commandRegistry(e => {
const {commands: Commands, arguments: Arguments} = e
e.register(
Commands.literal('restart')
.requires(commandSourceStack => hasFTBRanksPermission(commandSourceStack, 'server.restart', true))
.executes(ctx => restart(ctx))
)
})
ServerEvents.tick(e => {
if (restarting) {
i++
let server = e.server
if (i == 6000) {
server.runCommandSilent("/stop")
}
else if (i == times[event][0]) {
server.tell(times[event][1])
event++
}
}
})
```this one
...no that still causes an indexOutOfBounds error 19 times before it restarts the server 
uhhh
event %= times.length?
!(playTimeCache[username] === null) instead of !playTimeCache.contains(username)?