Skip to content

Commit

Permalink
save rainglow mode per-world
Browse files Browse the repository at this point in the history
  • Loading branch information
ix0rai committed Jun 1, 2024
1 parent dedc8c8 commit 9ed3c0f
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 47 deletions.
20 changes: 12 additions & 8 deletions src/main/java/io/ix0rai/rainglow/Rainglow.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.ix0rai.rainglow;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import folk.sisby.kaleido.lib.quiltconfig.api.serializers.TomlSerializer;
import folk.sisby.kaleido.lib.quiltconfig.implementor_api.ConfigEnvironment;
import io.ix0rai.rainglow.config.PerWorldConfig;
import io.ix0rai.rainglow.config.RainglowConfig;
import io.ix0rai.rainglow.data.*;
import net.fabricmc.api.ModInitializer;
Expand All @@ -15,6 +17,7 @@
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.random.RandomGenerator;
import net.minecraft.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -27,7 +30,8 @@ public class Rainglow implements ModInitializer {
private static final String FORMAT = "toml";
private static final ConfigEnvironment ENVIRONMENT = new ConfigEnvironment(FabricLoader.getInstance().getConfigDir(), FORMAT, TomlSerializer.INSTANCE);
public static final RainglowConfig CONFIG = RainglowConfig.create(ENVIRONMENT, "", MOD_ID, RainglowConfig.class);
public static final Gson GSON = new Gson();
public static final PerWorldConfig MODE_CONFIG = PerWorldConfig.create(ENVIRONMENT, MOD_ID, "per_world", PerWorldConfig.class);
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();

public static final String CUSTOM_NBT_KEY = "Colour";
public static final Identifier SERVER_MODE_DATA_ID = id("server_mode_data");
Expand All @@ -53,13 +57,13 @@ public static Identifier id(String id) {
return new Identifier(MOD_ID, id);
}

public static String generateRandomColourId(RandomGenerator random) {
var colours = CONFIG.getMode().getColours();
public static String generateRandomColourId(World world, RandomGenerator random) {
var colours = MODE_CONFIG.getMode(world).getColours();
return colours.get(random.nextInt(colours.size())).getId();
}

public static boolean colourUnloaded(RainglowEntity entityType, String colour) {
var colours = CONFIG.getMode().getColours();
public static boolean colourUnloaded(World world, RainglowEntity entityType, String colour) {
var colours = MODE_CONFIG.getMode(world).getColours();
return !colours.contains(RainglowColour.get(colour)) && !colour.equals(entityType.getDefaultColour().getId());
}

Expand All @@ -76,12 +80,12 @@ public static Text translatableText(String key) {
return Text.translatable(translatableTextKey(key));
}

public static RainglowColour getColour(RainglowEntity entityType, DataTracker tracker, RandomGenerator random) {
public static RainglowColour getColour(World world, RainglowEntity entityType, DataTracker tracker, RandomGenerator random) {
// generate random colour if the squid's colour isn't currently loaded
String colour = tracker.get(entityType.getTrackedData());
if (colourUnloaded(entityType, colour)) {
if (colourUnloaded(world, entityType, colour)) {
// Use last generated colour if not null else generate a new colour
tracker.set(entityType.getTrackedData(), generateRandomColourId(random));
tracker.set(entityType.getTrackedData(), generateRandomColourId(world, random));
colour = tracker.get(entityType.getTrackedData());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void onInitializeClient() {
// otherwise the client's custom would be used
ValueList<String> customColours = ValueList.create("", payload.customMode().stream().map(RainglowColour::getId).toArray(String[]::new));
Rainglow.CONFIG.customColours.setOverride(customColours);
Rainglow.CONFIG.mode.setOverride(payload.currentMode());
Rainglow.CONFIG.defaultMode.setOverride(payload.currentMode());

var rarities = ValueMap.builder(0);
for (var entry : payload.rarities().entrySet()) {
Expand Down
96 changes: 96 additions & 0 deletions src/main/java/io/ix0rai/rainglow/config/PerWorldConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.ix0rai.rainglow.config;

import com.mojang.datafixers.util.Either;
import folk.sisby.kaleido.api.ReflectiveConfig;
import folk.sisby.kaleido.lib.quiltconfig.api.annotations.Comment;
import folk.sisby.kaleido.lib.quiltconfig.api.annotations.SerializedNameConvention;
import folk.sisby.kaleido.lib.quiltconfig.api.metadata.NamingSchemes;
import folk.sisby.kaleido.lib.quiltconfig.api.values.TrackedValue;
import folk.sisby.kaleido.lib.quiltconfig.api.values.ValueMap;
import io.ix0rai.rainglow.Rainglow;
import io.ix0rai.rainglow.data.RainglowMode;
import io.ix0rai.rainglow.mixin.MinecraftServerAccessor;
import net.minecraft.client.MinecraftClient;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.util.WorldSavePath;
import net.minecraft.world.World;

import java.nio.file.Files;
import java.nio.file.Path;

@SerializedNameConvention(NamingSchemes.SNAKE_CASE)
public class PerWorldConfig extends ReflectiveConfig {
@Comment("The mode used for each non-local world.")
@Comment("Note that for singleplayer worlds, the mode is saved in the world folder in the file \"rainglow.json\".")
public final TrackedValue<ValueMap<String>> modesByWorld = this.map("").build();

public RainglowMode getMode(World world) {
var saveName = getSaveName(world);
String mode = null;

if (saveName.right().isPresent()) {
mode = modesByWorld.value().get(saveName.right().get());
} else if (saveName.left().isPresent()) {
Path path = saveName.left().get().resolve("rainglow.json");
if (Files.exists(path)) {
try {
var data = Rainglow.GSON.fromJson(Files.readString(path), RainglowJson.class);
if (data != null) {
mode = data.mode;
}
} catch (Exception e) {
Rainglow.LOGGER.error("Failed to load Rainglow config for world " + saveName.left().get(), e);
}
} else {
save(saveName.left().get(), RainglowMode.get(Rainglow.CONFIG.defaultMode.value()));
}
}

if (mode == null) {
return RainglowMode.get(Rainglow.CONFIG.defaultMode.value());
} else {
return RainglowMode.get(mode);
}
}

public void setMode(World world, RainglowMode mode) {
var saveName = getSaveName(world);
if (saveName.right().isPresent()) {
modesByWorld.value().put(saveName.right().get(), mode.getId());
} else if (saveName.left().isPresent()) {
save(saveName.left().get(), mode);
}
}

private static void save(Path worldPath, RainglowMode mode) {
Path path = worldPath.resolve("rainglow.json");
var data = new RainglowJson(mode.getId());

try {
Files.writeString(path, Rainglow.GSON.toJson(data));
} catch (Exception e) {
Rainglow.LOGGER.error("Failed to save Rainglow config for world " + worldPath, e);
}
}

@SuppressWarnings("ConstantConditions")
private static Either<Path, String> getSaveName(World world) {
if (!world.isClient) {
if (world.getServer() instanceof DedicatedServer dedicatedServer) {
return Either.right(dedicatedServer.getLevelName()); // "world" or something
} else {
return Either.left(((MinecraftServerAccessor) world.getServer()).getSession().getDirectory(WorldSavePath.ROOT));
}
} else {
if (MinecraftClient.getInstance().isInSingleplayer()) {
return Either.left(((MinecraftServerAccessor) MinecraftClient.getInstance().getServer()).getSession().getDirectory(WorldSavePath.ROOT));
} else {
return Either.right(MinecraftClient.getInstance().getCurrentServerEntry().address);
}
}
}

private record RainglowJson(String mode) {

}
}
17 changes: 3 additions & 14 deletions src/main/java/io/ix0rai/rainglow/config/RainglowConfig.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.ix0rai.rainglow.config;

import folk.sisby.kaleido.api.ReflectiveConfig;
import folk.sisby.kaleido.lib.quiltconfig.api.annotations.Alias;
import folk.sisby.kaleido.lib.quiltconfig.api.annotations.Comment;
import folk.sisby.kaleido.lib.quiltconfig.api.annotations.SerializedNameConvention;
import folk.sisby.kaleido.lib.quiltconfig.api.metadata.NamingSchemes;
import folk.sisby.kaleido.lib.quiltconfig.api.values.TrackedValue;
import folk.sisby.kaleido.lib.quiltconfig.api.values.ValueList;
import folk.sisby.kaleido.lib.quiltconfig.api.values.ValueMap;
import io.ix0rai.rainglow.Rainglow;
import io.ix0rai.rainglow.data.RainglowColour;
import io.ix0rai.rainglow.data.RainglowEntity;
import io.ix0rai.rainglow.data.RainglowMode;
Expand All @@ -21,26 +21,15 @@
public class RainglowConfig extends ReflectiveConfig {
@Comment("The currently active rainglow mode, which determines the possible colours for entities to spawn with.")
@Comment("If custom, will be reset to the default mode if you join a server that does not have the mode.")
public final TrackedValue<String> mode = this.value("rainbow");
@Alias("mode")
public final TrackedValue<String> defaultMode = this.value("rainbow");
@Comment("The rarity of coloured entities, with 0 making all entities vanilla and 100 making all entities coloured.")
public final TrackedValue<ValueMap<Integer>> rarities = this.createMap(100);
@Comment("Toggles for disabling colours for each entity.")
public final TrackedValue<ValueMap<Boolean>> toggles = this.createMap(true);
@Comment("The custom colours to use when the mode is set to custom.")
public final TrackedValue<ValueList<String>> customColours = this.list("", RainglowMode.getDefaultCustom().stream().map(RainglowColour::getId).toArray(String[]::new));

public RainglowMode getMode() {
var mode = RainglowMode.get(this.mode.value());

if (mode == null) {
Rainglow.LOGGER.warn("unknown mode {}, defaulting to rainbow", this.mode.value());
this.mode.setValue("rainbow");
return getMode();
}

return mode;
}

public List<RainglowColour> getCustom() {
return this.customColours.value().stream().map(RainglowColour::get).collect(Collectors.toList());
}
Expand Down
20 changes: 18 additions & 2 deletions src/main/java/io/ix0rai/rainglow/config/RainglowConfigScreen.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,27 @@ public class RainglowConfigScreen extends Screen implements ScreenWithUnsavedWar
public RainglowConfigScreen(@Nullable Screen parent) {
super(TITLE);
this.parent = parent;
this.mode = RainglowMode.get(Rainglow.CONFIG.mode.getRealValue());
this.mode = getMode();
this.saveButton = ButtonWidget.builder(Rainglow.translatableText("config.save"), button -> this.save()).build();
this.saveButton.active = false;
}

private void setMode(RainglowMode mode) {
if (MinecraftClient.getInstance().world == null) {
Rainglow.CONFIG.defaultMode.setValue(mode.getId());
} else {
Rainglow.MODE_CONFIG.setMode(MinecraftClient.getInstance().world, mode);
}
}

private RainglowMode getMode() {
if (MinecraftClient.getInstance().world == null) {
return RainglowMode.get(Rainglow.CONFIG.defaultMode.getRealValue());
} else {
return Rainglow.MODE_CONFIG.getMode(MinecraftClient.getInstance().world);
}
}

private TextWidget getInfoText() {
if (MinecraftClient.getInstance().isInSingleplayer()) {
return new TextWidget((Rainglow.RAINGLOW_DATAPACKS.size() == 1 ? Rainglow.translatableText("config.loaded_builtin", RainglowMode.values().size()) : Rainglow.translatableText("config.loaded_datapacks", RainglowMode.values().size(), Rainglow.RAINGLOW_DATAPACKS.size())), this.textRenderer).setTextColor(0x00FFFF);
Expand Down Expand Up @@ -150,7 +166,7 @@ private void save() {
}
}

Rainglow.CONFIG.mode.setValue(this.mode.getId());
this.setMode(this.mode);
this.saveButton.active = false;
}

Expand Down
9 changes: 5 additions & 4 deletions src/main/java/io/ix0rai/rainglow/data/RainglowEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.Identifier;
import net.minecraft.util.random.RandomGenerator;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
Expand Down Expand Up @@ -72,11 +73,11 @@ public Item getItem(int index) {
return RainglowColour.values()[index].getItem();
}

public RainglowColour readNbt(NbtCompound nbt, RandomGenerator random) {
public RainglowColour readNbt(World world, NbtCompound nbt, RandomGenerator random) {
String colour = nbt.getString(Rainglow.CUSTOM_NBT_KEY);

if (Rainglow.colourUnloaded(this, colour)) {
colour = Rainglow.generateRandomColourId(random);
if (Rainglow.colourUnloaded(world, this, colour)) {
colour = Rainglow.generateRandomColourId(world, random);
}

return RainglowColour.get(colour);
Expand Down Expand Up @@ -110,7 +111,7 @@ public static RainglowEntity get(Entity entity) {
}

public void overrideTexture(Entity entity, CallbackInfoReturnable<Identifier> cir) {
RainglowColour colour = Rainglow.getColour(this, entity.getDataTracker(), entity.getWorld().getRandom());
RainglowColour colour = Rainglow.getColour(entity.getWorld(), this, entity.getDataTracker(), entity.getWorld().getRandom());

// if the colour is default we don't need to override the method
// this optimises a tiny bit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class RainglowNetworking {
public static void syncConfig(ServerPlayerEntity player) {
// note: client does not need to know if server sync is enabled or not
// they already know that it is enabled because they are receiving this packet
ServerPlayNetworking.send(player, new ConfigSyncPayload(Rainglow.CONFIG.mode.value(), Rainglow.CONFIG.getCustom(), Rainglow.CONFIG.getToggles(), Rainglow.CONFIG.getRarities()));
ServerPlayNetworking.send(player, new ConfigSyncPayload(Rainglow.CONFIG.defaultMode.value(), Rainglow.CONFIG.getCustom(), Rainglow.CONFIG.getToggles(), Rainglow.CONFIG.getRarities()));
}

public record ConfigSyncPayload(String currentMode, List<RainglowColour> customMode, Map<RainglowEntity, Boolean> enabledMobs, Map<RainglowEntity, Integer> rarities) implements CustomPayload {
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/io/ix0rai/rainglow/mixin/AllayEntityMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,26 @@ protected void initDataTracker(Builder builder, CallbackInfo ci) {

@Inject(method = "writeCustomDataToNbt", at = @At("TAIL"))
public void writeCustomDataToNbt(NbtCompound nbt, CallbackInfo ci) {
RainglowColour colour = Rainglow.getColour(RainglowEntity.ALLAY, this.getDataTracker(), this.random);
RainglowColour colour = Rainglow.getColour(this.getWorld(), RainglowEntity.ALLAY, this.getDataTracker(), this.random);
nbt.putString(Rainglow.CUSTOM_NBT_KEY, colour.getId());
}

@Inject(method = "readCustomDataFromNbt", at = @At("TAIL"))
public void readCustomDataFromNbt(NbtCompound nbt, CallbackInfo ci) {
this.setVariant(RainglowEntity.ALLAY.readNbt(nbt, this.random));
this.setVariant(RainglowEntity.ALLAY.readNbt(this.getWorld(), nbt, this.random));
}

// triggered when an allay duplicates, to apply the same colour as parent
@Redirect(method = "duplicate", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;spawnEntity(Lnet/minecraft/entity/Entity;)Z"))
public boolean spawnWithColour(World instance, Entity entity) {
RainglowColour colour = Rainglow.getColour(RainglowEntity.ALLAY, this.getDataTracker(), this.random);
RainglowColour colour = Rainglow.getColour(this.getWorld(), RainglowEntity.ALLAY, this.getDataTracker(), this.random);
entity.getDataTracker().set(RainglowEntity.ALLAY.getTrackedData(), colour.getId());
return this.getWorld().spawnEntity(entity);
}

@Override
public RainglowColour getVariant() {
return Rainglow.getColour(RainglowEntity.ALLAY, this.getDataTracker(), this.random);
return Rainglow.getColour(this.getWorld(), RainglowEntity.ALLAY, this.getDataTracker(), this.random);
}

@Override
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/ix0rai/rainglow/mixin/DyeItemMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ private void useOnEntity(ItemStack stack, PlayerEntity user, LivingEntity entity
String colour = getDye(stack);
RainglowEntity entityType = RainglowEntity.get(entity);

if (entityType != null && !Rainglow.colourUnloaded(entityType, colour)
if (entityType != null && !Rainglow.colourUnloaded(user.getWorld(), entityType, colour)
&& Rainglow.CONFIG.isEntityEnabled(entityType)
&& !Rainglow.getColour(entityType, entity.getDataTracker(), entity.getWorld().getRandom()).getId().equals(colour)) {
&& !Rainglow.getColour(user.getWorld(), entityType, entity.getDataTracker(), entity.getWorld().getRandom()).getId().equals(colour)) {
entity.getWorld().playSoundFromEntity(user, entity, SoundEvents.BLOCK_AMETHYST_CLUSTER_BREAK, SoundCategory.PLAYERS, 5.0f, 1.0f);
if (!user.getWorld().isClient()) {
stack.decrement(1);
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/io/ix0rai/rainglow/mixin/GlowSquidEntityMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ protected void initDataTracker(Builder builder, CallbackInfo ci) {

@Inject(method = "writeCustomDataToNbt", at = @At("TAIL"))
public void writeCustomDataToNbt(NbtCompound nbt, CallbackInfo ci) {
RainglowColour colour = Rainglow.getColour(RainglowEntity.GLOW_SQUID, this.getDataTracker(), this.getRandom());
RainglowColour colour = Rainglow.getColour(this.getWorld(), RainglowEntity.GLOW_SQUID, this.getDataTracker(), this.getRandom());
nbt.putString(Rainglow.CUSTOM_NBT_KEY, colour.getId());
}

@Inject(method = "readCustomDataFromNbt", at = @At("TAIL"))
public void readCustomDataFromNbt(NbtCompound nbt, CallbackInfo ci) {
this.setVariant(RainglowEntity.GLOW_SQUID.readNbt(nbt, this.getRandom()));
this.setVariant(RainglowEntity.GLOW_SQUID.readNbt(this.getWorld(), nbt, this.getRandom()));
}

/**
Expand All @@ -49,7 +49,7 @@ public void readCustomDataFromNbt(NbtCompound nbt, CallbackInfo ci) {
*/
@Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;addParticle(Lnet/minecraft/particle/ParticleEffect;DDDDDD)V"), cancellable = true)
public void tickMovement(CallbackInfo ci) {
RainglowColour colour = Rainglow.getColour(RainglowEntity.GLOW_SQUID, this.getDataTracker(), this.getRandom());
RainglowColour colour = Rainglow.getColour(this.getWorld(), RainglowEntity.GLOW_SQUID, this.getDataTracker(), this.getRandom());

if (colour != RainglowColour.BLUE) {
// we add 100 to g to let the mixin know that we want to override the method
Expand All @@ -60,7 +60,7 @@ public void tickMovement(CallbackInfo ci) {

@Override
public RainglowColour getVariant() {
return Rainglow.getColour(RainglowEntity.GLOW_SQUID, this.getDataTracker(), this.getRandom());
return Rainglow.getColour(this.getWorld(), RainglowEntity.GLOW_SQUID, this.getDataTracker(), this.getRandom());
}

@Override
Expand All @@ -85,7 +85,7 @@ protected SquidEntityMixin(EntityType<? extends WaterCreatureEntity> entityType,
private int spawnParticles(ServerWorld instance, ParticleEffect particle, double x, double y, double z, int count, double deltaX, double deltaY, double deltaZ, double speed) {
if (((Object) this) instanceof GlowSquidEntity) {
// send in custom colour data
RainglowColour colour = Rainglow.getColour(RainglowEntity.GLOW_SQUID, this.getDataTracker(), this.getRandom());
RainglowColour colour = Rainglow.getColour(this.getWorld(), RainglowEntity.GLOW_SQUID, this.getDataTracker(), this.getRandom());
int index = colour.ordinal();
// round x to 1 decimal place and append index data to the next two
return ((ServerWorld) this.getWorld()).spawnParticles(particle, (Math.round(x * 10)) / 10D + index / 1000D, y + 0.5, z, 0, deltaX, deltaY, deltaZ, speed);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.ix0rai.rainglow.mixin;

import net.minecraft.server.MinecraftServer;
import net.minecraft.world.storage.WorldSaveStorage;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;

@Mixin(MinecraftServer.class)
public interface MinecraftServerAccessor {
@Accessor
WorldSaveStorage.Session getSession();
}
Loading

0 comments on commit 9ed3c0f

Please sign in to comment.