Compare commits

..

6 Commits

49 changed files with 1128 additions and 10 deletions

View File

@ -3,7 +3,7 @@ plugins {
id 'maven-publish'
}
version = "0.0.2"
version = "0.0.3"
group = "net.Chipperfluff"
repositories {

10
chipper
View File

@ -67,6 +67,16 @@ inject_options() {
if [[ -f "./config/servers.dat" ]]; then
cp "./config/servers.dat" "$dir/servers.dat"
fi
# server.properties
if [[ -f "./config/server.properties" ]]; then
cp "./config/server.properties" "$dir/server.properties"
fi
# server icon
if [[ -f "./config/server-icon.png" ]]; then
cp "./config/server-icon.png" "$dir/server-icon.png"
fi
}
ensure_server_files() {

BIN
config/server-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

58
config/server.properties Normal file
View File

@ -0,0 +1,58 @@
#Minecraft server properties
#Mon Dec 22 17:38:51 CET 2025
allow-flight=false
allow-nether=true
broadcast-console-to-ops=true
broadcast-rcon-to-ops=true
difficulty=easy
enable-command-block=true
enable-jmx-monitoring=false
enable-query=false
enable-rcon=false
enable-status=true
enforce-secure-profile=true
enforce-whitelist=false
entity-broadcast-range-percentage=100
force-gamemode=false
function-permission-level=2
gamemode=survival
generate-structures=true
generator-settings={}
hardcore=false
hide-online-players=false
initial-disabled-packs=
initial-enabled-packs=vanilla,fabric
level-name=world
level-seed=
level-type=minecraft\:normal
max-chained-neighbor-updates=1000000
max-players=20
max-tick-time=60000
max-world-size=29999984
motd=§6chipi §edev §7server\n§8local test instance
network-compression-threshold=256
online-mode=false
op-permission-level=4
player-idle-timeout=0
prevent-proxy-connections=false
pvp=true
query.port=25565
rate-limit=0
rcon.password=
rcon.port=25575
require-resource-pack=false
resource-pack=
resource-pack-prompt=
resource-pack-sha1=
server-ip=
server-port=25565
simulation-distance=10
spawn-animals=true
spawn-monsters=true
spawn-npcs=true
spawn-protection=16
sync-chunk-writes=true
text-filtering-config=
use-native-transport=true
view-distance=10
white-list=false

View File

@ -9,6 +9,7 @@ import net.Chipperfluff.chipi.effect.ModEffects;
import net.Chipperfluff.chipi.entity.ModEntities;
import net.Chipperfluff.chipi.entity.SpawnLogic;
import net.Chipperfluff.chipi.entity.MepEntity;
import net.Chipperfluff.chipi.entity.PlayerJrEntity;
import net.Chipperfluff.chipi.item.ModItemGroups;
import net.Chipperfluff.chipi.item.ModItems;
import net.Chipperfluff.chipi.server.ChipiServerEvents;
@ -16,6 +17,7 @@ import net.Chipperfluff.chipi.sound.ModSounds;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.object.builder.v1.entity.FabricDefaultAttributeRegistry;
import net.Chipperfluff.chipi.util.TickScheduler;
import net.Chipperfluff.chipi.item.music.ModMusicDiscs;
public class ChipiMod implements ModInitializer {
@ -35,12 +37,15 @@ public class ChipiMod implements ModInitializer {
SpawnLogic.register();
ModSounds.register();
ModMusicDiscs.registerAll();
TickScheduler.init();
CommandHandler.register();
FabricDefaultAttributeRegistry.register(ModEntities.MEP, MepEntity.createMepAttributes());
FabricDefaultAttributeRegistry.register(ModEntities.PLAYER_JR, PlayerJrEntity.createAttributes());
ChipiServerEvents.register();
}
}

View File

@ -7,5 +7,6 @@ public class ModEntityRenderers {
public static void register() {
EntityRendererRegistry.register(ModEntities.MEP, MepRenderer::new);
EntityRendererRegistry.register(ModEntities.PLAYER_JR, PlayerJrRenderer::new);
}
}

View File

@ -0,0 +1,53 @@
package net.Chipperfluff.chipi.client.entity;
import net.Chipperfluff.chipi.entity.PlayerJrEntity;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.BipedEntityRenderer;
import net.minecraft.client.render.entity.EntityRendererFactory;
import net.minecraft.client.render.entity.model.BipedEntityModel;
import net.minecraft.client.render.entity.model.EntityModelLayers;
import net.minecraft.client.util.DefaultSkinHelper;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
public class PlayerJrRenderer
extends BipedEntityRenderer<PlayerJrEntity, BipedEntityModel<PlayerJrEntity>> {
public PlayerJrRenderer(EntityRendererFactory.Context ctx) {
super(ctx, new BipedEntityModel<>(ctx.getPart(EntityModelLayers.PLAYER)), 0.3f);
}
@Override
public void render(
PlayerJrEntity entity,
float yaw,
float tickDelta,
MatrixStack matrices,
VertexConsumerProvider vertices,
int light
) {
matrices.scale(0.5f, 0.5f, 0.5f);
super.render(entity, yaw, tickDelta, matrices, vertices, light);
}
@Override
public Identifier getTexture(PlayerJrEntity entity) {
String dadName = entity.getDadName();
if (dadName == null || dadName.isBlank()) {
return DefaultSkinHelper.getTexture();
}
/*
* THIS is how vanilla + mods do it:
* - stable Identifier
* - async skin download
* - cached by SkinProvider
*/
Identifier skin = AbstractClientPlayerEntity.getSkinId(dadName);
AbstractClientPlayerEntity.loadSkin(skin, dadName);
return skin;
}
}

View File

@ -12,6 +12,7 @@ public final class CommandHandler {
(dispatcher, registryAccess, environment) -> {
ChpCommand.register(dispatcher);
CspCommand.register(dispatcher);
SpawnJrCommand.register(dispatcher);
}
);
}

View File

@ -0,0 +1,89 @@
package net.Chipperfluff.chipi.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.context.CommandContext;
import net.Chipperfluff.chipi.entity.ModEntities;
import net.Chipperfluff.chipi.entity.PlayerJrEntity;
import net.minecraft.command.argument.EntityArgumentType;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
public final class SpawnJrCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher) {
dispatcher.register(
CommandManager.literal("spawnjr")
.requires(src -> src.hasPermissionLevel(2))
.then(
CommandManager.argument("dad", EntityArgumentType.player())
.executes(SpawnJrCommand::execute)
)
);
}
private static int execute(CommandContext<ServerCommandSource> ctx) {
ServerCommandSource source = ctx.getSource();
System.out.println("[spawnjr] ================================");
System.out.println("[spawnjr] Command invoked");
try {
ServerWorld world = source.getWorld();
ServerPlayerEntity dad = EntityArgumentType.getPlayer(ctx, "dad");
ServerPlayerEntity spawner = source.getPlayerOrThrow();
BlockPos pos = spawner.getBlockPos();
System.out.println("[spawnjr] Dad = " + dad.getName().getString());
System.out.println("[spawnjr] Dad UUID = " + dad.getUuidAsString());
System.out.println("[spawnjr] World = " + world.getRegistryKey().getValue());
System.out.println("[spawnjr] Spawn pos = " + pos);
PlayerJrEntity jr = new PlayerJrEntity(ModEntities.PLAYER_JR, world);
System.out.println("[spawnjr] Entity constructed");
jr.refreshPositionAndAngles(
pos.getX() + 0.5,
pos.getY(),
pos.getZ() + 0.5,
world.random.nextFloat() * 360f,
0f
);
// single source of truth
jr.setDad(dad);
System.out.println("[spawnjr] Dad relationship set");
boolean spawned = world.spawnEntity(jr);
System.out.println("[spawnjr] spawnEntity() returned = " + spawned);
if (!spawned) {
source.sendError(Text.literal("[spawnjr] Spawn failed (entity rejected by world)"));
return 0;
}
System.out.println("[spawnjr] SUCCESS");
System.out.println("[spawnjr] ================================");
return 1;
} catch (Exception e) {
System.out.println("[spawnjr] ================================");
System.out.println("[spawnjr] CRASH");
System.out.println("[spawnjr] hey message Chipperfluff");
System.out.println("[spawnjr] Exception: " + e.getClass().getName());
System.out.println("[spawnjr] Message: " + e.getMessage());
e.printStackTrace();
source.sendError(Text.literal(
"[spawnjr] Internal error. Check logs. (hey message Chipperfluff)"
));
return 0;
}
}
}

