Skip to content

Commit

Permalink
Merge pull request #256 from WildfireRomeo/feature/cape
Browse files Browse the repository at this point in the history
Capes for Contributors Implementation
  • Loading branch information
WildfireRomeo authored Dec 8, 2024
2 parents 90e1076 + c668dc4 commit 9066535
Show file tree
Hide file tree
Showing 7 changed files with 308 additions and 3 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Creating this to keep track of most major changes made without having to sift through the commits.

VERSION 4.3.1
- Added Rotational Horizontal Physics? (if that makes any sense lol)

- Added Horizontal Breast Physics (if that makes any sense lol)
- Added a Secret


VERSION 4.3
Expand Down
145 changes: 145 additions & 0 deletions src/main/java/com/wildfire/main/cape/CapeProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Wildfire's Female Gender Mod is a female gender mod created for Minecraft.
* Copyright (C) 2023-present WildfireRomeo
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.wildfire.main.cape;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalNotification;
import com.mojang.authlib.GameProfile;
import com.wildfire.main.WildfireGender;
import net.fabricmc.fabric.mixin.client.rendering.CapeFeatureRendererMixin;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.entity.feature.CapeFeatureRenderer;
import net.minecraft.client.session.Session;
import net.minecraft.client.texture.NativeImage;
import net.minecraft.client.texture.NativeImageBackedTexture;
import net.minecraft.client.util.SkinTextures;
import net.minecraft.util.Identifier;
import net.minecraft.util.Util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.FileNotFoundException;
import java.math.BigInteger;
import java.net.URI;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
import java.util.regex.Pattern;

public class CapeProvider {


/**
* Sentinel {@link Identifier} returned if the player's cape was obtained successfully, but they have no cape to display
*/
public static final Identifier NO_CAPE = Identifier.of(WildfireGender.MODID, "no_cape");

public static final LoadingCache<GameProfile, CompletableFuture<Identifier>> CACHE = CacheBuilder.newBuilder()
.expireAfterAccess(Duration.ofMinutes(30))
.removalListener(CapeProvider::remove)
.build(new CacheLoader<>() {
@NotNull
@Override
public CompletableFuture<@Nullable Identifier> load(@NotNull GameProfile key) {
return loadCape(key);
}
});

private static final Pattern USERNAME = Pattern.compile("^[a-z0-9_]{1,16}$", Pattern.CASE_INSENSITIVE);
private static final String CAPE_URL = "https://femalegendermod.net/capes/{uuid}.png";

private static void remove(RemovalNotification<GameProfile, CompletableFuture<@Nullable Identifier>> entry) {
var future = entry.getValue();
if(future == null) {
return; // this shouldn't happen!
}

var id = future.getNow(null);
if(id != null) {
MinecraftClient.getInstance().getTextureManager().destroyTexture(id); //destroy texture immediately
}
}

// This loads the cape for one player, doesn't matter if it's the player or not.
private static CompletableFuture<@Nullable Identifier> loadCape(GameProfile player) {
return CompletableFuture.supplyAsync(() -> {
if(!USERNAME.matcher(player.getName()).matches()) {
// immediately ignore any obviously invalid usernames (such as those from npcs)
return null;
}
Identifier texture = tryUrl(player, CAPE_URL.replace("{uuid}", player.getId().toString()));

/*if(texture == null) { //fallback url if existed, which it doesn't.
texture = tryUrl(player, FALLBACK_CAPE_URL.replace("{uuid}", player.getId().toString()));
}*/
return texture;
}, Util.getIoWorkerExecutor());
}

// This is a provider specific implementation.
// Images are usually 46x22 or 92x44, and these work as expected (64x32, 128x64).
// There are edge cages with sizes 184x88, 1024x512 and 2048x1024,
// but these should work alright.
private static NativeImage uncrop(NativeImage in) {
int srcHeight = in.getHeight(), srcWidth = in.getWidth();
int zoom = (int) Math.ceil(in.getHeight() / 32f);
NativeImage out = new NativeImage(64 * zoom, 32 * zoom, true);
for (int x = 0; x < srcWidth; x++) {
for (int y = 0; y < srcHeight; y++) {
out.setColorArgb(x, y, in.getColorArgb(x, y));
}
}
return out;
}

// Try to load a cape from an URL.
// If this fails, it'll return null, and let us try another url.
private static @Nullable Identifier tryUrl(GameProfile player, String urlFrom) {
try {
WildfireGender.LOGGER.debug("Attempting to fetch cape from {}", urlFrom);
var url = URI.create(urlFrom).toURL();
var tex = uncrop(NativeImage.read(url.openStream()));
WildfireGender.LOGGER.debug("Got cape texture");
var nIBT = new NativeImageBackedTexture(tex);
var id = WildfireGender.MODID + "/cape/" + player.getId().toString().replace("-", "");

MinecraftClient.getInstance().getTextureManager().registerTexture(Identifier.of(id), nIBT);

return Identifier.of(id);

} catch(FileNotFoundException e) {
// Getting the cape was successful! But there's no cape, so don't retry.
WildfireGender.LOGGER.debug("No cape texture found");
return NO_CAPE;
} catch(Exception e) {
WildfireGender.LOGGER.error("Failed to fetch cape texture", e);
return null;
}
}

private CapeProvider() { }

}
27 changes: 27 additions & 0 deletions src/main/java/com/wildfire/main/cape/SkinTexturesWildfire.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Wildfire's Female Gender Mod is a female gender mod created for Minecraft.
* Copyright (C) 2023-present WildfireRomeo
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.wildfire.main.cape;

