#List of all players to ever join the server (including offline, and preferably `ServerPlayer[]`)

1 messages · Page 1 of 1 (latest)

dapper hinge
#

im making a custom /playtime command, and for /playtime top, i need a list of all offline players to construct a leaderboard with, and i have no idea where to start

fallow prismBOT
#

Once your ticket has been resolved, please close it with </ticket close:1054771505520717835> command!

bold merlin
#

you could potentially create a leaderboard that tracks all players joining

#

but interfacing with that through kubeJS might be a challenge

dapper hinge
#

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

bold merlin
#

you could on the player join event, grab the UUID with event.player.uuid

dapper hinge
#
const $EntityHelper = Java.loadClass('ovh.corail.tombstone.helper.EntityHelper')
let offlinePlayer = $EntityHelper.getOfflinePlayer(server, uuid)
bold merlin
#

and store it in a json file

dapper hinge
#

hm, i could get a list of teams from FTBTeams, and get their owners

#

wait,, no... what if ppl join other ppls teams

bold merlin
#

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

dapper hinge
#

i'm too far into this shit to go that route now haha

#

i have like 16h in creating this damn command, and im not about to quit now

bold merlin
#

just make the command pull from that leaderboard ¯_(ツ)_/¯

dapper hinge
#
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?

bold merlin
#

just .uuid works

#

instaed of .getUUID

dapper hinge
#

but uuid is protected?

#

oh wait, beans

cloud cryptBOT
#

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.

dapper hinge
#

its just syntactic sugar yeh

bold merlin
#

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

cloud cryptBOT
dapper hinge
#

it should work, and rhino, rarely mix

bold merlin
#

xD

dapper hinge
#

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

dapper hinge
#

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

split mist
#

it is the wrapper

dapper hinge
#

wym?

split mist
#

Rhino/KubeJS wraps String to UUID, and it gets confused when it have both

dapper hinge
#

so, there is no fix?

split mist
#

also I'm suspicious OFFLINE player doesn't mean offline, it means unauthenticated via Microsoft Account player pepethink

dapper hinge
#

hm... that's true...

#

corail is closed source, so no way to tell

#

unless i manage to get it to run

bold merlin
#

just use scoreboards

#

the sunk cost fallacy is getting you really hard

dapper hinge
#

no

simple inlet
#

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

dapper hinge
#

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?
split mist
#

yes

#

but you would need to know the player

dapper hinge
# dapper hinge ```java source.getServer().getProfileCache().getAsync(playerName, profileOpt -> ...

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;
    }
}
dapper hinge
simple inlet
#

no filesystem access

limpid nexus
#

Oh I needed to scroll down further

dapper hinge
#

yeah, i figured that out ages ago lmao

dapper hinge
split mist
#

.getAsync( I think this will hang kubejs, we are having problem when running async stuff via rhino

dapper hinge
#

wait...

#

WAIT

#

CAN I READ .JSON FROM INSIDE THE WORLD FOLDER?

limpid nexus
#

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

dapper hinge
#

i do, because i get the UUIDs from usernamecache, then read /world/stats/<uuid>.json

split mist
#

in the end what do you need to do/check stuff on offline players?

dapper hinge
#

stats.minecraft:play_time

limpid nexus
#

That’s pretty simple

dapper hinge
#

which is in the stats/<uuid>.json

simple inlet
#

ah, I see, I don't mess with dedicated servers enough

limpid nexus
#

Just make sure to be efficient with your file IO

#

Cache anyone’s offline playtime, and only load files that aren’t currently cached.

dapper hinge
#

uh, how do? xd

limpid nexus
#

Just store a map in the global variable

#

where UUID is mapped to playtime

dapper hinge
#

how to check if a player with a given UUID is online or not then?

limpid nexus
#

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

split mist
#

server.getPlayer(UUID) if return null it is prob offline, no?

dapper hinge
#

idk... will it? xd

limpid nexus
#

Possibly, or it may throw an error

dapper hinge
#

and try catch is broken?

limpid nexus
#

Yeah

simple inlet
#

it returns null

split mist
#

not so broken

#

it works most of the time

#

sometimes it doesnt

limpid nexus
split mist
#

I do use it too, at least used to use

limpid nexus
#

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

split mist
#

404

#

ok

#

works now

limpid nexus
#

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

dapper hinge
#

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

limpid nexus
#

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.

dapper hinge
#

okay, gimme... some minutes lemme try to implement what i think you're saying

#

i have to refactor my entire codebase

limpid nexus
#

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

dapper hinge
#

i already am

limpid nexus
#

Good

dapper hinge
#

but all my functions were inside ServerEvents.commandRegistry

#

i now need them in both ServerEvents.commandRegistry, and PlayerEvents.loggedOut

limpid nexus
#

I would just define the functions outside the event hooks

dapper hinge
#

so i have to move everything

limpid nexus
#

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

dapper hinge
#

i do apologise for lack of comments haha

#

im terrible at that

#

but atleast i (mostly) have typehinting

bold merlin
#

you coulda just uploaded the file

#

gnome has pastes

dapper hinge
#

eh, its easier to just do a paste.gg, than go find the file in file explorer haha

bold merlin
#

Why are you using commands for tellraw

#

I swear there's a tellraw function

#

I think the standard event.server.tell allows for formatting

dapper hinge
#

e_shruggies i asked someone how to do tellraw and they told me this

#

cos i prefer formatting it as json, since im used to it

bold merlin
#

ye the tell works

#

with json formatting

#

I just tested it xD

dapper hinge
#

ah... well, i already have this haha

bold merlin
#

also why are you using backticks for strings

#

thats just cursed

dapper hinge
#

wym?

#

backticks are template strings

bold merlin
#

use single quotes

dapper hinge
#

thats not a template string though

bold merlin
#

unless I'm being stupid they do the same thing

#

there's a chance I'm getting languages confused

dapper hinge
bold merlin
#

oh yep I am

dapper hinge
#

'' != templateString

bold merlin
#

nevermind

dapper hinge
#

xd

bold merlin
#

I'm getting langs confused

dapper hinge
#

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

bold merlin
#
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"}])
dapper hinge
#

infact i did it a bunch while writing this code ur looking at lmao

dapper hinge
#

yeah, i was writing stuff for computercraft literally all night before i went to uni in the morning (without sleep) xd

bold merlin
#

(lua is my least favourite language, just because of arrays etc starting at 1, which has always drived me up the wall)

dapper hinge
#

yeah... but,, computercraft

bold merlin
#

I know its a stupid grudge, its just my autistic brain hates it sooo much

bold merlin
#

and its more performant, cleaner and easier to read

dapper hinge
#

more performant? rlly?

#

wait

#

no

#

ur version tells the whole server

bold merlin
dapper hinge
#

not the individual player

bold merlin
#

wait 1 sec

#

and I screwed up something

dapper hinge
#

xdd

#

ty for all the help <33

simple inlet
#

it would probably just be player.tell

bold merlin
#

oh I just forgot the brackets

bold merlin
#
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

dapper hinge
#

wait

#

ignore the .getUsername() on 217, just spotted it and changed it

bold merlin
#

you didn't remove the speech marks on all the identifiers

#

like text

dapper hinge
#

ohh right gotcha

bold merlin
#

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"}])
dapper hinge
#

i think JS doesn't care abt that though, does it?

#

ill do it anyway

bold merlin
dapper hinge
#

also, why is it called beans?

cloud cryptBOT
#

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.

bold merlin
#

¯_(ツ)_/¯

dapper hinge
# limpid nexus Player A is offline. Player B uses /playtime top. Playtime top scans all, not cu...

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]
    }
})
bold merlin
#

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

