diff --git a/gradle.properties b/gradle.properties index 3287362..b5c6297 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.20.1 yarn_mappings=1.20.1+build.10 loader_version=0.18.3 # Mod Properties -mod_version=26.2.23 +mod_version=26.2.23.1 maven_group=dev.tggamesyt archives_base_name=szar # Dependencies diff --git a/src/client/java/dev/tggamesyt/szar/client/HybridSkinManager.java b/src/client/java/dev/tggamesyt/szar/client/HybridSkinManager.java new file mode 100644 index 0000000..2c5131d --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/HybridSkinManager.java @@ -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 CACHE = new HashMap<>(); + private static final Map 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 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); + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/KidRenderer.java b/src/client/java/dev/tggamesyt/szar/client/KidRenderer.java new file mode 100644 index 0000000..ce3f61e --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/KidRenderer.java @@ -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> { + + 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); + } +} \ No newline at end of file diff --git a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java index 853ea6d..364dca6 100644 --- a/src/client/java/dev/tggamesyt/szar/client/SzarClient.java +++ b/src/client/java/dev/tggamesyt/szar/client/SzarClient.java @@ -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 diff --git a/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerModelMixin.java b/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerModelMixin.java new file mode 100644 index 0000000..d401510 --- /dev/null +++ b/src/client/java/dev/tggamesyt/szar/client/mixin/PlayerModelMixin.java @@ -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 { + + @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; + } + } +} \ No newline at end of file diff --git a/src/client/resources/szar.client.mixins.json b/src/client/resources/szar.client.mixins.json index c67f807..5c5c0e9 100644 --- a/src/client/resources/szar.client.mixins.json +++ b/src/client/resources/szar.client.mixins.json @@ -9,7 +9,8 @@ "RadiatedItemRendererMixin", "SplashOverlayMixin", "TGnameMixin", - "TGcapeMixin" + "TGcapeMixin", + "PlayerModelMixin" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/java/dev/tggamesyt/szar/KidEntity.java b/src/main/java/dev/tggamesyt/szar/KidEntity.java new file mode 100644 index 0000000..9053eee --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/KidEntity.java @@ -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 AGE = + DataTracker.registerData(KidEntity.class, TrackedDataHandlerRegistry.INTEGER); + private static final TrackedData> PARENT_A = + DataTracker.registerData(KidEntity.class, TrackedDataHandlerRegistry.OPTIONAL_UUID); + private static final TrackedData> PARENT_B = + DataTracker.registerData(KidEntity.class, TrackedDataHandlerRegistry.OPTIONAL_UUID); + + public static final int MAX_AGE = 360000; // 30 days * 10h + + public KidEntity(EntityType 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")); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/KidGoToBedGoal.java b/src/main/java/dev/tggamesyt/szar/KidGoToBedGoal.java new file mode 100644 index 0000000..93c9539 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/KidGoToBedGoal.java @@ -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; + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/PregnantEffect.java b/src/main/java/dev/tggamesyt/szar/PregnantEffect.java new file mode 100644 index 0000000..52a3796 --- /dev/null +++ b/src/main/java/dev/tggamesyt/szar/PregnantEffect.java @@ -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); + } +} \ No newline at end of file diff --git a/src/main/java/dev/tggamesyt/szar/Szar.java b/src/main/java/dev/tggamesyt/szar/Szar.java index 70ca0ab..49a9f01 100644 --- a/src/main/java/dev/tggamesyt/szar/Szar.java +++ b/src/main/java/dev/tggamesyt/szar/Szar.java @@ -1,20 +1,16 @@ package dev.tggamesyt.szar; import com.google.common.collect.ImmutableSet; -import com.mojang.serialization.Codec; -import dev.tggamesyt.szar.PlaneAnimation; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.biome.v1.BiomeModifications; 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.ServerTickEvents; 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.itemgroup.v1.FabricItemGroup; import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent; 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.object.builder.v1.block.FabricBlockSettings; 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.TrackedDataHandlerRegistry; import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.entity.passive.VillagerEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.*; @@ -44,21 +41,16 @@ import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvent; import net.minecraft.sound.SoundEvents; 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.text.Text; import net.minecraft.util.ActionResult; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.Rarity; -import net.minecraft.util.collection.DataPool; -import net.minecraft.util.math.Box; -import net.minecraft.util.math.random.Random; +import net.minecraft.util.math.BlockPos; import net.minecraft.village.TradeOffer; import net.minecraft.village.VillagerProfession; import net.minecraft.world.Heightmap; -import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.BiomeKeys; import net.minecraft.world.gen.GenerationStep; 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.HeightRangePlacementModifier; 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.poi.PointOfInterestType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.core.jmx.Server; import java.util.HashMap; import java.util.List; @@ -161,6 +150,15 @@ public class Szar implements ModInitializer { .dimensions(EntityDimensions.fixed(0.6F, 1.8F)) // player-sized .build() ); + public static final EntityType Kid = + Registry.register( + Registries.ENTITY_TYPE, + new Identifier(MOD_ID, "kid"), + FabricEntityTypeBuilder.create(SpawnGroup.CREATURE, + KidEntity::new) // ✅ matches EntityType + .dimensions(EntityDimensions.fixed(0.6F, 1.8F)) + .build() + ); public static final EntityType EpsteinEntityType = Registry.register( Registries.ENTITY_TYPE, @@ -284,6 +282,7 @@ public class Szar implements ModInitializer { }) .build() ); + private final Map sleepingPlayers = new HashMap<>(); @Override public void onInitialize() { ServerCosmetics.init(); @@ -435,6 +434,10 @@ public class Szar implements ModInitializer { NiggerEntityType, NiggerEntity.createAttributes() ); + FabricDefaultAttributeRegistry.register( + Kid, + KidEntity.createAttributes() + ); FabricDefaultAttributeRegistry.register( EpsteinEntityType, 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 = Registry.register( @@ -635,6 +651,10 @@ public class Szar implements ModInitializer { new Identifier(MOD_ID, "radiation"), new RadiationStatusEffect() ); + public static final StatusEffect PREGNANT = + Registry.register(Registries.STATUS_EFFECT, + new Identifier(Szar.MOD_ID, "pregnant"), + new PregnantEffect()); public static final RegistryKey RADIATION_DAMAGE = RegistryKey.of(RegistryKeys.DAMAGE_TYPE, new Identifier(MOD_ID, "radiation")); public static final Item AK_AMMO = Registry.register( @@ -1105,5 +1125,36 @@ public class Szar implements ModInitializer { )); } } + public static final Map 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()); + } } diff --git a/src/main/resources/assets/szar/lang/en_us.json b/src/main/resources/assets/szar/lang/en_us.json index 571a36f..a55f100 100644 --- a/src/main/resources/assets/szar/lang/en_us.json +++ b/src/main/resources/assets/szar/lang/en_us.json @@ -70,5 +70,7 @@ "death.attack.radiation": "%1$s radiated away", "death.attack.radiation.player": "%1$s was lethally irradiated by %2$s", "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" } diff --git a/src/main/resources/assets/szar/textures/mob_effect/pregnant.png b/src/main/resources/assets/szar/textures/mob_effect/pregnant.png new file mode 100644 index 0000000..31df621 Binary files /dev/null and b/src/main/resources/assets/szar/textures/mob_effect/pregnant.png differ