Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: validate gradle wrapper
uses: gradle/actions/wrapper-validation@v4
uses: gradle/actions/wrapper-validation@v6
- name: setup jdk
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: '21'
java-version: '25'
distribution: 'microsoft'
- name: make gradle wrapper executable
run: chmod +x ./gradlew
- name: build
run: ./gradlew build
- name: capture build artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: Artifacts
path: build/libs/
29 changes: 11 additions & 18 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
plugins {
id 'fabric-loom' version '1.10-SNAPSHOT'
id "net.fabricmc.fabric-loom" version "${loom_version}"
id 'maven-publish'
}

version = "${project.mod_version}-${project.minecraft_version}"
version = "${project.mod_version}+${project.minecraft_version}"
group = project.maven_group

base {
Expand All @@ -22,10 +22,9 @@ repositories {
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
implementation "net.fabricmc:fabric-loader:${project.loader_version}"

modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
implementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
}

processResources {
Expand All @@ -41,31 +40,25 @@ processResources {
}
}

def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
it.options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
it.options.release.set(targetJavaVersion)
}
it.options.release = 25
}

java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()

sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
}

jar {
inputs.property "projectName", project.name

from("LICENSE") {
rename { "${it}_${project.archivesBaseName}" }
rename { "${it}_${project.name}"}
}
}