View File

@ -9,8 +9,19 @@ import net.minecraft.util.Identifier;
public class ModEffects {
public static final StatusEffect CHIPI_BLESSING = new ChipiBlessingEffect();
public static final StatusEffect PREGNANT = new PregnantEffect();
public static void register() {
Registry.register(Registries.STATUS_EFFECT, new Identifier(ChipiMod.MOD_ID, "chipi_blessing"), CHIPI_BLESSING);
Registry.register(
Registries.STATUS_EFFECT,
new Identifier(ChipiMod.MOD_ID, "chipi_blessing"),
CHIPI_BLESSING
);
Registry.register(
Registries.STATUS_EFFECT,
new Identifier(ChipiMod.MOD_ID, "pregnant"),
PREGNANT
);
}
}

View File

@ -0,0 +1,127 @@
package net.Chipperfluff.chipi.effect;
import net.Chipperfluff.chipi.entity.ModEntities;
import net.Chipperfluff.chipi.entity.PlayerJrEntity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.attribute.EntityAttributeInstance;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.attribute.EntityAttributeModifier;
import net.minecraft.entity.effect.StatusEffect;
import net.minecraft.entity.effect.StatusEffectCategory;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import java.util.UUID;
public class PregnantEffect extends StatusEffect {
public static final int TOTAL_DURATION = 20 * 60 * 10; // 10 min
private static final double MAX_SLOW = 0.90;
private static final UUID SPEED_UUID =
UUID.fromString("7c8b6b8f-4b6c-4f6f-9b7b-01c8e9f8a111");
public PregnantEffect() {
super(StatusEffectCategory.HARMFUL, 0xFFB6C1);
}
/* ---------- helpers used by mixin ---------- */
/** 0.0 → normal jump, 1.0 → no jump */
public static double getJumpClamp(ServerPlayerEntity player) {
StatusEffectInstance inst = player.getStatusEffect(ModEffects.PREGNANT);
if (inst == null) return 0.0;
double progress =
1.0 - (inst.getDuration() / (double) TOTAL_DURATION);
return Math.min(Math.max(progress, 0.0), 1.0);
}
/* ---------- ticking ---------- */
@Override
public boolean canApplyUpdateEffect(int duration, int amplifier) {
return true;
}
@Override
public void applyUpdateEffect(LivingEntity entity, int amplifier) {
if (!(entity instanceof ServerPlayerEntity player)) return;
StatusEffectInstance inst = player.getStatusEffect(this);
if (inst == null) return;
int remaining = inst.getDuration();
double progress =
1.0 - (remaining / (double) TOTAL_DURATION);
progress = Math.min(Math.max(progress, 0.0), 1.0);
applySpeedModifier(player, progress * MAX_SLOW);
if (remaining % 400 == 0 && remaining > 20) {
player.sendMessage(
Text.literal("Your knees file a formal complaint…"),
true
);
}
if (remaining == 1) {
finishBirth(player);
}
}
private void applySpeedModifier(ServerPlayerEntity player, double slowAmount) {
EntityAttributeInstance attr =
player.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED);
if (attr == null) return;
EntityAttributeModifier old = attr.getModifier(SPEED_UUID);
if (old != null) attr.removeModifier(old);
if (slowAmount > 0.01) {
attr.addTemporaryModifier(new EntityAttributeModifier(
SPEED_UUID,
"Pregnancy slowdown",
-slowAmount,
EntityAttributeModifier.Operation.MULTIPLY_TOTAL
));
}
}
private void finishBirth(ServerPlayerEntity dad) {
// cleanup
EntityAttributeInstance attr =
dad.getAttributeInstance(EntityAttributes.GENERIC_MOVEMENT_SPEED);
if (attr != null) {
EntityAttributeModifier old = attr.getModifier(SPEED_UUID);
if (old != null) attr.removeModifier(old);
}
dad.removeStatusEffect(this);
PlayerJrEntity jr = ModEntities.PLAYER_JR.create(dad.getWorld());
if (jr == null) return;
jr.refreshPositionAndAngles(
dad.getX(), dad.getY(), dad.getZ(),
dad.getYaw(), dad.getPitch()
);
jr.setDad(dad);
dad.getWorld().spawnEntity(jr);
dad.sendMessage(
Text.literal(
"You're a dad now. The names " +
jr.getName().getString() +
". Warning: it might cheat."
),
false
);
}
}

