Jump to content

How to save/read data per world? [1.19.2][SOLVED]


RInventor7

Recommended Posts

Hi! I would like to read/save a text file to world folder. But I don’t know how to get the world/level name as a string nor how to get a world’s directory (current world, where the player is in).

I managed to get the world name in singleplayer and only on server like this

String worldName = level.toString().substring(level.toString().indexOf("[")+1).replace("]", "");

There’s probably a method that would do that and return me the name even on client and multiplayer. Does anybody have any ideas or suggestions? Thanks.

Edited by RInventor7
Solved
Link to comment
Share on other sites

Thanks for the solution.

1) Since I am making my mod interact with some other files located in the world folder I would still like to be able to somehow get the path to that folder (using Java.io.file) if possible.

2) For data saving I tried to use the SavedData. But I can’t get it working. Here’s my saved data class:

package com.rinventor.ptmmod.util;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.saveddata.SavedData;

public class PTMSavedData extends SavedData {
	
	private int test = 0;

	public int getTest() {
		return this.test;
	}

	public void setTest(int test) {
		this.test = test;
		this.setDirty();
	}

	public static PTMSavedData create() {
		return new PTMSavedData();
	}
	
	public static PTMSavedData load(CompoundTag tag) {
		PTMSavedData data = create();
		data.test = tag.getInt("test");
		return data;
	}

	public CompoundTag save(CompoundTag tag) {
		tag.putInt("test", test);
		return tag;
	}

	public static void manage(MinecraftServer server) {
		server.overworld().getDataStorage().computeIfAbsent(PTMSavedData::load, PTMSavedData::create, "ptm-file");
	}
	
}

This should be correct? Now I have problems reading and writing the data. When world closes I’m trying to save some data, but it doesn’t even create .dat file. I’m probably doing it incorrectly:

PTMSavedData.manage(event.getLevel().getServer());
PTMSavedData data = PTMSavedData.create();
data.setTest(9);

And since it doesn’t create the file, it can’t read anything and will default the test int to 0. When I’m reading it (maybe also incorrectly?)

PTMSavedData.manage(level.getServer());
PTMSavedData data = new PTMSavedData();
int testInt = data.getTest();

What am I doing wrong? Thanks.

Link to comment
Share on other sites

  • RInventor7 changed the title to How to save/read data per world? [1.19.2]
18 hours ago, RInventor7 said:

1) Since I am making my mod interact with some other files located in the world folder I would still like to be able to somehow get the path to that folder (using Java.io.file) if possible.

Can I ask the reasoning for this? It seems kinda strange to need to touch any of the save's files imo.

18 hours ago, RInventor7 said:

This should be correct? Now I have problems reading and writing the data. When world closes I’m trying to save some data, but it doesn’t even create .dat file. I’m probably doing it incorrectly:

Yep, you're essentially creating the saved data and attaching that to the current level, and then creating another saved data without doing anything. #computeIfAbsent returns the current save data from the level or creates it if does not exist, so you should change #manage to return your saved data and the modify that.

Link to comment
Share on other sites

37 minutes ago, ChampionAsh5357 said:

#computeIfAbsent returns the current save data from the level or creates it if does not exist, so you should change #manage to return your saved data and the modify that.

Thanks. That works now.

37 minutes ago, ChampionAsh5357 said:

Can I ask the reasoning for this? It seems kinda strange to need to touch any of the save's files imo.

I’m basically trying to solve this other issue. Since I wish the player to hear my sound on server as well without registering them and without knowing their file names, I created a custom sound player. Since texture packs can be different for all players on server I decided that the sound files should be in the world folder, which should be the same for all the player playing on the same server. I just need my custom sound player to be able to find the world folder and locate the files to play them.

Link to comment
Share on other sites

2 hours ago, RInventor7 said:

Since texture packs can be different for all players on server I decided that the sound files should be in the world folder, which should be the same for all the player playing on the same server. I just need my custom sound player to be able to find the world folder and locate the files to play them.

You do realize you could force a server to load a resource pack, so that wouldn't matter. Then you can have some reader on the client with the data for the sounds and then just play them as required.

Link to comment
Share on other sites

5 hours ago, ChampionAsh5357 said:

You do realize you could force a server to load a resource pack.

Yes, already tried.

5 hours ago, ChampionAsh5357 said:

