Add pregnancy effect and related features, including jump clamping and denial of drinking milk
This commit is contained in:
parent
932254b1d9
commit
36e2b9a886
@ -9,8 +9,19 @@ import net.minecraft.util.Identifier;
|
|||||||
public class ModEffects {
|
public class ModEffects {
|
||||||
|
|
||||||
public static final StatusEffect CHIPI_BLESSING = new ChipiBlessingEffect();
|
public static final StatusEffect CHIPI_BLESSING = new ChipiBlessingEffect();
|
||||||
|
public static final StatusEffect PREGNANT = new PregnantEffect();
|
||||||
|
|
||||||
public static void register() {
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
127
src/main/java/net/Chipperfluff/chipi/effect/PregnantEffect.java
Normal file
127
src/main/java/net/Chipperfluff/chipi/effect/PregnantEffect.java
Normal 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 name’s " +
|
||||||
|
jr.getName().getString() +
|
||||||
|
". Warning: it might cheat."
|
||||||
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
package net.Chipperfluff.chipi.entity;
|
package net.Chipperfluff.chipi.entity;
|
||||||
|
|
||||||
import net.minecraft.entity.EntityType;
|
import net.minecraft.entity.EntityType;
|
||||||
import net.minecraft.entity.ai.goal.*;
|
import net.minecraft.entity.ai.goal.Goal;
|
||||||
import net.minecraft.entity.ai.pathing.Path;
|
import net.minecraft.entity.ai.goal.LookAroundGoal;
|
||||||
import net.minecraft.entity.ai.pathing.PathNode;
|
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.DefaultAttributeContainer;
|
||||||
import net.minecraft.entity.attribute.EntityAttributes;
|
import net.minecraft.entity.attribute.EntityAttributes;
|
||||||
import net.minecraft.entity.damage.DamageSource;
|
import net.minecraft.entity.damage.DamageSource;
|
||||||
@ -17,83 +18,51 @@ import net.minecraft.util.math.BlockPos;
|
|||||||
import net.minecraft.util.math.Vec3d;
|
import net.minecraft.util.math.Vec3d;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class PlayerJrEntity extends PathAwareEntity {
|
public class PlayerJrEntity extends PathAwareEntity {
|
||||||
|
|
||||||
/* ---------------- CONFIG ---------------- */
|
/* ================= RULES (CHEATING FRIEND) ================= */
|
||||||
|
|
||||||
private static final int CHAT_INTERVAL = 20 * 6;
|
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
|
||||||
|
|
||||||
// When to consider cheating
|
private static final int LAND_RADIUS = 6; // search dry landing within this radius
|
||||||
private static final double FAR_DISTANCE = 30.0; // if far and path sucks -> cheat
|
private static final double LAND_HORIZONTAL_DISTANCE = 2.0; // must be this close (horiz) to land
|
||||||
private static final int MAX_PATH_LENGTH = 45; // path too long -> cheat
|
private static final double HOVER_HEIGHT = 1.25; // hover above landing block
|
||||||
private static final int MAX_VERTICAL_GAP = 6; // dad much higher than path end -> cheat
|
private static final int MIN_FLY_TICKS = 20; // must fly at least this long
|
||||||
private static final int STUCK_CHECK_EVERY = 20; // check progress every 1s
|
|
||||||
private static final double STUCK_MIN_PROGRESS = 1.2; // blocks per second expected
|
|
||||||
private static final int STUCK_STRIKES_TO_FLY = 3; // needs 3 bad checks (avoids spam)
|
|
||||||
|
|
||||||
// Cheat (flight) controls
|
private static final double FLY_SPEED = 0.55;
|
||||||
private static final int FLY_COOLDOWN_TICKS = 100; // max once per 5 seconds
|
private static final double FLY_ACCEL = 0.25;
|
||||||
private static final int MIN_FLY_TICKS = 30; // must stay in "creative" at least 1.5s
|
private static final int MAX_VERTICAL_GAP = 6; // if dad is this high above -> fly
|
||||||
private static final int LAND_STABLE_TICKS = 6; // must be stable on a standable spot for this long
|
|
||||||
private static final double LAND_DISTANCE = 2.2; // near landing target
|
|
||||||
private static final double LANDING_SEARCH_RADIUS = 6.0;
|
|
||||||
|
|
||||||
// Flight feel
|
/* ================= STATE ================= */
|
||||||
private static final double FLY_SPEED = 0.55; // horizontal
|
|
||||||
private static final double FLY_ACCEL = 0.20; // smoothing (0..1)
|
|
||||||
private static final double LAND_DESCEND_SPEED = 0.15; // gentle descent
|
|
||||||
private static final double HOVER_HEIGHT_ABOVE_DAD = 2.6; // where it hovers when no landing spot
|
|
||||||
|
|
||||||
/* ---------------- STATE ---------------- */
|
|
||||||
|
|
||||||
private UUID dadUuid;
|
private UUID dadUuid;
|
||||||
private String dadName = "";
|
private String dadName = "";
|
||||||
private int chatCooldown = 40;
|
|
||||||
|
|
||||||
private enum TravelMode {
|
private enum Mode { GROUND, FLYING }
|
||||||
GROUND,
|
private Mode mode = Mode.GROUND;
|
||||||
FLYING,
|
|
||||||
LANDING
|
|
||||||
}
|
|
||||||
|
|
||||||
private TravelMode travelMode = TravelMode.GROUND;
|
private int flyTicks = 0;
|
||||||
|
|
||||||
// Anti-spam + stability
|
// Stuck tracking (ground)
|
||||||
private int flyCooldown = 0; // counts down
|
private int stuckTicks = 0;
|
||||||
private int flyTicks = 0; // how long we've been flying
|
private double lastDadHorizDist = -1;
|
||||||
private int landingStableTicks = 0; // stable-on-ground counter
|
|
||||||
|
|
||||||
// "Is it actually stuck?" tracking
|
// Landing / hover state
|
||||||
private int stuckCheckTimer = STUCK_CHECK_EVERY;
|
private BlockPos landingSpot = null;
|
||||||
private Vec3d lastStuckPos = null;
|
private BlockPos forcedLandingSpot = null;
|
||||||
private int stuckStrikes = 0;
|
|
||||||
|
|
||||||
// landing target caching (reduces jitter)
|
/* ================= CONSTRUCTOR ================= */
|
||||||
private BlockPos landingTarget = null;
|
|
||||||
private int landingTargetRefresh = 0;
|
|
||||||
|
|
||||||
/* ---------------- CHAT ---------------- */
|
|
||||||
|
|
||||||
private static final List<String> MESSAGES = List.of(
|
|
||||||
"hold on…",
|
|
||||||
"yeah no this path is cooked",
|
|
||||||
"creative moment, don’t look",
|
|
||||||
"brb disabling gravity",
|
|
||||||
"pathfinding said no",
|
|
||||||
"minecraft moment detected"
|
|
||||||
);
|
|
||||||
|
|
||||||
/* ---------------- CONSTRUCTOR ---------------- */
|
|
||||||
|
|
||||||
public PlayerJrEntity(EntityType<? extends PathAwareEntity> type, World world) {
|
public PlayerJrEntity(EntityType<? extends PathAwareEntity> type, World world) {
|
||||||
super(type, world);
|
super(type, world);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- ATTRIBUTES ---------------- */
|
|
||||||
|
|
||||||
public static DefaultAttributeContainer.Builder createAttributes() {
|
public static DefaultAttributeContainer.Builder createAttributes() {
|
||||||
return MobEntity.createMobAttributes()
|
return MobEntity.createMobAttributes()
|
||||||
.add(EntityAttributes.GENERIC_MAX_HEALTH, 10.0)
|
.add(EntityAttributes.GENERIC_MAX_HEALTH, 10.0)
|
||||||
@ -101,13 +70,13 @@ public class PlayerJrEntity extends PathAwareEntity {
|
|||||||
.add(EntityAttributes.GENERIC_FOLLOW_RANGE, 64.0);
|
.add(EntityAttributes.GENERIC_FOLLOW_RANGE, 64.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- DAD ---------------- */
|
/* ================= DAD ================= */
|
||||||
|
|
||||||
public void setDad(ServerPlayerEntity dad) {
|
public void setDad(ServerPlayerEntity dad) {
|
||||||
this.dadUuid = dad.getUuid();
|
dadUuid = dad.getUuid();
|
||||||
this.dadName = dad.getGameProfile().getName();
|
dadName = dad.getGameProfile().getName();
|
||||||
this.setCustomName(Text.literal(this.dadName + " jr."));
|
setCustomName(Text.literal(dadName + " jr."));
|
||||||
this.setCustomNameVisible(true);
|
setCustomNameVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServerPlayerEntity getDad() {
|
private ServerPlayerEntity getDad() {
|
||||||
@ -115,428 +84,296 @@ public class PlayerJrEntity extends PathAwareEntity {
|
|||||||
return getWorld().getServer().getPlayerManager().getPlayer(dadUuid);
|
return getWorld().getServer().getPlayerManager().getPlayer(dadUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDadName() {
|
public String getDadName() { return dadName; }
|
||||||
return dadName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getDadUuid() {
|
/* ================= AI ================= */
|
||||||
return dadUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------- GOALS ---------------- */
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initGoals() {
|
protected void initGoals() {
|
||||||
this.goalSelector.add(1, new SwimGoal(this));
|
goalSelector.add(1, new SwimGoal(this));
|
||||||
this.goalSelector.add(2, new FollowDadGoal(this));
|
goalSelector.add(2, new FollowDadGoal(this));
|
||||||
this.goalSelector.add(3, new WanderAroundGoal(this, 0.8));
|
goalSelector.add(3, new LookAtEntityGoal(this, PlayerEntity.class, 6f));
|
||||||
this.goalSelector.add(4, new LookAtEntityGoal(this, PlayerEntity.class, 6f));
|
goalSelector.add(4, new LookAroundGoal(this));
|
||||||
this.goalSelector.add(5, new LookAroundGoal(this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- TICK ---------------- */
|
/* ================= TICK ================= */
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
super.tick();
|
super.tick();
|
||||||
if (getWorld().isClient) return;
|
if (getWorld().isClient) return;
|
||||||
|
|
||||||
|
// Never fall damage ever (you asked this specifically)
|
||||||
|
this.fallDistance = 0.0f;
|
||||||
|
|
||||||
ServerPlayerEntity dad = getDad();
|
ServerPlayerEntity dad = getDad();
|
||||||
if (dad == null) return;
|
if (dad == null) return;
|
||||||
|
|
||||||
if (flyCooldown > 0) flyCooldown--;
|
switch (mode) {
|
||||||
|
case GROUND -> doGroundBrain(dad);
|
||||||
// movement state machine
|
case FLYING -> doFlyingBrain(dad);
|
||||||
switch (travelMode) {
|
|
||||||
case GROUND -> {
|
|
||||||
evaluateCheatNeed(dad);
|
|
||||||
}
|
|
||||||
case FLYING -> {
|
|
||||||
flyTicks++;
|
|
||||||
doFlying(dad);
|
|
||||||
}
|
|
||||||
case LANDING -> {
|
|
||||||
flyTicks++;
|
|
||||||
doLanding(dad);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// chat
|
/* ================= CORE BRAIN: GROUND ================= */
|
||||||
chatCooldown--;
|
|
||||||
if (chatCooldown <= 0 && this.squaredDistanceTo(dad) < 36 && !MESSAGES.isEmpty()) {
|
private void doGroundBrain(ServerPlayerEntity dad) {
|
||||||
dad.sendMessage(Text.literal(MESSAGES.get(this.random.nextInt(MESSAGES.size()))), true);
|
// Water cheat: if land is nearby, prefer walking out; if no path, fly briefly to land.
|
||||||
chatCooldown = CHAT_INTERVAL;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- CHEAT DECISION ---------------- */
|
// Update stuck tracking (only on ground mode)
|
||||||
|
updateStuckTracking(dad);
|
||||||
private void evaluateCheatNeed(ServerPlayerEntity dad) {
|
if (stuckTicks >= STUCK_TICKS_TO_FLY) {
|
||||||
// Too close? don't cheat.
|
startFlying(dad);
|
||||||
double distSq = this.squaredDistanceTo(dad);
|
|
||||||
if (distSq < (12.0 * 12.0)) {
|
|
||||||
resetStuckTracking(); // near dad, don't keep strikes
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If on cooldown, we still allow normal pathing but don't fly.
|
// Too far behind (impatient cheat)
|
||||||
if (flyCooldown > 0) return;
|
if (horizontalDistanceTo(dad) > FORCE_FLY_DISTANCE) {
|
||||||
|
startFlying(dad);
|
||||||
Path path = this.getNavigation().getCurrentPath();
|
return;
|
||||||
|
|
||||||
boolean far = distSq > (FAR_DISTANCE * FAR_DISTANCE);
|
|
||||||
boolean noPath = path == null;
|
|
||||||
boolean tooLong = path != null && path.getLength() > MAX_PATH_LENGTH;
|
|
||||||
boolean finishedButStillFar = path != null && path.isFinished() && far;
|
|
||||||
boolean pathWater = path != null && pathTouchesWater(path);
|
|
||||||
boolean dadHighButPathLow = path != null && dadIsHighButPathStaysLow(dad, path);
|
|
||||||
|
|
||||||
// "stuck" detection (only counts if far-ish)
|
|
||||||
if (distSq > (18.0 * 18.0)) {
|
|
||||||
boolean stuck = checkStuck();
|
|
||||||
if (stuck) stuckStrikes++;
|
|
||||||
else stuckStrikes = Math.max(0, stuckStrikes - 1);
|
|
||||||
} else {
|
|
||||||
stuckStrikes = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean pathIsCooked = noPath || tooLong || finishedButStillFar || pathWater || dadHighButPathLow;
|
// Dad is way above us (can't reach)
|
||||||
boolean trulyStuck = stuckStrikes >= STUCK_STRIKES_TO_FLY;
|
double dy = dad.getY() - getY();
|
||||||
|
if (dy > MAX_VERTICAL_GAP && horizontalDistanceTo(dad) > 2.0) {
|
||||||
// Only cheat if far AND (path is cooked OR truly stuck)
|
|
||||||
if (far && (pathIsCooked || trulyStuck)) {
|
|
||||||
startFlying(dad);
|
startFlying(dad);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkStuck() {
|
private void updateStuckTracking(ServerPlayerEntity dad) {
|
||||||
stuckCheckTimer--;
|
boolean tryingToWalk = !getNavigation().isIdle();
|
||||||
if (stuckCheckTimer > 0) return false;
|
double dadDist = horizontalDistanceTo(dad);
|
||||||
|
double dadSpeedSq = dad.getVelocity().horizontalLengthSquared();
|
||||||
|
|
||||||
stuckCheckTimer = STUCK_CHECK_EVERY;
|
if (!tryingToWalk || dadDist <= STUCK_MIN_DISTANCE || dadSpeedSq > DAD_MOVING_EPS_SQ) {
|
||||||
|
stuckTicks = 0;
|
||||||
Vec3d now = this.getPos();
|
lastDadHorizDist = dadDist;
|
||||||
if (lastStuckPos == null) {
|
return;
|
||||||
lastStuckPos = now;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double moved = now.distanceTo(lastStuckPos);
|
if (lastDadHorizDist < 0) {
|
||||||
lastStuckPos = now;
|
lastDadHorizDist = dadDist;
|
||||||
|
stuckTicks = 0;
|
||||||
// expected progress in 1s ~ STUCK_MIN_PROGRESS blocks
|
return;
|
||||||
return moved < STUCK_MIN_PROGRESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetStuckTracking() {
|
double closing = lastDadHorizDist - dadDist; // positive means getting closer
|
||||||
stuckCheckTimer = STUCK_CHECK_EVERY;
|
if (closing < STUCK_MIN_CLOSING) stuckTicks++;
|
||||||
lastStuckPos = null;
|
else stuckTicks = 0;
|
||||||
stuckStrikes = 0;
|
|
||||||
|
lastDadHorizDist = dadDist;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean pathTouchesWater(Path path) {
|
private void resetStuckTracking(ServerPlayerEntity dad) {
|
||||||
for (int i = 0; i < path.getLength(); i++) {
|
stuckTicks = 0;
|
||||||
PathNode node = path.getNode(i);
|
lastDadHorizDist = horizontalDistanceTo(dad);
|
||||||
BlockPos pos = new BlockPos(node.x, node.y, node.z);
|
|
||||||
if (!getWorld().getFluidState(pos).isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean dadIsHighButPathStaysLow(ServerPlayerEntity dad, Path path) {
|
/* ================= CORE BRAIN: FLYING ================= */
|
||||||
if (path.getLength() == 0) return false;
|
|
||||||
PathNode end = path.getNode(path.getLength() - 1);
|
|
||||||
int endY = end.y;
|
|
||||||
int dadY = dad.getBlockY();
|
|
||||||
return dadY - endY >= MAX_VERTICAL_GAP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------- FLYING ---------------- */
|
|
||||||
|
|
||||||
private void startFlying(ServerPlayerEntity dad) {
|
private void startFlying(ServerPlayerEntity dad) {
|
||||||
if (travelMode != TravelMode.GROUND) return;
|
mode = Mode.FLYING;
|
||||||
|
|
||||||
travelMode = TravelMode.FLYING;
|
|
||||||
flyTicks = 0;
|
flyTicks = 0;
|
||||||
landingStableTicks = 0;
|
|
||||||
|
|
||||||
landingTarget = null;
|
landingSpot = null;
|
||||||
landingTargetRefresh = 0;
|
|
||||||
|
|
||||||
this.setNoGravity(true);
|
setNoGravity(true);
|
||||||
this.getNavigation().stop();
|
getNavigation().stop();
|
||||||
this.setVelocity(Vec3d.ZERO);
|
setVelocity(Vec3d.ZERO);
|
||||||
this.velocityDirty = true;
|
velocityDirty = true;
|
||||||
|
|
||||||
// cooldown starts now (prevents spam toggles)
|
dad.sendMessage(Text.literal(getName().getString() + " switched to Game Mode Creative"), false);
|
||||||
flyCooldown = FLY_COOLDOWN_TICKS;
|
|
||||||
|
|
||||||
dad.sendMessage(Text.literal("* " + getName().getString() + " switched to Creative Mode"), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doFlying(ServerPlayerEntity dad) {
|
private void doFlyingBrain(ServerPlayerEntity dad) {
|
||||||
// refresh landing target occasionally (not every tick to avoid jitter)
|
flyTicks++;
|
||||||
if (landingTargetRefresh-- <= 0) {
|
|
||||||
landingTargetRefresh = 6; // update ~3 times/sec
|
double horiz = horizontalDistanceTo(dad);
|
||||||
landingTarget = findLandingSpotBlock(dad);
|
|
||||||
|
if (flyTicks >= MIN_FLY_TICKS) {
|
||||||
|
BlockPos candidate = forcedLandingSpot;
|
||||||
|
if (candidate == null) {
|
||||||
|
candidate = findLandingSpotNear(dad.getBlockPos(), LAND_RADIUS);
|
||||||
}
|
}
|
||||||
|
if (candidate != null && !wouldImmediatelyRefly(dad, candidate)) {
|
||||||
Vec3d target;
|
landingSpot = candidate;
|
||||||
|
if (horizontalDistanceTo(landingSpot) <= LAND_HORIZONTAL_DISTANCE) {
|
||||||
if (landingTarget != null) {
|
|
||||||
target = Vec3d.ofCenter(landingTarget);
|
|
||||||
} else {
|
|
||||||
// no landing spot => hover above dad and keep searching
|
|
||||||
target = dad.getPos().add(0, HOVER_HEIGHT_ABOVE_DAD, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a landing block and we are close enough, go LANDING
|
|
||||||
if (landingTarget != null) {
|
|
||||||
double dist = this.getPos().distanceTo(Vec3d.ofCenter(landingTarget));
|
|
||||||
if (dist < LAND_DISTANCE) {
|
|
||||||
travelMode = TravelMode.LANDING;
|
|
||||||
landingStableTicks = 0;
|
|
||||||
// stop forward movement before landing phase
|
|
||||||
this.setVelocity(Vec3d.ZERO);
|
|
||||||
this.velocityDirty = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// smooth creative-like movement
|
|
||||||
flyTowardSmooth(target);
|
|
||||||
this.lookAtEntity(dad, 30f, 30f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doLanding(ServerPlayerEntity dad) {
|
|
||||||
// MUST stay in "creative" for at least MIN_FLY_TICKS before returning to ground
|
|
||||||
if (flyTicks < MIN_FLY_TICKS) {
|
|
||||||
// keep holding position / gentle settle
|
|
||||||
this.setVelocity(Vec3d.ZERO);
|
|
||||||
this.velocityDirty = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we lost landing target, go back to flying (but keep creative)
|
|
||||||
if (landingTarget == null) {
|
|
||||||
travelMode = TravelMode.FLYING;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If landing target became invalid, go back to flying (don’t drop)
|
|
||||||
if (!canStandAt(landingTarget)) {
|
|
||||||
travelMode = TravelMode.FLYING;
|
|
||||||
landingTarget = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to be exactly centered above the landing block
|
|
||||||
Vec3d center = Vec3d.ofCenter(landingTarget);
|
|
||||||
Vec3d pos = this.getPos();
|
|
||||||
|
|
||||||
// horizontal adjust (gentle)
|
|
||||||
Vec3d horiz = new Vec3d(center.x - pos.x, 0, center.z - pos.z);
|
|
||||||
double horizLen = horiz.length();
|
|
||||||
|
|
||||||
double vx = 0, vz = 0;
|
|
||||||
if (horizLen > 0.08) {
|
|
||||||
Vec3d hn = horiz.normalize().multiply(Math.min(FLY_SPEED * 0.6, horizLen));
|
|
||||||
vx = hn.x;
|
|
||||||
vz = hn.z;
|
|
||||||
}
|
|
||||||
|
|
||||||
// descend gently until feet are on standable position
|
|
||||||
double vy = -LAND_DESCEND_SPEED;
|
|
||||||
|
|
||||||
// If we are basically at block level, stop vertical movement
|
|
||||||
double dy = center.y - pos.y;
|
|
||||||
if (dy > -0.15 && dy < 0.65) {
|
|
||||||
vy = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setVelocity(vx, vy, vz);
|
|
||||||
this.velocityDirty = true;
|
|
||||||
|
|
||||||
// "stable landing" means: our current feet pos is standable and we are almost not moving
|
|
||||||
BlockPos feet = this.getBlockPos();
|
|
||||||
boolean stableFeet = canStandAt(feet) || feet.equals(landingTarget);
|
|
||||||
boolean slow = this.getVelocity().lengthSquared() < 0.01;
|
|
||||||
|
|
||||||
if (stableFeet && slow) {
|
|
||||||
landingStableTicks++;
|
|
||||||
} else {
|
|
||||||
landingStableTicks = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (landingStableTicks >= LAND_STABLE_TICKS) {
|
|
||||||
stopFlying(dad);
|
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) {
|
private void stopFlying(ServerPlayerEntity dad) {
|
||||||
travelMode = TravelMode.GROUND;
|
setNoGravity(false);
|
||||||
|
mode = Mode.GROUND;
|
||||||
|
|
||||||
this.setVelocity(Vec3d.ZERO);
|
landingSpot = null;
|
||||||
this.velocityDirty = true;
|
forcedLandingSpot = null;
|
||||||
this.setNoGravity(false);
|
|
||||||
|
|
||||||
// after returning, clear stuck tracking so it doesn't instantly cheat again
|
dad.sendMessage(Text.literal(getName().getString() + " switched to Game Mode Survival"), false);
|
||||||
resetStuckTracking();
|
|
||||||
|
|
||||||
// don't instantly re-cheat; cooldown already running, but this avoids instant flip due to path recalculation
|
|
||||||
landingTarget = null;
|
|
||||||
landingTargetRefresh = 0;
|
|
||||||
|
|
||||||
dad.sendMessage(Text.literal("* " + getName().getString() + " returned to Survival Mode"), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================= MOVEMENT HELPERS ================= */
|
||||||
|
|
||||||
private void flyTowardSmooth(Vec3d target) {
|
private void flyTowardSmooth(Vec3d target) {
|
||||||
Vec3d dir = target.subtract(this.getPos());
|
Vec3d delta = target.subtract(getPos());
|
||||||
if (dir.lengthSquared() < 0.0005) {
|
double distSq = delta.lengthSquared();
|
||||||
// gently stop
|
if (distSq < 0.0006) {
|
||||||
Vec3d v = this.getVelocity().multiply(0.5);
|
setVelocity(getVelocity().multiply(0.6));
|
||||||
if (v.lengthSquared() < 0.0002) v = Vec3d.ZERO;
|
velocityDirty = true;
|
||||||
this.setVelocity(v);
|
|
||||||
this.velocityDirty = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec3d desired = dir.normalize().multiply(FLY_SPEED);
|
Vec3d desired = delta.normalize().multiply(FLY_SPEED);
|
||||||
|
|
||||||
// smoothing: v = v*(1-a) + desired*a
|
// stronger steer when far (prevents drifting)
|
||||||
Vec3d current = this.getVelocity();
|
double accel = (distSq > 64.0) ? Math.min(0.55, FLY_ACCEL * 2.0) : FLY_ACCEL;
|
||||||
Vec3d blended = current.multiply(1.0 - FLY_ACCEL).add(desired.multiply(FLY_ACCEL));
|
|
||||||
|
|
||||||
this.setVelocity(blended);
|
Vec3d blended = getVelocity().multiply(1.0 - accel).add(desired.multiply(accel));
|
||||||
this.velocityDirty = true;
|
setVelocity(blended);
|
||||||
|
velocityDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- LANDING TARGET SEARCH ---------------- */
|
private double horizontalDistanceTo(ServerPlayerEntity dad) {
|
||||||
|
double dx = dad.getX() - getX();
|
||||||
|
double dz = dad.getZ() - getZ();
|
||||||
|
return Math.sqrt(dx * dx + dz * dz);
|
||||||
|
}
|
||||||
|
|
||||||
private BlockPos findLandingSpotBlock(ServerPlayerEntity dad) {
|
private double horizontalDistanceTo(BlockPos pos) {
|
||||||
BlockPos center = dad.getBlockPos();
|
double dx = pos.getX() + 0.5 - getX();
|
||||||
double bestDist = Double.MAX_VALUE;
|
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;
|
BlockPos best = null;
|
||||||
|
double bestScore = Double.MAX_VALUE;
|
||||||
|
|
||||||
int radius = (int) Math.ceil(LANDING_SEARCH_RADIUS);
|
int minY = center.getY() - 3;
|
||||||
|
|
||||||
// prefer roughly same Y as dad feet-ish (+0..+2)
|
|
||||||
int minY = center.getY() - 2;
|
|
||||||
int maxY = center.getY() + 3;
|
int maxY = center.getY() + 3;
|
||||||
|
|
||||||
for (int dx = -radius; dx <= radius; dx++) {
|
for (int dx = -radius; dx <= radius; dx++) {
|
||||||
for (int dz = -radius; dz <= radius; dz++) {
|
for (int dz = -radius; dz <= radius; dz++) {
|
||||||
// quick circle check
|
if ((dx * dx + dz * dz) > radius * radius) continue;
|
||||||
if ((dx * dx + dz * dz) > (LANDING_SEARCH_RADIUS * LANDING_SEARCH_RADIUS)) continue;
|
|
||||||
|
|
||||||
for (int y = minY; y <= maxY; y++) {
|
for (int y = minY; y <= maxY; y++) {
|
||||||
BlockPos pos = new BlockPos(center.getX() + dx, y, center.getZ() + dz);
|
BlockPos p = new BlockPos(center.getX() + dx, y, center.getZ() + dz);
|
||||||
if (!canStandAt(pos)) continue;
|
if (!canStandAt(p)) continue;
|
||||||
|
|
||||||
// also avoid landing inside water or on water edge
|
// prefer closer to dad AND closer to us a bit
|
||||||
if (!getWorld().getFluidState(pos).isEmpty()) continue;
|
double toCenter = p.getSquaredDistance(center);
|
||||||
if (!getWorld().getFluidState(pos.down()).isEmpty()) continue;
|
double toMe = this.getPos().squaredDistanceTo(Vec3d.ofCenter(p));
|
||||||
|
double score = toCenter * 1.0 + toMe * 0.10;
|
||||||
|
|
||||||
double d = this.getPos().squaredDistanceTo(Vec3d.ofCenter(pos));
|
if (score < bestScore) {
|
||||||
if (d < bestDist) {
|
bestScore = score;
|
||||||
bestDist = d;
|
best = p;
|
||||||
best = pos;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return best;
|
return best;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canStandAt(BlockPos pos) {
|
private boolean canStandAt(BlockPos pos) {
|
||||||
// feet + head must be air
|
World w = getWorld();
|
||||||
if (!getWorld().getBlockState(pos).isAir()) return false;
|
|
||||||
if (!getWorld().getBlockState(pos.up()).isAir()) return false;
|
|
||||||
|
|
||||||
// block below must be solid
|
// feet + head air
|
||||||
|
if (!w.getBlockState(pos).isAir()) return false;
|
||||||
|
if (!w.getBlockState(pos.up()).isAir()) return false;
|
||||||
|
|
||||||
|
// solid below
|
||||||
BlockPos below = pos.down();
|
BlockPos below = pos.down();
|
||||||
return getWorld().getBlockState(below).isSolidBlock(getWorld(), below);
|
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 “don’t be stupid”)
|
||||||
|
// (fluid check already catches lava fluid; this catches blocks like magma? optional)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- DEATH ---------------- */
|
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
|
@Override
|
||||||
public void onDeath(DamageSource source) {
|
public boolean handleFallDamage(float fallDistance, float damageMultiplier, DamageSource source) {
|
||||||
super.onDeath(source);
|
return false;
|
||||||
if (getWorld().isClient) return;
|
|
||||||
|
|
||||||
getWorld().createExplosion(
|
|
||||||
this,
|
|
||||||
getX(),
|
|
||||||
getY(),
|
|
||||||
getZ(),
|
|
||||||
3.0f,
|
|
||||||
World.ExplosionSourceType.MOB
|
|
||||||
);
|
|
||||||
|
|
||||||
ServerPlayerEntity dad = getDad();
|
|
||||||
if (dad != null) {
|
|
||||||
dad.sendMessage(Text.literal("oh dad im gonna go"), false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- NAME ---------------- */
|
/* ================= SAVE / LOAD ================= */
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getNameLabelHeight() {
|
|
||||||
return this.getHeight() + 1.5f; // YES. STILL CORRECT.
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------- SAVE ---------------- */
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeCustomDataToNbt(NbtCompound nbt) {
|
public void writeCustomDataToNbt(NbtCompound nbt) {
|
||||||
if (dadUuid != null) nbt.putUuid("Dad", dadUuid);
|
if (dadUuid != null) nbt.putUuid("Dad", dadUuid);
|
||||||
nbt.putString("DadName", dadName);
|
nbt.putString("DadName", dadName);
|
||||||
nbt.putString("TravelMode", travelMode.name());
|
nbt.putString("Mode", mode.name());
|
||||||
nbt.putInt("FlyCooldown", flyCooldown);
|
|
||||||
nbt.putInt("FlyTicks", flyTicks);
|
nbt.putInt("FlyTicks", flyTicks);
|
||||||
nbt.putInt("LandingStable", landingStableTicks);
|
|
||||||
nbt.putInt("StuckStrikes", stuckStrikes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readCustomDataFromNbt(NbtCompound nbt) {
|
public void readCustomDataFromNbt(NbtCompound nbt) {
|
||||||
if (nbt.containsUuid("Dad")) dadUuid = nbt.getUuid("Dad");
|
if (nbt.containsUuid("Dad")) dadUuid = nbt.getUuid("Dad");
|
||||||
if (nbt.contains("DadName")) dadName = nbt.getString("DadName");
|
if (nbt.contains("DadName")) dadName = nbt.getString("DadName");
|
||||||
|
if (nbt.contains("Mode")) {
|
||||||
if (nbt.contains("TravelMode")) {
|
mode = Mode.valueOf(nbt.getString("Mode"));
|
||||||
travelMode = TravelMode.valueOf(nbt.getString("TravelMode"));
|
setNoGravity(mode != Mode.GROUND);
|
||||||
this.setNoGravity(travelMode != TravelMode.GROUND);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nbt.contains("FlyCooldown")) flyCooldown = nbt.getInt("FlyCooldown");
|
|
||||||
if (nbt.contains("FlyTicks")) flyTicks = nbt.getInt("FlyTicks");
|
if (nbt.contains("FlyTicks")) flyTicks = nbt.getInt("FlyTicks");
|
||||||
if (nbt.contains("LandingStable")) landingStableTicks = nbt.getInt("LandingStable");
|
|
||||||
if (nbt.contains("StuckStrikes")) stuckStrikes = nbt.getInt("StuckStrikes");
|
|
||||||
|
|
||||||
// reset volatile runtime fields
|
// volatile reset
|
||||||
stuckCheckTimer = STUCK_CHECK_EVERY;
|
landingSpot = null;
|
||||||
lastStuckPos = null;
|
forcedLandingSpot = null;
|
||||||
landingTarget = null;
|
lastDadHorizDist = -1;
|
||||||
landingTargetRefresh = 0;
|
stuckTicks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- FOLLOW GOAL ---------------- */
|
/* ================= FOLLOW GOAL ================= */
|
||||||
|
|
||||||
static class FollowDadGoal extends Goal {
|
static class FollowDadGoal extends Goal {
|
||||||
private final PlayerJrEntity jr;
|
private final PlayerJrEntity jr;
|
||||||
|
FollowDadGoal(PlayerJrEntity jr) { this.jr = jr; }
|
||||||
FollowDadGoal(PlayerJrEntity jr) {
|
|
||||||
this.jr = jr;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canStart() {
|
public boolean canStart() {
|
||||||
return jr.travelMode == TravelMode.GROUND && jr.getDad() != null;
|
return jr.mode == Mode.GROUND && jr.getDad() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
package net.Chipperfluff.chipi.item;
|
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.LivingEntity;
|
||||||
|
import net.minecraft.entity.effect.StatusEffectInstance;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.item.Item;
|
import net.minecraft.item.Item;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
|
import net.minecraft.item.ItemUsage;
|
||||||
import net.minecraft.item.Items;
|
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.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 {
|
public class PlayerMilkItem extends Item {
|
||||||
|
|
||||||
@ -18,6 +22,30 @@ public class PlayerMilkItem extends Item {
|
|||||||
super(settings);
|
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
|
@Override
|
||||||
public UseAction getUseAction(ItemStack stack) {
|
public UseAction getUseAction(ItemStack stack) {
|
||||||
return UseAction.DRINK;
|
return UseAction.DRINK;
|
||||||
@ -28,10 +56,23 @@ public class PlayerMilkItem extends Item {
|
|||||||
return 32;
|
return 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================= FINISH DRINK ================= */
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemStack finishUsing(ItemStack stack, World world, LivingEntity user) {
|
public ItemStack finishUsing(ItemStack stack, World world, LivingEntity user) {
|
||||||
|
|
||||||
if (!world.isClient && user instanceof PlayerEntity player) {
|
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(
|
world.playSound(
|
||||||
null,
|
null,
|
||||||
player.getBlockPos(),
|
player.getBlockPos(),
|
||||||
@ -42,8 +83,10 @@ public class PlayerMilkItem extends Item {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user instanceof PlayerEntity player && !player.getAbilities().creativeMode) {
|
// ✅ CORRECT vanilla behavior:
|
||||||
return new ItemStack(Items.BUCKET);
|
// consumes milk and gives bucket
|
||||||
|
if (user instanceof PlayerEntity player) {
|
||||||
|
return ItemUsage.exchangeStack(stack, player, new ItemStack(Items.BUCKET));
|
||||||
}
|
}
|
||||||
|
|
||||||
return stack;
|
return stack;
|
||||||
|
|||||||
@ -1,10 +1,15 @@
|
|||||||
package net.Chipperfluff.chipi.mixin;
|
package net.Chipperfluff.chipi.mixin;
|
||||||
|
|
||||||
import com.mojang.authlib.GameProfile;
|
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.Chipperfluff.chipi.util.ChipiTrackedData;
|
||||||
import net.minecraft.entity.data.DataTracker;
|
import net.minecraft.entity.data.DataTracker;
|
||||||
|
import net.minecraft.entity.effect.StatusEffectInstance;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
|
import net.minecraft.util.math.Vec3d;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
@ -14,11 +19,63 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||||||
@Mixin(PlayerEntity.class)
|
@Mixin(PlayerEntity.class)
|
||||||
public abstract class PlayerEntityMixin {
|
public abstract class PlayerEntityMixin {
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
INIT: tracked data
|
||||||
|
============================================================ */
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At("TAIL"))
|
@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;
|
PlayerEntity self = (PlayerEntity)(Object)this;
|
||||||
DataTracker tracker = self.getDataTracker();
|
DataTracker tracker = self.getDataTracker();
|
||||||
|
|
||||||
tracker.startTracking(ChipiTrackedData.CHIPI_ENERGY, 1.0f);
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user