dapper hinge
#

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

simple inlet
#

Pretty sure the point is that IO is slow, and should be minimized

dapper hinge
#

since, when i update the cache, i only update it if its not already cached

bold merlin
#

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

dapper hinge
#

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

simple inlet
#

Can you not just get the stats of the player before the log out and save them?

bold merlin
#

^

#

thats what me and (I think pie)

#

was saying

#

*were

dapper hinge
#

riiight, so that i only do fileIO on the first time that the command is ran?

bold merlin
#

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

dapper hinge
#

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

simple inlet
#

yeah, that would be my suggestion

#

Otherwise you might risk somebody running the command multiple times before the array is populated

dapper hinge
#

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)
}*/

bold merlin
#

yup

#

that was my suggestion

dapper hinge
#

ServerEvents.loaded?

simple inlet
#

probably

dapper hinge
#
PlayerEvents.loggedOut(e => {
    let player = e.player
    playTimeCache[player.username] = getPlaytime(player)
})

ServerEvents.loaded(e => {
    updatePlaytimeCache(e.server)
})
bold merlin
#

seems good

dapper hinge
bold merlin
#

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)

dapper hinge
#

no >:)

bold merlin
#

D;

#

but look at this...

#

think fo how much shorter you could make it

dapper hinge
#
if (condition) {
  code
}
else {
  code
}
``` is a standard, its called K&R
bold merlin
#

thats C tho?

#

this is J

#

*JS

dapper hinge
#

"commonly used in C, C++ and other curly brace programming languages"

bold merlin
#

I've never seen line breaks after if blocks before else statements

#

in JS

dapper hinge
#

e_shruggies 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™️

bold merlin
#

I mean I'm autistic but the line break just feels wrong to me

#

guess its what we're used to

dapper hinge
#

i write it how it feels right, and scritches the itch in my brain haha

#

i started out in python, and js if (condition) { code } else { code } is the closest to ```py
if cond:
...
else:
...

bold merlin
#

I started out in python too

dapper hinge
#

...okay now i gotta listen to that song haha

#

...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

bold merlin
#
  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

bold merlin
dapper hinge
#

last time it was an array... everything broke

bold merlin
#

all thats doing is joining the first however many elements with a comma

#

then replacing the last comma with and

dapper hinge
#

that will playtimeArray will error, because sometimes unit4 and value4 dont exist

bold merlin
#

why do they sometimes not exist??

dapper hinge
#
  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

bold merlin
#

surely that would cause errors with the original as well?

#

if its goign to cause errors with the new one

dapper hinge
#

no, because the template strings only get ran when sparsePlaytime has enough values to run them

bold merlin
#

ohh I get it now

dapper hinge
#

in your one, you define them all at once

bold merlin
#

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

dapper hinge
#

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

bold merlin
#

I scrapped like 15 hours of work

#

and rewrote it all

#

to make it cleaner

#

a few days ago

dapper hinge
bold merlin
#

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

bold merlin
#

*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

dapper hinge
#

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

bold merlin
#

if it was me I'd rewrite the entire system storing all of the playtime values xD

dapper hinge
#

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

bold merlin
#

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

dapper hinge
#

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 = s
    1000
    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?

bold merlin
#

¯_(ツ)_/¯

#

I would use a counter

#

so no waits ever occur

dapper hinge
#

how?

bold merlin
#

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

dapper hinge
#
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 haha

#

uhhh

#

event %= times.length?

dapper hinge
#

!(playTimeCache[username] === null) instead of !playTimeCache.contains(username)?