View File

@ -23,6 +23,16 @@ public final class ModEntities {
.build()
);
public static final EntityType<PlayerJrEntity> PLAYER_JR =
Registry.register(
Registries.ENTITY_TYPE,
new Identifier("chipi", "player_jr"),
FabricEntityTypeBuilder
.create(SpawnGroup.CREATURE, PlayerJrEntity::new)
.dimensions(EntityDimensions.fixed(0.45f, 0.9f)) // small
.build()
);
private ModEntities() {
}
}

View File

@ -0,0 +1,389 @@
package net.Chipperfluff.chipi.entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.ai.goal.Goal;
import net.minecraft.entity.ai.goal.LookAroundGoal;
import net.minecraft.entity.ai.goal.LookAtEntityGoal;
import net.minecraft.entity.ai.goal.SwimGoal;
import net.minecraft.entity.attribute.DefaultAttributeContainer;
import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.mob.MobEntity;
import net.minecraft.entity.mob.PathAwareEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import java.util.UUID;
public class PlayerJrEntity extends PathAwareEntity {
/* ================= RULES (CHEATING FRIEND) ================= */
private static final double FORCE_FLY_DISTANCE = 50.0; // fly if farther than this (horizontal)
private static final int STUCK_TICKS_TO_FLY = 40; // 2 seconds of no horizontal progress
private static final double STUCK_MIN_CLOSING = 0.10; // horizontal progress threshold
private static final double STUCK_MIN_DISTANCE = 6.0; // don't care if already close
private static final double DAD_MOVING_EPS_SQ = 0.02; // if dad is moving, don't call it stuck
private static final int LAND_RADIUS = 6; // search dry landing within this radius
private static final double LAND_HORIZONTAL_DISTANCE = 2.0; // must be this close (horiz) to land
private static final double HOVER_HEIGHT = 1.25; // hover above landing block
private static final int MIN_FLY_TICKS = 20; // must fly at least this long
private static final double FLY_SPEED = 0.55;
private static final double FLY_ACCEL = 0.25;
private static final int MAX_VERTICAL_GAP = 6; // if dad is this high above -> fly
/* ================= STATE ================= */
private UUID dadUuid;
private String dadName = "";
private enum Mode { GROUND, FLYING }
private Mode mode = Mode.GROUND;
private int flyTicks = 0;
// Stuck tracking (ground)
private int stuckTicks = 0;
private double lastDadHorizDist = -1;
// Landing / hover state
private BlockPos landingSpot = null;
private BlockPos forcedLandingSpot = null;
/* ================= CONSTRUCTOR ================= */
public PlayerJrEntity(EntityType<? extends PathAwareEntity> type, World world) {
super(type, world);
}
public static DefaultAttributeContainer.Builder createAttributes() {
return MobEntity.createMobAttributes()
.add(EntityAttributes.GENERIC_MAX_HEALTH, 10.0)
.add(EntityAttributes.GENERIC_MOVEMENT_SPEED, 0.25)
.add(EntityAttributes.GENERIC_FOLLOW_RANGE, 64.0);
}
/* ================= DAD ================= */
public void setDad(ServerPlayerEntity dad) {
dadUuid = dad.getUuid();
dadName = dad.getGameProfile().getName();
setCustomName(Text.literal(dadName + " jr."));
setCustomNameVisible(true);
}
private ServerPlayerEntity getDad() {
if (dadUuid == null || getWorld().isClient) return null;
return getWorld().getServer().getPlayerManager().getPlayer(dadUuid);
}
public String getDadName() { return dadName; }
/* ================= AI ================= */
@Override
protected void initGoals() {
goalSelector.add(1, new SwimGoal(this));
goalSelector.add(2, new FollowDadGoal(this));
goalSelector.add(3, new LookAtEntityGoal(this, PlayerEntity.class, 6f));
goalSelector.add(4, new LookAroundGoal(this));
}
/* ================= TICK ================= */
@Override
public void tick() {
super.tick();
if (getWorld().isClient) return;
// Never fall damage ever (you asked this specifically)
this.fallDistance = 0.0f;
ServerPlayerEntity dad = getDad();
if (dad == null) return;
switch (mode) {
case GROUND -> doGroundBrain(dad);
case FLYING -> doFlyingBrain(dad);
}
}
/* ================= CORE BRAIN: GROUND ================= */
private void doGroundBrain(ServerPlayerEntity dad) {
// Water cheat: if land is nearby, prefer walking out; if no path, fly briefly to land.
if (this.isTouchingWater()) {
BlockPos escape = findLandingSpotNear(this.getBlockPos(), LAND_RADIUS);
if (escape != null) {
boolean canWalk = getNavigation().startMovingTo(
escape.getX() + 0.5,
escape.getY(),
escape.getZ() + 0.5,
1.1
);
if (canWalk) {
resetStuckTracking(dad);
return;
}
forcedLandingSpot = escape;
startFlying(dad);
return;
}
}
// Update stuck tracking (only on ground mode)
updateStuckTracking(dad);
if (stuckTicks >= STUCK_TICKS_TO_FLY) {
startFlying(dad);
return;
}
// Too far behind (impatient cheat)
if (horizontalDistanceTo(dad) > FORCE_FLY_DISTANCE) {
startFlying(dad);
return;
}
// Dad is way above us (can't reach)
double dy = dad.getY() - getY();
if (dy > MAX_VERTICAL_GAP && horizontalDistanceTo(dad) > 2.0) {
startFlying(dad);
}
}
private void updateStuckTracking(ServerPlayerEntity dad) {
boolean tryingToWalk = !getNavigation().isIdle();
double dadDist = horizontalDistanceTo(dad);
double dadSpeedSq = dad.getVelocity().horizontalLengthSquared();
if (!tryingToWalk || dadDist <= STUCK_MIN_DISTANCE || dadSpeedSq > DAD_MOVING_EPS_SQ) {
stuckTicks = 0;
lastDadHorizDist = dadDist;
return;
}
if (lastDadHorizDist < 0) {
lastDadHorizDist = dadDist;
stuckTicks = 0;
return;
}
double closing = lastDadHorizDist - dadDist; // positive means getting closer
if (closing < STUCK_MIN_CLOSING) stuckTicks++;
else stuckTicks = 0;
lastDadHorizDist = dadDist;
}
private void resetStuckTracking(ServerPlayerEntity dad) {
stuckTicks = 0;
lastDadHorizDist = horizontalDistanceTo(dad);
}
/* ================= CORE BRAIN: FLYING ================= */
private void startFlying(ServerPlayerEntity dad) {
mode = Mode.FLYING;
flyTicks = 0;
landingSpot = null;
setNoGravity(true);
getNavigation().stop();
setVelocity(Vec3d.ZERO);
velocityDirty = true;
dad.sendMessage(Text.literal(getName().getString() + " switched to Game Mode Creative"), false);
}
private void doFlyingBrain(ServerPlayerEntity dad) {
flyTicks++;
double horiz = horizontalDistanceTo(dad);
if (flyTicks >= MIN_FLY_TICKS) {
BlockPos candidate = forcedLandingSpot;
if (candidate == null) {
candidate = findLandingSpotNear(dad.getBlockPos(), LAND_RADIUS);
}
if (candidate != null && !wouldImmediatelyRefly(dad, candidate)) {
landingSpot = candidate;
if (horizontalDistanceTo(landingSpot) <= LAND_HORIZONTAL_DISTANCE) {
stopFlying(dad);
return;
}
}
}
BlockPos targetSpot = forcedLandingSpot != null ? forcedLandingSpot : landingSpot;
Vec3d target = targetSpot != null
? Vec3d.ofCenter(targetSpot).add(0, HOVER_HEIGHT, 0)
: dad.getPos().add(0, 2.2, 0);
flyTowardSmooth(target);
lookAtEntity(dad, 30f, 30f);
}
private void stopFlying(ServerPlayerEntity dad) {
setNoGravity(false);
mode = Mode.GROUND;
landingSpot = null;
forcedLandingSpot = null;
dad.sendMessage(Text.literal(getName().getString() + " switched to Game Mode Survival"), false);
}
/* ================= MOVEMENT HELPERS ================= */
private void flyTowardSmooth(Vec3d target) {
Vec3d delta = target.subtract(getPos());
double distSq = delta.lengthSquared();
if (distSq < 0.0006) {
setVelocity(getVelocity().multiply(0.6));
velocityDirty = true;
return;
}
Vec3d desired = delta.normalize().multiply(FLY_SPEED);
// stronger steer when far (prevents drifting)
double accel = (distSq > 64.0) ? Math.min(0.55, FLY_ACCEL * 2.0) : FLY_ACCEL;
Vec3d blended = getVelocity().multiply(1.0 - accel).add(desired.multiply(accel));
setVelocity(blended);
velocityDirty = true;
}
private double horizontalDistanceTo(ServerPlayerEntity dad) {
double dx = dad.getX() - getX();
double dz = dad.getZ() - getZ();
return Math.sqrt(dx * dx + dz * dz);
}
private double horizontalDistanceTo(BlockPos pos) {
double dx = pos.getX() + 0.5 - getX();
double dz = pos.getZ() + 0.5 - getZ();
return Math.sqrt(dx * dx + dz * dz);
}
/* ================= LAND SEARCH (DRY + SAFE) ================= */
private BlockPos findLandingSpotNear(BlockPos center, int radius) {
BlockPos best = null;
double bestScore = Double.MAX_VALUE;
int minY = center.getY() - 3;
int maxY = center.getY() + 3;
for (int dx = -radius; dx <= radius; dx++) {
for (int dz = -radius; dz <= radius; dz++) {
if ((dx * dx + dz * dz) > radius * radius) continue;
for (int y = minY; y <= maxY; y++) {
BlockPos p = new BlockPos(center.getX() + dx, y, center.getZ() + dz);
if (!canStandAt(p)) continue;
// prefer closer to dad AND closer to us a bit
double toCenter = p.getSquaredDistance(center);
double toMe = this.getPos().squaredDistanceTo(Vec3d.ofCenter(p));
double score = toCenter * 1.0 + toMe * 0.10;
if (score < bestScore) {
bestScore = score;
best = p;
}
}
}
}
return best;
}
private boolean canStandAt(BlockPos pos) {
World w = getWorld();
// feet + head air
if (!w.getBlockState(pos).isAir()) return false;
if (!w.getBlockState(pos.up()).isAir()) return false;
// solid below
BlockPos below = pos.down();
if (!w.getBlockState(below).isSolidBlock(w, below)) return false;
// not in fluid, not standing on fluid
if (!w.getFluidState(pos).isEmpty()) return false;
if (!w.getFluidState(below).isEmpty()) return false;
// also avoid lava blocks around feet area (simple dont be stupid)
// (fluid check already catches lava fluid; this catches blocks like magma? optional)
return true;
}
private boolean wouldImmediatelyRefly(ServerPlayerEntity dad, BlockPos landPos) {
if (horizontalDistanceTo(dad) > FORCE_FLY_DISTANCE) return true;
if (dad.getY() - landPos.getY() > MAX_VERTICAL_GAP) return true;
return false;
}
/* ================= FALL DAMAGE ================= */
@Override
public boolean handleFallDamage(float fallDistance, float damageMultiplier, DamageSource source) {
return false;
}
/* ================= SAVE / LOAD ================= */
@Override
public void writeCustomDataToNbt(NbtCompound nbt) {
if (dadUuid != null) nbt.putUuid("Dad", dadUuid);
nbt.putString("DadName", dadName);
nbt.putString("Mode", mode.name());
nbt.putInt("FlyTicks", flyTicks);
}
@Override
public void readCustomDataFromNbt(NbtCompound nbt) {
if (nbt.containsUuid("Dad")) dadUuid = nbt.getUuid("Dad");
if (nbt.contains("DadName")) dadName = nbt.getString("DadName");
if (nbt.contains("Mode")) {
mode = Mode.valueOf(nbt.getString("Mode"));
setNoGravity(mode != Mode.GROUND);
}
if (nbt.contains("FlyTicks")) flyTicks = nbt.getInt("FlyTicks");
// volatile reset
landingSpot = null;
forcedLandingSpot = null;
lastDadHorizDist = -1;
stuckTicks = 0;
}
/* ================= FOLLOW GOAL ================= */
static class FollowDadGoal extends Goal {
private final PlayerJrEntity jr;
FollowDadGoal(PlayerJrEntity jr) { this.jr = jr; }
@Override
public boolean canStart() {
return jr.mode == Mode.GROUND && jr.getDad() != null;
}
@Override
public void tick() {
ServerPlayerEntity dad = jr.getDad();
if (dad == null) return;
if (jr.squaredDistanceTo(dad) > 4) {
jr.getNavigation().startMovingTo(dad, 1.0);
}
}
}
}

