diff --git a/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java b/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java index 64038eb6..0771db9d 100644 --- a/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java +++ b/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java @@ -3,6 +3,7 @@ package com.minelittlepony.unicopia; import java.util.function.Predicate; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.MagicImmune; import com.minelittlepony.unicopia.item.enchantment.WantItNeedItEnchantment; @@ -18,11 +19,13 @@ public interface EquinePredicates { Predicate CHANGELING = physicalRaceMatches(Race.CHANGELING::equals); Predicate RACE_INTERACT_WITH_CLOUDS = raceMatches(Race::canInteractWithClouds); + Predicate RAGING = IS_PLAYER.and(SpellType.RAGE::isOn); Predicate PLAYER_EARTH = IS_PLAYER.and(ofRace(Race.EARTH)); Predicate PLAYER_BAT = IS_PLAYER.and(BAT); Predicate PLAYER_UNICORN = IS_PLAYER.and(raceMatches(Race::canCast)); Predicate PLAYER_CHANGELING = IS_PLAYER.and(ofRace(Race.CHANGELING)); + Predicate PLAYER_KIRIN = IS_PLAYER.and(ofRace(Race.KIRIN)); Predicate PLAYER_PEGASUS = IS_PLAYER.and(e -> ((PlayerEntity)e).getAbilities().creativeMode || RACE_INTERACT_WITH_CLOUDS.test(e)); Predicate PLAYER_CAN_USE_EARTH = IS_PLAYER.and(raceMatches(Race::canUseEarth)); diff --git a/src/main/java/com/minelittlepony/unicopia/InteractionManager.java b/src/main/java/com/minelittlepony/unicopia/InteractionManager.java index 609aede2..0e507331 100644 --- a/src/main/java/com/minelittlepony/unicopia/InteractionManager.java +++ b/src/main/java/com/minelittlepony/unicopia/InteractionManager.java @@ -27,6 +27,7 @@ public class InteractionManager { public static final int SOUND_GLIDING = 4; public static final int SOUND_MAGIC_BEAM = 5; public static final int SOUND_HEART_BEAT = 6; + public static final int SOUND_KIRIN_RAGE = 7; public static final int SCREEN_DISPELL_ABILITY = 0; diff --git a/src/main/java/com/minelittlepony/unicopia/USounds.java b/src/main/java/com/minelittlepony/unicopia/USounds.java index 60774fcc..fc9a1454 100644 --- a/src/main/java/com/minelittlepony/unicopia/USounds.java +++ b/src/main/java/com/minelittlepony/unicopia/USounds.java @@ -25,9 +25,12 @@ public interface USounds { SoundEvent ENTITY_PLAYER_CHANGELING_FEED = ENTITY_GENERIC_DRINK; SoundEvent ENTITY_PLAYER_CHANGELING_CLIMB = ENTITY_CHICKEN_STEP; SoundEvent ENTITY_PLAYER_UNICORN_TELEPORT = register("entity.player.unicorn.teleport"); + SoundEvent ENTITY_PLAYER_KIRIN_RAGE = ENTITY_POLAR_BEAR_WARNING; + SoundEvent ENTITY_PLAYER_KIRIN_RAGE_LOOP = register("entity.player.kirin.rage.loop"); SoundEvent ENTITY_PLAYER_EARS_RINGING = register("entity.player.ears_ring"); SoundEvent ENTITY_PLAYER_HEARTBEAT = register("entity.player.heartbeat"); + SoundEvent ENTITY_PLAYER_HEARTBEAT_LOOP = register("entity.player.heartbeat_loop"); SoundEvent ENTITY_PLAYER_WOLOLO = register("entity.player.wololo"); SoundEvent ENTITY_PLAYER_WHISTLE = register("entity.player.whistle"); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java b/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java index 76773051..137bb64a 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java @@ -53,6 +53,9 @@ public interface Abilities { Ability HANG = register(new BatPonyHangAbility(), "hang", AbilitySlot.TERTIARY); Ability EEEE = register(new BatEeeeAbility(), "eee", AbilitySlot.SECONDARY); + // kirin + Ability RAGE = register(new KirinRageAbility(), "rage", AbilitySlot.PRIMARY); + static > T register(T power, String name, AbilitySlot slot) { Identifier id = Unicopia.id(name); BY_SLOT.computeIfAbsent(slot, s -> new LinkedHashSet<>()).add(power); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/KirinRageAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/KirinRageAbility.java new file mode 100644 index 00000000..92d0074c --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/KirinRageAbility.java @@ -0,0 +1,88 @@ +package com.minelittlepony.unicopia.ability; + +import java.util.Optional; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.USounds; +import com.minelittlepony.unicopia.ability.data.Hit; +import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.block.state.StateMaps; +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.minecraft.particle.ParticleTypes; +import net.minecraft.sound.SoundCategory; +import net.minecraft.text.Text; + +/** + * Kirin ability to transform into a nirik + */ +public class KirinRageAbility implements Ability { + @Override + public int getWarmupTime(Pony player) { + return 30; + } + + @Override + public int getCooldownTime(Pony player) { + return 60; + } + + @Override + public boolean canUse(Race race) { + return race == Race.KIRIN; + } + + @Nullable + @Override + public Optional prepare(Pony player) { + return Hit.INSTANCE; + } + + @Override + public Hit.Serializer getSerializer() { + return Hit.SERIALIZER; + } + + @Override + public double getCostEstimate(Pony player) { + return 0; + } + + @Override + public boolean apply(Pony player, Hit data) { + + if (player.consumeSuperMove()) { + player.getMagicalReserves().getCharge().set(0); + SpellType.RAGE.withTraits().apply(player, CastingMethod.INNATE); + } else { + int type = 1 + player.asWorld().random.nextInt(4); + player.asEntity().sendMessage(Text.translatable("ability.unicopia.too_calm." + type), true); + if (type == 4) { + player.getMagicalReserves().getCharge().addPercent(1); + } + player.asEntity().addExhaustion(1.5F); + + if (StateMaps.BURNABLE.convert(player.asWorld(), player.getOrigin().down())) { + player.playSound(USounds.SPELL_FIRE_CRACKLE, 1); + } + } + + return true; + } + + @Override + public void warmUp(Pony player, AbilitySlot slot) { + player.spawnParticles(ParticleTypes.LAVA, 4); + player.getMagicalReserves().getEnergy().addPercent(1.03F); + if (player.asEntity().age % 15 == 0) { + player.asWorld().playSound(player.asEntity(), player.getOrigin(), USounds.ENTITY_PLAYER_KIRIN_RAGE, SoundCategory.PLAYERS, 1F, 0.0125F); + } + } + + @Override + public void coolDown(Pony player, AbilitySlot slot) { + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RageAbilitySpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RageAbilitySpell.java new file mode 100644 index 00000000..c961a7fd --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RageAbilitySpell.java @@ -0,0 +1,124 @@ +package com.minelittlepony.unicopia.ability.magic.spell; + +import com.minelittlepony.unicopia.InteractionManager; +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.*; +import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; +import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation.Recipient; +import com.minelittlepony.unicopia.entity.player.Pony; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.damage.DamageTypes; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; +import net.minecraft.world.World.ExplosionSourceType; +import net.minecraft.world.explosion.Explosion; +import net.minecraft.world.explosion.ExplosionBehavior; + +/** + * Internal. + *

+ * Used by the Rage ability. + */ +public class RageAbilitySpell extends AbstractSpell { + private int age; + private int ticksExtenguishing; + + public RageAbilitySpell(CustomisedSpellType type) { + super(type); + setHidden(true); + } + + @Override + public boolean tick(Caster source, Situation situation) { + + if (situation != Situation.BODY || source.asEntity().isRemoved()) { + return false; + } + + if (source.asEntity().isInsideWaterOrBubbleColumn()) { + ticksExtenguishing++; + source.playSound(SoundEvents.ENTITY_GENERIC_EXTINGUISH_FIRE, 1); + source.spawnParticles(ParticleTypes.CLOUD, 12); + setDirty(); + } else { + ticksExtenguishing = 0; + } + + if (ticksExtenguishing > 10) { + return false; + } + + BlockPos pos = source.getOrigin(); + + if (!source.isClient()) { + if (age == 0) { + source.asWorld().createExplosion(source.asEntity(), source.damageOf(DamageTypes.FIREBALL), new ExplosionBehavior(){ + @Override + public boolean canDestroyBlock(Explosion explosion, BlockView world, BlockPos pos, BlockState state, float power) { + return false; + } + }, source.getOriginVector(), 0, true, ExplosionSourceType.MOB); + + if (source instanceof Pony pony) { + pony.setAnimation(Animation.ARMS_UP, Recipient.ANYONE, 12); + } + source.playSound(SoundEvents.ENTITY_POLAR_BEAR_WARNING, 2F, 0.1F); + } + + if (source.asEntity().isOnGround() && source.asWorld().isAir(pos) && age % 10 == 0) { + source.asWorld().setBlockState(pos, Blocks.FIRE.getDefaultState()); + } + + if (source instanceof Pony pony) { + if (pony.asEntity().getAttackCooldownProgress(0) == 0) { + LivingEntity adversary = pony.asEntity().getPrimeAdversary(); + if (adversary != null) { + adversary.setOnFireFor(10); + } + } + } + } else { + if (age % 5 == 0) { + source.spawnParticles(ParticleTypes.LAVA, 4); + source.subtractEnergyCost(Math.min(12, 3 + source.asEntity().getVelocity().length() * 0.1)); + } + } + + if (source instanceof Pony pony) { + if (source.isClient() && pony.asEntity().getAttackCooldownProgress(0) == 0) { + InteractionManager.instance().playLoopingSound(source.asEntity(), InteractionManager.SOUND_KIRIN_RAGE, source.asWorld().random.nextLong()); + } + pony.getMagicalReserves().getEnergy().add(0.5F + (age / 1000F)); + pony.getMagicalReserves().getMana().add(-1); + if (pony.getMagicalReserves().getMana().get() <= 0) { + return false; + } + } + + if (source.asWorld().hasRain(pos.up()) && source.asWorld().random.nextInt(15) == 0) { + source.playSound(SoundEvents.ENTITY_GENERIC_EXTINGUISH_FIRE, 0.3F); + source.spawnParticles(ParticleTypes.CLOUD, 3); + } + + age++; + setDirty(); + return true; + } + + @Override + public void toNBT(NbtCompound compound) { + super.toNBT(compound); + compound.putInt("age", age); + } + + @Override + public void fromNBT(NbtCompound compound) { + super.fromNBT(compound); + age = compound.getInt("age"); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java index d5607e7d..63c5eddc 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SpellType.java @@ -14,6 +14,7 @@ import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.DispersableDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.RainboomAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell; +import com.minelittlepony.unicopia.ability.magic.spell.RageAbilitySpell; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.ThrowableSpell; import com.minelittlepony.unicopia.ability.magic.spell.TimeControlAbilitySpell; @@ -49,6 +50,7 @@ public final class SpellType implements Affine, SpellPredicate< public static final SpellType CHANGELING_DISGUISE = register("disguise", Affinity.BAD, 0x19E48E, false, SpellTraits.EMPTY, DispersableDisguiseSpell::new); public static final SpellType RAINBOOM = register("rainboom", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, RainboomAbilitySpell::new); + public static final SpellType RAGE = register("rage", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, RageAbilitySpell::new); public static final SpellType TIME_CONTROL = register("time_control", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, TimeControlAbilitySpell::new); public static final SpellType FROST = register("frost", Affinity.GOOD, 0xEABBFF, true, IceSpell.DEFAULT_TRAITS, IceSpell::new); diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/StateMaps.java b/src/main/java/com/minelittlepony/unicopia/block/state/StateMaps.java index d2f238eb..62407c35 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/state/StateMaps.java +++ b/src/main/java/com/minelittlepony/unicopia/block/state/StateMaps.java @@ -7,6 +7,7 @@ public interface StateMaps { BlockStateConverter ICE_AFFECTED = of("ice"); BlockStateConverter SILVERFISH_AFFECTED = of("infestation"); BlockStateConverter FIRE_AFFECTED = of("fire"); + BlockStateConverter BURNABLE = of("burnable"); ReversableBlockStateConverter HELLFIRE_AFFECTED = of("hellfire"); private static ReversableBlockStateConverter of(String name) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java b/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java index 56e72bf9..0be0da61 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java +++ b/src/main/java/com/minelittlepony/unicopia/client/ClientInteractionManager.java @@ -1,12 +1,15 @@ package com.minelittlepony.unicopia.client; +import java.lang.ref.WeakReference; import java.util.Map; import java.util.Optional; import java.util.function.Predicate; +import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.FlightType; import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.USounds; @@ -20,11 +23,13 @@ import com.minelittlepony.unicopia.entity.player.dummy.DummyClientPlayerEntity; import com.minelittlepony.unicopia.server.world.Ether; import com.mojang.authlib.GameProfile; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.minecraft.client.MinecraftClient; import net.minecraft.client.sound.AggressiveBeeSoundInstance; import net.minecraft.client.sound.MovingMinecartSoundInstance; import net.minecraft.client.sound.PassiveBeeSoundInstance; -import net.minecraft.client.sound.SoundManager; +import net.minecraft.client.sound.TickableSoundInstance; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; @@ -45,6 +50,8 @@ public class ClientInteractionManager extends InteractionManager { private final Optional clientWorld = Optional.of(() -> MinecraftClient.getInstance().world); + private final Int2ObjectMap> playingSounds = new Int2ObjectOpenHashMap<>(); + @Override public Optional getCasterView(BlockView view) { if (view instanceof ServerWorld world) { @@ -61,44 +68,63 @@ public class ClientInteractionManager extends InteractionManager { @Override public void playLoopingSound(Entity source, int type, long seed) { client.execute(() -> { - SoundManager soundManager = client.getSoundManager(); - if (type == SOUND_EARS_RINGING && source instanceof LivingEntity living) { - soundManager.playNextTick(new LoopingSoundInstance<>(living, + play(type, () -> new LoopingSoundInstance<>(living, createTicker(100).and(e -> !e.isRemoved()), USounds.ENTITY_PLAYER_EARS_RINGING, 0.01F, 2, Random.create(seed)).setFadeIn() ); } else if (type == SOUND_BEE && source instanceof BeeEntity bee) { - soundManager.playNextTick( + play(type, () -> bee.hasAngerTime() ? new AggressiveBeeSoundInstance(bee) : new PassiveBeeSoundInstance(bee) ); } else if (type == SOUND_MINECART && source instanceof AbstractMinecartEntity minecart) { - soundManager.playNextTick(new MovingMinecartSoundInstance(minecart)); + play(type, () -> new MovingMinecartSoundInstance(minecart)); } else if (type == SOUND_CHANGELING_BUZZ && source instanceof PlayerEntity player) { - soundManager.playNextTick(new MotionBasedSoundInstance<>(USounds.ENTITY_PLAYER_CHANGELING_BUZZ, player, e -> { + play(type, () -> new MotionBasedSoundInstance<>(USounds.ENTITY_PLAYER_CHANGELING_BUZZ, player, e -> { PlayerPhysics physics = Pony.of(e).getPhysics(); return physics.isFlying() && physics.getFlightType() == FlightType.INSECTOID; }, 0.25F, 0.5F, 0.66F, Random.create(seed))); } else if (type == SOUND_GLIDING && source instanceof PlayerEntity player && isClientPlayer(player)) { - soundManager.playNextTick(new MotionBasedSoundInstance<>(USounds.Vanilla.ITEM_ELYTRA_FLYING, player, e -> { + play(type, () -> new MotionBasedSoundInstance<>(USounds.Vanilla.ITEM_ELYTRA_FLYING, player, e -> { Pony pony = Pony.of(e); return pony.getPhysics().isFlying() && pony.getPhysics().getFlightType().isAvian(); }, 0, 1, 1, Random.create(seed))); } else if (type == SOUND_GLIDING && source instanceof PlayerEntity player) { - soundManager.playNextTick(new MotionBasedSoundInstance<>(USounds.ENTITY_PLAYER_PEGASUS_FLYING, player, e -> { + play(type, () -> new MotionBasedSoundInstance<>(USounds.ENTITY_PLAYER_PEGASUS_FLYING, player, e -> { Pony pony = Pony.of(e); return pony.getPhysics().isFlying() && pony.getPhysics().getFlightType().isAvian(); }, 0, 1, 1, Random.create(seed))); } else if (type == SOUND_MAGIC_BEAM) { - soundManager.playNextTick(new LoopedEntityTrackingSoundInstance(USounds.SPELL_CAST_SHOOT, 0.3F, 1F, source, seed)); + play(type, () -> new LoopedEntityTrackingSoundInstance(USounds.SPELL_CAST_SHOOT, 0.3F, 1F, source, seed)); } else if (type == SOUND_HEART_BEAT) { - soundManager.playNextTick(new NonLoopingFadeOutSoundInstance(USounds.ENTITY_PLAYER_HEARTBEAT, SoundCategory.PLAYERS, 0.3F, Random.create(seed), 80L)); + play(type, () -> new NonLoopingFadeOutSoundInstance(USounds.ENTITY_PLAYER_HEARTBEAT_LOOP, SoundCategory.PLAYERS, 0.3F, Random.create(seed), 80L)); + } else if (type == SOUND_KIRIN_RAGE) { + play(type, () -> new FadeOutSoundInstance(USounds.ENTITY_PLAYER_KIRIN_RAGE_LOOP, SoundCategory.AMBIENT, 0.3F, Random.create(seed)) { + @Override + protected boolean shouldKeepPlaying() { + return EquinePredicates.RAGING.test(source); + } + }); } }); } + private void play(int type, Supplier soundSupplier) { + WeakReference activeSound = playingSounds.get(type); + TickableSoundInstance existing; + if (activeSound == null || (existing = activeSound.get()) == null || existing.isDone()) { + existing = soundSupplier.get(); + playingSounds.put(type, new WeakReference<>(existing)); + playNow(existing); + } + } + + private void playNow(TickableSoundInstance sound) { + client.getSoundManager().playNextTick(sound); + } + static Predicate createTicker(int ticks) { int[] ticker = new int[] {ticks}; return entity -> ticker[0]-- > 0; diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java index bc8c5e76..23eead6a 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java @@ -113,7 +113,7 @@ public class UHud { float exhaustion = pony.getMagicalReserves().getExhaustion().getPercentFill(); - if (exhaustion > 0.5F) { + if (exhaustion > 0.5F || EquinePredicates.RAGING.test(client.player)) { Random rng = client.world.random; hudX += rng.nextFloat() - 0.5F; hudY += rng.nextFloat() - 0.5F; @@ -264,7 +264,7 @@ public class UHud { client.getSoundManager().play( heartbeatSound = new LoopingSoundInstance<>(client.player, player -> { return partySound == null && Pony.of(player).getMagicalReserves().getExhaustion().getPercentFill() > 0.5F; - }, USounds.ENTITY_PLAYER_HEARTBEAT, 1, 1, client.world.random) + }, USounds.ENTITY_PLAYER_HEARTBEAT_LOOP, 1, 1, client.world.random) ); } @@ -273,6 +273,17 @@ public class UHud { renderVignette(context, 0x880000, exhaustion * radius, 0.1F + radius * 0.3F, scaledWidth, scaledHeight); } + + float anger = pony.getMagicalReserves().getCharge().getPercentFill(); + + if (pony.getObservedSpecies() == Race.KIRIN && anger >= 1F) { + float radius = (1 + (float)Math.sin(client.player.age / 25F)) / 5F; + renderVignette(context, 0x000000, anger * radius, 0.1F + radius * 0.3F, scaledWidth, scaledHeight); + } + + if (EquinePredicates.RAGING.test(client.player)) { + context.fill(0, 0, scaledWidth, scaledHeight, 0x3AFF0000); + } } private void renderVignette(DrawContext context, int color, float alpha, float radius, int scaledWidth, int scaledHeight) { diff --git a/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegateImpl.java b/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegateImpl.java index 9b4a600d..f8f0ec24 100644 --- a/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegateImpl.java +++ b/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegateImpl.java @@ -89,8 +89,15 @@ public class TrinketsDelegateImpl implements TrinketsDelegate { TrinketsApi.registerTrinket(item, new UnicopiaTrinket(item)); } + private Optional getTrinketComponent(LivingEntity entity) { + try { + return TrinketsApi.getTrinketComponent(entity); + } catch (Throwable ingnored) {} + return Optional.empty(); + } + public Optional getInventory(LivingEntity entity, Identifier slot) { - return TrinketsApi.getTrinketComponent(entity) + return getTrinketComponent(entity) .map(component -> component.getInventory() .getOrDefault(slot.getNamespace(), Map.of()) .getOrDefault(slot.getPath(), null) @@ -98,7 +105,7 @@ public class TrinketsDelegateImpl implements TrinketsDelegate { } public Stream getInventories(LivingEntity entity) { - return TrinketsApi.getTrinketComponent(entity) + return getTrinketComponent(entity) .stream() .map(component -> component.getInventory()) .flatMap(groups -> groups.values().stream()) @@ -106,7 +113,7 @@ public class TrinketsDelegateImpl implements TrinketsDelegate { } public Optional getGroup(LivingEntity entity, Identifier slotId) { - return TrinketsApi.getTrinketComponent(entity) + return getTrinketComponent(entity) .stream() .map(component -> component.getGroups().get(slotId.getNamespace())) .findFirst(); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerAttributes.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerAttributes.java index 38fa5f23..1b396f98 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerAttributes.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerAttributes.java @@ -1,8 +1,11 @@ package com.minelittlepony.unicopia.entity.player; +import java.util.List; import java.util.UUID; +import java.util.function.Predicate; import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; import com.minelittlepony.unicopia.util.Tickable; @@ -11,20 +14,52 @@ import net.minecraft.entity.attribute.EntityAttributeInstance; import net.minecraft.entity.attribute.EntityAttributeModifier; import net.minecraft.entity.attribute.EntityAttributeModifier.Operation; import net.minecraft.entity.attribute.EntityAttributes; -import net.minecraft.entity.player.PlayerEntity; public class PlayerAttributes implements Tickable { - private static final EntityAttributeModifier EARTH_PONY_STRENGTH = - new EntityAttributeModifier(UUID.fromString("777a5505-521e-480b-b9d5-6ea54f259564"), "Earth Pony Strength", 0.6, Operation.MULTIPLY_TOTAL); - private static final EntityAttributeModifier EARTH_PONY_MINING_SPEED = - new EntityAttributeModifier(UUID.fromString("9fc9e269-152e-0b48-9bd5-564a546e59f2"), "Earth Pony Mining Speed", 0.5, Operation.MULTIPLY_TOTAL); - private static final EntityAttributeModifier EARTH_PONY_KNOCKBACK_RESISTANCE = - new EntityAttributeModifier(UUID.fromString("79e269a8-03e8-b9d5-5853-e25fdcf6706d"), "Earth Pony Knockback Resistance", 6, Operation.ADDITION); + private final static List ATTRIBUTES = List.of( + new ToggleableAttribute( + new EntityAttributeModifier(UUID.fromString("777a5505-521e-480b-b9d5-6ea54f259564"), "Earth Pony Strength", 0.6, Operation.MULTIPLY_TOTAL), + List.of(EntityAttributes.GENERIC_ATTACK_DAMAGE, EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE), + pony -> pony.getCompositeRace().canUseEarth() + ), + new ToggleableAttribute( + new EntityAttributeModifier(UUID.fromString("79e269a8-03e8-b9d5-5853-e25fdcf6706d"), "Earth Pony Knockback Resistance", 6, Operation.ADDITION), + List.of(EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE), + pony -> pony.getCompositeRace().canUseEarth() && pony.asEntity().isSneaking() + ), + new ToggleableAttribute( + new EntityAttributeModifier(UUID.fromString("9fc9e269-152e-0b48-9bd5-564a546e59f2"), "Earth Pony Mining Speed", 0.5, Operation.MULTIPLY_TOTAL), + List.of(UEntityAttributes.EXTRA_MINING_SPEED), + pony -> pony.getCompositeRace().canUseEarth() + ), - private static final EntityAttributeModifier PEGASUS_SPEED = - new EntityAttributeModifier(UUID.fromString("9e2699fc-3b8d-4f71-9d2d-fb92ee19b4f7"), "Pegasus Speed", 0.2, Operation.MULTIPLY_TOTAL); - private static final EntityAttributeModifier PEGASUS_REACH = - new EntityAttributeModifier(UUID.fromString("707b50a8-03e8-40f4-8553-ecf67025fd6d"), "Pegasus Reach", 1.5, Operation.ADDITION); + new ToggleableAttribute( + new EntityAttributeModifier(UUID.fromString("9e2699fc-3b8d-4f71-9d2d-fb92ee19b4f7"), "Pegasus Speed", 0.2, Operation.MULTIPLY_TOTAL), + List.of(EntityAttributes.GENERIC_MOVEMENT_SPEED, EntityAttributes.GENERIC_ATTACK_SPEED), + pony -> pony.getCompositeRace().canFly() + ), + new ToggleableAttribute( + new EntityAttributeModifier(UUID.fromString("707b50a8-03e8-40f4-8553-ecf67025fd6d"), "Pegasus Reach", 1.5, Operation.ADDITION), + List.of(UEntityAttributes.EXTENDED_REACH_DISTANCE), + pony -> pony.getCompositeRace().canFly() + ), + + new ToggleableAttribute( + new EntityAttributeModifier(UUID.fromString("79e269a8-03e8-b9d5-5853-e25fdcf6706e"), "Kirin Knockback Vulnerability", -2, Operation.ADDITION), + List.of(EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE), + pony -> pony.getCompositeRace().includes(Race.KIRIN) + ), + new ToggleableAttribute( + new EntityAttributeModifier(UUID.fromString("4991fde9-c685-4930-bbd2-d7a228728bfe"), "Kirin Rage Speed", 0.7, Operation.MULTIPLY_TOTAL), + List.of(EntityAttributes.GENERIC_MOVEMENT_SPEED, + EntityAttributes.GENERIC_ATTACK_SPEED, + EntityAttributes.GENERIC_ATTACK_DAMAGE, + EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE, + EntityAttributes.GENERIC_ATTACK_KNOCKBACK + ), + SpellType.RAGE::isOn + ) + ); public static final UUID HEALTH_SWAPPING_MODIFIER_ID = UUID.fromString("7b93803e-4b25-11ed-951e-00155d43e0a2"); @@ -40,31 +75,23 @@ public class PlayerAttributes implements Tickable { @Override public void tick() { - PlayerEntity entity = pony.asEntity(); - Race.Composite race = pony.getCompositeRace(); - - boolean earth = race.canUseEarth(); - boolean flight = race.canFly(); - - toggleAttribute(entity, EntityAttributes.GENERIC_ATTACK_DAMAGE, EARTH_PONY_STRENGTH, earth); - toggleAttribute(entity, EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE, EARTH_PONY_STRENGTH, earth); - toggleAttribute(entity, EntityAttributes.GENERIC_KNOCKBACK_RESISTANCE, EARTH_PONY_KNOCKBACK_RESISTANCE, earth && entity.isSneaking()); - toggleAttribute(entity, EntityAttributes.GENERIC_MOVEMENT_SPEED, PEGASUS_SPEED, flight); - toggleAttribute(entity, EntityAttributes.GENERIC_ATTACK_SPEED, PEGASUS_SPEED, flight); - toggleAttribute(entity, UEntityAttributes.EXTENDED_REACH_DISTANCE, PEGASUS_REACH, flight); - toggleAttribute(entity, UEntityAttributes.EXTRA_MINING_SPEED, EARTH_PONY_MINING_SPEED, earth); + ATTRIBUTES.forEach(attribute -> attribute.update(pony)); } - private void toggleAttribute(PlayerEntity entity, EntityAttribute attribute, EntityAttributeModifier modifier, boolean enable) { - EntityAttributeInstance instance = entity.getAttributeInstance(attribute); + record ToggleableAttribute(EntityAttributeModifier modifier, List attributes, Predicate test) { + public void update(Pony pony) { + boolean enable = test.test(pony); + attributes.forEach(attribute -> { + EntityAttributeInstance instance = pony.asEntity().getAttributeInstance(attribute); - if (enable) { - if (!instance.hasModifier(modifier)) { - instance.addPersistentModifier(modifier); - } - } else { - instance.tryRemoveModifier(modifier.getId()); + if (enable) { + if (!instance.hasModifier(modifier)) { + instance.addPersistentModifier(modifier); + } + } else { + instance.tryRemoveModifier(modifier.getId()); + } + }); } } - } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index d2075aa3..356d099f 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -13,6 +13,7 @@ import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.*; import com.minelittlepony.unicopia.ability.magic.*; import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; +import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.trait.TraitDiscovery; @@ -52,6 +53,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import net.minecraft.particle.ParticleTypes; import net.minecraft.registry.tag.DamageTypeTags; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; @@ -386,7 +388,7 @@ public class Pony extends Living implements Copyable, Update if (!getPhysics().isFlying() && !entity.getAbilities().flying && climbingPos != null && getObservedSpecies() == Race.CHANGELING) { Vec3d vel = entity.getVelocity(); - if (entity.isSneaky()) { + if (entity.isSneaking()) { entity.setVelocity(vel.x, 0, vel.z); } @@ -435,6 +437,24 @@ public class Pony extends Living implements Copyable, Update distanceClimbed = 0; } + if (getObservedSpecies() == Race.KIRIN) { + var charge = getMagicalReserves().getCharge(); + + if (charge.getPercentFill() >= 1) { + var energy = getMagicalReserves().getEnergy(); + if (energy.getPercentFill() < 0.002F) { + energy.addPercent(1.03F); + if (entity.age % 25 == 0) { + playSound(USounds.ENTITY_PLAYER_HEARTBEAT, 0.17F + (float)entity.getWorld().random.nextGaussian() * 0.03F, 0.5F); + } + } + } + + if (entity.getAttackCooldownProgress(0) == 0) { + charge.addPercent(3); + } + } + return super.beforeUpdate(); } @@ -488,7 +508,7 @@ public class Pony extends Living implements Copyable, Update private void updateAnimations() { if (distanceClimbed > 0 - && ((animation.isOf(Animation.CLIMB) && entity.isSneaky()) || animation.isOf(Animation.HANG)) + && ((animation.isOf(Animation.CLIMB) && entity.isSneaking()) || animation.isOf(Animation.HANG)) && entity.getClimbingPos().isPresent() && entity.getVelocity().length() < 0.08F) { if (animation.renderBothArms()) { @@ -644,6 +664,21 @@ public class Pony extends Living implements Copyable, Update public Optional modifyDamage(DamageSource cause, float amount) { + if (getObservedSpecies() == Race.KIRIN) { + var charge = getMagicalReserves().getCharge(); + charge.addPercent(MathHelper.clamp(amount / 10F, 5, 15)); + float anger = charge.getPercentFill(); + getMagicalReserves().getEnergy().addPercent(50 * anger); + playSound(USounds.ENTITY_PLAYER_KIRIN_RAGE, 0.2F, 1.25F); + spawnParticles(ParticleTypes.LAVA, 2); + + if (anger > 0 && entity.getWorld().random.nextFloat() < anger / 2F) { + if (consumeSuperMove()) { + SpellType.RAGE.withTraits().apply(this, CastingMethod.INNATE); + } + } + } + if (!cause.isIn(DamageTypeTags.BYPASSES_SHIELD) && !cause.isOf(DamageTypes.MAGIC) && !cause.isIn(DamageTypeTags.IS_FIRE) diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java index 3618c588..9bef62f6 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java @@ -8,6 +8,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.minelittlepony.unicopia.entity.duck.LavaAffine; +import com.minelittlepony.unicopia.EquinePredicates; +import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.duck.EntityDuck; @@ -37,7 +40,28 @@ abstract class MixinEntity implements EntityDuck { @Inject(method = "isFireImmune", at = @At("HEAD"), cancellable = true) private void onIsFireImmune(CallbackInfoReturnable info) { - if (isLavaAffine()) { + if (isLavaAffine() || (this instanceof Equine.Container c) && c.get().getCompositeRace().includes(Race.KIRIN)) { + info.setReturnValue(true); + } + } + + @Inject(method = "isSneaky", at = @At("HEAD"), cancellable = true) + private void onIsSneaky(CallbackInfoReturnable info) { + if (EquinePredicates.PLAYER_KIRIN.test((Entity)(Object)this)) { + info.setReturnValue(true); + } + } + + @Inject(method = "getMaxAir", at = @At("HEAD"), cancellable = true) + private void onGetMaxAir(CallbackInfoReturnable info) { + if (EquinePredicates.PLAYER_KIRIN.test((Entity)(Object)this)) { + info.setReturnValue(150); + } + } + + @Inject(method = "doesRenderOnFire", at = @At("HEAD"), cancellable = true) + private void onDoesRenderOnFire(CallbackInfoReturnable info) { + if (EquinePredicates.RAGING.test((Entity)(Object)this)) { info.setReturnValue(true); } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinWardenEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinWardenEntity.java new file mode 100644 index 00000000..e2e3d9cc --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinWardenEntity.java @@ -0,0 +1,22 @@ +package com.minelittlepony.unicopia.mixin; + +import org.jetbrains.annotations.Nullable; +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.CallbackInfoReturnable; + +import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.entity.Living; +import net.minecraft.entity.Entity; +import net.minecraft.entity.mob.WardenEntity; + +@Mixin(WardenEntity.class) +abstract class MixinWardenEntity { + @Inject(method = "isValidTarget", at = @At("HEAD"), cancellable = true) + public void onIsValidTarget(@Nullable Entity entity, CallbackInfoReturnable info) { + if (Living.getOrEmpty(entity).filter(l -> l.getCompositeRace().includes(Race.KIRIN)).isPresent()) { + info.setReturnValue(false); + } + } +} diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 33e7c417..605d3bc9 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -10,6 +10,10 @@ "ability.unicopia.indoors": "I can't see the sky from here", "ability.unicopia.too_low": "I need to get higher up", "ability.unicopia.clear_skies": "The skies already look pretty clear", + "ability.unicopia.too_calm.1": "I need to get angrier...", + "ability.unicopia.too_calm.2": "I don't feel angry...", + "ability.unicopia.too_calm.3": "Bruce made it look easier than this...", + "ability.unicopia.too_calm.4": "Celestia give me strength...", "itemGroup.unicopia.items": "Unicopia", "itemGroup.unicopia.foraging": "Unicopia - Foraging", @@ -394,6 +398,7 @@ "ability.unicopia.capture_cloud": "Bust Cloud", "ability.unicopia.disguise": "Change Form", "ability.unicopia.rainboom": "Sonic Rainboom", + "ability.unicopia.rage": "Rage", "gui.unicopia.trait.label": "Element of %s", "gui.unicopia.trait.group": "\n %s", diff --git a/src/main/resources/assets/unicopia/sounds.json b/src/main/resources/assets/unicopia/sounds.json index 3055364c..aa07354f 100644 --- a/src/main/resources/assets/unicopia/sounds.json +++ b/src/main/resources/assets/unicopia/sounds.json @@ -65,6 +65,13 @@ "unicopia:changeling/buzz0" ] }, + "entity.player.kirin.rage.loop": { + "category": "player", + "sounds": [ + { "name": "unicopia:kirin/rage/dark_matter", "stream": true }, + { "name": "unicopia:kirin/rage/dogged", "stream": true } + ] + }, "entity.player.ears_ring": { "category": "ambient", "subtitle": "unicopia.subtitle.ears_ringing", @@ -72,11 +79,18 @@ "unicopia:ears/ringing" ] }, + "entity.player.heartbeat_loop": { + "category": "ambient", + "subtitle": "unicopia.subtitle.heartbeat", + "sounds": [ + "unicopia:heartbeat/heartbeat_loop" + ] + }, "entity.player.heartbeat": { "category": "ambient", "subtitle": "unicopia.subtitle.heartbeat", "sounds": [ - "unicopia:heartbeat/heartbeat" + "unicopia:heartbeat/heartbeat_1" ] }, "ambient.wind.gust": { diff --git a/src/main/resources/assets/unicopia/sounds/heartbeat/heartbeat_1.ogg b/src/main/resources/assets/unicopia/sounds/heartbeat/heartbeat_1.ogg new file mode 100644 index 00000000..20f86154 Binary files /dev/null and b/src/main/resources/assets/unicopia/sounds/heartbeat/heartbeat_1.ogg differ diff --git a/src/main/resources/assets/unicopia/sounds/heartbeat/heartbeat.ogg b/src/main/resources/assets/unicopia/sounds/heartbeat/heartbeat_loop.ogg similarity index 100% rename from src/main/resources/assets/unicopia/sounds/heartbeat/heartbeat.ogg rename to src/main/resources/assets/unicopia/sounds/heartbeat/heartbeat_loop.ogg diff --git a/src/main/resources/assets/unicopia/sounds/kirin/rage/dark_matter.ogg b/src/main/resources/assets/unicopia/sounds/kirin/rage/dark_matter.ogg new file mode 100644 index 00000000..6568db52 Binary files /dev/null and b/src/main/resources/assets/unicopia/sounds/kirin/rage/dark_matter.ogg differ diff --git a/src/main/resources/assets/unicopia/sounds/kirin/rage/dogged.ogg b/src/main/resources/assets/unicopia/sounds/kirin/rage/dogged.ogg new file mode 100644 index 00000000..6c6bfb39 Binary files /dev/null and b/src/main/resources/assets/unicopia/sounds/kirin/rage/dogged.ogg differ diff --git a/src/main/resources/assets/unicopia/textures/gui/ability/rage.png b/src/main/resources/assets/unicopia/textures/gui/ability/rage.png new file mode 100644 index 00000000..c6f33f20 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/gui/ability/rage.png differ diff --git a/src/main/resources/data/unicopia/state_maps/burnable.json b/src/main/resources/data/unicopia/state_maps/burnable.json new file mode 100644 index 00000000..1f1abe8b --- /dev/null +++ b/src/main/resources/data/unicopia/state_maps/burnable.json @@ -0,0 +1,109 @@ +{ + "parent": "unicopia:fire", + "replace": false, + "entries": [ + { + "match": [ + { "tag": "minecraft:logs_that_burn" } + ], + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:coal_block", + "chance": 0.15 + } + }, + + { + "match": [ + { "state": "minecraft:snow" }, + { "state": "minecraft:snow_block" }, + { "builtin": "plants" } + ], + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:air" + } + }, + { + "match": [ + { "state": "minecraft:ice" }, + { "state": "minecraft:frosted_ice" } + ], + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:water" + } + }, + { + "match": { "state": "minecraft:clay" }, + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:brown_concrete" + } + }, + { + "match": [ + { "state": "minecraft:obsidian" }, + { "state": "unicopia:frosted_obsidian" } + ], + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:lava" + } + }, + { + "match": { "state": "minecraft:grass_block" }, + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:dirt" + } + }, + { + "match": { "state": "minecraft:mossy_cobblestone" }, + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:cobblestone" + } + }, + { + "match": { "state": "minecraft:mossy_cobblestone_wall" }, + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:cobblestone_wall" + } + }, + { + "match": [ + { "state": "minecraft:mossy_stone_bricks" }, + { "state": "minecraft:infested_mossy_stone_bricks" } + ], + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:stone_bricks" + } + }, + { + "match": { "state": "minecraft:podzol" }, + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:coarse_dirt" + } + }, + { + "match": { "state": "minecraft:farmland" }, + "apply": { + "action": "unicopia:set_property", + "property": "moisture", + "value": 0 + } + }, + { + "match": { "state": "minecraft:dirt" }, + "apply": { + "action": "unicopia:set_state", + "state": "minecraft:coarse_dirt", + "chance": 0.15 + } + } + ] +} diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index edb4eff6..c3d70dca 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -43,6 +43,7 @@ "MixinStateManagerBuilder", "MixinBlockState", "MixinTargetPredicate", + "MixinWardenEntity", "MixinWorld", "MixinWorldChunk", "trinkets.MixinTrinketSurvivalSlot",