kid
This commit is contained in:
@@ -6,7 +6,7 @@ minecraft_version=1.20.1
|
|||||||
yarn_mappings=1.20.1+build.10
|
yarn_mappings=1.20.1+build.10
|
||||||
loader_version=0.18.3
|
loader_version=0.18.3
|
||||||
# Mod Properties
|
# Mod Properties
|
||||||
mod_version=26.2.23
|
mod_version=26.2.23.1
|
||||||
maven_group=dev.tggamesyt
|
maven_group=dev.tggamesyt
|
||||||
archives_base_name=szar
|
archives_base_name=szar
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|||||||
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,
|
Szar.NiggerEntityType,
|
||||||
NiggerEntityRenderer::new
|
NiggerEntityRenderer::new
|
||||||
);
|
);
|
||||||
|
EntityRendererRegistry.register(
|
||||||
|
Szar.Kid,
|
||||||
|
KidRenderer::new
|
||||||
|
);
|
||||||
EntityRendererRegistry.register(
|
EntityRendererRegistry.register(
|
||||||
Szar.AtomEntityType,
|
Szar.AtomEntityType,
|
||||||
AtomEntityRenderer::new
|
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",
|
"RadiatedItemRendererMixin",
|
||||||
"SplashOverlayMixin",
|
"SplashOverlayMixin",
|
||||||
"TGnameMixin",
|
"TGnameMixin",
|
||||||
"TGcapeMixin"
|
"TGcapeMixin",
|
||||||
|
"PlayerModelMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|||||||
142
src/main/java/dev/tggamesyt/szar/KidEntity.java
Normal file
142
src/main/java/dev/tggamesyt/szar/KidEntity.java
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package dev.tggamesyt.szar;
|
||||||
|
|
||||||
|
import net.minecraft.block.Blocks;
|
||||||
|
import net.minecraft.entity.EntityType;
|
||||||
|
import net.minecraft.entity.ai.goal.*;
|
||||||
|
import net.minecraft.entity.attribute.DefaultAttributeContainer;
|
||||||
|
import net.minecraft.entity.attribute.EntityAttributes;
|
||||||
|
import net.minecraft.entity.data.DataTracker;
|
||||||
|
import net.minecraft.entity.data.TrackedData;
|
||||||
|
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
|
||||||
|
import net.minecraft.entity.mob.PathAwareEntity;
|
||||||
|
import net.minecraft.entity.passive.VillagerEntity;
|
||||||
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.nbt.NbtCompound;
|
||||||
|
import net.minecraft.server.world.ServerWorld;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class KidEntity extends PathAwareEntity {
|
||||||
|
|
||||||
|
private static final TrackedData<Integer> AGE =
|
||||||
|
DataTracker.registerData(KidEntity.class, TrackedDataHandlerRegistry.INTEGER);
|
||||||
|
private static final TrackedData<Optional<UUID>> PARENT_A =
|
||||||
|
DataTracker.registerData(KidEntity.class, TrackedDataHandlerRegistry.OPTIONAL_UUID);
|
||||||
|
private static final TrackedData<Optional<UUID>> PARENT_B =
|
||||||
|
DataTracker.registerData(KidEntity.class, TrackedDataHandlerRegistry.OPTIONAL_UUID);
|
||||||
|
|
||||||
|
public static final int MAX_AGE = 360000; // 30 days * 10h
|
||||||
|
|
||||||
|
public KidEntity(EntityType<KidEntity> type, World world) {
|
||||||
|
super(type, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initDataTracker() {
|
||||||
|
super.initDataTracker();
|
||||||
|
this.dataTracker.startTracking(AGE, 0);
|
||||||
|
this.dataTracker.startTracking(PARENT_A, Optional.empty());
|
||||||
|
this.dataTracker.startTracking(PARENT_B, Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAgeFraction() {
|
||||||
|
return Math.min(dataTracker.get(AGE) / (float) MAX_AGE, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initGoals() {
|
||||||
|
this.goalSelector.add(0, new SwimGoal(this));
|
||||||
|
this.goalSelector.add(1, new FleeEntityGoal<>(this, PlayerEntity.class, 6F, 1.2, 1.5));
|
||||||
|
this.goalSelector.add(2, new WanderAroundFarGoal(this, 1.0));
|
||||||
|
this.goalSelector.add(3, new LookAroundGoal(this));
|
||||||
|
//this.goalSelector.add(4, new KidGoToBedGoal(this, 1.0));
|
||||||
|
|
||||||
|
this.targetSelector.add(1, new RevengeGoal(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
super.tick();
|
||||||
|
|
||||||
|
if (!getWorld().isClient) {
|
||||||
|
int age = dataTracker.get(AGE);
|
||||||
|
age++;
|
||||||
|
dataTracker.set(AGE, age);
|
||||||
|
|
||||||
|
// Remove brutal killing; just let the kid “grow up” visually
|
||||||
|
if (age > MAX_AGE) {
|
||||||
|
// Optionally, you can clamp AGE to MAX_AGE to avoid overflow
|
||||||
|
dataTracker.set(AGE, MAX_AGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forget anger quickly
|
||||||
|
if (this.getAttacker() != null && age % 60 == 0) {
|
||||||
|
this.setTarget(null);
|
||||||
|
this.setAttacker(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DefaultAttributeContainer.Builder createAttributes() {
|
||||||
|
return PathAwareEntity.createMobAttributes()
|
||||||
|
.add(EntityAttributes.GENERIC_MAX_HEALTH, 20.0)
|
||||||
|
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25)
|
||||||
|
.add(EntityAttributes.GENERIC_ATTACK_DAMAGE, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getGrowthScale() {
|
||||||
|
return 0.3f + getAgeFraction() * 0.7f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getParentA() {
|
||||||
|
return this.dataTracker.get(PARENT_A).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getParentB() {
|
||||||
|
return this.dataTracker.get(PARENT_B).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for consistent skin generation
|
||||||
|
public long getHybridSeed() {
|
||||||
|
UUID a = getParentA();
|
||||||
|
UUID b = getParentB();
|
||||||
|
|
||||||
|
if (a == null || b == null) return 0L;
|
||||||
|
|
||||||
|
return a.getMostSignificantBits() ^ b.getLeastSignificantBits();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParents(UUID a, UUID b) {
|
||||||
|
this.dataTracker.set(PARENT_A, Optional.ofNullable(a));
|
||||||
|
this.dataTracker.set(PARENT_B, Optional.ofNullable(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeCustomDataToNbt(NbtCompound nbt) {
|
||||||
|
super.writeCustomDataToNbt(nbt);
|
||||||
|
|
||||||
|
UUID a = getParentA();
|
||||||
|
UUID b = getParentB();
|
||||||
|
|
||||||
|
if (a != null) nbt.putUuid("ParentA", a);
|
||||||
|
if (b != null) nbt.putUuid("ParentB", b);
|
||||||
|
|
||||||
|
nbt.putInt("Age", dataTracker.get(AGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readCustomDataFromNbt(NbtCompound nbt) {
|
||||||
|
super.readCustomDataFromNbt(nbt);
|
||||||
|
|
||||||
|
if (nbt.containsUuid("ParentA"))
|
||||||
|
dataTracker.set(PARENT_A, Optional.of(nbt.getUuid("ParentA")));
|
||||||
|
|
||||||
|
if (nbt.containsUuid("ParentB"))
|
||||||
|
dataTracker.set(PARENT_B, Optional.of(nbt.getUuid("ParentB")));
|
||||||
|
|
||||||
|
dataTracker.set(AGE, nbt.getInt("Age"));
|
||||||
|
}
|
||||||
|
}
|
||||||
116
src/main/java/dev/tggamesyt/szar/KidGoToBedGoal.java
Normal file
116
src/main/java/dev/tggamesyt/szar/KidGoToBedGoal.java
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package dev.tggamesyt.szar;
|
||||||
|
|
||||||
|
import dev.tggamesyt.szar.KidEntity;
|
||||||
|
import net.minecraft.block.BedBlock;
|
||||||
|
import net.minecraft.entity.ai.goal.Goal;
|
||||||
|
import net.minecraft.server.world.ServerWorld;
|
||||||
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
public class KidGoToBedGoal extends Goal {
|
||||||
|
|
||||||
|
private final KidEntity kid;
|
||||||
|
private final double speed;
|
||||||
|
private BlockPos targetBed;
|
||||||
|
private BlockPos pathTarget;
|
||||||
|
|
||||||
|
public KidGoToBedGoal(KidEntity kid, double speed) {
|
||||||
|
this.kid = kid;
|
||||||
|
this.speed = speed;
|
||||||
|
this.setControls(EnumSet.of(Control.MOVE, Control.LOOK));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canStart() {
|
||||||
|
if (kid.getWorld().isClient) return false;
|
||||||
|
|
||||||
|
long time = kid.getWorld().getTimeOfDay() % 24000L;
|
||||||
|
if (time < 13000L || time > 23000L) return false; // night only
|
||||||
|
|
||||||
|
targetBed = findNearestBed();
|
||||||
|
if (targetBed == null) return false;
|
||||||
|
|
||||||
|
pathTarget = getAdjacentWalkable(targetBed);
|
||||||
|
return pathTarget != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockPos findNearestBed() {
|
||||||
|
ServerWorld world = (ServerWorld) kid.getWorld();
|
||||||
|
BlockPos pos = kid.getBlockPos();
|
||||||
|
int radius = 16;
|
||||||
|
|
||||||
|
for (int dx = -radius; dx <= radius; dx++) {
|
||||||
|
for (int dy = -radius; dy <= radius; dy++) {
|
||||||
|
for (int dz = -radius; dz <= radius; dz++) {
|
||||||
|
BlockPos check = pos.add(dx, dy, dz);
|
||||||
|
if (world.getBlockState(check).getBlock() instanceof BedBlock) {
|
||||||
|
return check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockPos getAdjacentWalkable(BlockPos bed) {
|
||||||
|
// pick one of the 4 cardinal blocks adjacent to bed that is walkable
|
||||||
|
ServerWorld world = (ServerWorld) kid.getWorld();
|
||||||
|
BlockPos[] candidates = {
|
||||||
|
bed.north(),
|
||||||
|
bed.south(),
|
||||||
|
bed.east(),
|
||||||
|
bed.west()
|
||||||
|
};
|
||||||
|
for (BlockPos candidate : candidates) {
|
||||||
|
if (world.isAir(candidate) && world.isAir(candidate.up())) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // no free adjacent block
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldContinue() {
|
||||||
|
return pathTarget != null && !kid.getNavigation().isIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start() {
|
||||||
|
if (pathTarget != null && targetBed != null) {
|
||||||
|
kid.setSleepingPosition(targetBed);
|
||||||
|
kid.getNavigation().startMovingTo(
|
||||||
|
pathTarget.getX() + 0.5,
|
||||||
|
pathTarget.getY(),
|
||||||
|
pathTarget.getZ() + 0.5,
|
||||||
|
speed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
if (pathTarget != null) {
|
||||||
|
double dx = pathTarget.getX() + 0.5 - kid.getX();
|
||||||
|
double dy = pathTarget.getY() - kid.getY();
|
||||||
|
double dz = pathTarget.getZ() + 0.5 - kid.getZ();
|
||||||
|
if (dx*dx + dy*dy + dz*dz > 1.0) { // ~1 block distance
|
||||||
|
kid.getNavigation().startMovingTo(
|
||||||
|
pathTarget.getX() + 0.5,
|
||||||
|
pathTarget.getY(),
|
||||||
|
pathTarget.getZ() + 0.5,
|
||||||
|
speed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
if (targetBed != null) {
|
||||||
|
kid.clearSleepingPosition();
|
||||||
|
targetBed = null;
|
||||||
|
pathTarget = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/main/java/dev/tggamesyt/szar/PregnantEffect.java
Normal file
50
src/main/java/dev/tggamesyt/szar/PregnantEffect.java
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package dev.tggamesyt.szar;
|
||||||
|
|
||||||
|
import net.minecraft.entity.LivingEntity;
|
||||||
|
import net.minecraft.entity.attribute.AttributeContainer;
|
||||||
|
import net.minecraft.entity.effect.StatusEffect;
|
||||||
|
import net.minecraft.entity.effect.StatusEffectCategory;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
|
import net.minecraft.server.world.ServerWorld;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class PregnantEffect extends StatusEffect {
|
||||||
|
|
||||||
|
public PregnantEffect() {
|
||||||
|
super(StatusEffectCategory.BENEFICIAL, 0xFF66CC); // pink color
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canApplyUpdateEffect(int duration, int amplifier) {
|
||||||
|
return false; // no ticking needed, only care about end
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRemoved(LivingEntity entity, AttributeContainer attributes, int amplifier) {
|
||||||
|
if (!entity.getWorld().isClient && entity instanceof ServerPlayerEntity player) {
|
||||||
|
ServerWorld world = (ServerWorld) player.getWorld();
|
||||||
|
|
||||||
|
UUID partnerUuid = Szar.pregnantPartners.remove(player.getUuid());
|
||||||
|
ServerPlayerEntity partner;
|
||||||
|
if (partnerUuid != null) {
|
||||||
|
partner = (ServerPlayerEntity) world.getPlayerByUuid(partnerUuid);
|
||||||
|
} else {
|
||||||
|
partner = world.getPlayers().stream()
|
||||||
|
.filter(p -> p != player)
|
||||||
|
.min((a, b) -> Double.compare(player.squaredDistanceTo(a), player.squaredDistanceTo(b)))
|
||||||
|
.orElse(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
KidEntity kid = Szar.Kid.create(world);
|
||||||
|
if (kid != null) {
|
||||||
|
kid.refreshPositionAndAngles(player.getX(), player.getY(), player.getZ(),
|
||||||
|
player.getYaw(), player.getPitch());
|
||||||
|
kid.setParents(player.getUuid(), partner.getUuid());
|
||||||
|
world.spawnEntity(kid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onRemoved(entity, attributes, amplifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,16 @@
|
|||||||
package dev.tggamesyt.szar;
|
package dev.tggamesyt.szar;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.mojang.serialization.Codec;
|
|
||||||
import dev.tggamesyt.szar.PlaneAnimation;
|
|
||||||
import net.fabricmc.api.ModInitializer;
|
import net.fabricmc.api.ModInitializer;
|
||||||
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
|
import net.fabricmc.fabric.api.biome.v1.BiomeModifications;
|
||||||
import net.fabricmc.fabric.api.biome.v1.BiomeSelectors;
|
import net.fabricmc.fabric.api.biome.v1.BiomeSelectors;
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||||
import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
|
import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
|
||||||
import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback;
|
|
||||||
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
|
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
|
||||||
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
|
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
|
||||||
import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent;
|
import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent;
|
||||||
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
|
||||||
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
|
|
||||||
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
|
||||||
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
import net.fabricmc.fabric.api.object.builder.v1.block.FabricBlockSettings;
|
||||||
import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry;
|
import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry;
|
||||||
@@ -29,6 +25,7 @@ import net.minecraft.entity.data.DataTracker;
|
|||||||
import net.minecraft.entity.data.TrackedData;
|
import net.minecraft.entity.data.TrackedData;
|
||||||
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
|
import net.minecraft.entity.data.TrackedDataHandlerRegistry;
|
||||||
import net.minecraft.entity.effect.StatusEffect;
|
import net.minecraft.entity.effect.StatusEffect;
|
||||||
|
import net.minecraft.entity.effect.StatusEffectInstance;
|
||||||
import net.minecraft.entity.passive.VillagerEntity;
|
import net.minecraft.entity.passive.VillagerEntity;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.item.*;
|
import net.minecraft.item.*;
|
||||||
@@ -44,21 +41,16 @@ import net.minecraft.sound.SoundCategory;
|
|||||||
import net.minecraft.sound.SoundEvent;
|
import net.minecraft.sound.SoundEvent;
|
||||||
import net.minecraft.sound.SoundEvents;
|
import net.minecraft.sound.SoundEvents;
|
||||||
import net.minecraft.structure.StructurePieceType;
|
import net.minecraft.structure.StructurePieceType;
|
||||||
import net.minecraft.structure.rule.RuleTest;
|
|
||||||
import net.minecraft.structure.rule.RuleTestType;
|
|
||||||
import net.minecraft.structure.rule.TagMatchRuleTest;
|
import net.minecraft.structure.rule.TagMatchRuleTest;
|
||||||
import net.minecraft.text.Text;
|
import net.minecraft.text.Text;
|
||||||
import net.minecraft.util.ActionResult;
|
import net.minecraft.util.ActionResult;
|
||||||
import net.minecraft.util.Formatting;
|
import net.minecraft.util.Formatting;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.Rarity;
|
import net.minecraft.util.Rarity;
|
||||||
import net.minecraft.util.collection.DataPool;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.util.math.Box;
|
|
||||||
import net.minecraft.util.math.random.Random;
|
|
||||||
import net.minecraft.village.TradeOffer;
|
import net.minecraft.village.TradeOffer;
|
||||||
import net.minecraft.village.VillagerProfession;
|
import net.minecraft.village.VillagerProfession;
|
||||||
import net.minecraft.world.Heightmap;
|
import net.minecraft.world.Heightmap;
|
||||||
import net.minecraft.world.biome.Biome;
|
|
||||||
import net.minecraft.world.biome.BiomeKeys;
|
import net.minecraft.world.biome.BiomeKeys;
|
||||||
import net.minecraft.world.gen.GenerationStep;
|
import net.minecraft.world.gen.GenerationStep;
|
||||||
import net.minecraft.world.gen.YOffset;
|
import net.minecraft.world.gen.YOffset;
|
||||||
@@ -67,13 +59,10 @@ import net.minecraft.world.gen.placementmodifier.BiomePlacementModifier;
|
|||||||
import net.minecraft.world.gen.placementmodifier.CountPlacementModifier;
|
import net.minecraft.world.gen.placementmodifier.CountPlacementModifier;
|
||||||
import net.minecraft.world.gen.placementmodifier.HeightRangePlacementModifier;
|
import net.minecraft.world.gen.placementmodifier.HeightRangePlacementModifier;
|
||||||
import net.minecraft.world.gen.placementmodifier.SquarePlacementModifier;
|
import net.minecraft.world.gen.placementmodifier.SquarePlacementModifier;
|
||||||
import net.minecraft.world.gen.stateprovider.BlockStateProvider;
|
|
||||||
import net.minecraft.world.gen.stateprovider.WeightedBlockStateProvider;
|
|
||||||
import net.minecraft.world.gen.structure.StructureType;
|
import net.minecraft.world.gen.structure.StructureType;
|
||||||
import net.minecraft.world.poi.PointOfInterestType;
|
import net.minecraft.world.poi.PointOfInterestType;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.apache.logging.log4j.core.jmx.Server;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -161,6 +150,15 @@ public class Szar implements ModInitializer {
|
|||||||
.dimensions(EntityDimensions.fixed(0.6F, 1.8F)) // player-sized
|
.dimensions(EntityDimensions.fixed(0.6F, 1.8F)) // player-sized
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
public static final EntityType<KidEntity> Kid =
|
||||||
|
Registry.register(
|
||||||
|
Registries.ENTITY_TYPE,
|
||||||
|
new Identifier(MOD_ID, "kid"),
|
||||||
|
FabricEntityTypeBuilder.create(SpawnGroup.CREATURE,
|
||||||
|
KidEntity::new) // ✅ matches EntityType<KidEntity>
|
||||||
|
.dimensions(EntityDimensions.fixed(0.6F, 1.8F))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
public static final EntityType<EpsteinEntity> EpsteinEntityType =
|
public static final EntityType<EpsteinEntity> EpsteinEntityType =
|
||||||
Registry.register(
|
Registry.register(
|
||||||
Registries.ENTITY_TYPE,
|
Registries.ENTITY_TYPE,
|
||||||
@@ -284,6 +282,7 @@ public class Szar implements ModInitializer {
|
|||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
private final Map<UUID, BlockPos> sleepingPlayers = new HashMap<>();
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
ServerCosmetics.init();
|
ServerCosmetics.init();
|
||||||
@@ -435,6 +434,10 @@ public class Szar implements ModInitializer {
|
|||||||
NiggerEntityType,
|
NiggerEntityType,
|
||||||
NiggerEntity.createAttributes()
|
NiggerEntity.createAttributes()
|
||||||
);
|
);
|
||||||
|
FabricDefaultAttributeRegistry.register(
|
||||||
|
Kid,
|
||||||
|
KidEntity.createAttributes()
|
||||||
|
);
|
||||||
FabricDefaultAttributeRegistry.register(
|
FabricDefaultAttributeRegistry.register(
|
||||||
EpsteinEntityType,
|
EpsteinEntityType,
|
||||||
NiggerEntity.createAttributes()
|
NiggerEntity.createAttributes()
|
||||||
@@ -588,7 +591,20 @@ public class Szar implements ModInitializer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
ServerTickEvents.END_SERVER_TICK.register(server -> {
|
||||||
|
for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) {
|
||||||
|
if (player.isSleeping()) {
|
||||||
|
BlockPos bedPos = player.getSleepingPosition().orElse(null);
|
||||||
|
if (bedPos != null && !sleepingPlayers.containsKey(player.getUuid())) {
|
||||||
|
sleepingPlayers.put(player.getUuid(), bedPos);
|
||||||
|
checkSleepPairs(server, player, bedPos);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// remove on wakeup
|
||||||
|
sleepingPlayers.remove(player.getUuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
public static final StructurePieceType TNT_OBELISK_PIECE =
|
public static final StructurePieceType TNT_OBELISK_PIECE =
|
||||||
Registry.register(
|
Registry.register(
|
||||||
@@ -635,6 +651,10 @@ public class Szar implements ModInitializer {
|
|||||||
new Identifier(MOD_ID, "radiation"),
|
new Identifier(MOD_ID, "radiation"),
|
||||||
new RadiationStatusEffect()
|
new RadiationStatusEffect()
|
||||||
);
|
);
|
||||||
|
public static final StatusEffect PREGNANT =
|
||||||
|
Registry.register(Registries.STATUS_EFFECT,
|
||||||
|
new Identifier(Szar.MOD_ID, "pregnant"),
|
||||||
|
new PregnantEffect());
|
||||||
public static final RegistryKey<DamageType> RADIATION_DAMAGE =
|
public static final RegistryKey<DamageType> RADIATION_DAMAGE =
|
||||||
RegistryKey.of(RegistryKeys.DAMAGE_TYPE, new Identifier(MOD_ID, "radiation"));
|
RegistryKey.of(RegistryKeys.DAMAGE_TYPE, new Identifier(MOD_ID, "radiation"));
|
||||||
public static final Item AK_AMMO = Registry.register(
|
public static final Item AK_AMMO = Registry.register(
|
||||||
@@ -1105,5 +1125,36 @@ public class Szar implements ModInitializer {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static final Map<UUID, UUID> pregnantPartners = new HashMap<>();
|
||||||
|
private void checkSleepPairs(MinecraftServer server, ServerPlayerEntity sleeper, BlockPos bedPos) {
|
||||||
|
double maxDist = 2.0;
|
||||||
|
|
||||||
|
for (ServerPlayerEntity other : server.getPlayerManager().getPlayerList()) {
|
||||||
|
if (other == sleeper) continue;
|
||||||
|
|
||||||
|
if (other.isSleeping()) {
|
||||||
|
BlockPos otherPos = other.getSleepingPosition().orElse(null);
|
||||||
|
if (otherPos != null && otherPos.isWithinDistance(bedPos, maxDist)) {
|
||||||
|
|
||||||
|
// Determine who is holding the special item
|
||||||
|
if (isHoldingSpecial(sleeper)) {
|
||||||
|
// The OTHER player gets the effect
|
||||||
|
givePregnantEffect(other, sleeper);
|
||||||
|
} else if (isHoldingSpecial(other)) {
|
||||||
|
givePregnantEffect(sleeper, other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isHoldingSpecial(ServerPlayerEntity p) {
|
||||||
|
return p.getMainHandStack().getItem() == FASZITEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void givePregnantEffect(ServerPlayerEntity player, ServerPlayerEntity partner) {
|
||||||
|
player.addStatusEffect(new StatusEffectInstance(PREGNANT, 20 * 60 * 20, 0, false, false, true));
|
||||||
|
pregnantPartners.put(player.getUuid(), partner.getUuid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,5 +70,7 @@
|
|||||||
"death.attack.radiation": "%1$s radiated away",
|
"death.attack.radiation": "%1$s radiated away",
|
||||||
"death.attack.radiation.player": "%1$s was lethally irradiated by %2$s",
|
"death.attack.radiation.player": "%1$s was lethally irradiated by %2$s",
|
||||||
"entity.szar.merl": "Merl",
|
"entity.szar.merl": "Merl",
|
||||||
"item.szar.merl_spawn_egg": "Merl Spawn Egg"
|
"item.szar.merl_spawn_egg": "Merl Spawn Egg",
|
||||||
|
"effect.szar.pregnant": "Pregnant",
|
||||||
|
"entity.szar.kid": "Kid"
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
src/main/resources/assets/szar/textures/mob_effect/pregnant.png
Normal file
BIN
src/main/resources/assets/szar/textures/mob_effect/pregnant.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Reference in New Issue
Block a user