View File

@ -2,6 +2,7 @@ package net.Chipperfluff.chipi.item;
import net.Chipperfluff.chipi.ChipiMod;
import net.Chipperfluff.chipi.block.ModBlocks;
import net.Chipperfluff.chipi.item.music.ModMusicDiscs;
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack;
@ -50,6 +51,10 @@ public class ModItemGroups {
entries.add(ModItems.CHIPPER_SHOVEL);
entries.add(ModItems.CHIPPER_HOE);
// Music discs
for (var disc : ModMusicDiscs.getAll().values()) {
entries.add(disc);
}
})
.build()
);

View File

@ -1,16 +1,20 @@
package net.Chipperfluff.chipi.item;
import net.Chipperfluff.chipi.effect.ModEffects;
import net.Chipperfluff.chipi.sound.ModSounds;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUsage;
import net.minecraft.item.Items;
import net.minecraft.util.UseAction;
import net.minecraft.world.World;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.Chipperfluff.chipi.effect.ModEffects;
import net.Chipperfluff.chipi.sound.ModSounds;
import net.minecraft.sound.SoundCategory;
import net.minecraft.util.Hand;
import net.minecraft.util.UseAction;
import net.minecraft.util.TypedActionResult;
import net.minecraft.world.World;
import net.minecraft.text.Text;
public class PlayerMilkItem extends Item {
@ -18,6 +22,30 @@ public class PlayerMilkItem extends Item {
super(settings);
}
/* ================= DENY DRINKING ================= */
@Override
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
ItemStack stack = user.getStackInHand(hand);
// Already pregnant deny drinking
if (user.hasStatusEffect(ModEffects.PREGNANT)) {
if (!world.isClient) {
user.sendMessage(
Text.literal("You already feel something growing..."),
true
);
}
return TypedActionResult.fail(stack);
}
// Start drinking normally
user.setCurrentHand(hand);
return TypedActionResult.consume(stack);
}
/* ================= DRINK ANIMATION ================= */
@Override
public UseAction getUseAction(ItemStack stack) {
return UseAction.DRINK;
@ -28,10 +56,23 @@ public class PlayerMilkItem extends Item {
return 32;
}
/* ================= FINISH DRINK ================= */
@Override
public ItemStack finishUsing(ItemStack stack, World world, LivingEntity user) {
if (!world.isClient && user instanceof PlayerEntity player) {
// Apply pregnancy effect
player.addStatusEffect(new StatusEffectInstance(
ModEffects.PREGNANT,
20 * 60 * 10, // 10 minutes
0,
false,
true
));
// Play sound
world.playSound(
null,
player.getBlockPos(),
@ -42,8 +83,10 @@ public class PlayerMilkItem extends Item {
);
}
if (user instanceof PlayerEntity player && !player.getAbilities().creativeMode) {
return new ItemStack(Items.BUCKET);
// CORRECT vanilla behavior:
// consumes milk and gives bucket
if (user instanceof PlayerEntity player) {
return ItemUsage.exchangeStack(stack, player, new ItemStack(Items.BUCKET));
}
return stack;

View File

@ -0,0 +1,67 @@
package net.Chipperfluff.chipi.item.music;
import net.Chipperfluff.chipi.ChipiMod;
import net.minecraft.item.Item;
import net.minecraft.item.MusicDiscItem;
import net.minecraft.registry.Registries;
import net.minecraft.registry.Registry;
import net.minecraft.sound.SoundEvent;
import net.minecraft.util.Identifier;
import java.util.LinkedHashMap;
import java.util.Map;
public class ModMusicDiscs {
private static final Map<String, Item> DISCS = new LinkedHashMap<>();
private static final MusicDiscDef[] DEFINITIONS = {
new MusicDiscDef("aa9", 7, 5, 0),
new MusicDiscDef("abc", 7, 2, 11),
new MusicDiscDef("fs", 7, 2, 0),
new MusicDiscDef("phone", 7, 2, 11),
new MusicDiscDef("wha", 7, 1, 48),
new MusicDiscDef("who", 7, 2, 27),
new MusicDiscDef("working_as_intented", 7, 3, 45)
};
private static Item registerDisc(MusicDiscDef def) {
String id = "chipi_record_" + def.name();
Identifier identifier = new Identifier(ChipiMod.MOD_ID, id);
SoundEvent sound = Registry.register(
Registries.SOUND_EVENT,
identifier,
SoundEvent.of(identifier)
);
Item item = Registry.register(
Registries.ITEM,
identifier,
new MusicDiscItem(
def.comparatorOutput(),
sound,
new Item.Settings().maxCount(1),
toTicks(def.minutes(), def.seconds())
)
);
DISCS.put(def.name(), item);
return item;
}
public static Map<String, Item> getAll() {
return DISCS;
}
public static void registerAll() {
for (MusicDiscDef def : DEFINITIONS) {
registerDisc(def);
}
}
private static int toTicks(int minutes, int seconds) {
int totalSeconds = Math.max(0, minutes) * 60 + Math.max(0, seconds);
return totalSeconds * 20;
}
}

View File

@ -0,0 +1,8 @@
package net.Chipperfluff.chipi.item.music;
public record MusicDiscDef(
String name,
int comparatorOutput,
int minutes,
int seconds
) {}

View File

@ -1,10 +1,15 @@
package net.Chipperfluff.chipi.mixin;
import com.mojang.authlib.GameProfile;
import net.Chipperfluff.chipi.effect.ModEffects;
import net.Chipperfluff.chipi.effect.PregnantEffect;
import net.Chipperfluff.chipi.util.ChipiTrackedData;
import net.minecraft.entity.data.DataTracker;
import net.minecraft.entity.effect.StatusEffectInstance;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@ -14,11 +19,63 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(PlayerEntity.class)
public abstract class PlayerEntityMixin {
/* ============================================================
INIT: tracked data
============================================================ */
@Inject(method = "<init>", at = @At("TAIL"))
private void chipi$initTrackedData(World world, BlockPos pos, float yaw, GameProfile profile, CallbackInfo ci) {
private void chipi$initTrackedData(
World world,
BlockPos pos,
float yaw,
GameProfile profile,
CallbackInfo ci
) {
PlayerEntity self = (PlayerEntity)(Object)this;
DataTracker tracker = self.getDataTracker();
tracker.startTracking(ChipiTrackedData.CHIPI_ENERGY, 1.0f);
}
/* ============================================================
JUMP CLAMP: pregnancy logic
============================================================ */
@Inject(
method = "jump",
at = @At("HEAD"),
cancellable = true
)
private void chipi$pregnancyJumpClamp(CallbackInfo ci) {
if (!((Object)this instanceof ServerPlayerEntity player)) return;
StatusEffectInstance inst =
player.getStatusEffect(ModEffects.PREGNANT);
if (inst == null) return; // normal jump
int total = PregnantEffect.TOTAL_DURATION;
int remaining = inst.getDuration();
// progress 0.0 1.0
double progress =
1.0 - ((double)remaining / total);
progress = Math.min(Math.max(progress, 0.0), 1.0);
// vanilla jump velocity 0.42
double maxJump = 0.42 * (1.0 - progress);
// End stage: NO jumping at all
if (maxJump < 0.05) {
ci.cancel();
return;
}
Vec3d vel = player.getVelocity();
player.setVelocity(vel.x, maxJump, vel.z);
player.velocityDirty = true;
// stop vanilla jump
ci.cancel();
}
}

View File

@ -2,12 +2,30 @@
"death.attack.void_block": "§8%1$s stepped beyond safety — the Outside answered.",
"death.attack.chipi.void_block_fire": "§c%1$s tried to save themselves.§r §8It got worse.",
"effect.chipi.chipi_blessing": "§bChipi's Blessing",
"effect.chipi.pregnant": "§dyou are Pregnant",
"block.chipi.void_block": "Void Block",
"block.chipi.chipper_frame": "Chipper Frame",
"block.chipi.chipper_portal": "Chipper Portal",
"block.chipi.chipper_ore": "Chipper Ore",
"block.chipi.chipper_alloy_block": "Chipper Alloy Block",
"item.chipi.chipi_record_aa9": "Chipi Record Aa9",
"item.chipi.chipi_record_aa9.desc": "Chipi Record Aa9",
"item.chipi.chipi_record_abc": "Chipi Record Abc",
"item.chipi.chipi_record_abc.desc": "Chipi Record Abc",
"item.chipi.chipi_record_fs": "Chipi Record Fs",
"item.chipi.chipi_record_fs.desc": "Chipi Record Fs",
"item.chipi.chipi_record_phone": "Chipi Record Phone",
"item.chipi.chipi_record_phone.desc": "Chipi Record Phone",
"item.chipi.chipi_record_wha": "Chipi Record Wha",
"item.chipi.chipi_record_wha.desc": "Chipi Record Wha",
"item.chipi.chipi_record_who": "Chipi Record Who",
"item.chipi.chipi_record_who.desc": "Chipi Record Who",
"item.chipi.chipi_record_working_as_intented": "Chipi Record Working As Intented",
"item.chipi.chipi_record_working_as_intented.desc": "Chipi Record Working As Intented",
"itemGroup.chipi.chipi": "Chipi",
"item.chipi.void_block": "Void Block",

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "chipi:item/chipi_record_aa9"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "chipi:item/chipi_record_abc"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "chipi:item/chipi_record_fs"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "chipi:item/chipi_record_phone"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "chipi:item/chipi_record_wha"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "chipi:item/chipi_record_who"
}
}

View File

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "chipi:item/chipi_record_working_as_intented"
}
}

View File

@ -8,5 +8,26 @@
"sounds": [
"chipi:player_milk"
]
},
"chipi_record_aa9": {
"sounds": [{ "name": "chipi:chipi_record_aa9", "stream": true }]
},
"chipi_record_abc": {
"sounds": [{ "name": "chipi:chipi_record_abc", "stream": true }]
},
"chipi_record_fs": {
"sounds": [{ "name": "chipi:chipi_record_fs", "stream": true }]
},
"chipi_record_phone": {
"sounds": [{ "name": "chipi:chipi_record_phone", "stream": true }]
},
"chipi_record_wha": {
"sounds": [{ "name": "chipi:chipi_record_wha", "stream": true }]
},
"chipi_record_who": {
"sounds": [{ "name": "chipi:chipi_record_who", "stream": true }]
},
"chipi_record_working_as_intented": {
"sounds": [{ "name": "chipi:chipi_record_working_as_intented", "stream": true }]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

View File

@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"xxx",
"xyx",
"xxx"
],
"key": {
"x": { "item": "chipi:raw_chipper_ore" },
"y": { "item": "minecraft:emerald" }
},
"result": { "item": "chipi:chipi_record_aa9" }
}

