Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
40d7500
Update ModConfigDTO.java
personalized-advertising Jul 22, 2026
5b1994d
Update ClientPacketListenerMixin.java
personalized-advertising Jul 22, 2026
7fa308d
Update GameRendererMixin.java
personalized-advertising Jul 22, 2026
b0b167b
Update MinecraftMixin.java
personalized-advertising Jul 22, 2026
299fcbf
Update MultiPlayerGameModeMixin.java
personalized-advertising Jul 22, 2026
00fb956
Create ServerPolicies.java
personalized-advertising Jul 22, 2026
ea48b70
Create ServerPolicyPayload.java
personalized-advertising Jul 22, 2026
72bafdf
Update Freecam.java
personalized-advertising Jul 22, 2026
f061cd5
Create FabricServerPolicyNetworking.java
personalized-advertising Jul 22, 2026
a504167
Update FreecamFabric.java
personalized-advertising Jul 22, 2026
e6b5f8a
Update FreecamNeoforge.java
personalized-advertising Jul 22, 2026
92dc8c4
Update GameRendererMixin.java
personalized-advertising Jul 22, 2026
2f2055b
Update ClientPacketListenerMixin.java
personalized-advertising Jul 22, 2026
6f1bb05
Update ServerPolicyPayload.java
personalized-advertising Jul 22, 2026
05263ae
1.20.5 and above only
personalized-advertising Jul 22, 2026
81f00d9
Update ServerPolicyPayload.java
personalized-advertising Jul 22, 2026
02c0818
Create ForgeServerPolicyNetworking.java
personalized-advertising Jul 22, 2026
3530205
Update FreecamForge.java
personalized-advertising Jul 22, 2026
46e21a7
Delete forge/src/main/java/net/xolt/freecam/forge/ForgeServerPolicyNe…
personalized-advertising Jul 27, 2026
d9c1f62
Update FreecamNeoforge.java
personalized-advertising Jul 27, 2026
ed1cbfb
Update FreecamForge.java
personalized-advertising Jul 27, 2026
af49817
Update FreecamNeoforge.java
personalized-advertising Jul 27, 2026
7a2984f
Update FabricServerPolicyNetworking.java
personalized-advertising Jul 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions common/src/main/java/net/xolt/freecam/Freecam.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import net.xolt.freecam.config.ModBindings;
import net.xolt.freecam.config.ModConfig;
import net.xolt.freecam.config.keys.Tickable;
import net.xolt.freecam.network.ServerPolicies;
import net.xolt.freecam.tripod.TripodRegistry;
import net.xolt.freecam.tripod.TripodSlot;
import net.xolt.freecam.util.FreeCamera;
Expand Down Expand Up @@ -47,7 +48,7 @@ public static void preTick(Minecraft mc) {
// Disable if the previous tick asked us to,
// or Freecam is restricted on the current server
if ((disableNextTick || isRestrictedOnServer()) && isEnabled()) {
toggle();
disable();
}
disableNextTick = false;

Expand Down Expand Up @@ -83,8 +84,9 @@ public static void postTick(Minecraft mc) {
@ApiStatus.Internal
public static void onDisconnect() {
if (isEnabled()) {
toggle();
disable();
}
ServerPolicies.reset();
tripods.clear();
}

Expand Down Expand Up @@ -282,6 +284,18 @@ private static void onDisabled() {
}
}

@ApiStatus.Internal
public static void disable() {
if (tripodEnabled) {
onDisableTripod();
} else if (freecamEnabled) {
onDisableFreecam();
}
freecamEnabled = false;
tripodEnabled = false;
onDisabled();
}

private static void resetCamera(TripodSlot tripod) {
if (tripodEnabled && activeTripod != TripodSlot.NONE && activeTripod == tripod && freeCamera != null) {
moveToPlayer();
Expand Down Expand Up @@ -371,11 +385,22 @@ public static boolean isPlayerControlEnabled() {
return playerControlEnabled;
}

@ApiStatus.Internal
public static boolean shouldPreventInteractions() {
if (!isEnabled()) {
return false;
}
if (!ServerPolicies.allowInteract()) {
return true;
}
return !isPlayerControlEnabled() && ModConfig.get().shouldPreventInteractions();
}

@ApiStatus.Experimental
@ApiStatus.AvailableSince("1.2.4")
public static boolean isRestrictedOnServer() {
ServerData server = MC.getCurrentServer();
return server != null && !MC.hasSingleplayerServer()
&& ModConfig.get().isRestrictedOnServer(server.ip);
&& (!ServerPolicies.allowFreecam() || ModConfig.get().isRestrictedOnServer(server.ip));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.gson.JsonObject;
import net.minecraft.world.level.block.Block;
import net.xolt.freecam.config.MCAwareModConfig;
import net.xolt.freecam.network.ServerPolicies;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
Expand Down Expand Up @@ -50,17 +51,17 @@ public double getVerticalSpeed() {

@Override
public boolean ignoreAllCollision() {
return collision.ignoreAll;
return ServerPolicies.allowClipping() && collision.ignoreAll;
}

@Override
public boolean shouldCheckInitialCollision() {
return collision.alwaysCheck || !collision.ignoreAll;
return !ServerPolicies.allowClipping() || collision.alwaysCheck || !collision.ignoreAll;
}

@Override
public boolean ignoreCollisionWith(Block block) {
return collision.ignoreAll || collisionPredicate.shouldIgnore(block);
return ServerPolicies.allowClipping() && (collision.ignoreAll || collisionPredicate.shouldIgnore(block));
}

@Override
Expand All @@ -80,7 +81,7 @@ public boolean shouldShowHand() {

@Override
public boolean isFullBrightEnabled() {
return visual.fullBright;
return ServerPolicies.allowFullbright() && visual.fullBright;
}

@Override
Expand All @@ -100,11 +101,11 @@ public boolean shouldFreezePlayer() {

@Override
public boolean shouldPreventInteractions() {
return !utility.allowInteract;
return !ServerPolicies.allowInteract() || !utility.allowInteract;
}

public boolean allowInteractionsFrom(InteractionMode mode) {
return utility.allowInteract && utility.interactionMode == mode;
return ServerPolicies.allowInteract() && utility.allowInteract && utility.interactionMode == mode;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,34 @@
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

//? if <1.20.5 {
/*import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
import net.xolt.freecam.network.ServerPolicies;
*///? }

@Mixin(ClientPacketListener.class)
public class ClientPacketListenerMixin {

// Disables freecam when the player respawns/switches dimensions.
@Inject(method = "handleRespawn", at = @At("TAIL"))
private void onPlayerRespawn(CallbackInfo ci) {
if (Freecam.isEnabled()) {
Freecam.toggle();
Freecam.disable();
}
}

//? if <1.20.5 {
/*@Inject(method = "handleCustomPayload", at = @At("HEAD"))
private void onCustomPayload(ClientboundCustomPayloadPacket packet, CallbackInfo ci) {
if (!ServerPolicies.CHANNEL.equals(packet.getIdentifier().toString())) {
return;
}

FriendlyByteBuf data = packet.getData();
byte[] bytes = new byte[data.readableBytes()];
data.getBytes(data.readerIndex(), bytes);
ServerPolicies.applyBytes(bytes);
}
*///? }
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ private void onRenderItemInHand(CallbackInfo ci) {
// Disables block outlines when allowInteract is disabled.
@Inject(method = "shouldRenderBlockOutline", at = @At("HEAD"), cancellable = true)
private void onShouldRenderBlockOutline(CallbackInfoReturnable<Boolean> cir) {
if (Freecam.isEnabled() && !Freecam.isPlayerControlEnabled() && ModConfig.get().shouldPreventInteractions()) {
if (Freecam.shouldPreventInteractions()) {
cir.setReturnValue(false);
}
}
Expand Down
12 changes: 10 additions & 2 deletions common/src/main/java/net/xolt/freecam/mixins/MinecraftMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ private void onDoAttack(CallbackInfoReturnable<Boolean> ci) {
}
}

// Prevents item pick when allowInteract is disabled.
// Prevents item use when interactions are disabled.
@Inject(method = "startUseItem", at = @At("HEAD"), cancellable = true)
private void onUseItem(CallbackInfo ci) {
if (freecam$disableInteract()) {
ci.cancel();
}
}

// Prevents item pick when interactions are disabled.
//~ if >=26.1 pickBlock -> pickBlockOrEntity
@Inject(method = "pickBlockOrEntity", at = @At("HEAD"), cancellable = true)
private void onDoItemPick(CallbackInfo ci) {
Expand Down Expand Up @@ -75,6 +83,6 @@ private void onDisconnect(CallbackInfo ci) {

@Unique
private static boolean freecam$disableInteract() {
return Freecam.isEnabled() && !Freecam.isPlayerControlEnabled() && ModConfig.get().shouldPreventInteractions();
return Freecam.shouldPreventInteractions();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,28 @@
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.xolt.freecam.Freecam;
import net.xolt.freecam.config.ModConfig;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import static net.xolt.freecam.Freecam.MC;
//? if <=1.18.2 {
/*import net.minecraft.client.multiplayer.ClientLevel;
*///? }

import static net.xolt.freecam.Freecam.MC;

@Mixin(MultiPlayerGameMode.class)
public class MultiPlayerGameModeMixin {

// Prevents interacting with blocks when allowInteract is disabled.
@Inject(method = "useItemOn", at = @At("HEAD"), cancellable = true)
private void onInteractBlock(LocalPlayer player,
//? if <= 1.18.2
//ClientLevel level,
InteractionHand hand,
BlockHitResult hitResult,
CallbackInfoReturnable<InteractionResult> cir) {
//? if <=1.18.2
//ClientLevel level,
InteractionHand hand,
BlockHitResult hitResult,
CallbackInfoReturnable<InteractionResult> cir) {
if (freecam$disableInteract()) {
cir.setReturnValue(InteractionResult.PASS);
}
Expand Down Expand Up @@ -65,13 +63,13 @@ private void onInteractEntityAtLocation(Player player, Entity entity, EntityHitR
// Prevents attacking self.
@Inject(method = "attack", at = @At("HEAD"), cancellable = true)
private void onAttackEntity(Player player, Entity target, CallbackInfo ci) {
if (target == MC.player) {
if (target == MC.player || freecam$disableInteract()) {
ci.cancel();
}
}

@Unique
private static boolean freecam$disableInteract() {
return Freecam.isEnabled() && !Freecam.isPlayerControlEnabled() && ModConfig.get().shouldPreventInteractions();
return Freecam.shouldPreventInteractions();
}
}
103 changes: 103 additions & 0 deletions common/src/main/java/net/xolt/freecam/network/ServerPolicies.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package net.xolt.freecam.network;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;

public final class ServerPolicies {
public static final String CHANNEL = "freecam:server_config";
private static final Logger LOGGER = LoggerFactory.getLogger(ServerPolicies.class);
private static final Policies ALLOW_ALL = new Policies(true, true, true, true);
private static volatile Policies current = ALLOW_ALL;

public static boolean allowFreecam() {
return current.allowFreecam;
}

public static boolean allowClipping() {
return current.allowClipping;
}

public static boolean allowFullbright() {
return current.allowFullbright;
}

public static boolean allowInteract() {
return current.allowInteract;
}

public static void reset() {
current = ALLOW_ALL;
}

public static boolean applyBytes(byte[] payload) {
return applyJson(new String(payload, StandardCharsets.UTF_8));
}

public static boolean applyJson(String json) {
try {
JsonElement rootElement = parse(json);
if (!rootElement.isJsonObject()) {
throw new JsonParseException("root must be a JSON object");
}

JsonObject root = rootElement.getAsJsonObject();
Policies parsed = new Policies(
readBoolean(root, "allowFreecam"),
readBoolean(root, "allowClipping"),
readBoolean(root, "allowFullbright"),
readBoolean(root, "allowInteract")
);
current = parsed;
LOGGER.debug("Applied server Freecam policies: {}", parsed);
return true;
} catch (JsonParseException | IllegalStateException | ClassCastException e) {
LOGGER.warn("Ignoring invalid {} payload: {}", CHANNEL, e.getMessage());
return false;
}
}

private static JsonElement parse(String json) {
return new JsonParser().parse(json);
}

private static boolean readBoolean(JsonObject root, String key) {
JsonElement value = root.get(key);
if (value == null) {
return true;
}
if (!value.isJsonPrimitive() || !value.getAsJsonPrimitive().isBoolean()) {
throw new JsonParseException("'" + key + "' must be a boolean");
}
return value.getAsBoolean();
}

private static final class Policies {
private final boolean allowFreecam;
private final boolean allowClipping;
private final boolean allowFullbright;
private final boolean allowInteract;

private Policies(boolean allowFreecam, boolean allowClipping, boolean allowFullbright, boolean allowInteract) {
this.allowFreecam = allowFreecam;
this.allowClipping = allowClipping;
this.allowFullbright = allowFullbright;
this.allowInteract = allowInteract;
}

@Override
public String toString() {
return "Policies{" +
"allowFreecam=" + allowFreecam +
", allowClipping=" + allowClipping +
", allowFullbright=" + allowFullbright +
", allowInteract=" + allowInteract +
'}';
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//? if >=1.20.5 {
package net.xolt.freecam.network;

import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.EncoderException;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.Identifier;
import net.xolt.freecam.Freecam;

import java.nio.charset.StandardCharsets;

public record ServerPolicyPayload(String json) implements CustomPacketPayload {
public static final CustomPacketPayload.Type<ServerPolicyPayload> TYPE = new CustomPacketPayload.Type<>(identifier());
public static final StreamCodec<ByteBuf, ServerPolicyPayload> STREAM_CODEC = new StreamCodec<>() {
@Override
public ServerPolicyPayload decode(ByteBuf buffer) {
byte[] bytes = new byte[buffer.readableBytes()];
buffer.readBytes(bytes);
return new ServerPolicyPayload(new String(bytes, StandardCharsets.UTF_8));
}

@Override
public void encode(ByteBuf buffer, ServerPolicyPayload payload) {
byte[] bytes = payload.json().getBytes(StandardCharsets.UTF_8);
buffer.writeBytes(bytes);
}
};

@Override
public CustomPacketPayload.Type<? extends CustomPacketPayload> type() {
return TYPE;
}

private static Identifier identifier() {
//? if >=1.21 {
return Identifier.fromNamespaceAndPath(Freecam.MOD_ID, "server_config");
//? } else
//return new ResourceLocation(Freecam.MOD_ID, "server_config");
}
}
//? }
Loading