Then you can have some reader on the client with the data for the sounds and then just play them as required.

Well the reading is the problem. Reading and playing the sound like this

ResourceLocation resource1 = new ResourceLocation(MyMod.MOD_ID + ":" + filename);
SoundEvent se1 = new SoundEvent(resource1);
entity.playSound(se1, 1.0f, 1.0f);

will kick from the server saying that

java.lang.IllegalArgumentException: Can't find id for 'net.minecraft.sounds.SoundEvent@937bd60' in map Registry[ResourceKey[minecraft:root / minecraft:sound_event] (Experimental)]

Should I read and play the sound file somehow differently? I’m kinda clueless.

Link to comment
Share on other sites

13 hours ago, ChampionAsh5357 said:

Yes, you'll need to send a custom packet with the string instead and play it manually on the client.

Thanks, didn’t think of a custom packet myself. So I run the following code only on client?

ResourceLocation resource1 = new ResourceLocation(MyMod.MOD_ID + ":" + filename);
SoundEvent se1 = new SoundEvent(resource1);
entity.playSound(se1, 1.0f, 1.0f);

And then send a packet to server? Or I should run that code on server and send a packet to the client? Entity sound playing happens on client, does it? Or do I need to run this code in the packet handle function as well? Sorry, but I’m confused. 

On 2/20/2023 at 4:55 PM, ChampionAsh5357 said:

Use SavedData instead.

It works, but only on server-side? I have a custom screen that reads and sets some data that is saved in my SavedData instance. Since the screen is client-side only then MinecraftServer is null and accessing the SavedData will crash the game. How to fix that? (I don’t want to send a lot of custom packets back and forth)

Edited by RInventor7
Link to comment
Share on other sites

On 2/20/2023 at 9:54 PM, RInventor7 said:

1) Since I am making my mod interact with some other files located in the world folder I would still like to be able to somehow get the path to that folder (using Java.io.file) if possible.

On 2/21/2023 at 4:09 PM, ChampionAsh5357 said:

Can I ask the reasoning for this? It seems kinda strange to need to touch any of the save's files imo.

I have my own world based configuration file (.txt) which contains more than a minecraft’s configuration file could store, like custom object arrays and etc. I would just like to save and read from this file and store the file in the world folder (not in the configs folder what i managed to do at the moment). The reason for this is that then all the players on server would access the same file. Otherwise all players would have separate files on their computers which leads to data desync on server for the players??

I tried to fix this by implementing SavedData but now all the data between client and server is desynchronized. And SavedData can’t store custom object arrays? So for me the only option seems to be my custom configuration file which is always in-sync...

idk... Any better ideas??

Edited by RInventor7
Link to comment
Share on other sites

16 hours ago, RInventor7 said:

Thanks, didn’t think of a custom packet myself. So I run the following code only on client?

Well, you don't run that code specifically, you play the sound directly through the SoundEngine by passing in the ResourceLocation since the SoundEvent can't exist.

16 hours ago, RInventor7 said:

And then send a packet to server? Or I should run that code on server and send a packet to the client? Entity sound playing happens on client, does it? Or do I need to run this code in the packet handle function as well? Sorry, but I’m confused. 

The server should be the one sending the packet to the client. The only thing you would be sending is the name of the sound to play and the position on where to play it.

16 hours ago, RInventor7 said:

It works, but only on server-side? I have a custom screen that reads and sets some data that is saved in my SavedData instance. Since the screen is client-side only then MinecraftServer is null and accessing the SavedData will crash the game. How to fix that? (I don’t want to send a lot of custom packets back and forth)

As I mentioned, the server will be responsible for sending out the sounds assuming that there is a list of sounds the server can access that is guaranteed to be on the client. If you want this to be dynamic, you can have the server with a datapack containing that information.

10 hours ago, RInventor7 said:

I have my own world based configuration file (.txt) which contains more than a minecraft’s configuration file could store, like custom object arrays and etc. I would just like to save and read from this file and store the file in the world folder (not in the configs folder what i managed to do at the moment). The reason for this is that then all the players on server would access the same file. Otherwise all players would have separate files on their computers which leads to data desync on server for the players??

I tried to fix this by implementing SavedData but now all the data between client and server is desynchronized. And SavedData can’t store custom object arrays? So for me the only option seems to be my custom configuration file which is always in-sync...

