Skip to content

Commit

Permalink
Fabric networking implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Cadiboo committed Jun 16, 2024
1 parent 874d054 commit 4de5cb8
Show file tree
Hide file tree
Showing 32 changed files with 515 additions and 353 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.mojang.blaze3d.platform.InputConstants;
import io.github.cadiboo.nocubes.NoCubes;
import io.github.cadiboo.nocubes.config.NoCubesConfig;
import io.github.cadiboo.nocubes.network.NoCubesNetwork;
import io.github.cadiboo.nocubes.network.NoCubesNetworkClient;
import io.github.cadiboo.nocubes.util.ModUtil;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
Expand All @@ -20,9 +22,6 @@

import static io.github.cadiboo.nocubes.client.RenderHelper.reloadAllChunks;

/**
* @author Cadiboo
*/
public final class KeyMappings {

private static final Logger LOG = LogManager.getLogger();
Expand Down Expand Up @@ -93,10 +92,16 @@ private static void toggleLookedAtSmoothable(boolean changeAllStatesOfBlock) {
var newValue = !NoCubes.smoothableHandler.isSmoothable(targetedState);
var states = changeAllStatesOfBlock ? ModUtil.getStates(targetedState.getBlock()).toArray(BlockState[]::new) : new BlockState[] {targetedState};

if (!ClientUtil.platform.trySendC2SRequestUpdateSmoothable(player, newValue, states)) {
LOG.debug("toggleLookedAtSmoothable currentServerHasNoCubes={}", NoCubesNetworkClient.currentServerHasNoCubes);
if (!NoCubesNetworkClient.currentServerHasNoCubes) {
// The server doesn't have NoCubes, directly modify the smoothable state to hackily allow the player to have visuals
NoCubes.smoothableHandler.setSmoothable(newValue, states);
reloadAllChunks("toggleLookedAtSmoothable was pressed while connected to a server that doesn't have NoCubes installed");
} else {
// We're on a server (possibly singleplayer) with NoCubes installed
if (NoCubesNetwork.checkPermissionAndNotifyIfUnauthorised(player, Minecraft.getInstance().getSingleplayerServer()))
// Only send the packet if we have permission, don't send a packet that will be denied
ClientUtil.platform.sendC2SRequestUpdateSmoothable(newValue, states);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public static class Client {
public static boolean debugRecordMeshPerformance;
public static boolean debugOutlineNearbyMesh;
public static boolean debugSkipNoCubesRendering;

public static int hashChunkRenderSettings() {
return Objects.hash(NoCubesConfig.Client.betterGrassSides, NoCubesConfig.Client.moreSnow, NoCubesConfig.Client.fixPlantHeight, NoCubesConfig.Client.grassTufts);
}
}

/**
Expand Down Expand Up @@ -94,6 +98,14 @@ public enum MesherType {
this.instance = instance;
}
}

public static int hashChunkRenderSettings(Stream<Block> blocks) {
var smoothables = blocks
.flatMap(block -> ModUtil.getStates(block).stream())
.map(NoCubes.smoothableHandler::isSmoothable)
.toArray(Boolean[]::new);
return Objects.hash(NoCubesConfig.Server.mesher, NoCubesConfig.Server.forceVisuals, Arrays.hashCode(smoothables));
}
}

public static class Smoothables {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package io.github.cadiboo.nocubes.network;

import io.github.cadiboo.nocubes.NoCubes;
import io.github.cadiboo.nocubes.util.BlockStateSerializer;
import io.github.cadiboo.nocubes.util.ModUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.Function;

public class NoCubesNetwork {

/**
* From the minecraft wiki.
* 1. Ops can bypass spawn protection.
* 2. Ops can use /clear, /difficulty, /effect, /gamemode, /gamerule, /give, /summon, /setblock and /tp, and can edit command blocks.
* 3. Ops can use /ban, /deop, /whitelist, /kick, and /op.
* 4. Ops can use /stop.
*/
public static final int REQUIRED_PERMISSION_LEVEL = 2;

public static final String NETWORK_PROTOCOL_VERSION = "2";

public static boolean checkPermissionAndNotifyIfUnauthorised(Player player, @Nullable MinecraftServer connectedToServer) {
if (connectedToServer != null && connectedToServer.isSingleplayerOwner(player.getGameProfile()))
return true;
if (player.hasPermissions(REQUIRED_PERMISSION_LEVEL))
return true;
ModUtil.warnPlayer(player, NoCubes.MOD_ID + ".command.changeSmoothableNoPermission");
return false;
}

@FunctionalInterface
public interface SendS2CUpdateSmoothable {
void send(@Nullable ServerPlayer playerIfNotNullElseEveryone, boolean newValue, BlockState[] states);
}

public static void handleC2SRequestUpdateSmoothable(
ServerPlayer sender,
boolean newValue, BlockState[] states,
Consumer<Runnable> enqueueWork,
SendS2CUpdateSmoothable network
) {
if (NoCubesNetwork.checkPermissionAndNotifyIfUnauthorised(sender, sender.server)) {
var statesToUpdate = Arrays.stream(states)
.filter(s -> NoCubes.smoothableHandler.isSmoothable(s) != newValue)
.toArray(BlockState[]::new);
// Guards against useless config reload and/or someone spamming these packets to the server and the server spamming all clients
if (statesToUpdate.length == 0)
// Somehow the client is out of sync, just notify them
network.send(sender, newValue, states);
else {
enqueueWork.accept(() -> ModUtil.platform.updateServerConfigSmoothable(newValue, statesToUpdate));
// Send back update to all clients
network.send(null, newValue, statesToUpdate);
}
}
}

public interface Serializer {

static void encodeS2CUpdateServerConfig(FriendlyByteBuf buffer, byte[] data) {
buffer.writeByteArray(data);
}

static <T> T decodeS2CUpdateServerConfig(FriendlyByteBuf buffer, Function<byte[], T> constructor) {
var data = buffer.readByteArray();
return constructor.apply(data);
}

static void encodeUpdateSmoothable(FriendlyByteBuf buffer, boolean newValue, BlockState[] states) {
buffer.writeBoolean(newValue);
BlockStateSerializer.writeBlockStatesTo(buffer, states);
}

static <T> T decodeUpdateSmoothable(FriendlyByteBuf buffer, UpdateSmoothableConstructor<T> constructor) {
var newValue = buffer.readBoolean();
var states = BlockStateSerializer.readBlockStatesFrom(buffer);
return constructor.apply(newValue, states);
}

interface UpdateSmoothableConstructor<T> {
T apply(boolean newValue, BlockState[] states);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package io.github.cadiboo.nocubes.network;

import io.github.cadiboo.nocubes.NoCubes;
import io.github.cadiboo.nocubes.client.ClientUtil;
import io.github.cadiboo.nocubes.client.KeyMappings;
import io.github.cadiboo.nocubes.config.NoCubesConfig;
import io.github.cadiboo.nocubes.util.ModUtil;
import net.minecraft.world.level.block.state.BlockState;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.function.Consumer;

import static io.github.cadiboo.nocubes.client.RenderHelper.reloadAllChunks;

public class NoCubesNetworkClient {

private static final Logger LOG = LogManager.getLogger();
/**
* Only valid when connected to a server on the client.
* Contains random values from the most recently pinged server otherwise.
* Also valid for singleplayer integrated servers (always true).
*/
public static boolean currentServerHasNoCubes = false;

public static void handleS2CUpdateServerConfig(Consumer<Runnable> enqueueWork, byte[] configData) {
ClientUtil.platform.receiveSyncedServerConfig(configData);
}

public static void handleS2CUpdateSmoothable(Consumer<Runnable> enqueueWork, boolean newValue, BlockState[] states) {
enqueueWork.accept(() -> {
NoCubes.smoothableHandler.setSmoothable(newValue, states);
reloadAllChunks("the server told us that the smoothness of some states changed");
});
}

public static void onJoinedServer(boolean forgeAlreadyLoadedDefaultConfig) {
LOG.debug("Client joined server");
loadDefaultServerConfigIfWeAreOnAModdedServerWithoutNoCubes(forgeAlreadyLoadedDefaultConfig);
ClientUtil.sendPlayerInfoMessage();
ClientUtil.warnPlayerIfVisualsDisabled();
if (!currentServerHasNoCubes) {
// This lets players not phase through the ground on servers that don't have NoCubes installed
NoCubesConfig.Server.collisionsEnabled = false;
ClientUtil.warnPlayer(NoCubes.MOD_ID + ".notification.notInstalledOnServer", KeyMappings.translate(KeyMappings.TOGGLE_SMOOTHABLE_BLOCK_TYPE));
}
}

/**
* This lets NoCubes load properly on modded servers that don't have it installed
*/
private static void loadDefaultServerConfigIfWeAreOnAModdedServerWithoutNoCubes(boolean forgeAlreadyLoadedDefaultConfig) {
if (currentServerHasNoCubes) {
// Forge has synced the server config to us, no need to load the default (see ConfigSync.syncConfigs)
LOG.debug("Not loading default server config - current server has NoCubes installed");
return;
}

if (forgeAlreadyLoadedDefaultConfig) {
// Forge has already loaded the default server configs for us (see NetworkHooks#handleClientLoginSuccess(Connection))
LOG.debug("Not loading default server config - Forge has already loaded it for us");
return;
}

LOG.debug("Connected to a modded server that doesn't have NoCubes installed, loading default server config");
ModUtil.platform.loadDefaultServerConfig();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.cadiboo.nocubes.platform;

import net.minecraft.client.player.LocalPlayer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.resources.model.BakedModel;
Expand All @@ -16,14 +15,10 @@
* Client-only version of {@link IPlatform} that contains references to classes that only exist on the minecraft client.
*/
public interface IClientPlatform {

void updateClientVisuals(boolean render);

boolean trySendC2SRequestUpdateSmoothable(LocalPlayer player, boolean newValue, BlockState[] states);

void sendC2SRequestUpdateSmoothable(boolean newValue, BlockState[] states);
void receiveSyncedServerConfig(byte[] configData);
Component clientConfigComponent();

void forEachRenderLayer(BlockState state, Consumer<RenderType> action);

List<BakedQuad> getQuads(BakedModel model, BlockState state, Direction direction, RandomSource random, Object modelData, RenderType layer);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ public interface IPlatform {
ResourceLocation getRegistryName(Block block);
boolean isPlant(BlockState state);
void updateServerConfigSmoothable(boolean newValue, BlockState... states);
void loadDefaultServerConfig();
}
5 changes: 2 additions & 3 deletions fabric/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ base {
archivesName = "${mod_name}-fabric"
}
dependencies {
testImplementation(compileOnly(project(':common')))
minecraft "com.mojang:minecraft:${minecraft_version}"
mappings loom.officialMojangMappings()
modImplementation "net.fabricmc:fabric-loader:${fabric_loader_version}"
testImplementation(compileOnly(project(':common')))
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"

// // From https://github.com/MinecraftForge/MinecraftForge/blob/0579572148079a6bc483ff42404878962fc48702/build.gradle#L55
// def NIGHTCONFIG_VERSION = '3.6.0'
Expand All @@ -27,8 +28,6 @@ dependencies {
def sodium = modCompileOnly 'maven.modrinth:sodium:mc1.20.1-0.5.7'
modImplementation sodium
modImplementation 'maven.modrinth:iris:1.6.17+1.20.1'
// Fabric API appears to be required by Sodium
modImplementation 'net.fabricmc.fabric-api:fabric-api:0.92.0+1.20.1'
}

loom {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.github.cadiboo.nocubes.config;

import io.github.cadiboo.nocubes.client.ClientUtil;
import io.github.cadiboo.nocubes.client.KeyMappings;
import io.github.cadiboo.nocubes.util.ModUtil;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.world.level.block.state.BlockState;

import java.util.ArrayList;

// TODO: REPLACE WITH AN ACTUAL CONFIG SYSTEM
/**
* @see NoCubesConfig
*/
public final class NoCubesConfigImpl {

public static void updateServerConfigSmoothable(boolean newValue, BlockState[] states) {
warnConfigNotImplemented();
var whitelist = new ArrayList<String>();
var blacklist = new ArrayList<String>();
NoCubesConfig.Smoothables.updateUserDefinedSmoothableStringLists(newValue, states, whitelist, blacklist);
NoCubesConfig.Smoothables.recomputeInMemoryLookup(BuiltInRegistries.BLOCK.stream(), whitelist, blacklist, true);
}

public static void loadDefaultServerConfig() {
warnConfigNotImplemented();
NoCubesConfig.Common.debugEnabled = true;
NoCubesConfig.Client.render = true;
NoCubesConfig.Client.renderSelectionBox = true;
NoCubesConfig.Client.selectionBoxColor = new ColorParser.Color(0, 0, 0, 0x66).toRenderableColor();
NoCubesConfig.Server.mesher = NoCubesConfig.Server.MesherType.SurfaceNets.instance;
NoCubesConfig.Server.collisionsEnabled = true;
NoCubesConfig.Server.tempMobCollisionsDisabled = true;
NoCubesConfig.Server.extendFluidsRange = 3;
updateServerConfigSmoothable(true, new BlockState[0]);
}

public static byte[] readConfigFileBytes() {
return new byte[0];
}

public static void receiveSyncedServerConfig(byte[] configData) {
warnConfigNotImplemented();
// TODO: Actually use the data
assert configData.length == 0; // Since we are just debugging, and have no config system yet
ModUtil.platform.loadDefaultServerConfig();
}

static void warnConfigNotImplemented() {
// Copied and tweaked from the bit of NoCubesNetworkClient.onJoinedServer that sends the notification.notInstalledOnServer message
var message = "!!! The Fabric version of NoCubes does not have a config system yet - any changes you make will not be saved";
ClientUtil.warnPlayer(message, KeyMappings.translate(KeyMappings.TOGGLE_SMOOTHABLE_BLOCK_TYPE));
}

/**
* Implementation of {@link NoCubesConfig.Common}
*/
public static class Common {
}

/**
* Implementation of {@link NoCubesConfig.Client}
*/
public static class Client {
public static void updateRender(boolean render) {
warnConfigNotImplemented();
NoCubesConfig.Client.render = render;
}
}

/**
* Implementation of {@link NoCubesConfig.Server}
*/
public static class Server {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,38 @@


import io.github.cadiboo.nocubes.client.KeyMappings;
import io.github.cadiboo.nocubes.network.S2CUpdateNoCubesConfig;
import io.github.cadiboo.nocubes.network.NoCubesNetworkClient;
import io.github.cadiboo.nocubes.network.NoCubesNetworkFabric;
import io.github.cadiboo.nocubes.network.S2CUpdateServerConfig;
import io.github.cadiboo.nocubes.network.S2CUpdateSmoothable;
import io.netty.buffer.Unpooled;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.fabric.api.client.networking.v1.ClientLoginNetworking;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.minecraft.client.Minecraft;
import net.minecraft.network.FriendlyByteBuf;

import java.util.concurrent.CompletableFuture;

public class ClientInit implements ClientModInitializer {

@Override
public void onInitializeClient() {
ClientPlayNetworking.registerGlobalReceiver(S2CUpdateNoCubesConfig.TYPE, (player, packet, responseSender) -> {
// NoCubesNetwork.currentServerHasNoCubes = true;
});
KeyMappings.register(
KeyBindingHelper::registerKeyBinding,
keyBindingsOnTick -> ClientTickEvents.START_CLIENT_TICK.register(client -> keyBindingsOnTick.run())
);
ClientLoginNetworking.registerGlobalReceiver(S2CUpdateServerConfig.TYPE.getId(), (client, handler, buf, listenerAdder) -> {
NoCubesNetworkFabric.handleS2CUpdateServerConfigDuringLogin(client::execute, buf);
return CompletableFuture.completedFuture(new FriendlyByteBuf(Unpooled.buffer()));
});
ClientPlayNetworking.registerGlobalReceiver(S2CUpdateServerConfig.TYPE, (packet, player, responseSender) -> {
NoCubesNetworkClient.handleS2CUpdateServerConfig(Minecraft.getInstance()::execute, packet.data());
});
ClientPlayNetworking.registerGlobalReceiver(S2CUpdateSmoothable.TYPE, (packet, player, responseSender) -> {
NoCubesNetworkClient.handleS2CUpdateSmoothable(Minecraft.getInstance()::execute, packet.newValue(), packet.states());
});
}
}
Loading

0 comments on commit 4de5cb8

Please sign in to comment.