From c211a287af1c5a20665dafebff651317034bf92f Mon Sep 17 00:00:00 2001 From: Floral <49110090+floral-qua-floral@users.noreply.github.com> Date: Fri, 15 Nov 2024 21:03:00 -0500 Subject: [PATCH] Implemented Double Jumps and Triple Jumps. Implemented the Ground Pound's associated stomp type. Implemented repeat-ground-pounding, where Mario can hold the button down after pounding some blocks to slam them repeatedly. Repeat ground pounding is allowed when the previous pound caused some change to the blockstate. --- .../floralquafloral/bumping/BumpManager.java | 48 +++++++++-- .../handlers/BaselineBumpingHandler.java | 59 ++++++++----- .../handlers/PistonBumpingHandler.java | 2 - .../handlers/SpawnerBumpingHandler.java | 55 ++++++++++++ .../handlers/TrapdoorBumpingHandler.java | 2 +- .../moveable/MarioMainClientData.java | 7 +- .../mariodata/moveable/MarioServerData.java | 2 +- .../mariodata/moveable/MarioTravelData.java | 1 + .../action/AirborneActionDefinition.java | 2 +- .../action/baseactions/GroundPound.java | 4 +- .../action/baseactions/airborne/Backflip.java | 11 +-- .../baseactions/airborne/DoubleJump.java | 70 ++++++++++++++++ .../baseactions/airborne/TripleJump.java | 79 ++++++++++++++++++ .../grounded/GroundPoundLanding.java | 5 +- .../registries/stomp/ParsedStomp.java | 27 +++--- .../registries/stomp/StompHandler.java | 2 +- .../basestomptypes/GroundPoundStomp.java | 71 ++++++++++++++++ .../stomp/basestomptypes/JumpStomp.java | 11 ++- .../com/floralquafloral/util/MarioSFX.java | 1 + .../assets/qua_mario/lang/en_us.json | 1 + .../resources/assets/qua_mario/sounds.json | 6 ++ .../qua_mario/sounds/sfx/stomp/kick.ogg | Bin 0 -> 10363 bytes .../tags/damage_type/panic_causes.json | 8 ++ .../tags/block/always_repeat_bump.json | 6 ++ .../block/bump_regardless_of_hardness.json | 3 +- .../tags/block/never_repeat_bump.json | 8 ++ .../block/resistant_to_ceiling_bumps.json | 5 ++ src/main/resources/fabric.mod.json | 16 ++-- 28 files changed, 445 insertions(+), 67 deletions(-) create mode 100644 src/main/java/com/floralquafloral/bumping/handlers/SpawnerBumpingHandler.java create mode 100644 src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/DoubleJump.java create mode 100644 src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/TripleJump.java create mode 100644 src/main/java/com/floralquafloral/registries/stomp/basestomptypes/GroundPoundStomp.java create mode 100644 src/main/resources/assets/qua_mario/sounds/sfx/stomp/kick.ogg create mode 100644 src/main/resources/data/minecraft/tags/damage_type/panic_causes.json create mode 100644 src/main/resources/data/qua_mario/tags/block/always_repeat_bump.json create mode 100644 src/main/resources/data/qua_mario/tags/block/never_repeat_bump.json create mode 100644 src/main/resources/data/qua_mario/tags/block/resistant_to_ceiling_bumps.json diff --git a/src/main/java/com/floralquafloral/bumping/BumpManager.java b/src/main/java/com/floralquafloral/bumping/BumpManager.java index d521eab..d88fb02 100644 --- a/src/main/java/com/floralquafloral/bumping/BumpManager.java +++ b/src/main/java/com/floralquafloral/bumping/BumpManager.java @@ -7,7 +7,6 @@ import com.floralquafloral.mariodata.MarioClientSideData; import com.floralquafloral.mariodata.MarioData; import com.floralquafloral.mariodata.MarioDataManager; -import com.floralquafloral.mariodata.MarioDataPackets; import com.floralquafloral.mariodata.moveable.MarioMainClientData; import com.floralquafloral.mariodata.moveable.MarioServerData; import com.floralquafloral.mariodata.moveable.MarioTravelData; @@ -24,11 +23,13 @@ import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; import net.minecraft.client.world.ClientWorld; -import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.RegistryByteBuf; import net.minecraft.network.codec.PacketCodec; import net.minecraft.network.codec.PacketCodecs; import net.minecraft.network.packet.CustomPayload; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.BlockSoundGroup; import net.minecraft.sound.SoundCategory; @@ -45,11 +46,16 @@ import java.util.*; +import static com.floralquafloral.MarioQuaMario.MOD_ID; + public abstract class BumpManager { public static final Map BUMPED_BLOCKS = new HashMap<>(); public static final Set HIDDEN_BLOCKS = new HashSet<>(); private static final Set BLOCKS_TO_BUMP = new HashSet<>(); + public static final TagKey ALWAYS_REPEAT_BUMP = TagKey.of(RegistryKeys.BLOCK, Identifier.of(MOD_ID, "always_repeat_bump")); + public static final TagKey NEVER_REPEAT_BUMP = TagKey.of(RegistryKeys.BLOCK, Identifier.of(MOD_ID, "never_repeat_bump")); + private record BlockBumpingPlan(ClientWorld world, BlockPos pos, BlockState state, Direction direction) {} public static void registerPackets() { @@ -57,9 +63,11 @@ public static void registerPackets() { BumpC2SPayload.registerReceiver(); BumpS2CPayload.register(); + AllowRepeatBumpS2CPayload.register(); } public static void registerPacketsClient() { BumpS2CPayload.registerReceiver(); + AllowRepeatBumpS2CPayload.registerReceiver(); } private static BlockPos eyeAdjustmentParticlePos; @@ -79,7 +87,6 @@ public static void registerEventListeners() { if(!BLOCKS_TO_BUMP.isEmpty()) { for(BlockBumpingPlan plan : BLOCKS_TO_BUMP) { visuallyBumpBlock(plan.world, plan.pos, plan.direction); - MarioQuaMario.LOGGER.info("Pos1: {}, Pos2: {}, Equal: {}", plan.pos, eyeAdjustmentParticlePos, plan.pos.equals(eyeAdjustmentParticlePos)); if(plan.pos.equals(eyeAdjustmentParticlePos)) { eyeAdjustmentParticle = BUMPED_BLOCKS.get(plan.pos); } @@ -137,6 +144,7 @@ public static void bumpBlocks(MarioMainClientData data, ClientWorld world, Itera int modifiedStrength = baseStrength + data.getPowerUp().BUMP_STRENGTH_MODIFIER + data.getCharacter().BUMP_STRENGTH_MODIFIER; Set blockSoundGroups = new HashSet<>(); BlockPos lastPos = null; + data.getTimers().canRepeatPound = false; for(BlockPos bumpPos : blocks) { bumpAttemptCount++; lastPos = new BlockPos(bumpPos); @@ -165,7 +173,6 @@ public static void bumpBlocks(MarioMainClientData data, ClientWorld world, Itera ); if(direction == Direction.DOWN && bumpCount == bumpAttemptCount) { - MarioQuaMario.LOGGER.info("Bumped down with full success! Setting EAPP to {}", lastPos); eyeAdjustmentParticlePos = lastPos; } } @@ -239,10 +246,19 @@ public static void bumpResponseCommon( BlockState state, BlockPos pos, int baseStrength, int modifiedStrength, Direction direction ) { - for(BumpingHandler handler : HANDLERS) { - if(handler.bumpResponseCommon(data, travelData, world, state, pos, baseStrength, modifiedStrength, direction)) return; + response: { + for(BumpingHandler handler : HANDLERS) { + if(handler.bumpResponseCommon(data, travelData, world, state, pos, baseStrength, modifiedStrength, direction)) + break response; + } + BASELINE_HANDLER.bumpResponseCommon(data, travelData, world, state, pos, baseStrength, modifiedStrength, direction); + } + if(!state.isIn(NEVER_REPEAT_BUMP) && (state.isIn(ALWAYS_REPEAT_BUMP) || !state.equals(world.getBlockState(pos)))) { + if(data.getMario() instanceof ServerPlayerEntity marioServer) + ServerPlayNetworking.send(marioServer, new AllowRepeatBumpS2CPayload()); + else if(data.getMario().isMainPlayer()) + ((MarioMainClientData) data).getTimers().canRepeatPound = true; // is this even possible?? } - BASELINE_HANDLER.bumpResponseCommon(data, travelData, world, state, pos, baseStrength, modifiedStrength, direction); } public static void bumpResponseClients( @@ -315,4 +331,22 @@ public static void register() { PayloadTypeRegistry.playS2C().register(ID, CODEC); } } + + private record AllowRepeatBumpS2CPayload() implements CustomPayload { + public static final CustomPayload.Id ID = new CustomPayload.Id<>(Identifier.of(MarioQuaMario.MOD_ID, "allow_repeat_bump")); + public static final PacketCodec CODEC = PacketCodec.unit(new AllowRepeatBumpS2CPayload()); + public static void registerReceiver() { + ClientPlayNetworking.registerGlobalReceiver(ID, (payload, context) -> { + MarioMainClientData data = MarioMainClientData.getInstance(); + if(data != null) data.getTimers().canRepeatPound = true; + }); + } + + @Override public Id getId() { + return ID; + } + public static void register() { + PayloadTypeRegistry.playS2C().register(ID, CODEC); + } + } } diff --git a/src/main/java/com/floralquafloral/bumping/handlers/BaselineBumpingHandler.java b/src/main/java/com/floralquafloral/bumping/handlers/BaselineBumpingHandler.java index 64e54d7..3aeb5cf 100644 --- a/src/main/java/com/floralquafloral/bumping/handlers/BaselineBumpingHandler.java +++ b/src/main/java/com/floralquafloral/bumping/handlers/BaselineBumpingHandler.java @@ -55,40 +55,61 @@ private ForcedSignalSpot(BlockPos position, World world) { public static final TagKey UNBREAKABLE_FROM_BUMPING = TagKey.of(RegistryKeys.BLOCK, Identifier.of(MOD_ID, "unbreakable_from_bumping")); // public static final TagKey BRICK_BLOCKS = TagKey.of(RegistryKeys.BLOCK, Identifier.of(MOD_ID, "brick_blocks")); + private float getAdjustedHardness(BlockState state, BlockView world, BlockPos pos, Direction direction) { + float adjustedHardness = state.getHardness(world, pos); + if(state.isTransparent(world, pos) && !state.hasSidedTransparency()) adjustedHardness *= 0.5F; + + return adjustedHardness; + } + @Override @NotNull public BumpLegality evaluateBumpLegality(BlockState state, BlockView world, BlockPos pos, int strength, Direction direction) { if(state.isIn(UNBUMPABLE)) return BumpLegality.IGNORE; if(state.isIn(BUMP_REGARDLESS_OF_HARDNESS) && strength >= 4) return BumpLegality.BUMP; if(state.isIn(EXTREMELY_EASY_TO_BUMP) && strength >= 1) return BumpLegality.BUMP; - float hardnessToolless = state.getHardness(world, pos); - if(state.isToolRequired()) hardnessToolless *= 3.33333333333F; - if(state.isTransparent(world, pos) && !state.hasSidedTransparency()) hardnessToolless *= 0.5F; - if(state.isIn(BlockTags.AXE_MINEABLE)) hardnessToolless *= 3.6F; - if(state.getBlock().toString().contains("brick")) hardnessToolless *= 0.5F; +// float hardnessToolless = state.getHardness(world, pos); +// if(state.isToolRequired()) hardnessToolless *= 3.33333333333F; +// if(state.isTransparent(world, pos) && !state.hasSidedTransparency()) hardnessToolless *= 0.5F; +// if(state.isIn(BlockTags.AXE_MINEABLE)) hardnessToolless *= 3.6F; +// if(state.getBlock().toString().contains("brick")) hardnessToolless *= 0.5F; - if(hardnessToolless == -1) return BumpLegality.IGNORE; + float adjustedHardness = getAdjustedHardness(state, world, pos, direction); + if(adjustedHardness == -1) return BumpLegality.IGNORE; if(strength <= 1) return BumpLegality.IGNORE; - if(strength == 2 && hardnessToolless <= 0.25F) return BumpLegality.SILENT_REACTION; - else if(hardnessToolless <= 2.5F * strength) return BumpLegality.BUMP; + if(strength == 2) return (adjustedHardness <= 0.25F) ? BumpLegality.SILENT_REACTION : BumpLegality.IGNORE; + if(adjustedHardness <= 0.75F * strength) return BumpLegality.BUMP; return BumpLegality.IGNORE; } @Override public boolean bumpResponseCommon(MarioData data, @Nullable MarioTravelData travelData, World world, BlockState state, BlockPos pos, int baseStrength, int modifiedStrength, Direction direction) { - float hardnessToolless = state.getHardness(world, pos); - if(state.isToolRequired()) hardnessToolless *= 3.33333333333F; - if(state.isTransparent(world, pos) && !state.hasSidedTransparency()) hardnessToolless *= 0.5F; - if(state.isIn(BlockTags.AXE_MINEABLE)) hardnessToolless *= 3.6F; - if(state.getBlock().toString().contains("brick")) hardnessToolless *= 0.5F; - - if(!state.isIn(UNBREAKABLE_FROM_BUMPING) && hardnessToolless != -1) { - if (modifiedStrength >= 4 && hardnessToolless <= 3.5 && world.breakBlock(pos, true, data.getMario())) - return true; - - if (modifiedStrength >= 2 && hardnessToolless <= 0.3 && world.breakBlock(pos, true, data.getMario())) + float adjustedHardness = getAdjustedHardness(state, world, pos, direction); + + attemptBreak: if(!state.isIn(UNBREAKABLE_FROM_BUMPING) && adjustedHardness != -1) { + if(modifiedStrength <= 1) break attemptBreak; + + // Super Mario spin-jumping can destroy fairly fragile blocks (ice, + if(modifiedStrength == 2) { + if(adjustedHardness <= 0.25F && world.breakBlock(pos, true, data.getMario())) + return true; + else break attemptBreak; + } +// if(!state.isToolRequired()) adjustedHardness -= 1; + + // Small Mario ground-pounding or bopping a ceiling can only break exceptionally fragile blocks (candles, moss, scaffolding) + if(modifiedStrength == 3) { + if(adjustedHardness < 0.2F && world.breakBlock(pos, true, data.getMario())) + return true; + else break attemptBreak; + } + + // Super Mario gets a bonus to breaking bricks + if(state.getBlock().toString().contains("brick")) adjustedHardness -= 1; + + if(adjustedHardness <= modifiedStrength * 0.25F && world.breakBlock(pos, true, data.getMario())) return true; } diff --git a/src/main/java/com/floralquafloral/bumping/handlers/PistonBumpingHandler.java b/src/main/java/com/floralquafloral/bumping/handlers/PistonBumpingHandler.java index 1f9cace..89ec46e 100644 --- a/src/main/java/com/floralquafloral/bumping/handlers/PistonBumpingHandler.java +++ b/src/main/java/com/floralquafloral/bumping/handlers/PistonBumpingHandler.java @@ -15,8 +15,6 @@ import org.jetbrains.annotations.Nullable; public class PistonBumpingHandler implements BumpingHandler { - public static boolean forceRetract; - @Override public @Nullable BumpLegality evaluateBumpLegality(BlockState state, BlockView world, BlockPos pos, int strength, Direction direction) { if(state.isOf(Blocks.PISTON_HEAD) && state.get(Properties.FACING) == Direction.UP) { diff --git a/src/main/java/com/floralquafloral/bumping/handlers/SpawnerBumpingHandler.java b/src/main/java/com/floralquafloral/bumping/handlers/SpawnerBumpingHandler.java new file mode 100644 index 0000000..3bab6e8 --- /dev/null +++ b/src/main/java/com/floralquafloral/bumping/handlers/SpawnerBumpingHandler.java @@ -0,0 +1,55 @@ +package com.floralquafloral.bumping.handlers; + +import com.floralquafloral.mariodata.MarioClientSideData; +import com.floralquafloral.mariodata.MarioData; +import com.floralquafloral.mariodata.moveable.MarioTravelData; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.SpawnerBlock; +import net.minecraft.block.TrialSpawnerBlock; +import net.minecraft.block.entity.MobSpawnerBlockEntity; +import net.minecraft.block.entity.TrialSpawnerBlockEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class SpawnerBumpingHandler implements BumpingHandler { + @Override + public @Nullable BumpLegality evaluateBumpLegality(BlockState state, BlockView world, BlockPos pos, int strength, Direction direction) { + if(state.isOf(Blocks.SPAWNER) || state.isOf(Blocks.TRIAL_SPAWNER)) { + return BumpLegality.BUMP; + } + return null; + } + + @Override + public boolean bumpResponseCommon(MarioData data, @Nullable MarioTravelData travelData, World world, BlockState state, BlockPos pos, int baseStrength, int modifiedStrength, Direction direction) { + if(world instanceof ServerWorld serverWorld) { + if(world.getBlockEntity(pos) instanceof MobSpawnerBlockEntity spawnerEntity) { + for(int incrementeroo = 0; incrementeroo < 7; incrementeroo++) { + spawnerEntity.getLogic().serverTick(serverWorld, pos); + } + return true; + } + + if(world.getBlockEntity(pos) instanceof TrialSpawnerBlockEntity trialSpawnerEntity) { + for(int incrementeroo = 0; incrementeroo < 3; incrementeroo++) { + trialSpawnerEntity.getSpawner().trySpawnMob(serverWorld, pos); + } + return true; + } + } + + return false; + } + + @Override + public boolean bumpResponseClients(MarioClientSideData data, ClientWorld world, BlockState state, BlockPos pos, int baseStrength, int modifiedStrength, Direction direction) { + return false; + } +} diff --git a/src/main/java/com/floralquafloral/bumping/handlers/TrapdoorBumpingHandler.java b/src/main/java/com/floralquafloral/bumping/handlers/TrapdoorBumpingHandler.java index 0854465..3c79208 100644 --- a/src/main/java/com/floralquafloral/bumping/handlers/TrapdoorBumpingHandler.java +++ b/src/main/java/com/floralquafloral/bumping/handlers/TrapdoorBumpingHandler.java @@ -18,7 +18,7 @@ public class TrapdoorBumpingHandler implements BumpingHandler { @Override public @Nullable BumpLegality evaluateBumpLegality(BlockState state, BlockView world, BlockPos pos, int strength, Direction direction) { - if(state.isIn(BlockTags.TRAPDOORS)) { + if(state.isIn(BlockTags.TRAPDOORS) && strength >= (state.isIn(BlockTags.WOODEN_TRAPDOORS) ? 3 : 4)) { if(state.get(Properties.OPEN)) { // It's an open trapdoor; check to see if we'd be knocking it closed if ( diff --git a/src/main/java/com/floralquafloral/mariodata/moveable/MarioMainClientData.java b/src/main/java/com/floralquafloral/mariodata/moveable/MarioMainClientData.java index 359b1ed..6ef983d 100644 --- a/src/main/java/com/floralquafloral/mariodata/moveable/MarioMainClientData.java +++ b/src/main/java/com/floralquafloral/mariodata/moveable/MarioMainClientData.java @@ -55,7 +55,7 @@ public MarioMainClientData(ClientPlayerEntity mario) { public final float[] CAMERA_ROTATIONS = new float[3]; @Override public void setActionTransitionless(ParsedAction action) { - MarioQuaMario.LOGGER.info("MarioMainClientData setAction to " + action.ID); +// MarioQuaMario.LOGGER.info("MarioMainClientData setAction to " + action.ID); if(action != getAction()) getTimers().actionTimer = 0; // CPM animation @@ -102,6 +102,9 @@ public MarioMainClientData(ClientPlayerEntity mario) { getAction().travelHook(this); getAction().attemptTransitions(this, TransitionPhase.POST_TICK); + getTimers().jumpLandingTime--; + getTimers().doubleJumpLandingTime--; + applyModifiedVelocity(); marioClient.move(MovementType.SELF, marioClient.getVelocity()); @@ -182,7 +185,7 @@ private static class ClientButton implements ButtonInput { } private boolean unbuffer() { - this.pressBuffer = 1; + this.pressBuffer = 0; return true; } diff --git a/src/main/java/com/floralquafloral/mariodata/moveable/MarioServerData.java b/src/main/java/com/floralquafloral/mariodata/moveable/MarioServerData.java index 9a5c8c3..9e60af3 100644 --- a/src/main/java/com/floralquafloral/mariodata/moveable/MarioServerData.java +++ b/src/main/java/com/floralquafloral/mariodata/moveable/MarioServerData.java @@ -34,7 +34,7 @@ public MarioServerData(ServerPlayerEntity mario) { @Override public void setActionTransitionless(ParsedAction action) { - MarioQuaMario.LOGGER.info("MarioServerData setAction to {}", action.ID); +// MarioQuaMario.LOGGER.info("MarioServerData setAction to {}", action.ID); if(this.getAction().ANIMATION != null) CPMIntegration.commonAPI.playAnimation(PlayerEntity.class, this.marioServer, this.getAction().ANIMATION, 0); diff --git a/src/main/java/com/floralquafloral/mariodata/moveable/MarioTravelData.java b/src/main/java/com/floralquafloral/mariodata/moveable/MarioTravelData.java index 68dba2b..6a4046a 100644 --- a/src/main/java/com/floralquafloral/mariodata/moveable/MarioTravelData.java +++ b/src/main/java/com/floralquafloral/mariodata/moveable/MarioTravelData.java @@ -56,5 +56,6 @@ class MarioTimers { public int doubleJumpLandingTime = 0; public boolean jumpCapped = false; + public boolean canRepeatPound = true; } } diff --git a/src/main/java/com/floralquafloral/registries/states/action/AirborneActionDefinition.java b/src/main/java/com/floralquafloral/registries/states/action/AirborneActionDefinition.java index 1278617..57ab48e 100644 --- a/src/main/java/com/floralquafloral/registries/states/action/AirborneActionDefinition.java +++ b/src/main/java/com/floralquafloral/registries/states/action/AirborneActionDefinition.java @@ -54,7 +54,7 @@ public static ActionTransitionDefinition makeJumpCapTransition(ActionDefinition public static final ActionTransitionDefinition GROUND_POUND = new ActionTransitionDefinition( "qua_mario:ground_pound_windup", data -> data.getInputs().DUCK.isPressed(), - null, + data -> data.getMario().fallDistance *= 0.4F, (data, isSelf, seed) -> data.playSoundEvent(MarioSFX.GROUND_POUND_PRE, seed) ); } diff --git a/src/main/java/com/floralquafloral/registries/states/action/baseactions/GroundPound.java b/src/main/java/com/floralquafloral/registries/states/action/baseactions/GroundPound.java index 36450a3..93869f2 100644 --- a/src/main/java/com/floralquafloral/registries/states/action/baseactions/GroundPound.java +++ b/src/main/java/com/floralquafloral/registries/states/action/baseactions/GroundPound.java @@ -39,7 +39,7 @@ public class GroundPound implements ActionDefinition { return SlidingStatus.NOT_SLIDING; } @Override public @Nullable Identifier getStompType() { - return Identifier.of("qua_mario", "stomp"); + return Identifier.of("qua_mario", "ground_pound"); } @Override public void travelHook(MarioTravelData data) { @@ -73,7 +73,7 @@ public class GroundPound implements ActionDefinition { new ActionTransitionDefinition( "qua_mario:ground_pound_landing", AirborneActionDefinition.AerialTransitions.BASIC_LANDING.EVALUATOR, - data -> {}, + data -> data.setForwardStrafeVel(0, 0), (data, isSelf, seed) -> data.playSoundEvent(MarioSFX.GROUND_POUND, seed) ) ); diff --git a/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/Backflip.java b/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/Backflip.java index 149dbd2..080f690 100644 --- a/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/Backflip.java +++ b/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/Backflip.java @@ -56,15 +56,8 @@ else airborneAccel(data, @Override public List getPostTickTransitions() { return List.of( - AerialTransitions.makeJumpCapTransition(this, 0.765), - AerialTransitions.GROUND_POUND - ); - } - - @Override - public List getPostMoveTransitions() { - return List.of( - AerialTransitions.BASIC_LANDING + AerialTransitions.GROUND_POUND, + AerialTransitions.makeJumpCapTransition(this, 0.765) ); } } diff --git a/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/DoubleJump.java b/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/DoubleJump.java new file mode 100644 index 0000000..0b03fde --- /dev/null +++ b/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/DoubleJump.java @@ -0,0 +1,70 @@ +package com.floralquafloral.registries.states.action.baseactions.airborne; + +import com.floralquafloral.MarioQuaMario; +import com.floralquafloral.mariodata.MarioClientSideData; +import com.floralquafloral.mariodata.moveable.MarioTravelData; +import com.floralquafloral.registries.states.action.GroundedActionDefinition; +import com.floralquafloral.stats.CharaStat; +import com.floralquafloral.stats.StatCategory; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +import static com.floralquafloral.util.MixedEasing.*; + +public class DoubleJump extends Jump { + @Override public @NotNull Identifier getID() { + return Identifier.of(MarioQuaMario.MOD_ID, "double_jump"); + } + @Override public @Nullable String getAnimationName() { + return "double-jump"; + } + + public static CharaStat DOUBLE_JUMP_VEL = new CharaStat(0.939, StatCategory.JUMP_VELOCITY); + public static CharaStat DOUBLE_JUMP_VEL_ADDEND = new CharaStat(0.08, StatCategory.JUMP_VELOCITY); + public static CharaStat DOUBLE_JUMP_SPEED_THRESHOLD = new CharaStat(0, + StatCategory.WALKING, StatCategory.FORWARD, StatCategory.THRESHOLD); + + @Override public List getPostTickTransitions() { + return List.of( + AerialTransitions.GROUND_POUND, + AerialTransitions.makeJumpCapTransition(this, 0.285) + ); + } + + @Override + public List getPostMoveTransitions() { + return List.of( + AerialTransitions.TRIPLE_JUMPABLE_LANDING + ); + } + + private ActionTransitionInjection makeInjectionFrom(String otherAction) { + return new ActionTransitionInjection( + ActionTransitionInjection.InjectionPlacement.BEFORE, + otherAction, + ActionTransitionInjection.ActionCategory.GROUNDED, + new ActionTransitionDefinition("qua_mario:double_jump", + data -> + data.getTimers().jumpLandingTime > 0 + && data.getForwardVel() > DOUBLE_JUMP_SPEED_THRESHOLD.get(data) + && GroundedActionDefinition.GroundedTransitions.JUMP.EVALUATOR.shouldTransition(data), + data -> GroundedActionDefinition.GroundedTransitions.performJump(data, DOUBLE_JUMP_VEL, DOUBLE_JUMP_VEL_ADDEND), + (data, isSelf, seed) -> { + data.playJumpSound(seed); + data.voice(MarioClientSideData.VoiceLine.DOUBLE_JUMP, seed); + } + ) + ); + } + + @Override + public List getTransitionInjections() { + return List.of( + makeInjectionFrom("qua_mario:jump"), + makeInjectionFrom("qua_mario:p_jump") + ); + } +} diff --git a/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/TripleJump.java b/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/TripleJump.java new file mode 100644 index 0000000..023c785 --- /dev/null +++ b/src/main/java/com/floralquafloral/registries/states/action/baseactions/airborne/TripleJump.java @@ -0,0 +1,79 @@ +package com.floralquafloral.registries.states.action.baseactions.airborne; + +import com.floralquafloral.MarioQuaMario; +import com.floralquafloral.mariodata.MarioClientSideData; +import com.floralquafloral.registries.states.action.GroundedActionDefinition; +import com.floralquafloral.stats.CharaStat; +import com.floralquafloral.stats.StatCategory; +import com.floralquafloral.util.Easings; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +import static com.floralquafloral.util.MixedEasing.*; + +public class TripleJump extends Jump { + @Override public @NotNull Identifier getID() { + return Identifier.of(MarioQuaMario.MOD_ID, "triple_jump"); + } + @Override public @Nullable String getAnimationName() { + return "triple-jump"; + } + @Override @Nullable public CameraAnimationSet getCameraAnimations() { + return new CameraAnimationSet( + new CameraAnimation( + false, 0.85F, + (progress, offsets) -> offsets[1] = Easings.easeOutQuad(progress) * 360 + ), + null, + null + ); + } + + public static CharaStat TRIPLE_JUMP_VEL = new CharaStat(1.175, StatCategory.JUMP_VELOCITY); + public static CharaStat TRIPLE_JUMP_SPEED_THRESHOLD = new CharaStat(0.34, + StatCategory.RUNNING, StatCategory.FORWARD, StatCategory.THRESHOLD); + + @Override public List getPostTickTransitions() { + return List.of( + AerialTransitions.GROUND_POUND, + AerialTransitions.makeJumpCapTransition(this, 0.65) + ); + } + + private ActionTransitionInjection makeInjectionFrom(String otherAction) { + return new ActionTransitionInjection( + ActionTransitionInjection.InjectionPlacement.BEFORE, + otherAction, + ActionTransitionInjection.ActionCategory.GROUNDED, + new ActionTransitionDefinition("qua_mario:triple_jump", + data -> + data.getTimers().doubleJumpLandingTime > 0 + && data.getForwardVel() > TRIPLE_JUMP_SPEED_THRESHOLD.get(data) + && GroundedActionDefinition.GroundedTransitions.JUMP.EVALUATOR.shouldTransition(data), + data -> GroundedActionDefinition.GroundedTransitions.performJump(data, TRIPLE_JUMP_VEL, null), + (data, isSelf, seed) -> { + data.playJumpSound(seed); + data.voice(MarioClientSideData.VoiceLine.TRIPLE_JUMP, seed); + } + ) + ); + } + + @Override + public List getPostMoveTransitions() { + return List.of( + AerialTransitions.BASIC_LANDING + ); + } + + @Override + public List getTransitionInjections() { + return List.of( + makeInjectionFrom("qua_mario:jump"), + makeInjectionFrom("qua_mario:p_jump") + ); + } +} diff --git a/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/GroundPoundLanding.java b/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/GroundPoundLanding.java index 2a0987f..8623edc 100644 --- a/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/GroundPoundLanding.java +++ b/src/main/java/com/floralquafloral/registries/states/action/baseactions/grounded/GroundPoundLanding.java @@ -51,8 +51,11 @@ public void groundedTravel(MarioTravelData data) { @Override public List getPreTickTransitions() { return List.of( + new ActionTransitionDefinition("qua_mario:ground_pound", + data -> data.getTimers().actionTimer > 3 && data.getInputs().DUCK.isHeld() && data.getTimers().canRepeatPound + ), new ActionTransitionDefinition("qua_mario:basic", - data -> (data.getTimers().actionTimer > 3 && !data.getInputs().DUCK.isHeld()) + data -> (data.getTimers().actionTimer > 4 && !data.getInputs().DUCK.isHeld()) ) ); } diff --git a/src/main/java/com/floralquafloral/registries/stomp/ParsedStomp.java b/src/main/java/com/floralquafloral/registries/stomp/ParsedStomp.java index 4ebc2c2..feeabfe 100644 --- a/src/main/java/com/floralquafloral/registries/stomp/ParsedStomp.java +++ b/src/main/java/com/floralquafloral/registries/stomp/ParsedStomp.java @@ -34,6 +34,7 @@ import org.jetbrains.annotations.Nullable; import java.util.List; +import java.util.Objects; public class ParsedStomp { public final Identifier ID; @@ -62,7 +63,7 @@ public ParsedStomp(StompDefinition definition) { this.POST_STOMP_ACTION = definition.getPostStompAction(); } - public void executeServer(MarioServerData data, Entity target, boolean affectMario, boolean harmless, long seed) { + public boolean executeServer(MarioServerData data, Entity target, boolean affectMario, boolean harmless, long seed) { ServerPlayerEntity mario = data.getMario(); // DamageSource damageSource = makeDamageSource(mario.getServerWorld(), this.DAMAGE_TYPE, mario); StompDamageSource stompDamageSource = new StompDamageSource(mario.getServerWorld(), this.DAMAGE_TYPE, mario); @@ -98,13 +99,16 @@ else if (entry.attribute().value().equals(EntityAttributes.GENERIC_ARMOR_TOUGHNE float damage = this.DEFINITION.calculateDamage(data, mario, attackingArmor, armor, target); stompDamageSource.piercing = 2.0F * toughness; - target.damage(stompDamageSource, Math.max(1.0F, damage - 0.6F * stompDamageSource.piercing)); - - if(affectMario) { - this.DEFINITION.executeTravellers(data, target, harmless); - data.setActionTransitionless(RegistryManager.ACTIONS.get(this.POST_STOMP_ACTION)); - StompHandler.networkStomp(data.getMario(), target, this, harmless, seed); + if(target.damage(stompDamageSource, Math.max(1.0F, damage - 0.6F * stompDamageSource.piercing))) { + if(affectMario) { + this.DEFINITION.executeTravellers(data, target, harmless); + if(this.POST_STOMP_ACTION != null) + data.setActionTransitionless(Objects.requireNonNull(RegistryManager.ACTIONS.get(this.POST_STOMP_ACTION))); + StompHandler.networkStomp(data.getMario(), target, this, harmless, seed); + } + return true; } + return false; } public void executeClient(MarioClientSideData data, boolean isSelf, Entity target, boolean harmless, long seed) { if(this.SOUND_EVENT != null) { @@ -124,6 +128,8 @@ public void executeClient(MarioClientSideData data, boolean isSelf, Entity targe if(data instanceof MarioMoveableData moveableData) { moveableData.getTimers().jumpCapped = false; this.DEFINITION.executeTravellers(moveableData, target, harmless); + if(this.POST_STOMP_ACTION != null) + moveableData.setActionTransitionless(Objects.requireNonNull(RegistryManager.ACTIONS.get(this.POST_STOMP_ACTION))); moveableData.applyModifiedVelocity(); } @@ -132,7 +138,7 @@ public void executeClient(MarioClientSideData data, boolean isSelf, Entity targe public boolean attempt(MarioServerData data, Vec3d movement) { ServerPlayerEntity mario = data.getMario(); - List targets = mario.getWorld().getOtherEntities(mario, mario.getBoundingBox().stretch(movement)); + List targets = mario.getWorld().getOtherEntities(mario, mario.getBoundingBox().stretch(movement.multiply(1, 2, 1))); boolean affectMario = true; long seed = RandomSeed.getSeed(); @@ -171,10 +177,7 @@ else if(this.PAINFUL_STOMP_RESPONSE == StompDefinition.PainfulStompResponse.BOUN } - - executeServer(data, target, affectMario, harmless, seed); - - return true; + return executeServer(data, target, affectMario, harmless, seed); } private static DamageSource makeDamageSource(ServerWorld world, RegistryKey key, Entity attacker) { diff --git a/src/main/java/com/floralquafloral/registries/stomp/StompHandler.java b/src/main/java/com/floralquafloral/registries/stomp/StompHandler.java index a740ad1..e2cc769 100644 --- a/src/main/java/com/floralquafloral/registries/stomp/StompHandler.java +++ b/src/main/java/com/floralquafloral/registries/stomp/StompHandler.java @@ -70,7 +70,7 @@ public static void registerReceiver() { } MarioPlayerData data = getMarioData(mario); stompType.executeClient((MarioClientSideData) data, mario.isMainPlayer(), target, payload.harmless, payload.seed); - data.setActionTransitionless(RegistryManager.ACTIONS.get(stompType.POST_STOMP_ACTION)); +// data.setActionTransitionless(RegistryManager.ACTIONS.get(stompType.POST_STOMP_ACTION)); }); } diff --git a/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/GroundPoundStomp.java b/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/GroundPoundStomp.java new file mode 100644 index 0000000..83de95c --- /dev/null +++ b/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/GroundPoundStomp.java @@ -0,0 +1,71 @@ +package com.floralquafloral.registries.stomp.basestomptypes; + +import com.floralquafloral.MarioQuaMario; +import com.floralquafloral.mariodata.MarioClientSideData; +import com.floralquafloral.mariodata.MarioData; +import com.floralquafloral.mariodata.moveable.MarioTravelData; +import com.floralquafloral.registries.stomp.StompDefinition; +import com.floralquafloral.registries.stomp.StompHandler; +import com.floralquafloral.stats.CharaStat; +import com.floralquafloral.stats.StatCategory; +import com.floralquafloral.util.MarioSFX; +import net.minecraft.entity.Entity; +import net.minecraft.entity.MovementType; +import net.minecraft.item.ItemStack; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class GroundPoundStomp implements StompDefinition { + @Override public @NotNull Identifier getID() { + return Identifier.of(MarioQuaMario.MOD_ID, "ground_pound"); + } + + public final CharaStat BASE_DAMAGE = new CharaStat(7, StatCategory.STOMP_BASE_DAMAGE); + + @Override public boolean mustFallOnTarget() { + return false; + } + + @Override public @NotNull PainfulStompResponse getPainfulStompResponse() { + return PainfulStompResponse.INJURY; + } + + @Override public boolean shouldAttemptMounting() { + return true; + } + + @Override public boolean canHitNonLiving() { + return true; + } + + @Override public @NotNull Identifier getDamageType() { + return Identifier.of(MarioQuaMario.MOD_ID, "ground_pound"); + } + @Override public @Nullable SoundEvent getSoundEvent() { + return MarioSFX.KICK; + } + + @Override public @Nullable Identifier getPostStompAction() { + return null; + } + + @Override public boolean canStompTarget(MarioData data, Entity target) { + return !target.getType().isIn(StompHandler.IMMUNE_TO_BASIC_STOMP_TAG); + } + + @Override public float calculateDamage(MarioData data, ServerPlayerEntity mario, ItemStack equipment, float equipmentArmorValue, Entity target) { + return ((float) BASE_DAMAGE.get(data)) + equipmentArmorValue; + } + + @Override public void executeTravellers(MarioTravelData data, Entity target, boolean harmless) { + double deltaY = data.getMario().getY() - (target.getY() - target.getHeight()); + } + + @Override public void executeClients(MarioClientSideData data, boolean isSelf, Entity target, boolean harmless, long seed) { + + } +} diff --git a/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/JumpStomp.java b/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/JumpStomp.java index 6a56316..0d252e0 100644 --- a/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/JumpStomp.java +++ b/src/main/java/com/floralquafloral/registries/stomp/basestomptypes/JumpStomp.java @@ -63,8 +63,15 @@ public class JumpStomp implements StompDefinition { } @Override public void executeTravellers(MarioTravelData data, Entity target, boolean harmless) { - double deltaY = data.getMario().getY() - (target.getY() - target.getHeight()); - data.getMario().move(MovementType.PISTON, new Vec3d(0, deltaY, 0)); + double deltaY = (target.getY() + target.getHeight()) - data.getMario().getY(); +// MarioQuaMario.LOGGER.info("executeTravellers 1:" +// + "\nTarget: " + target +// + "\nTargetY: " + (target.getY() + target.getHeight()) +// + "\nMarioY: " + data.getMario().getY() +// + "\ndeltaY: " + (data.getMario().getY()) +// ); + data.getMario().move(MovementType.SELF, new Vec3d(0, deltaY, 0)); +// data.getMario().setPos(data.getMario().getX(), target.getY() + target.getHeight(), data.getMario().getZ()); data.setYVel(BOUNCE_VEL.get(data)); } diff --git a/src/main/java/com/floralquafloral/util/MarioSFX.java b/src/main/java/com/floralquafloral/util/MarioSFX.java index f6a1f28..c16c20e 100644 --- a/src/main/java/com/floralquafloral/util/MarioSFX.java +++ b/src/main/java/com/floralquafloral/util/MarioSFX.java @@ -24,6 +24,7 @@ public final class MarioSFX { public static final SoundEvent STOMP_SPIN = makeStompSound("spin"); public static final SoundEvent STOMP_HEAVY = makeStompSound("heavy"); public static final SoundEvent STOMP_YOSHI = makeStompSound("yoshi"); + public static final SoundEvent KICK = makeStompSound("kick"); public static final SoundEvent DUCK = makeActionSound("duck"); public static final SoundEvent UNDUCK = makeActionSound("unduck"); diff --git a/src/main/resources/assets/qua_mario/lang/en_us.json b/src/main/resources/assets/qua_mario/lang/en_us.json index 8f90a58..07c90a8 100644 --- a/src/main/resources/assets/qua_mario/lang/en_us.json +++ b/src/main/resources/assets/qua_mario/lang/en_us.json @@ -41,6 +41,7 @@ "subtitles.qua_mario.stomp.spin": "Something gets stomped", "subtitles.qua_mario.stomp.heavy": "Something gets stomped", "subtitles.qua_mario.stomp.yoshi": "Something gets crushed", + "subtitles.qua_mario.stomp.kick": "Something gets attacked", "subtitles.qua_mario.action.duck": "Ducking", "subtitles.qua_mario.action.unduck": "Un-ducking", "subtitles.qua_mario.action.ground_pound_pre": "Aerial flip", diff --git a/src/main/resources/assets/qua_mario/sounds.json b/src/main/resources/assets/qua_mario/sounds.json index 67d3e6f..e635b76 100644 --- a/src/main/resources/assets/qua_mario/sounds.json +++ b/src/main/resources/assets/qua_mario/sounds.json @@ -89,6 +89,12 @@ "qua_mario:sfx/stomp/yoshi" ] }, + "sfx.stomp.kick": { + "subtitle": "subtitles.qua_mario.stomp.kick", + "sounds": [ + "qua_mario:sfx/stomp/kick" + ] + }, "sfx.action.duck": { "subtitle": "subtitles.qua_mario.action.duck", "sounds": [ diff --git a/src/main/resources/assets/qua_mario/sounds/sfx/stomp/kick.ogg b/src/main/resources/assets/qua_mario/sounds/sfx/stomp/kick.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b4c84cd9197d9c17c50a22337aedce73f7f02b4c GIT binary patch literal 10363 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