idk... Any better ideas??

I mean, it wouldn't be in sync no matter which way you do it if you don't send a packet. The only reason you are able to say that it's fine now is because the server and client are the same instance. If on a dedicated server, you would run into the same issues.

Link to comment
Share on other sites

13 hours ago, ChampionAsh5357 said:

The server should be the one sending the packet to the client. The only thing you would be sending is the name of the sound to play and the position on where to play it.

Sending now the packet to client in order to play the sounds.

if (!level.isClientSide()) {
  //Send msg to client with filename and position
  ModNetwork.INSTANCE.send(PacketDistributor.ALL.noArg(), new MyS2CMessage(pos, filename));
}

This works fine on singleplayer, but on multiplayer I’ll get a server error:

[Server thread/ERROR] [ne.mi.ne.si.IndexedMessageCodec/SIMPLENET]: Received invalid message com.rinventor.mymod.util.sound.MyS2CMessage on channel mymod:network

Here’s my S2C Packet class. Maybe I’m doing something wrong (again)?

package com.rinventor.mymod.util.sound;

import java.util.function.Supplier;

import com.rinventor.mymod.MyMod;

import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.Level;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.network.NetworkEvent;

public class MyS2CMessage {

	public final BlockPos pos;
	public final String filename;

    public MyS2CMessage(BlockPos pos, String data) {
    	this.pos = pos;
    	this.filename = data;
    }

    public MyS2CMessage(FriendlyByteBuf buffer) {
    	this(buffer.readBlockPos(), buffer.readUtf());
    }

    public void encode(FriendlyByteBuf buffer) {
    	buffer.writeBlockPos(this.pos);
    	buffer.writeUtf(this.filename);
    }

    public static void handle(MyS2CMessage msg, Supplier<NetworkEvent.Context> ctx) {
        ctx.get().enqueueWork(() ->
            // Make sure it's only executed on the physical client
            DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> MyMessageClient.handlePacket(msg, ctx))
        );
        ctx.get().setPacketHandled(true);
    }
    
    public class MyMessageClient
    {
    	@SuppressWarnings("resource")
		public static void handlePacket(MyS2CMessage msg, Supplier<NetworkEvent.Context> ctx) {
    		Level level = Minecraft.getInstance().level;
    	    BlockPos pos = msg.pos;
    	    if (level.isLoaded(pos)) {
    	    	int x = pos.getX();
    	    	int y = pos.getY();
    	    	int z = pos.getZ();
    	    	level.playLocalSound(x, y, z, new SoundEvent(new ResourceLocation(MyMod.MOD_ID + ":" + msg.filename)), SoundSource.AMBIENT, 1, 1, false);
    	    }
    	}
    	
    }

}
Edited by RInventor7
Link to comment
Share on other sites

Since all other custom packets ware working and are working, and I have never had this type of an error before I think my network is working perfectly fine and it must be registered. And the message is registered like that:

INSTANCE.messageBuilder(MyS2CMessage.class, messageID++, NetworkDirection.PLAY_TO_CLIENT)
			.encoder(MyS2CMessage::encode).decoder(MyS2CMessage::new)
			.consumerMainThread(MyS2CMessage::handle).add();

Maybe something else is wrong? It crashes the server anyway when I try to play the sound by sending the custom packet.

Server crash report.

Edited by RInventor7
Crash report added
Link to comment
Share on other sites

On 2/20/2023 at 4:55 PM, ChampionAsh5357 said:

Use SavedData instead.

Using saved data when I have 200 variables is not very easy. I’m mean that I would like to access the variables more easily, like static variables. I don’t want to make a MySavedData object. Is it possible to make the saved data work with static variables and then maybe send some custom packets to sync the static variables with client? Maybe it’s possible to read all the variable in and sync them when player joins the world and then have a static method to save changes in the static variables to the MySavedData instance and set it dirty then (I don’t want to set and save each variable separately using setter nor do I wish to use getters in order to access the variables)? How can I achieve that?

package com.rinventor.mymod.core.data;

import net.minecraft.nbt.CompoundTag;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.saveddata.SavedData;

public class PTMData {
	
	private static int test = 0;
	
	public static class PTMSavedData extends SavedData
	{
		private int test = 0;
		
		private static PTMSavedData create() {
			return new PTMSavedData();
		}
		