Expand Down
12 changes: 6 additions & 6 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G
org.gradle.parallel=true
# Fabric Properties
# check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.8
yarn_mappings=1.21.8+build.1
loader_version=0.17.2
loom_version=1.11-SNAPSHOT
minecraft_version=26.1
loader_version=0.18.6
loom_version=1.15-SNAPSHOT
# Mod Properties
mod_version=1.1
mod_version=1.2
maven_group=me.collinb
archives_base_name=modernbetapatches
# Dependencies
# check this on https://modmuss50.me/fabric.html
fabric_version=0.133.4+1.21.8
fabric_version=0.145.1+26.1
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pluginManagement {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
mavenCentral()
gradlePluginPortal()
}
}
31 changes: 15 additions & 16 deletions src/main/java/me/collinb/modernbetapatches/ModernBetaPatches.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,41 @@
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.decoration.ArmorStandEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.world.entity.decoration.ArmorStand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ModernBetaPatches implements ClientModInitializer {
public static final Logger LOGGER = LoggerFactory.getLogger("modernbetapatches");

public static String currentServer = null;

@Override
public void onInitializeClient() {
ClientPlayConnectionEvents.JOIN.register((clientPlayNetworkHandler, packetSender, minecraftClient) -> {
if (minecraftClient.getCurrentServerEntry() != null) {
currentServer = minecraftClient.getCurrentServerEntry().address;
ClientPlayConnectionEvents.JOIN.register((clientPlayNetworkHandler, _, minecraftClient) -> {
if (minecraftClient.getCurrentServer() != null) {
currentServer = minecraftClient.getCurrentServer().ip;
if (isModernBeta()) {
CapeManager.fetchCape(clientPlayNetworkHandler.getProfile().getId());
CapeManager.fetchCape(clientPlayNetworkHandler.getLocalGameProfile().id());
}
} else {
currentServer = null;
}
});

ClientPlayConnectionEvents.DISCONNECT.register(((clientPlayNetworkHandler, minecraftClient) -> currentServer = null));
ClientPlayConnectionEvents.DISCONNECT.register(((_, _) -> currentServer = null));

ClientTickEvents.END_CLIENT_TICK.register((client) -> {
if (!isModernBeta()) return;
if (client.world == null) return;
if (client.level == null) return;

for (Entity entity : client.world.getEntities()) {
if (entity instanceof ArmorStandEntity armorStandEntity) {
for (net.minecraft.world.entity.Entity entity : client.level.entitiesForRendering()) {
if (entity instanceof ArmorStand armorStandEntity) {

// Check if paper is on armor stand head
if (!armorStandEntity.hasStackEquipped(EquipmentSlot.HEAD)) continue;
ItemStack headItem = armorStandEntity.getEquippedStack(EquipmentSlot.HEAD);
var headItem = armorStandEntity.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.HEAD);

if (headItem.isOf(Items.PAPER)) {
if (headItem.is(net.minecraft.world.item.Items.PAPER)) {
armorStandEntity.discard();
}
}
Expand Down
79 changes: 49 additions & 30 deletions src/main/java/me/collinb/modernbetapatches/manager/CapeManager.java
Original file line number Diff line number Diff line change
@@ -1,57 +1,76 @@
package me.collinb.modernbetapatches.manager;

import net.minecraft.client.MinecraftClient;
import net.minecraft.client.texture.NativeImage;
import net.minecraft.client.texture.NativeImageBackedTexture;
import net.minecraft.util.Identifier;
import com.mojang.blaze3d.platform.NativeImage;
import me.collinb.modernbetapatches.ModernBetaPatches;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.core.ClientAsset;
import net.minecraft.resources.Identifier;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;

public class CapeManager {
private static final Map<UUID, Identifier> capes = new HashMap<>();
private static final Set<UUID> capes = Collections.newSetFromMap(new ConcurrentHashMap<>());

public static void fetchCape(UUID uuid) {
public static void fetchCape(@NonNull UUID uuid) {
String path = uuid.toString().toLowerCase(Locale.ROOT);
String formattedPath = "textures/" + path + ".png";
String url = "https://cape.modernbeta.org/cape/" + uuid;

Identifier id = Identifier.fromNamespaceAndPath("modernbetacapes", formattedPath);

CompletableFuture.runAsync(() -> {
try (InputStream stream = URI.create(url).toURL().openStream()) {
NativeImage image = NativeImage.read(stream);

NativeImage reformatted = new NativeImage(NativeImage.Format.RGBA, 64, 32, true);
reformatted.fillRect(0, 0, 64, 32, 0x00000000);

for (int y = 0; y < image.getHeight(); ++y) {
for (int x = 0; x < image.getWidth(); ++x) {
int color = image.getColorArgb(x, y);
reformatted.setColorArgb(x, y, color);
}
}
NativeImage reformatted = getReformatted(image);

Identifier id = Identifier.of("modernbetacapes", uuid.toString());
image.close();

MinecraftClient.getInstance().execute(() -> {
MinecraftClient.getInstance().getTextureManager()
.registerTexture(id, new NativeImageBackedTexture(id::toString, reformatted));
capes.put(uuid, id);
Minecraft.getInstance().execute(() -> {
Minecraft.getInstance().getTextureManager().register(id, new DynamicTexture(id::toString, reformatted));
capes.add(uuid);
ModernBetaPatches.LOGGER.info("Registered cape {}", formattedPath);
});
} catch (IOException ignored) {
// Cape not found, remove identifier
} catch (Exception e) {
capes.remove(uuid);
}
});
}

public static Identifier getCape(UUID uuid) {
return capes.get(uuid);
private static @NonNull NativeImage getReformatted(@NonNull NativeImage image) {
NativeImage reformatted = new NativeImage(NativeImage.Format.RGBA, 64, 32, true);
reformatted.fillRect(0, 0, 64, 32, 0x00000000);

int[] sourcePixels = image.getPixelsABGR();
int width = Math.min(image.getWidth(), 64);
int height = Math.min(image.getHeight(), 32);

for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int color = sourcePixels[x + y * width];
reformatted.setPixelABGR(x, y, color);
}
}
return reformatted;
}

public static ClientAsset.@Nullable Texture getCape(UUID uuid) {
if (hasModernBetaCape(uuid)) {
String path = uuid.toString().toLowerCase(Locale.ROOT);
Identifier capeIdentifier = Identifier.fromNamespaceAndPath("modernbetacapes", path);
return new ClientAsset.ResourceTexture(capeIdentifier);
}
return null;
}

public static boolean isModernBetaCape(Identifier id) {
return id != null && "modernbetacapes".equals(id.getNamespace());
public static boolean hasModernBetaCape(UUID uuid) {
return capes.contains(uuid);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package me.collinb.modernbetapatches.mixin;

import me.collinb.modernbetapatches.manager.CapeManager;
import net.minecraft.client.renderer.entity.player.AvatarRenderer;
import net.minecraft.client.renderer.entity.state.AvatarRenderState;
import net.minecraft.world.entity.Avatar;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

@Mixin(AvatarRenderer.class)
public abstract class AvatarRendererMixin {

@Inject(method = "extractRenderState*", at = @At("TAIL"))
private void forceCapeVisibility(Avatar entity, AvatarRenderState state, float partialTicks, CallbackInfo ci) {
if (CapeManager.hasModernBetaCape(entity.getUUID())) {
state.showCape = true;
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package me.collinb.modernbetapatches.mixin;

import com.mojang.authlib.GameProfile;
import me.collinb.modernbetapatches.ModernBetaPatches;
import me.collinb.modernbetapatches.manager.CapeManager;
import net.minecraft.client.multiplayer.PlayerInfo;
import net.minecraft.core.ClientAsset;
import net.minecraft.world.entity.player.PlayerSkin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import java.util.UUID;
import java.util.function.Supplier;

@Mixin(PlayerInfo.class)
public abstract class PlayerInfoMixin {

@Shadow
@Final
private GameProfile profile;

@Inject(method = "createSkinLookup", at = @At("TAIL"))
private static void loadModernBetaCape(GameProfile profile, CallbackInfoReturnable<Supplier<PlayerSkin>> cir) {
if (ModernBetaPatches.isModernBeta()) {
UUID uuid = profile.id();
CapeManager.fetchCape(uuid);
}
}

@Inject(method = "getSkin", at = @At("TAIL"), cancellable = true)
private void overrideSkinTextures(CallbackInfoReturnable<PlayerSkin> cir) {
if (ModernBetaPatches.isModernBeta()) {
PlayerSkin originalSkinTextures = cir.getReturnValue();
ClientAsset.Texture modernBetaCape = CapeManager.getCape(profile.id());
PlayerSkin newSkinTextures = new PlayerSkin(
originalSkinTextures.body(),
(modernBetaCape == null ? originalSkinTextures.cape() : modernBetaCape),
originalSkinTextures.elytra(),
originalSkinTextures.model(),
originalSkinTextures.secure()
);
cir.setReturnValue(newSkinTextures);
}
}
}
Loading