View File

@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"xxx",
"xyx",
"xxx"
],
"key": {
"x": { "item": "minecraft:orange_wool" },
"y": { "item": "minecraft:emerald" }
},
"result": { "item": "chipi:chipi_record_abc" }
}

View File

@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"xxx",
"xyx",
"xxx"
],
"key": {
"x": { "item": "minecraft:sweet_berries" },
"y": { "item": "minecraft:emerald" }
},
"result": { "item": "chipi:chipi_record_fs" }
}

View File

@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"xxx",
"xyx",
"xxx"
],
"key": {
"x": { "item": "minecraft:iron_ingot" },
"y": { "item": "minecraft:emerald" }
},
"result": { "item": "chipi:chipi_record_phone" }
}

View File

@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"xxx",
"xyx",
"xxx"
],
"key": {
"x": { "item": "chipi:chipper_alloy" },
"y": { "item": "minecraft:emerald" }
},
"result": { "item": "chipi:chipi_record_wha" }
}

View File

@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"xxx",
"xyx",
"xxx"
],
"key": {
"x": { "item": "minecraft:blue_wool" },
"y": { "item": "minecraft:emerald" }
},
"result": { "item": "chipi:chipi_record_who" }
}

View File

@ -0,0 +1,13 @@
{
"type": "minecraft:crafting_shaped",
"pattern": [
"xxx",
"xyx",
"xxx"
],
"key": {
"x": { "item": "minecraft:glass" },
"y": { "item": "minecraft:emerald" }
},
"result": { "item": "chipi:chipi_record_working_as_intented" }
}

View File

@ -0,0 +1,12 @@
{
"replace": false,
"values": [
"chipi:chipi_record_aa9",
"chipi:chipi_record_abc",
"chipi:chipi_record_fs",
"chipi:chipi_record_phone",
"chipi:chipi_record_wha",
"chipi:chipi_record_who",
"chipi:chipi_record_working_as_intented"
]
}