-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add /packcontrol to enable/disable/hide resource packs
- Loading branch information
Showing
8 changed files
with
319 additions
and
2 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
src/main/java/com/lovetropics/extras/client/ClientPackControl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package com.lovetropics.extras.client; | ||
|
||
import com.google.common.collect.Sets; | ||
import com.google.gson.Gson; | ||
import com.google.gson.GsonBuilder; | ||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonParser; | ||
import com.lovetropics.extras.data.packcontrol.PackControl; | ||
import com.mojang.logging.LogUtils; | ||
import com.mojang.serialization.JsonOps; | ||
import net.minecraft.Util; | ||
import net.minecraft.client.Minecraft; | ||
import net.minecraft.server.packs.repository.Pack; | ||
import net.minecraft.server.packs.repository.PackRepository; | ||
import net.neoforged.fml.loading.FMLLoader; | ||
import org.slf4j.Logger; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.BufferedWriter; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.CompletionException; | ||
|
||
public class ClientPackControl { | ||
private static final Logger LOGGER = LogUtils.getLogger(); | ||
|
||
private static final Path STATE_PATH = FMLLoader.getGamePath().resolve("config/client_pack_control.json"); | ||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); | ||
|
||
private static CompletableFuture<PackControl.State> stateFuture; | ||
|
||
static { | ||
stateFuture = CompletableFuture.supplyAsync(ClientPackControl::readState).exceptionally(throwable -> { | ||
LOGGER.error("Failed to read pack control state", throwable); | ||
return PackControl.State.DEFAULT; | ||
}); | ||
} | ||
|
||
public static void updatePacks(PackControl.State state) { | ||
PackControl.State oldState = state(); | ||
if (oldState.equals(state)) { | ||
return; | ||
} | ||
handleStateChange(Minecraft.getInstance(), oldState, state); | ||
stateFuture = CompletableFuture.completedFuture(state); | ||
Util.ioPool().submit(() -> storeState(state)); | ||
} | ||
|
||
private static void handleStateChange(Minecraft minecraft, PackControl.State oldState, PackControl.State newState) { | ||
PackRepository packRepository = minecraft.getResourcePackRepository(); | ||
|
||
Set<String> newlyEnabled = Sets.difference(newState.enabled(), oldState.enabled()); | ||
Set<String> newlyDisabled = Sets.difference(oldState.enabled(), newState.enabled()); | ||
|
||
newlyEnabled.forEach(packRepository::addPack); | ||
newlyDisabled.forEach(packRepository::removePack); | ||
|
||
minecraft.options.updateResourcePacks(packRepository); | ||
} | ||
|
||
private static PackControl.State readState() { | ||
if (!Files.exists(STATE_PATH)) { | ||
return PackControl.State.DEFAULT; | ||
} | ||
try (BufferedReader reader = Files.newBufferedReader(STATE_PATH)) { | ||
return PackControl.State.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).getOrThrow(); | ||
} catch (IOException e) { | ||
throw new CompletionException(e); | ||
} | ||
} | ||
|
||
private static void storeState(PackControl.State state) { | ||
try (BufferedWriter writer = Files.newBufferedWriter(STATE_PATH)) { | ||
JsonElement json = PackControl.State.CODEC.encodeStart(JsonOps.INSTANCE, state).getOrThrow(); | ||
GSON.toJson(json, writer); | ||
} catch (Exception e) { | ||
LOGGER.error("Failed to write pack control state", e); | ||
} | ||
} | ||
|
||
public static PackControl.State state() { | ||
return stateFuture.join(); | ||
} | ||
|
||
public static Collection<Pack> removeHidden(Collection<Pack> packs, PackRepository repository) { | ||
PackControl.State state = state(); | ||
if (state.hidden().isEmpty()) { | ||
return packs; | ||
} | ||
List<Pack> newPacks = new ArrayList<>(packs); | ||
Collection<String> selectedIds = repository.getSelectedIds(); | ||
if (newPacks.removeIf(pack -> { | ||
// Let the player keep it if they already had it enabled | ||
String id = pack.getId(); | ||
return state.hidden().contains(id) && !selectedIds.contains(id); | ||
})) { | ||
return newPacks; | ||
} | ||
return packs; | ||
} | ||
} |
138 changes: 138 additions & 0 deletions
138
src/main/java/com/lovetropics/extras/data/packcontrol/PackControl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package com.lovetropics.extras.data.packcontrol; | ||
|
||
import com.lovetropics.extras.LTExtras; | ||
import com.lovetropics.extras.network.message.ClientboundUpdatePackControl; | ||
import com.mojang.brigadier.builder.LiteralArgumentBuilder; | ||
import com.mojang.serialization.Codec; | ||
import com.mojang.serialization.codecs.RecordCodecBuilder; | ||
import io.netty.buffer.ByteBuf; | ||
import net.minecraft.commands.CommandSourceStack; | ||
import net.minecraft.commands.Commands; | ||
import net.minecraft.core.HolderLookup; | ||
import net.minecraft.nbt.CompoundTag; | ||
import net.minecraft.nbt.NbtOps; | ||
import net.minecraft.network.codec.ByteBufCodecs; | ||
import net.minecraft.network.codec.StreamCodec; | ||
import net.minecraft.server.MinecraftServer; | ||
import net.minecraft.server.level.ServerPlayer; | ||
import net.minecraft.world.level.saveddata.SavedData; | ||
import net.neoforged.bus.api.SubscribeEvent; | ||
import net.neoforged.fml.common.EventBusSubscriber; | ||
import net.neoforged.neoforge.event.RegisterCommandsEvent; | ||
import net.neoforged.neoforge.event.entity.player.PlayerEvent; | ||
|
||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.function.BiFunction; | ||
import java.util.function.UnaryOperator; | ||
|
||
import static com.mojang.brigadier.arguments.StringArgumentType.getString; | ||
import static com.mojang.brigadier.arguments.StringArgumentType.string; | ||
import static net.minecraft.commands.Commands.argument; | ||
import static net.minecraft.commands.Commands.literal; | ||
|
||
@EventBusSubscriber(modid = LTExtras.MODID) | ||
public class PackControl extends SavedData { | ||
private static final Factory<PackControl> FACTORY = new Factory<>(PackControl::new, PackControl::load); | ||
|
||
private static final String STORAGE_ID = LTExtras.MODID + "_pack_control"; | ||
|
||
private State state = State.DEFAULT; | ||
|
||
@Override | ||
public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) { | ||
tag.put("state", State.CODEC.encodeStart(NbtOps.INSTANCE, state).getOrThrow()); | ||
return tag; | ||
} | ||
|
||
public static PackControl get(MinecraftServer server) { | ||
return server.overworld().getDataStorage().computeIfAbsent(FACTORY, STORAGE_ID); | ||
} | ||
|
||
private static PackControl load(CompoundTag tag, HolderLookup.Provider registries) { | ||
PackControl packControl = new PackControl(); | ||
State.CODEC.parse(NbtOps.INSTANCE, tag.get("state")).ifSuccess(state -> packControl.state = state); | ||
return packControl; | ||
} | ||
|
||
@SubscribeEvent | ||
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { | ||
if (event.getEntity() instanceof ServerPlayer player) { | ||
PackControl packControl = PackControl.get(player.getServer()); | ||
player.connection.send(new ClientboundUpdatePackControl(packControl.state)); | ||
} | ||
} | ||
|
||
@SubscribeEvent | ||
public static void onRegisterCommands(RegisterCommandsEvent event) { | ||
event.getDispatcher().register(literal("packcontrol") | ||
.requires(source -> source.hasPermission(Commands.LEVEL_GAMEMASTERS)) | ||
.then(packUpdater("enable", (state, packId) -> state.setEnabled(packId, true))) | ||
.then(packUpdater("disable", (state, packId) -> state.setEnabled(packId, false))) | ||
.then(packUpdater("hide", (state, packId) -> state.setHidden(packId, true))) | ||
.then(packUpdater("show", (state, packId) -> state.setHidden(packId, false))) | ||
); | ||
} | ||
|
||
private static LiteralArgumentBuilder<CommandSourceStack> packUpdater(String name, BiFunction<State, String, State> updater) { | ||
return literal(name) | ||
.then(argument("pack", string()) | ||
.executes(context -> { | ||
String pack = getString(context, "pack"); | ||
updateState(context.getSource().getServer(), state1 -> updater.apply(state1, pack)); | ||
return 1; | ||
}) | ||
); | ||
} | ||
|
||
public static void updateState(MinecraftServer server, UnaryOperator<State> operator) { | ||
PackControl packControl = PackControl.get(server); | ||
State newState = operator.apply(packControl.state); | ||
if (packControl.state.equals(newState)) { | ||
return; | ||
} | ||
packControl.state = newState; | ||
for (ServerPlayer player : server.getPlayerList().getPlayers()) { | ||
player.connection.send(new ClientboundUpdatePackControl(newState)); | ||
} | ||
packControl.setDirty(); | ||
} | ||
|
||
public record State(Set<String> hidden, Set<String> enabled) { | ||
public static final State DEFAULT = new State(Set.of(), Set.of()); | ||
|
||
private static final Codec<Set<String>> PACK_SET_CODEC = Codec.STRING.listOf().xmap(Set::copyOf, List::copyOf); | ||
|
||
public static final Codec<State> CODEC = RecordCodecBuilder.create(i -> i.group( | ||
PACK_SET_CODEC.fieldOf("hidden").forGetter(State::hidden), | ||
PACK_SET_CODEC.fieldOf("enabled").forGetter(State::enabled) | ||
).apply(i, State::new)); | ||
|
||
private static final StreamCodec<ByteBuf, Set<String>> PACK_SET_STREAM_CODEC = ByteBufCodecs.STRING_UTF8.apply(ByteBufCodecs.collection(HashSet::new)); | ||
|
||
public static final StreamCodec<ByteBuf, State> STREAM_CODEC = StreamCodec.composite( | ||
PACK_SET_STREAM_CODEC, State::hidden, | ||
PACK_SET_STREAM_CODEC, State::enabled, | ||
State::new | ||
); | ||
|
||
public State setHidden(String packId, boolean hidden) { | ||
return new State(setInSet(this.hidden, packId, hidden), enabled); | ||
} | ||
|
||
public State setEnabled(String packId, boolean enabled) { | ||
return new State(this.hidden, setInSet(this.enabled, packId, enabled)); | ||
} | ||
|
||
private static <T> Set<T> setInSet(Set<T> set, T value, boolean inSet) { | ||
Set<T> newSet = new HashSet<>(set); | ||
if (inSet) { | ||
newSet.add(value); | ||
} else { | ||
newSet.remove(value); | ||
} | ||
return newSet; | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/com/lovetropics/extras/data/packcontrol/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
@ParametersAreNonnullByDefault | ||
@MethodsReturnNonnullByDefault | ||
@FieldsAreNonnullByDefault | ||
package com.lovetropics.extras.data.packcontrol; | ||
|
||
import com.mojang.blaze3d.FieldsAreNonnullByDefault; | ||
import com.mojang.blaze3d.MethodsReturnNonnullByDefault; | ||
|
||
import javax.annotation.ParametersAreNonnullByDefault; |
25 changes: 25 additions & 0 deletions
25
src/main/java/com/lovetropics/extras/mixin/client/packcontrol/PackSelectionModelMixin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.lovetropics.extras.mixin.client.packcontrol; | ||
|
||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; | ||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; | ||
import com.lovetropics.extras.client.ClientPackControl; | ||
import net.minecraft.client.gui.screens.packs.PackSelectionModel; | ||
import net.minecraft.server.packs.repository.Pack; | ||
import net.minecraft.server.packs.repository.PackRepository; | ||
import org.spongepowered.asm.mixin.Mixin; | ||
import org.spongepowered.asm.mixin.injection.At; | ||
|
||
import java.util.Collection; | ||
|
||
@Mixin(PackSelectionModel.class) | ||
public class PackSelectionModelMixin { | ||
@WrapOperation(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/repository/PackRepository;getAvailablePacks()Ljava/util/Collection;")) | ||
private Collection<Pack> initGetAvailablePacks(PackRepository repository, Operation<Collection<Pack>> original) { | ||
return ClientPackControl.removeHidden(original.call(repository), repository); | ||
} | ||
|
||
@WrapOperation(method = "findNewPacks", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/repository/PackRepository;getAvailablePacks()Ljava/util/Collection;")) | ||
private Collection<Pack> findNewPacksGetAvailablePacks(PackRepository repository, Operation<Collection<Pack>> original) { | ||
return ClientPackControl.removeHidden(original.call(repository), repository); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/com/lovetropics/extras/mixin/client/packcontrol/package-info.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
@ParametersAreNonnullByDefault | ||
@MethodsReturnNonnullByDefault | ||
@FieldsAreNonnullByDefault | ||
package com.lovetropics.extras.mixin.client.packcontrol; | ||
|
||
import com.mojang.blaze3d.FieldsAreNonnullByDefault; | ||
import com.mojang.blaze3d.MethodsReturnNonnullByDefault; | ||
|
||
import javax.annotation.ParametersAreNonnullByDefault; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
src/main/java/com/lovetropics/extras/network/message/ClientboundUpdatePackControl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.lovetropics.extras.network.message; | ||
|
||
import com.lovetropics.extras.LTExtras; | ||
import com.lovetropics.extras.client.ClientPackControl; | ||
import com.lovetropics.extras.data.packcontrol.PackControl; | ||
import io.netty.buffer.ByteBuf; | ||
import net.minecraft.network.codec.StreamCodec; | ||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload; | ||
import net.neoforged.neoforge.network.handling.IPayloadContext; | ||
|
||
public record ClientboundUpdatePackControl( | ||
PackControl.State state | ||
) implements CustomPacketPayload { | ||
public static final StreamCodec<ByteBuf, ClientboundUpdatePackControl> STREAM_CODEC = PackControl.State.STREAM_CODEC.map(ClientboundUpdatePackControl::new, ClientboundUpdatePackControl::state); | ||
|
||
public static final Type<ClientboundUpdatePackControl> TYPE = new Type<>(LTExtras.location("update_resource_packs")); | ||
|
||
public static void handle(ClientboundUpdatePackControl packet, IPayloadContext context) { | ||
ClientPackControl.updatePacks(packet.state); | ||
} | ||
|
||
@Override | ||
public Type<ClientboundUpdatePackControl> type() { | ||
return TYPE; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters