#help-development
1 messages · Page 1333 of 1
i wanna get remote going though
why?
because im always using my remote server so others can join and it can stay running
theres my configuration
im not gonna start up a local server every time i want to debug
whats that
profile dump
okay its liostening now
i see in the console
still timeout connection its my tunnel then
Here is a tool that should help you in whatever it is you are wanting
never works
whats aprofile dump
it dumps whatever the server was doing into a file
you can play them back sort of
or just look to see what was or was not executing
so how do i use this
okay got remote debug working
so it does not look like intellij can map the codepoints to my local source code
i cant see what code its executing
well we have no idea what you are looking at
well some of the code steps should go through your plugin if you are executing a command
which won't be in the server source
since well its your plugin
he's trying to get the server to run an arbitrary command and capture the command output by passing it a spoofed command sender, which is supposed to log anything it receives
so he needs to see what's happening inside the nms/cratbukkit parts of the command execution, specifically whether either of them unwraps the commandsender somehow
I would imagine it would need to?
not for bukkit commands i don't think
vanilla commands get the unwrapped nms command sender
either way you'll probably want paper userdev's mojang-mapped sources, and ideally those for whatever build your server is on, so all the line numbers and other things align
the remote server doesn't exactly send your debugger the classes you're stepping through, just the class name and line numbers, so shit goes whack when you're looking at some mangled decompiled class in your ide
how do i get these
basically instead of a specific .jar artifact , you depend on paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT"), which adds a bunch of jar artifacts, one of them being the mojang-mapped server and another its sources
the downside is that it requires gradle which i would like fucking use apache ant over, but it is what it is
i use gradle so it sfine
or if you're building against/using spigot, you do the whatever buildtools nonsense, but i don't remember how that works
do they not have 1.12.2?
i doubt it
mojang mappings did not exist back then
glhf basically
if you are compiling against and running paper, there is api for a faux feedback CommandSender, but that also did not exist in 1.12 lol
oh neat, that's new to me
what's it called
i might have to do this again later and i'd rather not nms into command blocks a second time
Bukkit/Server#createCommandSender i believe
neat
https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/browse/src/main/java/org/bukkit/craftbukkit/CraftServer.java#902
https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/command/SimpleCommandMap.java#133
Dont really get it, it seems to only run plugincommands
guess i need to debug
ah i dont think paper dev thing works with my 1.12.2 setup
io.papermc.paperweight.PaperweightException: Exception executing remapMinecraft
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':1.12.2-spigot:paperweightUserdevSetup'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:135)
at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:288)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:133)
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:121)
at org.gradle.api.internal.tasks.execution.ProblemsTaskPathTrackingTaskExecuter.execute(ProblemsTaskPathTrackingTaskExecuter.java:41)
at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
i am doing this though
paperweight.paperDevBundle("1.21.10-R0.1-SNAPSHOT")
Remapping goes brr
Spigot doesn't have runtime remapping
But also paper shouldn't have runtime remapping on 1.12
Gotta love using older versions of things 😎
isnt there a similar thing for just craftbukkit stuff
Crazy how we went from no mappings in 1.12 to no obfuscation in 26.1
Do most players prefer to play the game with or without the "Force Unicode Font" set to true?
i doubt most players even know that setting exists
^
i do see a decent number of players using fonts from their resourcepacks, but i don't remember seeing many using the unicode font
i'd say it's about as popular as using pirate speak as your language
lmao
wanna find out?
It forces the game using specific font that support a lot of characters
Does it just disable the default font and make everything use unifont
yeah
one could say that it forces unicode font, even
i'll unicode your font
go ahead
I use that setting, i like the small font more
meow
I can't wait to have just one nms module going forward
Hopefully it works for future versions too
I mean breaking changes will still happen
That's sad
I like both, but I'm trying to make a resource pack with icons and stuff, but the icon is displayed very inconsistently between the two options. I have to find out which one is the majority and optimize the resource pack for them.
It'd be better if he will leave it up to the devs to update and check
Ewch new drop i'll add it to a stable version and then if something is untested the players will know
Mojang does like refactoring things
True but think of all the saved nms modules
ever since paper rolled out mojang mapped nms plugins and server, maintaining nms plugins has been a breeze
most of the time they work without building, build without changes, or need only some minor refactorings
i find that the bukkit api breaks more often than nms now, with sound names and potion effects and shit being changed
when should I depend on netty for my plugins?
When you need to sniff or modify packets
okay i will stick with console logging, this does not seem to work
looking at the bukkit code, consolecommandsender is converted to getServer().createCommandSourceStack with no reference to the sender methods
and my blockcommandsender cant be cast to craftblockcommandsender
id have to use reflection or something
theres also ProxiedCommandSender which is cast to ProxiedNativeCommandSender though which i cant create
Not sure if it's supposed to use that way
There's player precommand event
I'm not sure what you wanna do tho
i wanna get command output
its weird though that consolecommandsender doesnt even seem to capture plugincommands
It depends on server fork you're using
Some versions of paper for example didn't hook into simplecommandmap anymore
Later they added a delegate map for registering commands into brigadier anyway
ah yes
okay
that makes sense
return new Handler() {
@Override
public void publish(LogRecord record) {
if(record == null) return;
String message = record.getMessage();
if(message == null || message.isEmpty()) return;
if(output.length() > 0) output.append('\n');
output.append(message);
}
@Override
public void flush() {}
@Override
public void close() {}
};
``` using this from JUL now
worked well before
Bukkit.getLogger().addHandler(captureHandler);
used log4j before though i believe
this is new
lets test
might be cool to just fake a command sender like in newer versions
i think thats the best way
You might be able to do it still on even older versions
I think paper did the change since 1.20.6 to 1.21.4
Also did you check if brigadier has logging options?
Might be less hacky than fake console senders
well you can create your own command source stack
that would work
okay the handler doesnt capture anything either
lets do log4j again
guess this was a waste of time
did you try rcon yet
wdym
that's existed since like beta or something
myes
well not all servers have that enabled
and its like a network connection
slow
lots of logic
it's a completablefuture anyway
doesnt mean i wouldnt prefer a quick response
it's going to be quick on the scope of like under half a millisecond or something since it's a loopback connection
okay hmm
might make sense
ill try that
ill fall back to console capture if rcon is disabled
yeah that seems reasonable enough
rcon is not enabled by default it looks like right?
yeah since it allows anyone with the password and access to the port to run any commands they want
not something you want active by default
servers can enable it
but why would they
because your plugin installation guide tells them to
just like some plugin installation guides tell people to set up a mysql database
well my server actually had it enabled
but no i dont think its important enough to give people extra hustle in installation guide
lots of people dont even know what serve.rproperties is
there's like a 4k character limit for the response
not sure thats 4k: Lianecx has the following entity data: {Slot: 0b, id: "minecraft:white_shulker_box", count: 1, components: {"minecraft:block_entity_data": {id: "minecraft:shulker_box", Items...
lets see if its a diff issue
The Minecraft server can fragment responses across multiple packets. There's no simple way to know when the last response packet has been received; approaches include:
stupid
wtf
Wait until we receive a packet with a payload length < 4096 (not 100% reliable!)
Wait for n seconds
Send two command packets; the second command triggers a response from the server with the same Request ID, and from this we know that we've already received the full response to the first command.
The second packet should use a command that will not produce fragmented output
An alternative is for the second C->S packet to use an invalid type (say, 200); the server will respond with a 'Command response' packet with its payload set to 'Unknown request c8'. (200 in hexadecimal)
good shit
still seems to cutoff
idk
mean it returns these ... itself
i dont think i have that in my code
How does DonutsMP allow players to be teleported to coordinates like 100,000? Do they generate the chunks in real time because they have a powerful processor?
I think it's just a good cpu
I don't know if I should put the resource world on other servers; I have the nether, end and overworld
They're likely just using an optimized teleportation plugin, don't really need a high end CPU just to teleport somewhere lol
The problem isn't teleportation itself, but rather the loading of chunks from various players
Also not difficult. Not much different than regularly playing
Doesn't really take much to pre-load chunks either
My point here is that the donut always teleports to coordinates like x=100,000, which means that the donut always doesn't have these pre-generated chunks because nobody in this world has a disk to pre-generate a world of that dimension
Pre-generate a 100k * 100k world? That's my point
you can pregenerate them on the fly
^
your cpu won't always be at 100% load; you use the idle time to pregenerate some pre-chosen rtp spots as you're able
and then teleport the players to those spots on demand
this is what i was talking about when you first asked help here, with your rtp plugin
i remember spending several hours explaining the concept of a ring buffer
I ended up using an RTP plugin from the internet, but I'm scared now xd
or just generate a world and generate the "starting" chunks. Minecraft handles everything else via the world seed itself
You don't have to load in the entire world lol
Realistically, all you'd have to do is load in the same amount of chunks as the server normally would and you're fine
5x5 chunk area
now if you want to pre-load the entire world you're kinda fucked regardless
I don't think you have almost 7 TB lying around to pre-gen an entire 100k * 100k world 🙂
exactly. pre-gen 5x5 or even 10x10 and let the server generate the rest as needed lol
or 50k x 50k enough for 20 people
now that there's so many stuff cramped up below and above
spoof the player coordinates
keep everyone in a 1000x1000 box but spoof random coordinates in +/-3,000,000 every time they rtp
iirc there is or was a "coords obfuscator" plugin some rpg/hardcore servers used to make it harder to cheese with f3
you'd actually have to use maps and learn landmarks
not hide, but they filtered all packets to change the coordinates with random numbers
I think I'll use your solution with the ring buffer
XD
I want that
or like apply a random offset to them, which changes any time you teleport
speaking of chunk stuff. Debating on if I wanna try to re-create the chunking system in C++
Making a 2D ASCII game & it'd be an interesting-ish challenge
what i wanna do soon is my own dynmap
that will be interesting
and a good challenge
you know i've had this custom dynamic map idea rattling in my mind of thoughts for a while
earth map servers all mercator projection, which is shit and bad and makes greenland larger than the rest of the world combined
Needed? No. Not really.
Interesting? Yea, kinda lol
so i want to use a Dymaxion projection
that's interesting
which essentially splits the world into distinct triangles and other shapes
I already see how im gonna do it
it'd have a "portal boundary" in the oceans where you'd get teleported from one shape to the other
and for the dynamic map, you'd map it onto an actual sphere
To get a biome without loading the chunk, I just need to do world.getBiome(x, y, z).getKey().getKey(), right? I saw that ppl suggested me using world#getProvider to do this, but I don't think it's necessary
i don't know, check the nms
i remember there are calls that generate/load the chunk and calls that don't
The world#getBiome is taking 0.1ms. Would that prove the chunk isn't loading the chunks, I'd say?
depends on how fast you can load a chunk
In this case, if this loads the chunk, it also generates
Because the coordinates range randomly from -1 million to 1 million
I think it's impossible to generate and load in that time
It makes a call to WorldGenLevel#getNoiseBiome
So it probably doesn’t load the chunk
yeah that won't load it
If I use the ring buffer for my RTP plugin, after checking the biome, should I generate/load the chunk to check the valid Y coordinate for that location if there is one; if there isn't, should I not add it to the buffer? Or should the buffer only contain locations that were valid in the first step?
My concern is that if I check everything before adding the buffer and the buffer size is 100, then every time the plugin starts it will generate 100 chunks (in the worst case, which is mainly the case since the locations range from -1 million to 1 million). To solve this, I would have to save the buffer to disk, but I don't think that's a good solution
RTP plugins don't need to be complicated
the buffer should only contain locations that you can immediately teleport players to; the surrounding chunks should be pregenerated, and the location should be a valid teleport target
you can persist them if you really need to
the point of this is to do the work ahead of time, if you just put a bunch of points in a buffer without checking if they're valid, you might as well pick the points on the run and get rid of the buffer
it is probably better to persist them given that otherwise you'd be uselessly pregenerating locations at startup when you had already calculated completely valid ones at a point
Surrounding chunks should be something like the chunks a player loads when teleporting to a location in the worst case? The worst case would be view-distance value on the client at the maximum the server allows
My point in not saving would be due to the high disk usage
what high disk usage
why would saving some locations end up with high disk usage
it's just a worldname,x,y,z tuple, you can save tens of millions of those per gigabyte
If my buffer size is 100, I'll be generating huge chunks; if I don't save the locations of this buffer when the plugin shuts down, I'll have to find another 100 valid positions and generate 100 chunks and their corresponding chunks, and those positions shouldn't even be used anytime soon
That's why I think I should save the buffer positions when the plugin shuts down
you should, we agree
it sounded like you were saying you didn't want to save the buffer positions because they'd use a lot of disk space
but perhaps i misread
oh no
What I meant was that not saving would use high disk usage
no that's what I understood too lol
personally i don't bother saving them, i just generate 50ish fresh ones every restart, but i could see how that'd add up over time
It also depends on your world size
I was tempted at some point to make a random-location based generator for chunky
But I think that approach isn't even advantageous if the world is small; if it's small, you can just pre-generate the entire world
that way you'd generate just huge spots around random safe locations rather than in a radius, but that'd only really work for some gamemodes like UHC
Does UHC have chunk loading?
uhc for me is like a 100x100 map
i use it with conjunction with pregeneration, it still does help if your "is a position valid" conditions are stringent and it's difficult to find valid positions
can be bigger depending on the player count
Is this correct?
yeah sounds about right
pregenerating past that will still help when players start exploring around, but there will be some chunks you pregenerate that won't be visited, going to "waste"
Perhaps in this case it would be better to deal with the average case and not the worst-case scenario idk
depends on what your maximum view distance is i guess
yea
generating up to i'd say 10 chunks radius is sensible i think, but 32 is probably overkill
I don't know how many chunks a survival server should have, but that's something I'll have to research in the future
maybe 8-10
So maybe if this value be the surrounding chunks amount, I don't think it will take up much disk space
Managed to get the ASCII world & chunk stuff figured out.
Don't necessarily like it though kekw
It'd definitely be better w/ color, but still lol
but can you build a fortress in it for your dwarves
I mean, I can implement that functionality lol
I need to improve world-gen first though
k in my defense I cba to find fitting characters lmfao
makes me think of the guy who puts a dog in his games that represents him
I did slightly improve the world-gen. At least as okay it can get w/ google and ASCII lol
lol this probably helps more. I'll simplify some stuff when/if I get around to rewriting this as some stuff is rather useless
T for Tand
I know reading is hard, but you can do better than that
dropped that project for now, currently waiting for something else to run though lol
im sitting here thinking about stuff
like
do I want to actually do software engineering full time
waiting on custom tooling to finish up so I can test stuff lmfai
well if I don't, what else do I want to do
How do you guys plan things? (like SDLCs)
I wanna rewrite a plugin, from the ground up, and get an idea of everything it needs before i start.
I assure you a total of zero people in this Discord server are using any form of SDLCs for their plugins lol
^
I personally just update until the code base is a PITA or has enough technical debt.
If it's a private project I'll even go as far as scrapping the github history & starting entirely from scratch, using the old version as a reference for a from the ground up rewrite
I took a project management course last semester and used the knowledge from it to design a plugin rewrite
did not use any of the tehcniques I learned other than general skills I picked up during it to better organize my thoughts
lol
for minecraft pplugins it's just not necessary
^ PM is more for group projects or companies
don't get me wrong that class was VERY USEFUL 10/10 would recommend
I learned a lot of really good things in it, but it's not a necessary skill to have when working on something like a minecraft plugin
I guess if you want the experience, but it's still massively overkill
I recommend listing what you currently have, what you want to have, why you want those things, and doing iterations over it to improve the explanations and features
Worst case, you just end up doing this at some point kekw
i.e. this is what my doc ended up looking like
written fresh out of that class
wanted some hands on experience bc I was fkin struggling to understand lmao
note, probably over kill as fuck
oh 100% but it's not really required
I tend to get lost in the sauce when making new stuff
Similar to doing game design docs and stuff solo
mhm
not required, but useful lol
I wrote a cute lil pipeline to explain the stages of damage calculation
on another note, praying the changes I've made don't take the entire Java heap again KEKW
Trying to index a few thousand things in memory atm
oof
we had a server crash and I downloaded the heap dump forgetting that you need like 2x memory to read a heap dump
I don't need too, but it'd sure as hell make one of the custom tools I'm working on easier lmfao
I only have 32GB of ram that thing was like 20GB
been expanding my reverse engineering & analysis program all day lmao
😭
:)
Yeah but the thing is. When we started working on this plugin we underestimated what it would have, and it’s basically on very shaky stilts. Was thinking of consolidating everything we know now, to rewrite the plugin.
@ancient plank probably is
But thinking about it: the buffer might have invalid positions if I add the positions to the buffer and then someone leaves that position invalid
hey, i`ve downloaded quests plugin, do i need to setting my own quest or they alredy have the default quest
Incredibly unlikely in a 100k radius world, but if you're worried about it, you can remove the position from the buffer if the chunk is loaded for any reason
Congrats on downloading a plugin
But (a) this is a channel for dealing with developing your own plugins.
and (b) you may want to specify what “quests” plugins. Spigot is a marketplace, not a development team, where individuals may publish their own plugins, several of which may be “quest” plugins.
Move to #help-server
or what
At most some basic notes for a broad overview, maybe a few specific implementation details, but most of the improvements happen because I now know what system needs what and can improve it from the ground up (often resulting fully recoding the entire class from scratch as its the easiest in my opinion)
my grandfather would, when presented with any large or overwhelming problem, always say "start from the corner", referring to the side of a farm field
in similar fashion i just sit down and start hammering at it in whatever class seems like a decent entry point, writing //TODO's for things as i go
i always start with the SpringAbstractedMongoServiceBuilderWebProvider.Factory
and then get off all day long thinking about best practices and clean code and ideal paradigms for java
Are you saying that if the player teleports to a loaded location, then try the next location in the buffer?
I'm saying listen to chunk load event and if the chunk contains a location that is in the buffer, remove it from the buffer
oh good
This means it is impossible for points in the buffer to become invalid
But verifying whether a location is contained within a chunk, I don't know if that's an easy task
It is very simple
Build a chunk coordinate->buffer location map when you populate the buffer
And query it on chunk load
There isn't one
I know there's a website with the Minecraft nms code
nope! legally we're not allowed to host the code
gotta use either buildtools or paper's thing to get acess to it
Paper members send this website on chats
That's not source, but documentation
There is literally a website online with the Minecraft code
I think Minecraft now offers the code
i just dont remember the url
You'd still need a decompiler, as they only provide jars
de-obfuscated versions of MC don't even go back to whatever version you're usnig
They're only 1.21.11+ or whatever mojang said
I'm on 1.21.11
That's not what I'm looking for. I'll see if anyone knows
that's the only way you're getting it afaik
i found https://mcsrc.dev/
yea, uses the same jar I just sent lol
if only the huge text block that reads "How It Works" that shows up immediately when you open the website explained how the site does indeed not host and serve the code but rather the binary jar is downloaded and decompiled locally
oh well, i guess the world shall never know
which is why I've been saying what I've been saying lol
Is it possible to generate a chunk without loading it?
No, generation happens in memory
Generation also requires accessing adjacent chunks, so even if you wrote your own chunk generator that writes it to disk directly, you'd still need the adjacent chunks
not really, why would you? you can just instantly unload it
It's better to do nothing (just let paper unload them); I just want to generate the chunks without having to load them so that when a player gets close, I don't have to generate the chunks (only load them). Currently, since I need to generate them, I'll also load chunks without players nearby
https://pastes.dev/2GX4s5YsH5 If the surrounding chunks are loaded when generating the chunks, then I imagine this code doesn't work 100% because I'm generating the chunks around the location the player will teleport to
How many surrounding chunks are needed to generate a chunk?
Not sure, but couldn't you just pregenerate the whole world?
the server will load the adjacent chunks at the appropriate chunk stage/level transparently
it can't not work
it is 1_000_000 x 1_000_000 world
well okay that's a bit large for pregen lol
But then generating the chunks around it like I'm doing might not be necessary
for more about chunk stages, read the wiki or this thread where i explain it to someone else
it will still be necessary, because they won't be at the "full" stage otherwise
they will be at whatever stage is necessary to get the information for the generating chunk
generally 1 stage lower than the generating chunk
i see
also this
you can essentially think of it like building a pyramid; you can't just start from the center and pile it 100 stone blocks tall, because it will fall; it needs to be surrounded by adjacent columns of stone blocks, which in turn have to be supported by adjacent stone blocks, forming a step-pyramid shape
this is also why it is much cheaper to look for valid teleport positions in nearby chunks even if you can't find it in the chunk you randomly chosen; it's cheaper to make an already-tall pyramid slightly larger, than build a whole new pyramid somewhere else
i see
When a location to be placed in the buffer fails, do you recommend trying a nearby location then?
depends on the way how it fails; if it fails because it's water, it's very likely that you're in a river or a lake and many surrounding positions will also fail the same condition
The issue is that sometimes this might not be advantageous because the plugin might simply try multiple times (and this happens because it tries a nearby location, for example, a player building a huge trap), which ends up not being advantageous to simply "build the pyramid" from scratch
if it fails because it's e.g. covered by logs, it's probably a tree and a nearby position is still likely to be valid
if is water, will be a river on worst case because the ocean is stopped by the biome
it's much cheaper to find a valid position in a loaded chunk than it is to generate the chunk, so you don't want to spend 100ms generating a chunk and then discard it in 1ms
world.getHighestBlockAt(location.getBlockX(), location.getBlockZ(), HeightMap.MOTION_BLOCKING_NO_LEAVES)
That would never happen either, I imagine, because I do this
well, logs aren't leaves
you can end up being unlucky and picking the actual tree trunk for your location
or a cactus, or any number of other things
but getHighestBlock will return the air block
What can happen is that it spawns on top of a cactus, for example
no, it will return the highest non-colliding block except for leaves
i.e. it could be inside the leaves
on top of the tree trunk in the tree
or on top of a cactus (you don't want to rtp the player there either)
my point is that you will have this kind of "spurious" failure of the search conditions; just search a few nearby locations if you do
but for other types of failures that are indicative that the whole area might be invalid, it's better to pick a whole new chunk
Wouldn't getHighestBlock be the block above the highest X block that is collidable, except for X blocks which are leaves?
Because the tallest non-colliding block is usually the tallest possible Y-coordinate
HeightMap.MOTION_BLOCKING_NO_LEAVES indicates a heightmap that accounts for all colliding blocks except leaves
meaning it won't see leaves
meaning if you have a block of grass and 2 blocks of leaves above it, it will return the bottom-most of those leaves blocks; a spot inside the leaves
it can also return the block above the tree trunk of a tree, which will also be occupied by leaves
Yes, I agree, but I disagree with the part where you said that getHighestBlock returns the highest non-collidable block
it returns the block above the highest collidable block that isn't leaves
which itself could be leaves
yea now i agree
In this case, I need to check that the block chosen by getHighestBlock is not cactus, and that the two blocks above are not water, lava, leaves, etc
myes
actually, an air check is basically for the two blocks above
no need check all type of blocks for the two blocks
i'd check for air and some acceptable noncolliding blocks, like tallgrass, flowers, and ferns
i see
since quite many positions will be occupied by those rather than air
If you want to send me this check, I'm accepting xd
It's hardwork because I need to see all types of blocks
just eyeball at Material or go looking around plains in f3 and put everything you see that doesn't kill you into a switch block
or maybe material tags
Isn’t there an isPassable
there is, but that might not be right
e.g. i think sweet berries might be passable despite not being nice to spawn on top of
i see
You’d probably have to exclude a few
But that’s easier than explicitly including a bunch
i don't know, you can try it; using isPassable and a smaller manual blacklist would probably work as well
Why on earth are strawberries considered passable and flowers not?
flowers should be passable since they don't have a collision shape
oh
How does getChunkAsync work? When does the paper decide: Okay, this tick I'm going to load these chunks that were requested ticks ago?
there's a queue
I'm asking this because I'm using this method to load adjacent chunks from my buffer, and I never see the paper generating the adjacent chunks
well you don't see them, that's what transparent means
the server generates them up to whatever stage they need to be, but in bukkit, only the full stage is considered to be generated or loaded
If I generate the chunks with getChunk method, will the chunks that are first in the queue be delayed?
getChunkAt will push the chunk to the beginning of the queue, to be loaded "as fast as possible"
which generally is not good because now the server is putting everything else it's doing to the side just to generate the chunk
this? as in there existing chunk generation stages? yes
I asked if getChunkAt always adds the chunks to be loaded as quickly as possible and not simply load them
In that case, I asked if it had always been this way
getChunkAt loads it instantly, doesn't it?
this kind of "sync" loading has always existed, and was the only way to load chunks before paper made chunk loading asynchronous; there didn't used to be a queue at all, calling the method would simply synchronously get the chunk
from what vcs2 said no
if it does not return a CompletableFuture, but a Chunk object, then it's a blocking call
after asynchronous chunk generation/loading was added, this was replaced with a queue that the chunk worker threads watch and fulfill the requests in it as they're able
i'm fairly sure these days calling getChunkAt still uses those async threads (the generation doesn't happen on the main thread) but the main thread will block for it, stopping the game tick loop until it's done
as of like 1.13 or something
but back when it was all a single thread, there was no queue or chunk threads
myes
If I use getChunkAsync, the difference is that it's added to the queue to be generated/loaded to a later tick, and the game tick loop isn't paused to generate the chunk?
Since in my view, getChunkAsync is asynchronous, it doesn't make sense for the game's tick loop to pause and wait for the chunk to be generated
it doesn't care about ticks, it just puts in the queue and the chunk threads will get to it when they're able to; no thread will block for it as the result is a future
getChunkAt does the same thing, but it puts the chunk at the front of the queue and then .joins() the future, which will block the calling thread (which is going to be the main thread)
either way, you basically never ever want to use getChunkAt
i see
the only case where you would use it is if everything will explode if you don't get the chunk right now instantly
unless you're in onEnable or similar
perhaps
https://pastes.dev/2GX4s5YsH5 I was thinking I was overthinking using getChunkAsync and having to add pendingtasks int etc., but then no
i'd still do it with the async overload if at all possible, but yeah, it's sort of acceptable in onEnable and onDisable
why
i have no idea what this code is supposed to do
it looks like you're picking a random location, and then pushing it into the buffer viewDistance*viewDistance times?
what's the goal of that? Also if you do LocationUtil.getHighestSafeBlockYAt, don't you already have the chunk loaded after that? Why do you need another getChunkAtAsync after that?
yeah, accessing the heightmap of a chunk will load/generate the chunk
the check for isPrimaryThread is also not needed, the javadocs say that the future returned by getChunkAtAsync runs on main thread
sort of maybe
At each tick; if the buffer size is less than 100, then add a random valid location between -1_000_000 and +1_000_000 to the buffer. Furthermore, upon obtaining the location, the getChunkAsync method is used to generate the adjacent chunks (unfortunately I have to load them, but they will be quickly unloaded because nobody will be nearby in principle)
i'm not 100% sure if that's true in practice in every case, with how futures work
getChunkAsync is used to generate adjacent chunks
hm idk but the javadocs for getChunkAtAsync say "The future will always be executed synchronously on the main Server Thread."
why are you pushing the same location into the buffer in the "generate adjacent chunks" loop
I just need to add an if-check on for: if dx == 0 and dz == 0 then continue the for loop
are you doing a random TP plugin or what are you even doing?
why not add the location into the buffer first and then loop over the nearby chunks to generate them?
Note that I have an if-check if (++currentIteration[0] == totalIterations). This only ensures that the push is performed only once. I do this to add this location to the buffer only when adjacent chunks are generated
This way, I prevent the player from using /rtp and getting a position in the buffer where nearby chunks haven't yet been generated
there's no guarantee the that the first future completes after/before all the chunks are generated
since it's a queue, it's liable to complete before all the chunks are generated
if you want to make sure the chunks are generated, put all the futures in a list and then do CompletableFuture.allOf on them, and only then put the location in the buffer
The problem with this is that if someone enters this chunk before the push and adds blocks, and by chance places a block in a "valid" position that would be added to the buffer, that position will be in the buffer and is no longer valid, and the chunkloadevent will not be called after being added to the buffer. To solve this, simply implement the logic of chunkMap.computeIfAbsent... inside the push, before the for loop inside the RingBufferRunnable, so that if the chunkloadevent is called for a chunk that is in this map but not in the buffer, then add it to a cache structure so that when actually adding it to the buffer (inside for-loop on RingBufferRunnable), check if it is not in this cache structure and if it is, remove it from the chunkMap.
you're overthinking this a bit
I don't understand; if for iterate 200 times and if currentIterations is 200; so I'm guaranteed that the for has already iterated all the chunks
since the futures are guaranteed to be completed by the main thread i suppose the counter does work, but allOf is still the better approach
just be aware that this most likely won't work in any other context where you'd use futures
I don't understand why you think the counter shouldn't work xd
it happens to work in this case because all the futures are guaranteed to work on the same thread
Yes, of course; I know this works because what completes the CF is the main thread
without such a guarantee, you since you're not incrementing it atomically, it's liable to never reach totalIterations
either way, allOf is a much easier structure to reason about and also allows you to e.g. capture thrown exceptions in a cleaner way
I'm not using atomicinteger because I only increment/read it in the main thread
never heard about allOf. i'll see that
it's a future that completes once all of the passed futures are complete
so if you give it all the chunk futures, you have a "all chunks are done" future
yeah that's about right
I only forgot pendingTasks-- inside the allOf block
also remember to always either .join() or .exceptionally() on any future you run any code in
otherwise, you will not see any exceptions that occur
I don't usually add unchecked exceptions in a CF, but it seems like that's bad practice then
Do I see the exceptions this way? https://pastes.dev/IobncntZOh
no, that exception also gets swallowed
you need to take the exception and log it/handle it
the thing with unchecked exceptions is that you don't "add" them; they just happen, unexpectedly
e.g. suppose there's a CME on ringBuffer.push(location), or a NPE on location.setY; you will never see them unless you handle the exceptions
it's easy to forget about this because java usually has very reliable exception handling, but with futures, you must always remember to add an exception handler yourself, because java will not log/handle them for you
i see
i'm doing this way now:
.exceptionally(e -> {
logger.log(Level.SEVERE, "Exception loading chunks", e);
return null;
});
yeah, that's how you'll want to do it
I don't really like this because, for example, if I have an exceptionally in a service, every time I use the service, I have to add another exceptionally block
yes, this is the downside of futures
It ends up being just exceptionally nested blocks
well you generally don't need to nest them because of the way how they propagate throughout futures
but yes, it's still ass and very easy to neglect to put an exceptionally where you need it; it isn't always clear where you should or should not put it, either
this is why i personally prefer virtual threads nowadays
why generally?
as long as you .join or .thenX on a future, you don't need to .exceptionally on it; in case of join, the exception is automatically rethrown, and in case of thenX, that responsibility gets passed to the resulting future
My point is:
Let's say I have a service that does CompletableFuture.thenAcceptAsync(code block...).exceptionally(e -> return new QueryErrorResult());
Then, if I use the service's method, upon obtaining this future, and do something like thenAccept again, this thenAccept will be called regardless of whether an exception occurred or not. Therefore, whoever calls the service's future and uses thenAccept should call the exceptionally block after that right?
Sometimes the code inside the second thenaccept block is simple logic that you're sure will never cause an exception, but it would be considered bad practice not to add the exceptionally block ig
if it's a service/api method that returns a future, you don't necessarily have to do .exceptionally on it; you can decide that doing that is the caller's responsibility
otherwise these .exceptionally's will proliferate endlessly and your code will just be exception handlers
this is what i meant by "it's not always clear whether you should or should not put it, either"
either way, yes, futures are ass, i'm not arguing in their favor; for best results, look into virtual threads and use them in conjunction with futures
i see
e.g. your chunk loading example, with virtual threads, can be rewritten as
virtualExecutorService.execute(() -> {
List<CompletableFuture<Chunk>> futures = new ArrayList<>();
for (int dx = -viewDistance; dx <= viewDistance; dx++) {
for (int dz = -viewDistance; dz <= viewDistance; dz++) {
if (dx == 0 && dz == 0) {
continue;
}
int chunkX = cx + dx;
int chunkZ = cz + dz;
futures.add(world.getChunkAtAsync(chunkX, chunkZ));
}
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
location.setY(newY);
System.out.println("push: " + location + " to the buffer: " + Bukkit.isPrimaryThread());
ringBuffer.push(location);
});
What kinds of things make you use completablefuture today?
maybe a database pool and apis already built with completablefuture?
except you'd want to use an executorservice in place of a completablefuture for the top-level block
completablefutures have their place, as you can see i'm still using them and the allOf and other future style methods in the above example
the main difference is that instead of runAsync().thenX().thenX().thenAccept().exceptionally(), i.e. chaining futures, I use a virtual thread that blocks on each stage of the "chain"
this means i can use regular flow control and imperative-ish structures like helper methods and even recursion, all combined with futures
so yes, futures still have a place as e.g. the return type of a database query/api, or the chunk loading api in paper
it's just the way how you put them together that benefits from virtual threads
In this case, besides avoiding the exceptionally and thenRun blocks, does it also offer better performance?
maybe, god knows, probably not measurably
The only downside is that I'll need an extra pool for this
it's a virtual thread so it doesn't really use a pool
you can also sort of do it without virtual threads if you target java 8 or whatever, but it will be far less efficient than only-futures, since it'll block a platform thread
wdym platform thread?
oh i see
like an os thread, complete with its own allocation of memory for its call stack and other expensive things
now the os does also schedule os threads independently of hardware threads, so os threads are also a kind of "virtual" thread, but java virtual threads are much more lightweight
Currently, I create the executor like this: Executors.newVirtualThreadPerTaskExecutor(). My fear is that if this is called 1000 times, 1000 virtual threads could be created. Perhaps there's a way to limit the number of threads created, like in a fixed thread pool with OS threads, but I don't think that's necessary here because, from what I've seen, virtual threads are designed to support millions of virtual threads
the idea with virtual threads is that they block/sleep for most of their lifetime, and so are not mounted on os threads most of the time
meaning you can have 1000 virtual threads, and that is valid, as long as they are doing something like future.join(), io, or sleeping
you can create millions of VTs and your computer won't blow up
in the case that they all try to do work, yes, you will probably exhaust your platform threads
that's pretty much the point why they exist
And isn't that a problem here?
no, because look at what the virtual thread does
it tells the server to load a bunch of chunks, and then it sleeps until they're loaded; then it puts a location into a queue
what it itself does is very lightweight, so it won't be mounted on an os thread for a meaningful amount of time
what you don't want to do is spawn 1000 virtual threads that all try to compute the 50,000th prime number, because those won't sleep and will hog all the os threads
well, only a handful of them will run at a time since they ultimately run on a regular fjp but yeah
most will just sit there waiting for their turn
i mean yeah, but the point is it will behave like starting 1000 regular threads computing the 50,000th prime number
so you lose all the advantages of the thread being virtual
That's because the operation you mentioned is very time-consuming (it should take minutes or hours), and the virtual thread will have the overhead of constantly switching threads, meaning it will end up being worse. Whereas with CompleteFuture in this case, there won't be this overhead of switching threads and it should execute faster. Now, for other options like the one you used, virtual threads are good because it's an operation that, in the worst case, should take seconds, which is nothing compared to calculating a 50,000th prime number. Did I understand correctly?
i probably phrased it a bit poorly; the concern isn't time, but compute
it's fine for a virtual thread to live for minutes; it's not fine for it to do work for minutes
it's fine for it to wait on a chunk being generated; it's not fine for it to do anything computationally expensive, like computing a prime number
Isn't an operation that takes time computationally expensive?
Of course, if I use getChunksAsync and only get a response after 100 seconds, that's not computationally expensive even though it's a time-consuming operation. When I talk about operations that take time, I mean sync operations
Obtaining a response from the database is, I believe, faster than obtaining the 50,000th prime number. Therefore, I would say that accessing the database is computationally cheap relative to calculating that number
Thread.sleep(999999999999) takes time and is not computationally expensive
fair
accessing network resources like http call or database queries can take entire seconds and are not inherently computationally expensive
they might just take time due to network latency
and that is where VTs shine
I knew that, yeah; I just didn't know that could happen with synchronous methods like thread#sleep
they don't sit on a a whole ass OS thread waiting for the resource to become available
I think thread#sleep is the only sync case
the OS thread that manages the virtual thread is instead tasked with running other virtual threads
github actions would like to have a word
So you use VTs to connect to the database instead a fixed pool for example?
gha can eat tree bark
You can have a fixed pool of vt factory
It is possible tho
But that doesn't make sense xd
doesn't mean you should
Jakarta has a managed VT pool implementation for whatever reason as well, wonder if anyone uses that
You wouldn't say that if you knew the benefits of eating it
who is jakarta
formerly Java EE, it is an enterprise™ platform/framework
Well, if they work on Java enterprise, they're probably useful no?
They're the ones who created virtual threads, they should know in what situations they're useful, lol
uhhhhhhhhh
not necessarily, on the keynote showcasing them they said and I paraphrase "there's also a ManagedThreadFactory for virtual threads as well, if anyone ever wants something like that"
all enterprise shit is snake oil sold to The Suits™ to "increase productivity" that nobody actually doing any meaningful work needs or wants
that said, they aren't the same people who work on Java, Jakarta is a separate product
Okay. So I should use VTs to do database queries then?
instead of a fixed thread pool
to perform the queries, yes
anything that does IO or sleeps is natually a good target for VTs
just don't do compute on them
and keep using hikari?
yes
connection pooling is separate from thread pooling
all you just did was eliminate the necessity for thread pooling
er, wording there is strange
Is connection pooling good for using VTS? I know Hikari does that
yes, connection pooling solves a different problem from thread pooling
much like how creating and destroying a thread is expensive, creating and destroying a connection is expensive
And Hikari probably doesn't use vts xd
so it makes sense to reuse threads and connections from a pool
except in the case of virtual threads, which are a transparent abstraction of "reusing threads from a pool"
I recently refactored our base DatabaseProvider to use a virtual thread pool and everything broke
because the common fork pool is pretty smart and forgiving
whatever that means anyway
For example, Luckperms doesn't use virtual threads because it accepts Java 8 right?
The problem with this code is that the push is executed on a virtual thread and modifies an array that is not thread-safe. Should I make the array thread-safe just to use virtual threads here?
either that, or run and join a future that runs on the correct thread to do the push
I basically need to make everything thread-safe: the pendingTasks variable; the HashSet structure into a chunkMap inside the LocationRingBuffer etc or then do the push and pendingTasks-- on main thread
I don't know if, even with that, the use of virtual threads is still worthwhile on this case
in this particular case no, VTs don't help too much; all it saves is one .exceptionally block
but you can make this work with VTs, and they will be invaluable with more complicated threading logic, like flow control and multiple or optional dependencies
i posted the vt example just to give an example on how to go from pure futures to virtual threads
the pure futures one with the .exceptionally block is fine
But wouldn't it be less performant with VTs?
on this case obviously
perhaps marginally, but not measurably so
So here I would use VTs to have perhaps less performance (even if it's not measurable) and it would complicate the code considerably. Therefore, yeah, I don't think it's worthwhile here
But for example, I've already replaced my fixed thread pool with virtual threads and I know how they work, so in any case your code was a great help xd
VT's excel when there's flow control and complex dependencies, e.g.
//fetch dependencies in parallel
CompletableFuture<PlayerData> dataFuture = dataService.get(player);
CompletableFuture<Waystone> waystoneFuture = dataService.get(waystonePos);
PlayerData data = dataFuture.join();
Waystone waystone = waystoneFuture.join();
//legacy waystone; migrate
if (waystone == null) {
waystone = new Waystone(...);
dataService.save(waystone).join();
}
if (data.discoveredWaystones().contains(waystone.getUuid()) {
//open waystone GUI
}
else {
data = data.withDiscovery(waystone.getUuid())
CompletableFuture<Void> dataModifyFuture = dataService.save(data);
//play discovery animation
PlayerData owner = dataService.get(waystone.getOwnerId()).join();
owner = owner.withLog(player.getName() + " has found your waystone")
CompletableFuture.allOf(dataService.save(owner), dataModifyFuture).join();
}
this would be a nightmare to do with pure completablefutures alone; you'd need several levels of nesting
but with a virtual "orchestrator" thread that fetches data from (possibly separate thread-run) systems and blocks only when necessary, it becomes much simpler
yea i see
Why is the gui being opened inside the data fetching
this is the click handler for clicking a waystone
since it runs async, it's free to fetch data, and then act upon it however it likes
But then there will be a delay before my gui opens
there's a cache, but yes, in the worst case it's unavoidable
Load the data on chunk load :c
since the data is on a remote db, and we can't open the gui before we know you can open it, there can be a delay
in most cases i solve this with "spinners" in the gui itself
but in this case since the branch is half-gui half-not-gui, that's not applicable
in practice however the delay isn't noticeable since the db is very close topographically
i opted against any sort of eager caching like prefetching the waystones on chunk load, since the db and the waystones are being modified by multiple backends at the same time
I have no idea how to do that xd
i honestly wouldn't bother
if you really have a 1 million squared map and are picking positions randomly, if someone places lava on the exact place where you picked a rtp position, and a player teleports to it and dies - god probably wants that player dead and there's nothing you can do
they'll probably be struck by lightning 100 times in a row even if you save them
HAHA
https://imgur.com/a/n5yQdsj The problem with my implementation is that getHighestSafeBlockYAt loads the chunk, and in the chunkloadevent I remove the chunkkey loaded in pendingChunks. Therefore, the push will never be executed because my plugin has already loaded the chunk and removed pendingChunks key
If I had a way to know whether the chunk was loaded by my plugin or by a player, maybe I could solve this
But then there's the issue of a chunk being loaded by my plugin, and a player entering that chunk seconds after my plugin loads
i mean honestly i wouldn't bother listening to the chunk load
Surely you can just check if the position is valid again before the actual teleporting
And if it isn’t throw it out and get another one from the buffer
that's alright as well, as long as your conditions aren't super strict
i.e. you can check for lava and skip the position if it's covered in lava, or solid blocks
but i wouldn't run more complicated checks like "must be within 32 blocks of a tree", as that'd defeat the point of precomputing the positions
but honestly i wouldn't bother even with the checks on teleport; in a 1 million squared world with say 100 points in the buffer, it's so incredibly unlikely for any of them to be invalidated that the player might as well win the lottery
i suggested this approach before when you originally asked about a rtp plugin like 6 months ago, but that was under the impression that you had a much smaller, like 1k across, world
i wouldn't bother even with a 10k squared world, let alone 1 million squared
wdym 32 blocks of a tree
well in my rtp plugin i want the player to have easy access to wood
so i require the position to be within 32 block range of a tree
+1 thing to my rtp plugin lol
spawns in the middle of a mega desert
I just have no idea how to implement this
1 million world with just desert 😭
searching for trees? i just brute force it
that's the advantage of precomputing your rtp points, it doesn't really matter how long it takes
And how do you look for trees? Do you look for wood and leaves on top?
yeeees
If the player builds a vanilla tree, then it's a valid position if near
that's fine, a tree is a tree
as long as they get wood
if you want to do it cheaper, just blacklist any biomes without trees
What if there is only one tree near that location?
4 logs is enough to get started
fair enough
And how do you know that the wood/leaves aren't buried?
or inside a house idk
like they can be hidden
well i just find a log within a 32 block range, iterate upwards and see if i find blocks following the order log -> leaves -> air -> top of heightmap
it probably doesn't work in some cases and maybe misses some trees but whatever
Player making a tree inside a house:
But yeah, I don't think I'll worry about it
have ppl for evertyhing
stochastic correctness 💪
being correct 99% of the time is being right basically all of the time
I'll give a rank if the person spawns in an invalid block via rtp
that's the spirit
Imagine someone rtps into lava placed after the location was checked and next to an buried tree
that does remind me
lava and "springs", i.e. lone source blocks, don't generate fully-flown, and loading a chunk through the api only keeps it loaded for a few ticks at most
so it's entirely possible for you to rtp underneath a natural lavafall and for the lava to flow into you if you don't move fast enough
It’s a quick time event
i haven't ever seen this but it's theoretically possible
lol
Just give them fire resistance for a bit
i don't bother
Alternatively you could load the chunk with a ticket for a while and then check for a safe place
i only have like 10k blocks across world and 50 positions in the buffer, but even then its incredibly unlikely for any of them to be invalidated
is this resources world?
I have 1 million worlds so I don't have to worry about resetting the world bc is a resources world
supposing i had 100 concurrent players that all run the command every 10 minutes and place 1 lava block in the world per second, it takes 5 minutes for the buffer to completely cycle through, in which time 30,000 blocks have been turned into lava - out of the 100,000,000 blocks of the map; so there's a 0.03% chance for any given rtp point to have turned into lava
and that's with 100 players doing nothing but rtp'ing and running around with lava buckets
In my mind, prevention is better than cure. If someone loses items because of this, then I'm screwed
get IRP and just check the logs when someone does report it
Even worse is if someone loses because of this and has no proof, and I don't know if he's telling the truth or not
if the player did /rtp and a few seconds later died in lava, and the death pos is lava, they probably died from the rtp
in such a case just /irp restore the player
what we're talking about here is like trying to make sure UUID.randomUuid() doesn't by-chance generate the same uuid twice
if it does happen, be sure to get a lottery ticket
IRP u mean inventory rollback plus?
myeah
i haven't used coreprotect for a while (i forked prism a few years back), but IRP is complementary to a block logging plugin
irp snapshots the inventory on world changes, joins/quits, and before death
coreprotect back when i used it didn't really have inventory snapshots in any sense
in this case you'd use coreprotect to check if the block was lava at the time of death, and then irp to restore the lost items
So you're saying to add the location with timestamp the player teleports to in the RTP logs, and then if someone reports it, I can see that log with the timestamp and view it along with the IRP logs?
oh i see
i mean the command usage should be in your latest.log or whatever tar.gz logfile, as will be the death message
from there you can see when it was; then check if the block at that time was lava with coreprotect, and then restore the inventory of that death with irp
But if CoreProtect has inventory snapshots, why not use it now?
if it does, you can use it instead of irp
back when i used coreprotect, it didn't, but it's been a long time
the point is that you can just restore the inventory from a snapshot
In this case, coreprotect allows me to see when and where the player died, and I can then see if the location of the block where the player died was lava
And the locations of the nearby blocks if they are lava too
Anyway, I'll give invincibility for a few seconds when the player teleports to help with this and another things too
How does player#teleportAsync work? For example, if I type a command X that teleports me to location X using player#teleportAsync, but then another plugin, upon typing a different command Y, teleports me to location Y using player#teleport, is it possible that even though command Y was executed later, I might end up last in position X?
I think this results in weird bugs
no clue
the basic gist of teleportAsync is that it loads all the surrounding chunks async, before teleporting the player sync
i have no idea if there is any kind of "reservation" system to prevent the player from being teleported while the server waits on the chunk to load
that said i've never run into any issues with either
depends on the server impl
solid chance that modern versions of teleport just block teleportAsync
Folia would just throw an error on teleport
but I do think it'd use locks so they'd be sequential
So, if teleportAsync is something new, why simply remove it?
and with the join is teleportAsync still advantageous?
Depends on the impl
I'm talking about the implementation of the paper
Why would you ever want to join besides maintaining legacy compat
im on the latest version
joining teleportAsync on main is just going to cause a deadlock
Yay
Deadlock? like the new Valve game?
our brains are shrinking becos of chatgpt
nah
Yo don't talk about our goat like that
chat gpt just happens to be a solution to our brain rotting away
correlation does not imply causation
Hello! How can I get a list of all available Spigot versions programmatically? I know about hub.spigotmc.org/versions/ but is there a proper API for that, or is HTML parsing the only way?
Versions, as in?
Minecraft versions? e.g, 1.21.1, 1.8, etc? or spigot builds?
You can get Minecraft versions in general directly from https://launchermeta.mojang.com/mc/game/version_manifest.json
I need to get both. It is preferable to get the mc versions from the spigot portal, in case spigot does not support the new version
you could check the maven repo, since they're all -SNAPSHOT they all get they own unique ID, I think it's timestamp based or sth, idk https://hub.spigotmc.org/nexus/service/rest/repository/browse/public/org/spigotmc/spigot-api/
but what do you need it for?
Yeah but programmatically infers scraping or something, these are all heavily nested <tr>'s.
it's nexus, nexus got an API I think
and nexus also provides an easy to parse XML file for snapshot versions
Look at whatever BuildTools does and do that
nah u right @tender shard
GET https://hub.spigotmc.org/nexus/service/rest/v1/search?repository=public&maven.groupId=org.spigotmc&maven.artifactId=spigot-api
this works
I still wonder why they need that
Bebop mains
I'm doing this on @OnEnable method but this is null (i added WorldManager as soft-depend)
overWorld = Bukkit.getWorld("resources_world");
Why they load the worlds async?
Is that World instance necessary on startup?
I don't imagine you specifically need it in onEnable(). If you're just checking for it in a Listener or something, reevaluate if you even need a World instance. If you want to do something as soon as that world is available, use a WorldLoadEvent listener instead
You may have to set your plugin's load time to STARTUP though because POSTWORLD is the default
I dunno. It depends ¯_(ツ)_/¯
Hey! i've seen some plugins use a custom expiremental menus like this.. how can i read more abt it?
They’re called Dialogs
omg ty!
If I change the plugin logic, no. But I'd like to know why the world manager loads async
It's annoying because multiverse-core probably loads on onEnable, and I have to use two types of logic because worldmanager doesn't load the worlds when the plugin is enabled
https://pastes.dev/GaNb7VnfEK location#isGenerated is loads a chunk?
how else would it know if the chunk is generated
It needs to check if the chunk is on disk or not
Loading a chunk doesn't cause the chunk to be generated?
Normally it does, but not with that method
Also that is Paper code I'm not sure why you're asking about it here
if you're not running Spigot you should be in the Paper discord
So if I use location#isChunkGenerated on a location where a chunk hasn't been generated, this method checks if the chunk is on the disk but doesn't generate the chunk?
yes
Using this method at runtime is overkill then
Hello @sullen marlin, I lost my Spigot account (the 2FA). How do I recover it?
?support
You should have kept your backup codes secure 😭
^ email time. Discord is not the way to contact md
for spigot support*
pretty sure there's like... a two week ban or some shit too if they get the problem solved lol
can you put pdc on items?
yes
Where do those people end up?
I lost my plugins lmao 🤣 
Do backup codes arrive in Gmail?
No you got them when you enabled 2fa
with big red text telling you to save them in a secure place and print them out
Damn, I lost that phone
welp you're fucked
lol v:
If you email support from the email the account is bound to I think you can recover it
It’s not
what's the best way to parse enchantments by a string?
There you go
getByKey is deprecated too, Registry.ENCHANTMENTS is deprecated too
endless trail of deprecation
Read the deprecation note for enchantment registry
god
Same goes for getByKey
all of this crap just for a simple enchantment parse
On spigot it’s just Registry.ENCHANTMENT.match
https://pastes.dev/krDLmKAAF3 Am I doing this correctly to check if a location is in a chunk that has already been generated? I noticed that managedBlock is a blocking method and I believe it's not necessary here. Also, I think I can replace the use of getChunkAtImmediately with another method to get the chunk
Why are you copy pasting Paper code in to your project
or did you just post that as reference
https://jd.papermc.io/paper/1.21.11/org/bukkit/World.html#getChunkAtAsync(int,int,boolean) exists if you want to get a chunk async without generating it
declaration: package: org.bukkit, interface: World
Also you really should head over to the Paper discord
because the paper doesn't have a location#isGenerated async
Just linked you the method to use
So, to check if a chunk was generated, should I use world#getChunkAsync#thenAccept(chunk -> if (chunk.isGenerated() { (...) })); ?
yeah pretty much
i got it thanks
or at least if I understand the docs correctly
tbh it's not entirely clear what happens when the chunk doesn't exist
It doesn't seem to be working as expected yea
chunk#isGenerated returns true even though it has never been generated (probably getChunkAsync generates it).
Did you pass false to the generate param
oh no
if you don't pass false it will generate
i see
chunk is returning null when doing this: world.getChunkAtAsync(location, false).thenAccept(chunk -> { (...) }));
If it has never been generated before, then will it be null?
Or could a chunk not be null but not be generated?
Guess it's null when not generated then 🤷♂️
You really should ask in the Paper discord though
I'm banned there
lol
because "I'm running a cracked server" https://spark.lucko.me/jT9y5Voyfj
I'm banned there because I sent a soundcloud link
lmao
https://pastes.dev/I7OjmQyDQQ I'm using world#getChunkAtAsync before getting the block to prevent the chunk sync loading; am I doing well?
Yeah that's fine
https://prnt.sc/5VLqjNSNM44O
https://prnt.sc/_e8Pa_DA0U1h
Hey, does anyone know how to create custom GUIs like these screenshots?
I'm trying to understand how some plugins manage to completely customize the player's inventory view sometimes they fully remove the default inventory interface, and other times they keep the inventory slots but replace all the items with custom GUI elements
What GUI framework (if any) do people typically use to achieve this kind of result?
And technically, how does it work ? are they using packets to hide/modify the inventory, or is there another approach?
resource packs
with resource packs as optic said, usually people will use nexo, oraxen or itemsadder to glyph the inventory title with a massive sprite
additionally you may use custom item textures for icons etc
I know that they use resource pack but im talking about the fact they remove the player inventory part of the gui
u can override vanilla gui
So no need to remove the bottom part of generic54 png and display back the bottom part when needed with packet ?
Yeah but i realy want to only render the top inv
that why
like this one: https://prnt.sc/_e8Pa_DA0U1h
then you might need to remove the bottom part of generic 54
or well, u could use another inventory type for that
doesnt have to be generic 54
wdym ? like maybe use a 5 row interface ?
Most UIs will end up looking like this once you get into resource packs lol
yea for example or lets say shulker box (iirc that has a separate sprite)
but if u want that transparency effect, u likely need to erase it from an existing vanilla texture
Yeah that could work but it limits me to only 3 rows of buttons for my custom GUIs. I think I'm going to try a different approach like removing the bottom part of a Generic_XX inside resourcepack, and then manually sending the player's inventory interface back to them via packet outside of my customs GUI by putting the player inv interface inside the title of any gui they open
This way I can support custom GUIs from 1 to 6 rows and only render the top inventory portion. The only downside I can think of is the hover highlight effect on the player inventory slots that I probably can't remove, but I think that's acceptable and can just be ignored
just look at the OriginRealms resource pack, see how they handle it lol
Obfuscated iirc, but just load it up as a ZipFile or whatever & save the contents to disk
eh I mean a lot of servers just remove generic 54 completely
and then just reapply with a glyphed title
Im gonna take a look thank uu
Easiest way to see how this stuff is done is to just... look at a server that does it already lol
not really
depends tbf
it can be hard to reverse engineer
its not really the best to look at everyone else and see how its done, best practice is to do it yourself, think things through
Not too difficult to just load up one or both of these
https://modrinth.com/mod/server-pack-unlocker
https://modrinth.com/mod/nbttooltip
take time
it is a start though
9/10 times it'll be a glyph or invisible armor stand, or some sort of item with a custom display model
i mean like for this inventory title stuff, its common knowledge reverse engineering it is basically unnecessary
but for more intricate things like player stable models etc, u might wna look into how existing servers do it
not sure how player stable models would be handled actually.
Though the Display entity certainly helps lol
https://septicuss.notion.site/Basic-Concepts-f93bcf8045714a99886825954608a63c
There's this site too, though I'm not 100% sure how up to date it is kekw
I usually only do this if I get stuck on something
happens if I am doing something or working with something I am not super familiar with
InventoryFramework is shit and introduces breaking changes with each release
It also still relies on obfuscated mappings IIRC
For nms stuff like anvil input
Yoo is jitpack down ? bruh
how unamusing and predictable