import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;

public interface SkinTexturesWildfire {
void overrideCapeTexture(@Nullable Identifier texture);
@Nullable Identifier getOverriddenCapeTexture();
}
48 changes: 48 additions & 0 deletions src/main/java/com/wildfire/mixins/cape/PlayerListEntryMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Wildfire's Female Gender Mod is a female gender mod created for Minecraft.
* Copyright (C) 2023-present WildfireRomeo
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.wildfire.mixins.cape;


import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.mojang.authlib.GameProfile;
import com.wildfire.main.cape.CapeProvider;
import com.wildfire.main.cape.SkinTexturesWildfire;
import net.minecraft.client.network.PlayerListEntry;
import net.minecraft.client.util.SkinTextures;
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;

@Mixin(PlayerListEntry.class)
public abstract class PlayerListEntryMixin {
@Shadow
@Final
private GameProfile profile;

@SuppressWarnings("DataFlowIssue")
@ModifyReturnValue(method = "getSkinTextures", at = @At("RETURN"))
public SkinTextures wildfiregender$replaceCapeTexture(SkinTextures original) {
var cape = CapeProvider.CACHE.getUnchecked(profile);
var duck = ((SkinTexturesWildfire)(Object)original);
var tex = cape.getNow(null);
duck.overrideCapeTexture(tex != null && !tex.equals(CapeProvider.NO_CAPE) ? tex : null);
return original;
}
}
38 changes: 38 additions & 0 deletions src/main/java/com/wildfire/mixins/cape/SkinTexturesMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Wildfire's Female Gender Mod is a female gender mod created for Minecraft.
* Copyright (C) 2023-present WildfireRomeo
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.wildfire.mixins.cape;

import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.wildfire.main.cape.SkinTexturesWildfire;
import net.minecraft.client.util.SkinTextures;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;

@Mixin(SkinTextures.class)
public abstract class SkinTexturesMixin {
@ModifyReturnValue(method = {"capeTexture", "elytraTexture"}, at = @At("RETURN"))
public @Nullable Identifier wildfiregender$replaceCapeTexture(Identifier original) {
if(original == null) {
return ((SkinTexturesWildfire) this).getOverriddenCapeTexture();
}
return original;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Wildfire's Female Gender Mod is a female gender mod created for Minecraft.
* Copyright (C) 2023-present WildfireRomeo
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.wildfire.mixins.cape;


import com.wildfire.main.cape.SkinTexturesWildfire;
import net.minecraft.client.util.SkinTextures;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Implements;
import org.spongepowered.asm.mixin.Interface;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;

@Mixin(SkinTextures.class)
@Implements(@Interface(iface = SkinTexturesWildfire.class, prefix = "wildfiregender$", unique = true))
public abstract class SkinTexturesWildfireImplMixin {
private @Unique @Nullable Identifier wildfiregender$overriddenCapeTexture = null;

public void wildfiregender$overrideCapeTexture(@Nullable Identifier texture) {
this.wildfiregender$overriddenCapeTexture = texture;
}

public @Nullable Identifier wildfiregender$getOverriddenCapeTexture() {
return wildfiregender$overriddenCapeTexture;
}
}
6 changes: 5 additions & 1 deletion src/main/resources/wildfire_gender.mixins.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
"accessors.TextureManagerAccessor",
"accessors.TrimSpriteKeyConstructorAccessor",
"renderstate.LivingEntityRendererMixin",
"renderstate.LivingEntityRenderStateMixin"
"renderstate.LivingEntityRenderStateMixin",

"cape.PlayerListEntryMixin",
"cape.SkinTexturesWildfireImplMixin",
"cape.SkinTexturesMixin"
],
"injectors": {
"defaultRequire": 1
Expand Down

0 comments on commit 9066535

Please sign in to comment.