kid
This commit is contained in:
172
src/client/java/dev/tggamesyt/szar/client/HybridSkinManager.java
Normal file
172
src/client/java/dev/tggamesyt/szar/client/HybridSkinManager.java
Normal file
@@ -0,0 +1,172 @@
|
||||
package dev.tggamesyt.szar.client;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||
import dev.tggamesyt.szar.KidEntity;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.MinecraftClient;
|
||||
import net.minecraft.client.network.PlayerListEntry;
|
||||
import net.minecraft.client.texture.NativeImage;
|
||||
import net.minecraft.client.texture.NativeImageBackedTexture;
|
||||
import net.minecraft.client.texture.PlayerSkinProvider;
|
||||
import net.minecraft.client.util.DefaultSkinHelper;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class HybridSkinManager {
|
||||
|
||||
private static final Map<Long, Identifier> CACHE = new HashMap<>();
|
||||
private static final Map<UUID, NativeImage> SKIN_CACHE = new HashMap<>();
|
||||
|
||||
public static Identifier getHybridSkin(KidEntity entity) {
|
||||
long seed = entity.getHybridSeed();
|
||||
|
||||
if (CACHE.containsKey(seed)) return CACHE.get(seed);
|
||||
|
||||
NativeImage skinA = getParentSkin(entity.getParentA());
|
||||
NativeImage skinB = getParentSkin(entity.getParentB());
|
||||
|
||||
if (skinA == null || skinB == null) return DefaultSkinHelper.getTexture();
|
||||
|
||||
NativeImage result = new NativeImage(64, 64, true);
|
||||
Random r = new Random(seed);
|
||||
|
||||
copyHead(result, r.nextBoolean() ? skinA : skinB);
|
||||
copyTorso(result, r.nextBoolean() ? skinA : skinB);
|
||||
copyArms(result, r.nextBoolean() ? skinA : skinB);
|
||||
copyLegs(result, r.nextBoolean() ? skinA : skinB);
|
||||
|
||||
saveToDisk(result, seed);
|
||||
|
||||
Identifier id = MinecraftClient.getInstance()
|
||||
.getTextureManager()
|
||||
.registerDynamicTexture("jungle_" + entity.getUuid(),
|
||||
new NativeImageBackedTexture(result));
|
||||
|
||||
CACHE.put(seed, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static NativeImage getParentSkin(UUID uuid) {
|
||||
if (uuid == null) return null;
|
||||
if (SKIN_CACHE.containsKey(uuid)) return SKIN_CACHE.get(uuid);
|
||||
|
||||
MinecraftClient client = MinecraftClient.getInstance();
|
||||
NativeImage img = null;
|
||||
|
||||
// 1️⃣ Try mineskin.eu
|
||||
String name = null;
|
||||
PlayerListEntry entry = client.getNetworkHandler().getPlayerListEntry(uuid);
|
||||
if (entry != null) name = entry.getProfile().getName();
|
||||
|
||||
if (name != null) {
|
||||
try (InputStream in = new URL("https://mineskin.eu/skin/" + name).openStream()) {
|
||||
img = NativeImage.read(in);
|
||||
if (img.getWidth() != 64 || img.getHeight() != 64) img = null;
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// 2️⃣ Fallback to Mojang skin provider
|
||||
if (img == null) {
|
||||
try {
|
||||
GameProfile profile = entry != null ? entry.getProfile() : new GameProfile(uuid, name);
|
||||
PlayerSkinProvider provider = client.getSkinProvider();
|
||||
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures =
|
||||
provider.getTextures(profile);
|
||||
MinecraftProfileTexture skinTexture = textures.get(MinecraftProfileTexture.Type.SKIN);
|
||||
|
||||
if (skinTexture != null) {
|
||||
Identifier id = provider.loadSkin(skinTexture, MinecraftProfileTexture.Type.SKIN);
|
||||
try (InputStream stream = client.getResourceManager().getResource(id).get().getInputStream()) {
|
||||
img = NativeImage.read(stream);
|
||||
}
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// 3️⃣ Fallback to default skin
|
||||
if (img == null) img = loadDefaultSkin(new GameProfile(uuid, name));
|
||||
|
||||
SKIN_CACHE.put(uuid, img);
|
||||
return img;
|
||||
}
|
||||
|
||||
private static void copyRegion(NativeImage target, NativeImage source,
|
||||
int x, int y, int w, int h) {
|
||||
for (int dx = 0; dx < w; dx++) {
|
||||
for (int dy = 0; dy < h; dy++) {
|
||||
target.setColor(x + dx, y + dy, source.getColor(x + dx, y + dy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyPartWithOverlay(NativeImage target, NativeImage source,
|
||||
int innerX, int innerY, int w, int h,
|
||||
int outerX, int outerY) {
|
||||
// Copy inner layer
|
||||
copyRegion(target, source, innerX, innerY, w, h);
|
||||
// Copy outer layer
|
||||
copyRegion(target, source, outerX, outerY, w, h);
|
||||
}
|
||||
|
||||
// Updated coordinates as per your UV mapping
|
||||
private static void copyHead(NativeImage t, NativeImage s) {
|
||||
copyPartWithOverlay(t, s, 0, 0, 64, 16, 0, 16);
|
||||
}
|
||||
|
||||
private static void copyTorso(NativeImage t, NativeImage s) {
|
||||
copyPartWithOverlay(t, s, 16, 16, 24, 32, 16, 32);
|
||||
}
|
||||
|
||||
private static void copyArms(NativeImage t, NativeImage s) {
|
||||
// Right arm
|
||||
copyPartWithOverlay(t, s, 40, 16, 16, 32, 40, 32);
|
||||
// Left arm
|
||||
copyPartWithOverlay(t, s, 32, 48, 32, 16, 32, 48);
|
||||
}
|
||||
|
||||
private static void copyLegs(NativeImage t, NativeImage s) {
|
||||
// Right leg
|
||||
copyPartWithOverlay(t, s, 0, 16, 16, 32, 0, 32);
|
||||
// Left leg
|
||||
copyPartWithOverlay(t, s, 0, 48, 32, 16, 0, 48);
|
||||
}
|
||||
|
||||
|
||||
private static void saveToDisk(NativeImage img, long seed) {
|
||||
try {
|
||||
Path path = MinecraftClient.getInstance()
|
||||
.runDirectory.toPath()
|
||||
.resolve("hybrid_skins");
|
||||
Files.createDirectories(path);
|
||||
File file = path.resolve(seed + ".png").toFile();
|
||||
img.writeTo(file);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
private static NativeImage loadDefaultSkin(GameProfile profile) {
|
||||
try {
|
||||
Identifier fallback = DefaultSkinHelper.getTexture(profile.getId());
|
||||
try (InputStream stream = MinecraftClient.getInstance()
|
||||
.getResourceManager().getResource(fallback).get().getInputStream()) {
|
||||
return NativeImage.read(stream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new NativeImage(64, 64, true);
|
||||
}
|
||||
}
|
||||
54
src/client/java/dev/tggamesyt/szar/client/KidRenderer.java
Normal file
54
src/client/java/dev/tggamesyt/szar/client/KidRenderer.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package dev.tggamesyt.szar.client;
|
||||
|
||||
import dev.tggamesyt.szar.KidEntity;
|
||||
import net.fabricmc.api.EnvType;
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.minecraft.client.render.entity.EntityRendererFactory;
|
||||
import net.minecraft.client.render.entity.MobEntityRenderer;
|
||||
import net.minecraft.client.render.entity.model.EntityModelLayers;
|
||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
||||
import net.minecraft.client.util.math.MatrixStack;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
@Environment(EnvType.CLIENT)
|
||||
public class KidRenderer extends MobEntityRenderer<KidEntity, PlayerEntityModel<KidEntity>> {
|
||||
|
||||
private static final float MAX_HEAD_SCALE = 2.0f;
|
||||
private static final float MIN_BODY_SCALE = 0.3f; // starting scale for body
|
||||
private static final float MAX_BODY_SCALE = 1.0f; // final player scale
|
||||
|
||||
public KidRenderer(EntityRendererFactory.Context ctx) {
|
||||
super(ctx,
|
||||
new PlayerEntityModel<>(ctx.getPart(EntityModelLayers.PLAYER), false),
|
||||
0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier getTexture(KidEntity entity) {
|
||||
return HybridSkinManager.getHybridSkin(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scale(KidEntity entity, MatrixStack matrices, float tickDelta) {
|
||||
// Calculate growth fraction
|
||||
float ageFraction = entity.getAgeFraction(); // we’ll add this helper in KidEntity
|
||||
if (ageFraction > 1f) ageFraction = 1f;
|
||||
|
||||
// Scale body gradually from MIN_BODY_SCALE → MAX_BODY_SCALE
|
||||
float bodyScale = MIN_BODY_SCALE + (MAX_BODY_SCALE - MIN_BODY_SCALE) * ageFraction;
|
||||
matrices.scale(bodyScale, bodyScale, bodyScale);
|
||||
|
||||
// Scale head separately (start huge, shrink to normal)
|
||||
PlayerEntityModel<?> model = this.getModel();
|
||||
float headScale = MAX_HEAD_SCALE - (MAX_HEAD_SCALE - 1.0f) * ageFraction;
|
||||
model.head.xScale = headScale;
|
||||
model.head.yScale = headScale;
|
||||
model.head.zScale = headScale;
|
||||
// sleeping pose
|
||||
if (entity.isSleeping()) {
|
||||
matrices.translate(0, -0.5, 0); // lower the body
|
||||
model.body.pitch = 90f; // lay on side
|
||||
}
|
||||
super.scale(entity, matrices, tickDelta);
|
||||
}
|
||||
}
|
||||
@@ -205,6 +205,10 @@ public class SzarClient implements ClientModInitializer {
|
||||
Szar.NiggerEntityType,
|
||||
NiggerEntityRenderer::new
|
||||
);
|
||||
EntityRendererRegistry.register(
|
||||
Szar.Kid,
|
||||
KidRenderer::new
|
||||
);
|
||||
EntityRendererRegistry.register(
|
||||
Szar.AtomEntityType,
|
||||
AtomEntityRenderer::new
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package dev.tggamesyt.szar.client.mixin;
|
||||
|
||||
import dev.tggamesyt.szar.Szar;
|
||||
import net.minecraft.client.render.entity.model.PlayerEntityModel;
|
||||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.entity.effect.StatusEffectInstance;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
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(PlayerEntityModel.class)
|
||||
public abstract class PlayerModelMixin<T extends LivingEntity> {
|
||||
|
||||
@Inject(method = "setAngles", at = @At("TAIL"))
|
||||
private void growTorso(T entity, float limbAngle, float limbDistance,
|
||||
float animationProgress, float headYaw,
|
||||
float headPitch, CallbackInfo ci) {
|
||||
|
||||
if (!(entity instanceof PlayerEntity player)) return;
|
||||
|
||||
PlayerEntityModel<?> model =
|
||||
(PlayerEntityModel<?>)(Object)this;
|
||||
|
||||
// 🔁 RESET TO DEFAULT EVERY FRAME
|
||||
model.body.xScale = 1.0f;
|
||||
model.body.yScale = 1.0f;
|
||||
model.body.zScale = 1.0f;
|
||||
model.body.pivotZ = 0.0f;
|
||||
|
||||
if (player.hasStatusEffect(Szar.PREGNANT)) {
|
||||
|
||||
StatusEffectInstance effect =
|
||||
player.getStatusEffect(Szar.PREGNANT);
|
||||
|
||||
int maxDuration = 24000;
|
||||
float progress = 1f - (effect.getDuration() / (float) maxDuration);
|
||||
|
||||
// slow, controlled growth
|
||||
float growth = progress * 0.5f; // max 0.5x longer
|
||||
|
||||
model.body.zScale = 1.0f + growth;
|
||||
model.body.pivotZ = -growth * 4.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
"RadiatedItemRendererMixin",
|
||||
"SplashOverlayMixin",
|
||||
"TGnameMixin",
|
||||
"TGcapeMixin"
|
||||
"TGcapeMixin",
|
||||
"PlayerModelMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
Reference in New Issue
Block a user