		private static PTMSavedData load(CompoundTag tag) {
			PTMSavedData data = create();
			data.test = tag.getInt("test");
			return data;
		}

		public CompoundTag save(CompoundTag tag) {
			tag.putInt("test", test);
			return tag;
		}

		/*
		 * #server-side ONLY
		 * Gets current instance of the saved data
		 * Return new instance when on client
		 */
		public static PTMSavedData getInstance(LevelAccessor level) {
			MinecraftServer server = level.getServer();
			if (server == null) {
				return create();
			}
			return server.overworld().getDataStorage().computeIfAbsent(PTMSavedData::load, PTMSavedData::create, "ptm-data");
		}
		
		public void save() {
			this.setDirty();
		}

	}
	
	public static void save(LevelAccessor level) {
		if (!level.isClientSide()) {
			PTMSavedData data = PTMSavedData.getInstance(level);
			data.test = PTMData.test;
			//set other 200 varibales
			data.save();
		}
	}
	
	public static void read(LevelAccessor level) {
		if (!level.isClientSide()) {
			PTMSavedData data = PTMSavedData.getInstance(level);
			PTMData.test = data.test;
			//set other 200 varibales
			
			//sync PTMData.test with client using custom packet
		}
	}
	
}

When the player joins the world I would just call PTMData.read(); and after player changes some data (e.g. PTMData.test = 9;) I would call PTMData.save(); And if I want I can just access the static variables like PTMData.test. Only problem I can see is that when one player would change some data then other players would still have the old data. I now understand why the non-static way is better (because data always in-sync between all player on the server).

This all sounds too complicated. All I wish is to save the data on client to the SavedData and then get/access/change the data on client. But since SavedData is server-side it make everything more difficult. What would be the easiest and most reasonable way to do this? 

Edited by RInventor7
code added
Link to comment
Share on other sites

2 hours ago, RInventor7 said:

Maybe something else is wrong? It crashes the server anyway when I try to play the sound by sending the custom packet.

Well, it's suggesting it's an invalid message since you're probably passing in something which isn't valid. Additionally, you are enqueuing work twice because you are using `consumerMainThread`.

37 minutes ago, RInventor7 said:

Using saved data when I have 200 variables is not very easy

The fact that you have 200 variables means there's something wrong with what you're doing. Why would you have 200 of them anyways?

38 minutes ago, RInventor7 said:

This all sounds too complicated. All I wish is to save the data on client to the SavedData and then get/access/change the data on client. But since SavedData is server-side it make everything more difficult. What would be the easiest and most reasonable way to do this? 

Anything that involves saving information is server side. You're gonna need to use a packet either way to sync the data as required.

Link to comment
Share on other sites

14 hours ago, ChampionAsh5357 said:

Anything that involves saving information is server side. You're gonna need to use a packet either way to sync the data as required.

Okay, I’ll create custom packets then.

14 hours ago, ChampionAsh5357 said:

The fact that you have 200 variables means there's something wrong with what you're doing. Why would you have 200 of them anyways?

 I thought it was more but mod needs to save 87 variables (just counted them) per world to be exact. In addition to that the players can create new data by using custom block entities and I need to save all the data created by players and store it, so that it would be accessible after restarting the server and etc.

14 hours ago, ChampionAsh5357 said:

Well, it's suggesting it's an invalid message since you're probably passing in something which isn't valid.

Double checked everything and it all seems to be okay, yet it still crashes, but weirdly only when FMLEnvironment.dist == Dist.DEDICATED_SERVER. All the other S2C packets that I send will also crash when Dist == DEDICATED_SERVER. So there’s either something wrong with my packet (code above) or there’s something wrong with the registering of the packets (code above).

Edited by RInventor7
Link to comment
Share on other sites

On 2/24/2023 at 9:50 PM, RInventor7 said:

Double checked everything and it all seems to be okay, yet it still crashes, but weirdly only when FMLEnvironment.dist == Dist.DEDICATED_SERVER. All the other S2C packets that I send will also crash when Dist == DEDICATED_SERVER. So there’s either something wrong with my packet (code above) or there’s something wrong with the registering of the packets (code above).

Well, apparently my way of handling the pack is was not very good. Improved it and everything works now.  

Link to comment
Share on other sites

  • RInventor7 changed the title to How to save/read data per world? [1.19.2][SOLVED]

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.