-
-
Notifications
You must be signed in to change notification settings - Fork 5
Make ice balls freeze entities instead of slowing them down #121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
1e72a50
Make ice balls freeze entities instead of slowing them down
claude 1338210
Merge branch 'dev' into claude/issue-118-ikarms
Hugman76 03e54f8
Freezing progress
Hugman76 288fffb
Make frozen entities slippery and enderman dodge balls
Hugman76 53f00cc
Merge branch 'dev' into claude/issue-118-ikarms
Hugman76 4ffac46
Merge remote-tracking branch 'origin/dev' into claude/issue-118-ikarms
claude d90c1eb
Address review on the freezing PR
claude 1aee9c8
Make /freeze set explicit, and shatter ice balls on frozen targets
claude 8ed623e
Float frozen entities on the water surface
claude 475b517
Give /freeze set a time instead of a true/false
claude dac65a8
Tweak FreezeCommand
Hugman76 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
...io/src/client/java/fr/hugman/mubble/super_mario/client/keybind/FreezeStruggleHandler.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package fr.hugman.mubble.super_mario.client.keybind; | ||
|
|
||
| import fr.hugman.mubble.super_mario.network.protocol.common.custom.StruggleFreePayload; | ||
| import fr.hugman.mubble.super_mario.world.entity.freeze.Freezing; | ||
| import net.fabricmc.api.EnvType; | ||
| import net.fabricmc.api.Environment; | ||
| import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; | ||
| import net.minecraft.client.KeyMapping; | ||
| import net.minecraft.client.Minecraft; | ||
|
|
||
| /** | ||
| * Lets a frozen player smash their way out of the ice a little sooner by hammering the movement | ||
| * keys. | ||
| * <p> | ||
| * Only the presses themselves count, never the keys being held down: holding a direction is what a | ||
| * player does anyway when they run into the ice ball that froze them. | ||
| */ | ||
| @Environment(EnvType.CLIENT) | ||
| public class FreezeStruggleHandler { | ||
| public static void tick(Minecraft client) { | ||
| var player = client.player; | ||
| var options = client.options; | ||
| if (player == null) { | ||
| return; | ||
| } | ||
|
|
||
| int presses = consumeClicks(options.keyUp) + consumeClicks(options.keyDown) | ||
| + consumeClicks(options.keyLeft) + consumeClicks(options.keyRight); | ||
| // the keys are consumed either way: a press held over from before the freeze is not a struggle | ||
| if (presses == 0 || !Freezing.isFrozen(player)) { | ||
| return; | ||
| } | ||
| for (int i = 0; i < presses; i++) { | ||
| ClientPlayNetworking.send(StruggleFreePayload.INSTANCE); | ||
| } | ||
| } | ||
|
|
||
| private static int consumeClicks(KeyMapping key) { | ||
| int presses = 0; | ||
| while (key.consumeClick()) { | ||
| presses++; | ||
| } | ||
| return presses; | ||
| } | ||
| } |
66 changes: 66 additions & 0 deletions
66
.../src/client/java/fr/hugman/mubble/super_mario/client/mixin/FrozenEntityRendererMixin.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| package fr.hugman.mubble.super_mario.client.mixin; | ||
|
|
||
| import com.mojang.blaze3d.vertex.PoseStack; | ||
| import fr.hugman.mubble.super_mario.client.references.SuperMarioRenderStateDataKeys; | ||
| import fr.hugman.mubble.super_mario.client.renderer.entity.state.FreezeRenderData; | ||
| import net.fabricmc.api.EnvType; | ||
| import net.fabricmc.api.Environment; | ||
| import net.minecraft.client.renderer.SubmitNodeCollector; | ||
| import net.minecraft.client.renderer.entity.EntityRenderer; | ||
| import net.minecraft.client.renderer.entity.state.EntityRenderState; | ||
| import net.minecraft.client.renderer.state.level.CameraRenderState; | ||
| import net.minecraft.world.entity.Entity; | ||
| import net.minecraft.world.phys.Vec3; | ||
| 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; | ||
| import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; | ||
|
|
||
| /** | ||
| * Wraps a frozen entity in the block of ice holding it, and holds its animations still while it is | ||
| * in there. | ||
| */ | ||
| @Mixin(EntityRenderer.class) | ||
| @Environment(EnvType.CLIENT) | ||
| public class FrozenEntityRendererMixin<T extends Entity, S extends EntityRenderState> { | ||
| @Inject(method = "extractRenderState(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/client/renderer/entity/state/EntityRenderState;F)V", at = @At("TAIL")) | ||
| private void super_mario$extractFreeze(T entity, S state, float partialTicks, CallbackInfo ci) { | ||
| var freeze = FreezeRenderData.of(entity, partialTicks); | ||
| // set even when absent: render states are handed down from one entity to the next | ||
| state.setData(SuperMarioRenderStateDataKeys.FREEZE, freeze); | ||
| if (freeze != null) { | ||
| // winding the age back to what it was when the ice took hold stops everything driven by it | ||
| state.ageInTicks -= freeze.frozenFor(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Shakes a block of ice that is about to give. | ||
| * <p> | ||
| * The offset is added here rather than around the ice cube below, because this is the one the | ||
| * whole entity is drawn from: the ice and whatever is caught inside it shudder as the one thing. | ||
| */ | ||
| @Inject(method = "getRenderOffset", at = @At("RETURN"), cancellable = true) | ||
| private void super_mario$rattleTheIce(S state, CallbackInfoReturnable<Vec3> cir) { | ||
| var freeze = state.getData(SuperMarioRenderStateDataKeys.FREEZE); | ||
| // most of a freeze is spent perfectly still, and that half is not worth a vector for | ||
| if (freeze != null && freeze.rattle() != Vec3.ZERO) { | ||
| cir.setReturnValue(cir.getReturnValue().add(freeze.rattle())); | ||
| } | ||
| } | ||
|
|
||
| @Inject(method = "submit", at = @At("TAIL")) | ||
| private void super_mario$submitIceCube(S state, PoseStack poseStack, SubmitNodeCollector submitNodeCollector, CameraRenderState camera, CallbackInfo ci) { | ||
| var freeze = state.getData(SuperMarioRenderStateDataKeys.FREEZE); | ||
| if (freeze == null) { | ||
| return; | ||
| } | ||
| poseStack.pushPose(); | ||
| // the block model spans a whole block from the corner it is drawn at, hence the centering | ||
| poseStack.scale(state.boundingBoxWidth, state.boundingBoxHeight, state.boundingBoxWidth); | ||
| poseStack.translate(-0.5F, 0.0F, -0.5F); | ||
| submitNodeCollector.submitMovingBlock(poseStack, freeze.iceCube(), 0); | ||
| poseStack.popPose(); | ||
| } | ||
| } |
53 changes: 53 additions & 0 deletions
53
...lient/java/fr/hugman/mubble/super_mario/client/mixin/FrozenLivingEntityRendererMixin.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package fr.hugman.mubble.super_mario.client.mixin; | ||
|
|
||
| import fr.hugman.mubble.super_mario.client.references.SuperMarioRenderStateDataKeys; | ||
| import fr.hugman.mubble.super_mario.world.entity.freeze.FreezeSnapshot; | ||
| import net.fabricmc.api.EnvType; | ||
| import net.fabricmc.api.Environment; | ||
| import net.minecraft.client.model.EntityModel; | ||
| import net.minecraft.client.renderer.entity.LivingEntityRenderer; | ||
| import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; | ||
| import net.minecraft.util.ARGB; | ||
| import net.minecraft.world.entity.LivingEntity; | ||
| 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; | ||
| import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; | ||
|
|
||
| /** | ||
| * Gives a frozen entity the colour of the ice it is caught in, and holds the one animation the age | ||
| * alone does not drive. | ||
| * | ||
| * @see FrozenEntityRendererMixin | ||
| */ | ||
| @Mixin(LivingEntityRenderer.class) | ||
| @Environment(EnvType.CLIENT) | ||
| public class FrozenLivingEntityRendererMixin<T extends LivingEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>> { | ||
| /** The pale blue of the ice block, which whatever is seen through it takes on. */ | ||
| private static final int super_mario$ICE_TINT = 0xFFB9E4FF; | ||
|
|
||
| /** | ||
| * Puts the limbs back where they were the moment the ice took hold. | ||
| * <p> | ||
| * Vanilla reads them off a walk animation that runs itself down as soon as the entity stops | ||
| * moving, so a mob frozen mid-stride would ease into a resting pose over the next half second. | ||
| * The snapshot the entity took of itself does not move, and neither do the limbs read off it. | ||
| */ | ||
| @Inject(method = "extractRenderState(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;F)V", at = @At("TAIL")) | ||
| private void super_mario$holdThePoseWhileFrozen(T entity, S state, float partialTicks, CallbackInfo ci) { | ||
| if (state.getData(SuperMarioRenderStateDataKeys.FREEZE) == null) { | ||
| return; | ||
| } | ||
| var snapshot = (FreezeSnapshot) entity; | ||
| state.walkAnimationPos = snapshot.frozenWalkPos(); | ||
| state.walkAnimationSpeed = snapshot.frozenWalkSpeed(); | ||
| } | ||
|
|
||
| @Inject(method = "getModelTint", at = @At("RETURN"), cancellable = true) | ||
| private void super_mario$tintWhileFrozen(S state, CallbackInfoReturnable<Integer> cir) { | ||
| if (state.getData(SuperMarioRenderStateDataKeys.FREEZE) != null) { | ||
| cir.setReturnValue(ARGB.multiply(cir.getReturnValueI(), super_mario$ICE_TINT)); | ||
| } | ||
| } | ||
| } | ||
4 changes: 4 additions & 0 deletions
4
...nt/java/fr/hugman/mubble/super_mario/client/references/SuperMarioRenderStateDataKeys.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
...ient/java/fr/hugman/mubble/super_mario/client/renderer/entity/state/FreezeRenderData.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package fr.hugman.mubble.super_mario.client.renderer.entity.state; | ||
|
|
||
| import fr.hugman.mubble.super_mario.world.entity.freeze.Freezing; | ||
| import net.fabricmc.api.EnvType; | ||
| import net.fabricmc.api.Environment; | ||
| import net.minecraft.client.multiplayer.ClientLevel; | ||
| import net.minecraft.client.renderer.block.MovingBlockRenderState; | ||
| import net.minecraft.util.Mth; | ||
| import net.minecraft.world.entity.Entity; | ||
| import net.minecraft.world.level.block.Blocks; | ||
| import net.minecraft.world.phys.Vec3; | ||
| import org.jspecify.annotations.Nullable; | ||
|
|
||
| /** | ||
| * What the renderers need to know about the block of ice an entity is trapped in. | ||
| * | ||
| * @param frozenFor how long the entity has been frozen for, in ticks, interpolated within the tick. | ||
| * Subtracting it from an age gives the very same value on every single frame, which | ||
| * is what holds the animations of a frozen entity still. | ||
| * @param rattle how far the block of ice is off its resting place this frame, which is nothing at | ||
| * all until it is nearly out of time | ||
| * @param iceCube the ice block filling the entity hitbox, ready to be handed to the block renderer | ||
| */ | ||
| @Environment(EnvType.CLIENT) | ||
| public record FreezeRenderData(float frozenFor, Vec3 rattle, MovingBlockRenderState iceCube) { | ||
| /** How far the ice throws itself around, in blocks, by the time it is about to give. */ | ||
| private static final double RATTLE_AMPLITUDE = 0.06D; | ||
| /** How fast it does so, in radians per tick. Fast enough to read as a shudder rather than a sway. */ | ||
| private static final float RATTLE_FREQUENCY = 2.7F; | ||
| /** The two axes are run at different rates so that the shudder never settles into a straight line. */ | ||
| private static final float RATTLE_CROSS_FREQUENCY = 3.9F; | ||
|
|
||
| /** | ||
| * @return what to render around the entity, or {@code null} when it is not frozen | ||
| */ | ||
| @Nullable | ||
| public static FreezeRenderData of(Entity entity, float partialTicks) { | ||
| var freeze = Freezing.getState(entity); | ||
| if (freeze == null) { | ||
| return null; | ||
| } | ||
| long gameTime = entity.level().getGameTime(); | ||
|
|
||
| var iceCube = new MovingBlockRenderState(); | ||
| var pos = entity.blockPosition(); | ||
| iceCube.randomSeedPos = pos; | ||
| iceCube.blockPos = pos; | ||
| iceCube.blockState = Blocks.ICE.defaultBlockState(); | ||
| if (entity.level() instanceof ClientLevel level) { | ||
| iceCube.biome = level.getBiome(pos); | ||
| iceCube.cardinalLighting = level.cardinalLighting(); | ||
| iceCube.lightEngine = level.getLightEngine(); | ||
| } | ||
|
|
||
| float frozenFor = freeze.elapsed(gameTime) + partialTicks; | ||
| return new FreezeRenderData(frozenFor, rattleOf(freeze.remaining(gameTime) - partialTicks, frozenFor), iceCube); | ||
| } | ||
|
|
||
| /** | ||
| * Works out how hard the ice is shaking, which is the only warning anyone gets that it is about to | ||
| * let go. | ||
| * | ||
| * @param remaining how much of the freeze is left, in ticks, interpolated within the tick | ||
| * @param frozenFor how long the freeze has run for, in ticks, interpolated within the tick. It is | ||
| * what the shudder is driven off, so that it keeps going rather than restarting | ||
| * every frame. | ||
| */ | ||
| private static Vec3 rattleOf(float remaining, float frozenFor) { | ||
| if (remaining >= Freezing.RATTLE_DURATION) { | ||
| return Vec3.ZERO; | ||
| } | ||
| // it starts as a barely-there tremor and works itself up to the moment the ice gives | ||
| double amplitude = RATTLE_AMPLITUDE * (1.0D - Math.max(remaining, 0.0F) / Freezing.RATTLE_DURATION); | ||
| return new Vec3( | ||
| Mth.sin(frozenFor * RATTLE_FREQUENCY) * amplitude, | ||
| 0.0D, | ||
| Mth.sin(frozenFor * RATTLE_CROSS_FREQUENCY) * amplitude); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
...e-super_mario/src/main/java/fr/hugman/mubble/super_mario/commands/SuperMarioCommands.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package fr.hugman.mubble.super_mario.commands; | ||
|
|
||
| import fr.hugman.mubble.super_mario.server.commands.FreezeCommand; | ||
| import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; | ||
|
|
||
| public class SuperMarioCommands { | ||
| public static void register() { | ||
| CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> FreezeCommand.register(dispatcher)); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.