#Save & Load stuff
1 messages ยท Page 1 of 1 (latest)
yep
Save & Load stuff
its easier to track a convo in a thread
Yeah
ok lets see if you actually need to save and load...
These chests, they are spawned by a command always?
Well, the plugin saves locations of blocks as a sort of lootable blocks. If the server ever crashes, reloads, or something else happens, it will otherwise lose all those locations, and the user would have to make them all over
They arent chests actually lol. The ChestObject is just a name for an object that stores information
what object type?
Uhh I mean it's like a class
in game they are represented by?
how do I explain this lol
is there a block you spawn in game to display them?
https://paste.md-5.net/uvasonupor.java - Here's what's called a chestobject. You dont spawn a block in game to display it or anything. Ill explain what the plugin does:
You use /sc add [PARTICLE] [cooldown] [items] whilst looking at a block to save that exact block location (coordinate) as a lootable block. So you dont do anything with the block, but just its location. When a player right clicks that block, the particles disappear, the player gets a random item from items, and the cooldown starts. Once the cooldown has finished, the particles reappear and the "block" (location) is lootable again
Of course you can also /sc remove the blocks, and /sc list them all
There are probably a 100 easier ways of making this plugin, but this has already taken me like 40 hours and I really dont want to spend more time on it lol.
what spigot version are you building against?
yep
ok as there is no actual in game object to represent these, other than particles, they are quite detached from the game world so saving to file is ok, however you could consider using PDC instead
as you have most of it done, lets continue with saving to yaml
your ChestObject needs to implement as so java public class ChestObject implements ConfigurationSerializable {
it will tell you it's missing methods so let it add them
ohh I see
you need to create a method public static ChestObject deserialize (Map< String, Object> object)
in that method you will be reading a Map passed in to create a new ChestObject
and that new chestobject only gets created for the save and load functions right?
the deserialize method will be used by the config when it loads
so it gets run automatically?
yes
And I do that by taking all the information from the map, then creating a new chestobject with that information?
it will yes
one sec
ok
ok inside the deserialize method you need to read data from teh map and convert it to the data your constructor needs to make a new ChestObject
so Location, particle, int a List of ItemStacks
public static ChestObject deserialize(Map<String, Object> args) {
boolean isLooted = (boolean) args.get("isLooted");
Location location = (Location) args.get("location");
Particle particle = Particle.valueOf((String) args.get("particle"));
int cooldown = (int) args.get("cooldown");
List<ItemStack> items = (List<ItemStack>) args.get("items");```?
they will be in the same order we save them so we can just iterate the map
the last one for the List may not work, but you can try it
Why not?
casting to lists and the internal object is often a pain
now the last line of that method would be return new ChestObject(isLooted, location, particle, cooldown, items);
ok, what other methods did your IDE make you create in that class?
probably a serialize
Yeah
So far I got java public Map<String, Object> serialize() { Map<String, Object> result = new LinkedHashMap<>(); result.put("isLooted", isLooted); result.put("location", location); result.put("particle", particle.toString()); result.put("cooldown", cooldown); result.put("items", items); return result; }
ok in that need to dump all our data for this ChestObject
yep
did it create any other methods?
No, but I read that I had to add java public ChestObject() { } to the code for the serialization/deserialization to work
But Im not sure why
a blank constructor
yeah....
It says that this is required for the YAML deserialization to work.
at the very top of your onEnable add java ConfigurationSerialization.registerClass(ChestObject.class);
that lets Bukkit know about your object so it knows how to deserialize it
now test it
Gimme 1 sec, brb
It works!
Only thing is (which I noticed a while ago) is that some particles will result in an error. One of these is EXPLOSION
https://paste.md-5.net/uqamuzotow.cs
Do you know why this might happen?
The error doesnt tell me much
I guess that particle name isnt correct, which brings me to what was mentioned earlier: Auto-correct (auto-fill-in, whatever you want to call it). Can you explain to me how something like that would work? @summer mortar
yes tab complete
your command class, instead of extending CommandExcutor you use TabExecutor
And that's it?
not quite
it will make you create a new method for tab complete
in that method you return a List of acceptable entries depending on the length of args
Alright so it created public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { return null; }
yep, if you want it to offer particles when args length is 2
if (args.length == 2) return Particles.values();
actually you need to add that to a List
as Particles.values() returnes an array
Hmm alright
And what is 'Particles'?
I mean, I know what it is
but, it's not an existing thing is it
so return Arrays.asList(Particle.values());
Shouldnt it be Particles.values().toString()?
ah you'll probably have to stream to get teh names
stream?
if (args.length == 2) return Stream.of(Particle.values())
.map(Particle::name)
.collect(Collectors.toList());```
And what does the stream.of mean
it processes them one by one
Ah alright
map takes the entry name
and where should I create enumNames?
Ah alright, awesome. Let me test it
or is this not done
Shouldnt onCommand be removed? @summer mortar
no, thats still used
your class does two things now, onCommand processes the command when it's fully entered and the player presses enter. TabComplete just offers up suggestions.
Now Im getting an error from my main.java
private void registerCommands(ParticleHandler particleHandler) {
Objects.requireNonNull(getCommand("sc")).setExecutor(new AddParticleCommand(particleHandler));
}``` 'setExecutor(org.bukkit.command.CommandExecutor)' in 'org.bukkit.command.PluginCommand' cannot be applied to '(me.JustinS_2006.particles.AddParticleCommand)'
o
There we go, all fixed now lol
It works! It's amazing haha. Thanks so much for the help! ๐
you can improve teh tab suggestion
oh, how?
if (args.length == 2) return Stream.of(Particle.values())
.map(Particle::name)
.filter(str -> str.trim().contains(args(1).toUpperCase()))
.collect(Collectors.toList());```
would probably work
What's the difference
that shoudl allow you to start typing the name and it will shorten the list
ooh cool
it added the filter line
Ahh I see
uhh
well
When I try using the BLOCK_CRACK particle, I can't remove it, no particle appears, I cant loot it, and I get a looping error. :/
https://paste.md-5.net/lazarojero.bash
I see that it requires data, but is there a way I can make sure particles that "require data" arent allowed?
because it kinda breaks everything
even if I reload
possibly
you could try adding before the .map line in tabcompletejava .filter(Particle::getDataType != BlockData.class)
if that works to exclude teh crack entry
we can do a simple validity check in teh onCommand to exclude bad selections
Ill give it a try
Method reference expression is not expected here
tryjava .filter(enum - > Particle::getDataType != BlockData.class)
nope, one sec
.filter(entry -> entry.getDataType() != BlockData.class)
that should work
had to open my ide to syntax check ๐
My electricity and wifi just died and it fucked up discord and mc on my laptop so 1 sec
It doesnt tabcomplete blockcrack anymore, but it does tab complete the legacy particles. JustinS_2006 issued server command: /sc add LEGACY_BLOCK_CRACK 5 sand [16:55:49] [Server thread/WARN]: [ScrapCollection] Task #14 for ScrapCollection v1.0 generated an exception java.lang.IllegalArgumentException: Particle LEGACY_BLOCK_CRACK requires data, null provided at com.google.common.base.Preconditions.checkArgument(Preconditions.java:191) ~[server.jar:3284a-Spigot-3892929-0ab8487]
which also result in chaos and destruction
simple to exclude LEGACY_
yeah?
Stream.of(Particle.values())
.filter(entry -> entry.getDataType() != BlockData.class)
.map(Particle::name)
.filter(str -> str.trim().contains(args(1).toUpperCase()) && !str.startsWith("LEGACY_"))
.collect(Collectors.toList());```
Now it doesn't recommend anything anymore :/
once this is all workgin as you want it we can tidy up and simplify it
doh
fixed code
Exception when JustinS_2006 attempted to tab complete sc
org.bukkit.command.CommandException: Unhandled exception during tab completion for command '/sc ' in plugin ScrapCollection v1.0
at org.bukkit.command.PluginCommand.tabComplete(PluginCommand.java:150) ~[server.jar:3284a-Spigot-3892929-0ab8487]
at org.bukkit.command.Command.tabComplete(Command.java:93) ~[server.jar:3284a-Spigot-3892929-0ab8487]
at org.bukkit.command.SimpleCommandMap.tabComplete(SimpleCommandMap.java:231) ~[server.jar:3284a-Spigot-3892929-0ab8487] ....
:/
I'll need the whole exception
Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1
at me.JustinS_2006.particles.AddParticleCommand.lambda$onTabComplete$1(AddParticleCommand.java:103) ~[?:?]
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:178) ~[?:?]
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[?:?]
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[?:?]
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992) ~[?:?]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[?:?]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[?:?]
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[?:?]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[?:?]
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) ~[?:?]
at me.JustinS_2006.particles.AddParticleCommand.onTabComplete(AddParticleCommand.java:104) ~[?:?]
at org.bukkit.command.PluginCommand.tabComplete(PluginCommand.java:141) ~[server.jar:3284a-Spigot-3892929-0ab8487]
um you deleted the if (args.length == 2)
It works! Now, what has to be simplified?
ok now we don;t need to do all the streaming every time a key is pressed we can move some stuff to variables
ok, at the top of the class add a fieldjava List<String> validParticles = Stream.of(Particle.values()) .filter(entry -> entry.getDataType() == Void.class) .map(Particle::name) .filter(str -> !str.startsWith("LEGACY_")) .collect(Collectors.toList());
then where you were doing this replace withjava if (args.length == 2) return StringUtil.copyPartialMatches(args[1].toUpperCase(), validParticles, new ArrayList<String>(validParticles.size()));
in your onCommand you can then check they selected a valid particle and set a default if they didn't
My electricity and wifi died again, sorry for the sudden disappearance
I gtg for dinner now but Ill be back in 20min
StringUtil isn't recognised though
nvm
So like this?```java
List<String> validParticles = Stream.of(Particle.values())
.filter(entry -> entry.getDataType() == Void.class)
.map(Particle::name)
.filter(str -> !str.startsWith("LEGACY_"))
.collect(Collectors.toList());
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 2) return StringUtil.copyPartialMatches(args[1].toUpperCase(), validParticles, new ArrayList<String>(validParticles.size()));
return null;
}```
Btw feel free to ping me when replying. That way I can respond asap
yep looks good
you can use either StringUtil as it's packaged with Spigot or use teh Stream method.
return validParticles.stream()
.filter(str -> str.contains(args[1].toUpperCase()))
.collect(Collectors.toList());```or```java
return StringUtil.copyPartialMatches(args[1].toUpperCase(), validParticles, new ArrayList<String>(validParticles.size()));```
And what's the difference
StringUtils is cleaner but either works just as well as the other
all you have left is validate your input in onCommand
Particle particle = (validParticles.contains(args[2].toUppercase()) ? Particle.valueOf(args[2].toUppercase()) : Particle.ASH;```
That will ensure they always select a valid particle or it will set ASH
or whichever you want
somehow toUppercase doesnt work
Ah alright, thats useful
.toUpperCase() definately correct
you need to move that line down to where it actually picks the particle
So here?
Not sure if I still need that first line though
I dont think so
but I think it should be args[1] not args[2]
yep just replace yoru line with mine
looks like it should also be args[1] not 2
yeah just mentioned that
ah I see
Btw is this plugin easy to turn into a player-based plugin. So basically, everyone can right click the same block, and every player has their own cooldown? Because now if someone right clicks the block, the particles also disappear for me.
you could but it would not be so easy as you'd have to use packets to display the particles
Ah alright, well let's not do that then
Ill make sure there are enough scrap locations
I let someone else try the plugin and they got this error when I use /sc add ...
[12:53:34 WARN]: java.io.IOException: No such file or directory
[12:53:34 WARN]: at java.base/java.io.UnixFileSystem.createFileExclusively(Native Method)
[12:53:34 WARN]: at java.base/java.io.File.createNewFile(File.java:1043)
[12:53:34 WARN]: at ScrapCollection-1.0-SNAPSHOT.jar//me.JustinS_2006.particles.ParticleHandler.saveChestData(ParticleHandler.java:121)
[12:53:34 WARN]: at ScrapCollection-1.0-SNAPSHOT.jar//me.JustinS_2006.particles.ParticleHandler.AddLoc(ParticleHandler.java:38)
[12:53:34 WARN]: at ScrapCollection-1.0-SNAPSHOT.jar//me.JustinS_2006.particles.AddParticleCommand.onCommand(AddParticleCommand.java:66)``` It has to do with the path of the data file right?
uhhh
Where would I call that?
before you attempt to create a file. It will ensure teh directory exists
Ah alrigh
yep
๐
Still not working in his server :/
This time there is a folder and file.
Error:
java.lang.NullPointerException: Cannot invoke "org.bukkit.Location.add(double, double, double)" because "location" is null
at me.JustinS_2006.particles.ParticleHandler$1.run(ParticleHandler.java:88) ~[ScrapCollection-1.0-SNAPSHOT.jar:?]...```
the chest_data file:```
chests:
- ==: me.JustinS_2006.particles.ChestObject
isLooted: false
location:
==: org.bukkit.Location
world: galaxy
x: -1743.0
y: 107.0
z: -2937.0
pitch: 0.0
yaw: 0.0
particle: SCRAPE
cooldown: 1
items:
- ==: org.bukkit.inventory.ItemStack
v: 2730
type: SAND```
How can the location be null @summer mortar ?
I mean, I obviously added a particle to a location
I'd need to see your loading code
show me the deserialize method in your ChestObject
@restive depot
public static ChestObject deserialize(Map<String, Object> args) {
// Get all the variables from the Map, and return them as a ChstObject
boolean isLooted = (boolean) args.get("isLooted");
Location location = (Location) args.get("location");
Particle particle = Particle.valueOf((String) args.get("particle"));
int cooldown = (int) args.get("cooldown");
List<ItemStack> items = (List<ItemStack>) args.get("items");
return new ChestObject(isLooted, location, particle, cooldown, items);
}
public Map<String, Object> serialize() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("isLooted", isLooted);
result.put("location", location);
result.put("particle", particle.toString());
result.put("cooldown", cooldown);
result.put("items", items);
return result;
}```
I think the issue is Location location = entry.getKey();
/sc list also gives an error, and its the only line of code it has in common with the other method that fails
at what point does it fail, after a load?
check yoru log for errors during the load
I added the "THE LOCATION IS NUll" thingy, and it got triggeredjava public void spawnParticlesEverySecond() { BukkitRunnable runnable = new BukkitRunnable() { public void run() { for (Map.Entry<Location, ChestObject> entry : chests.entrySet()) { if (!entry.getValue().isLooted()) { Particle particle = entry.getValue().getParticle(); Location location = entry.getKey(); if(location == null){ Bukkit.broadcastMessage("LOCATION IS NULL !!!! >:("); return; } location.add(0.5, 1.2, 0.5); location.getWorld().spawnParticle(particle, location, 7, 0.15, 0.25, 0.15); location.subtract(0.5, 1.2, 0.5); } } } };
So it has to do with entry.getKey(), I think
No other errors.
Alright
if that works, show me your loading code
Loading code?
when you read it from the yaml
It keeps repeating the broadcast message
after restarting
So I basically start the server > /sc add... > restart > broadcastmessage appears
https://paste.md-5.net/ your main class
Remember that this error only occurs in his server. Not when I host my local server
you never load anything from your yaml
after your register your class you should be loading your data
Then why is it working?
in ParticleHandler
ohb, line 13 take the ParticleHandler off the front
you are defining a local variable and not using the field
line 13 should just be particleHandler = new ParticleHandler(this);
ohh of course
In particleHandler:
private void loadChestData() {
try {
File dataFolder = plugin.getDataFolder();
if (!dataFolder.exists()) {
dataFolder.mkdirs();
}
File dataFile = new File(dataFolder, CHEST_DATA_FILE);
if (!dataFile.exists()) {
return;
}
YamlConfiguration config = YamlConfiguration.loadConfiguration(dataFile);
List<ChestObject> chestList = (List<ChestObject>) config.getList("chests");
if (chestList == null) {
return;
}
for (ChestObject chest : chestList) {
chests.put(chest.getLocation(), chest);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void saveChestData() {
try {
File dataFile = new File(plugin.getDataFolder(), CHEST_DATA_FILE);
if (!dataFile.exists()) {
dataFile.createNewFile();
}
YamlConfiguration config = YamlConfiguration.loadConfiguration(dataFile);
List<ChestObject> chestList = new ArrayList<>(chests.values());
config.set("chests", chestList);
config.save(dataFile);
} catch (IOException e) {
e.printStackTrace();
}
}```
can you do it in a paste so it's easier to read
nm
opk your save code is not correct
nm = nvm?
oh
yep
why is it not correct
you call load on it when all you want to do is save
how?
YamlConfiguration config = YamlConfiguration.loadConfiguration(dataFile);
you need to create an empty one
you do but you shoudl not load in a save method
oh alright
so YamlConfiguration config = new YamlConfiguration();
if it lets you do that
it should as it's public
public ParticleHandler(Plugin plugin) {
this.plugin = plugin;
loadChestData();
}
In the particlehandler
ok on instancing
thats good?
yep should be fine
So long as the chests field in ParticleHandler is the same one used in your event listener
still doesn't work
the "LOCATION IS NULL" triggered
here's the chest_data:
chests:
- ==: me.JustinS_2006.particles.ChestObject
isLooted: false
location:
==: org.bukkit.Location
world: galaxy
x: -1744.0
y: 107.0
z: -2923.0
pitch: 0.0
yaw: 0.0
particle: SCRAPE
cooldown: 5
items:
- ==: org.bukkit.inventory.ItemStack
v: 2730
type: SAND```
@summer mortar idk what to do anymore
none of the "fixes" are fixing it
...
like... are all of my methods wrong
no
surprising lol
What's wrong about the deserialize method?
I think the config is using the constructor to create a new ChestObject but the deserialize is not filling in the fields
it is returning a new object
one sec
ok the deserialize method needs to return itself rather than a new instance
paste your chestObject class
@restive depot
um
this looks ok to me https://paste.md-5.net/pogonakumo.java
I added an overdide and removed the empty constructor as it's not needed
But how would that fix location from being null
it probably used reflection to find a constructor and used it, so it created an empty object
had no fields set
Location is null, once again
The debug code is the part that says the location is null, right?
yes
what should be debugged?
the location
well that already exists
debug location in deserialization method
k. Should I just check if it's null everywhere where it's used?
in the deserialization method
k
doh I see where the error is
it is in the deserialize method I think it's takign the class fields to construct the new object
um nope
debug it
The deserialization debug does not run
trigger
so...
it gets changed somewhere else
one sec then
ok add this constructor```java
@SuppressWarnings("unchecked")
public ChestObject(Map<String, Object> args) {
// Get all the variables from the Map, and return them as a ChestObject
isLooted = (boolean) args.get("isLooted");
location = (Location) args.get("location");
particle = Particle.valueOf((String) args.get("particle"));
cooldown = (int) args.get("cooldown");
items = (List<ItemStack>) args.get("items");
}```
Alright
Should I remove the empty ChestObject constructor?
yes
k
Alright so what does this constructor do?
the same as the deserialize did but to the local object
k
Shouldnt the error be caused by something in this though? java for (Map.Entry<Location, ChestObject> entry : chests.entrySet()) { if (!entry.getValue().isLooted()) { Particle particle = entry.getValue().getParticle(); Location location = entry.getValue().getLocation();
That location there is null
and is causing everything to break
no, that is just reading the already loaded map
maybe it's being read wrong there? Or maybe not
so somewhere empty/null objects have been put in the map
k
Still no. Same msg gets printed
smth like this?
yep
Whilst Im waiting for the host to check the console, how come the plugin worked perfectly in my 1.17.1 localhosted server, but not in his 1.17.1 spigot server?
no clue, makes no sense
Could it perhaps have to do with how his path is setup?
I mean, if it does, the error makes no sense
nope, path is from spigot so
weird shit
No error, nor message, commands work, but nothing gets saved.
add an api-version: 1.17 to your plugin.yml
He also got this error, though Im not sure how he got it [17:39:34 ERROR]: Could not load 'plugins/ScrapCollection-1.0-SNAPSHOT.jar' in folder 'plugins' org.bukkit.plugin.InvalidPluginException: java.lang.UnsupportedClassVersionError: me/JustinS_2006/particles/main has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 60.0 at org.bukkit.plugin.java.JavaPluginLoader.loadPlugin(JavaPluginLoader.java:157) ~[patched_1.17.1.jar:git-Paper-408] at org.bukkit.plugin.SimplePluginManager.loadPlugin(SimplePluginManager.java:414) ~[patched_1.17.1.jar:git-Paper-408] at org.bukkit.plugin.SimplePluginManager.loadPlugins(SimplePluginManager.java:322) ~[patched_1.17.1.jar:git-Paper-408] at org.bukkit.craftbukkit.v1_17_R1.CraftServer.loadPlugins(CraftServer.java:419) ~[patched_1.17.1.jar:git-Paper-408] at net.minecraft.server.dedicated.DedicatedServer.initServer(DedicatedServer.java:287) ~[patched_1.17.1.jar:git-Paper-408] at net.minecraft.server.MinecraftServer.runServer(MinecraftServer.java:1220) ~[patched_1.17.1.jar:git-Paper-408] at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:319) ~[patched_1.17.1.jar:git-Paper-408] at java.lang.Thread.run(Thread.java:831) ~[?:?] Caused by: java.lang.UnsupportedClassVersionError: me/JustinS_2006/particles/main has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 60.0 .... ... 7 more my guess is that the server version isnt 1.17
Was already there
wrong java version on the server
yeah, thats what I guess. Awaiting his response
I'm 101% sure it has to do on his side. Just started another quick server using server.pro, 1.17.1 spigot and it works just fine
So it must be on his side
Ugh. still not working.
The file gets saved. The command works. But when restarting, no errors, the file is still there, but it won't load. The locations are gone, but not from the file.
I also changed my plugin to java 16 as their server also turned to java 16
@summer mortar I found the issue. It's that the world is not recognised as world, because it's generated through a plugin (because you cant have more than 1 world in minecraft, you use a plugin such as multiverse). Do you know a workaround?
yes, the multicraft plugin has to load before yours
so he needs it on his server too
and the world must be created
Ah thanks for pointing it out, Ill check it out
How can I make a certain plugin load before a different one? The host that's being used won't allow you to change the order in which plugins start.
It's also a randomized order (I think?)
yes unless you add a depend to your plugin.yml
depend: [ MultiverseCore]
I think
Ohh so that's what depends are for?
may just be Multiverse, you'll need to look it up
I saw them but thought they were just kinda like libraries