diff --git a/src/main/java/com/minelittlepony/unicopia/Race.java b/src/main/java/com/minelittlepony/unicopia/Race.java index 1a214161..ac63cfff 100644 --- a/src/main/java/com/minelittlepony/unicopia/Race.java +++ b/src/main/java/com/minelittlepony/unicopia/Race.java @@ -27,6 +27,7 @@ import net.minecraft.registry.RegistryKey; public record Race (Supplier compositeSupplier, Availability availability, boolean canCast, FlightType flightType, boolean canUseEarth, boolean isNocturnal, boolean canHang) implements Affine { public static final String DEFAULT_ID = "unicopia:unset"; public static final Registry REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("race"), DEFAULT_ID); + public static final Registry COMMAND_REGISTRY = RegistryUtils.createDefaulted(Unicopia.id("race/grantable"), DEFAULT_ID); public static final RegistryKey> REGISTRY_KEY = REGISTRY.getKey(); private static final DynamicCommandExceptionType UNKNOWN_RACE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("race.unknown", id)); @@ -35,11 +36,15 @@ public record Race (Supplier compositeSupplier, Availability availabi } public static Race register(Identifier id, Availability availability, boolean magic, FlightType flight, boolean earth, boolean nocturnal, boolean canHang) { - return Registry.register(REGISTRY, id, new Race(Suppliers.memoize(() -> new Composite(REGISTRY.get(id), null)), availability, magic, flight, earth, nocturnal, canHang)); + Race race = Registry.register(REGISTRY, id, new Race(Suppliers.memoize(() -> new Composite(REGISTRY.get(id), null, null)), availability, magic, flight, earth, nocturnal, canHang)); + if (availability.isGrantable()) { + Registry.register(COMMAND_REGISTRY, id, race); + } + return race; } public static RegistryKeyArgumentType argument() { - return RegistryKeyArgumentType.registryKey(REGISTRY_KEY); + return RegistryKeyArgumentType.registryKey(COMMAND_REGISTRY.getKey()); } /** @@ -64,8 +69,8 @@ public record Race (Supplier compositeSupplier, Availability availabi return compositeSupplier.get(); } - public Composite composite(@Nullable Race pseudo) { - return pseudo == null ? composite() : new Composite(this, pseudo); + public Composite composite(@Nullable Race pseudo, @Nullable Race potential) { + return pseudo == null && potential == null ? composite() : new Composite(this, pseudo, potential); } @Override @@ -147,6 +152,10 @@ public record Race (Supplier compositeSupplier, Availability availabi return this; } + public Race or(Race other) { + return isEquine() ? this : other; + } + @Override public int hashCode() { return getId().hashCode(); @@ -194,7 +203,7 @@ public record Race (Supplier compositeSupplier, Availability availabi return REGISTRY.stream().filter(r -> r.isPermitted(player)).collect(Collectors.toSet()); } - public record Composite (Race physical, @Nullable Race pseudo) { + public record Composite (Race physical, @Nullable Race pseudo, @Nullable Race potential) { public Race collapsed() { return pseudo == null ? physical : pseudo; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java b/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java index f0964765..dbf06852 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/Abilities.java @@ -24,6 +24,9 @@ public interface Abilities { .toList(); }); + // all races + Ability CHANGE_FORM = register(new ChangeFormAbility(), "change_form", AbilitySlot.PRIMARY); + // unicorn / alicorn Ability CAST = register(new UnicornCastingAbility(), "cast", AbilitySlot.PRIMARY); Ability SHOOT = register(new UnicornProjectileAbility(), "shoot", AbilitySlot.PRIMARY); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java index 14bb8370..d9a977b4 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java @@ -179,6 +179,10 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable { return; } + if (cooldown > 100 && player.asEntity().isCreative()) { + cooldown = Math.max(10, cooldown - 100); + } + if (cooldown > 0 && cooldown-- > 0) { ability.coolDown(player, slot); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java new file mode 100644 index 00000000..fb60bdac --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java @@ -0,0 +1,99 @@ +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.entity.player.Pony; + +import net.minecraft.particle.ParticleTypes; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.Identifier; + +public class ChangeFormAbility implements Ability { + + @Override + public int getWarmupTime(Pony player) { + return 10; + } + + @Override + public int getCooldownTime(Pony player) { + return 1000; + } + + @Override + public boolean canUse(Race.Composite race) { + return race.potential() != null; + } + + @Override + public boolean canUse(Race race) { + return true; + } + + @Override + public Identifier getIcon(Pony player) { + Race potential = player.getCompositeRace().potential(); + if (potential == null) { + return Ability.super.getIcon(player); + } + return getId().withPath(p -> "textures/gui/ability/" + p + "_" + potential.getId().getPath() + ".png"); + } + + @Nullable + @Override + public Optional prepare(Pony player) { + return Hit.of(canUse(player.getCompositeRace())); + } + + @Override + public Hit.Serializer getSerializer() { + return Hit.SERIALIZER; + } + + @Override + public double getCostEstimate(Pony player) { + return 5; + } + + @Override + public boolean apply(Pony player, Hit data) { + if (prepare(player).isEmpty()) { + return false; + } + + player.subtractEnergyCost(5); + + Race.Composite composite = player.getCompositeRace(); + Race actualRace = player.getSpecies(); + player.setSpecies(composite.potential()); + player.setSuppressedRace(actualRace.availability().isGrantable() ? actualRace : Race.UNSET); + + return true; + } + + @Override + public void warmUp(Pony player, AbilitySlot slot) { + player.getMagicalReserves().getExertion().addPercent(6); + + if (player.getAbilities().getStat(slot).getWarmup() % 5 == 0) { + player.asWorld().playSound(null, player.getOrigin(), SoundEvents.BLOCK_BUBBLE_COLUMN_WHIRLPOOL_INSIDE, SoundCategory.PLAYERS); + } + + if (player.asWorld().random.nextInt(5) == 0) { + player.asWorld().playSound(null, player.getOrigin(), USounds.Vanilla.BLOCK_BUBBLE_COLUMN_BUBBLE_POP, SoundCategory.PLAYERS); + } + + player.spawnParticles(ParticleTypes.BUBBLE_COLUMN_UP, 15); + player.spawnParticles(ParticleTypes.BUBBLE_POP, 15); + } + + @Override + public void coolDown(Pony player, AbilitySlot slot) { + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java index 086032bf..9f6bb09c 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java @@ -60,8 +60,7 @@ public class EarthPonyKickAbility implements Ability { @Override public Identifier getIcon(Pony player) { - Identifier id = Abilities.REGISTRY.getId(this); - return new Identifier(id.getNamespace(), "textures/gui/ability/" + id.getPath() + return getId().withPath(p -> "textures/gui/ability/" + p + "_" + player.getObservedSpecies().getId().getPath() + "_" + (getKickDirection(player) > 0 ? "forward" : "backward") + ".png"); 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 63d32a65..221d4dc3 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -66,6 +66,7 @@ import net.minecraft.world.GameRules; public class Pony extends Living implements Copyable, UpdateCallback { private static final TrackedData RACE = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.STRING); + private static final TrackedData SUPPRESSED_RACE = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.STRING); static final TrackedData ENERGY = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT); static final TrackedData EXHAUSTION = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT); @@ -120,6 +121,7 @@ public class Pony extends Living implements Copyable, Update this.mana = addTicker(new ManaContainer(this)); player.getDataTracker().startTracking(RACE, Race.DEFAULT_ID); + player.getDataTracker().startTracking(SUPPRESSED_RACE, Race.DEFAULT_ID); addTicker(this::updateAnimations); addTicker(this::updateBatPonyAbilities); @@ -223,8 +225,13 @@ public class Pony extends Living implements Copyable, Update @Override public void setSpecies(Race race) { race = race.validate(entity); + Race current = getSpecies(); + entity.getDataTracker().set(RACE, Race.REGISTRY.getId(race.validate(entity)).toString()); + if (race != current) { + clearSuppressedRace(); + } + ticksInSun = 0; - entity.getDataTracker().set(RACE, Race.REGISTRY.getId(race).toString()); gravity.updateFlightState(); entity.sendAbilitiesUpdate(); @@ -232,6 +239,18 @@ public class Pony extends Living implements Copyable, Update UCriteria.PLAYER_CHANGE_RACE.trigger(entity); } + public void setSuppressedRace(Race race) { + entity.getDataTracker().set(SUPPRESSED_RACE, Race.REGISTRY.getId(race.validate(entity)).toString()); + } + + public void clearSuppressedRace() { + setSuppressedRace(Race.UNSET); + } + + private Race getSuppressedRace() { + return Race.fromName(entity.getDataTracker().get(SUPPRESSED_RACE), Race.UNSET); + } + public TraitDiscovery getDiscoveries() { return discoveries; } @@ -354,17 +373,19 @@ public class Pony extends Living implements Copyable, Update @Override public boolean beforeUpdate() { if (compositeRace.includes(Race.UNSET) || entity.age % 2 == 0) { + Race intrinsicRace = getSpecies(); + Race suppressedRace = getSuppressedRace(); compositeRace = getSpellSlot() .get(SpellPredicate.IS_MIMIC, true) .map(AbstractDisguiseSpell::getDisguise) .map(EntityAppearance::getAppearance) .flatMap(Pony::of) .map(Pony::getSpecies) - .orElseGet(this::getSpecies).composite( + .orElse(intrinsicRace).composite( AmuletSelectors.UNICORN_AMULET.test(entity) ? Race.UNICORN : AmuletSelectors.ALICORN_AMULET.test(entity) ? Race.ALICORN - : AmuletSelectors.PEARL_NECKLACE.test(entity) ? Race.SEAPONY - : null + : null, + AmuletSelectors.PEARL_NECKLACE.test(entity) ? suppressedRace.or(Race.SEAPONY) : null ); } @@ -794,6 +815,7 @@ public class Pony extends Living implements Copyable, Update public void toSyncronisedNbt(NbtCompound compound) { super.toSyncronisedNbt(compound); compound.putString("playerSpecies", Race.REGISTRY.getId(getSpecies()).toString()); + compound.putString("suppressedSpecies", Race.REGISTRY.getId(getSuppressedRace()).toString()); compound.putFloat("magicExhaustion", magicExhaustion); compound.putInt("ticksInSun", ticksInSun); compound.putBoolean("hasShades", hasShades); @@ -819,6 +841,7 @@ public class Pony extends Living implements Copyable, Update public void fromSynchronizedNbt(NbtCompound compound) { super.fromSynchronizedNbt(compound); setSpecies(Race.fromName(compound.getString("playerSpecies"), Race.HUMAN)); + setSuppressedRace(Race.fromName(compound.getString("suppressedSpecies"), Race.UNSET)); powers.fromNBT(compound.getCompound("powers")); gravity.fromNBT(compound.getCompound("gravity")); charms.fromNBT(compound.getCompound("charms"));