Implement kirin ability

This commit is contained in:
Sollace 2023-10-09 22:05:41 +01:00
parent dda20777f7
commit fc1b461046
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
24 changed files with 559 additions and 53 deletions

View file

@ -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<Entity> CHANGELING = physicalRaceMatches(Race.CHANGELING::equals);
Predicate<Entity> RACE_INTERACT_WITH_CLOUDS = raceMatches(Race::canInteractWithClouds);
Predicate<Entity> RAGING = IS_PLAYER.and(SpellType.RAGE::isOn);
Predicate<Entity> PLAYER_EARTH = IS_PLAYER.and(ofRace(Race.EARTH));
Predicate<Entity> PLAYER_BAT = IS_PLAYER.and(BAT);
Predicate<Entity> PLAYER_UNICORN = IS_PLAYER.and(raceMatches(Race::canCast));
Predicate<Entity> PLAYER_CHANGELING = IS_PLAYER.and(ofRace(Race.CHANGELING));
Predicate<Entity> PLAYER_KIRIN = IS_PLAYER.and(ofRace(Race.KIRIN));
Predicate<Entity> PLAYER_PEGASUS = IS_PLAYER.and(e -> ((PlayerEntity)e).getAbilities().creativeMode || RACE_INTERACT_WITH_CLOUDS.test(e));
Predicate<Entity> PLAYER_CAN_USE_EARTH = IS_PLAYER.and(raceMatches(Race::canUseEarth));

View file

@ -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;

View file

@ -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");

View file

@ -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 extends Ability<?>> T register(T power, String name, AbilitySlot slot) {
Identifier id = Unicopia.id(name);
BY_SLOT.computeIfAbsent(slot, s -> new LinkedHashSet<>()).add(power);

View file

@ -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<Hit> {
@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<Hit> prepare(Pony player) {
return Hit.INSTANCE;
}
@Override
public Hit.Serializer<Hit> 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) {
}
}

View file

@ -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.
* <p>
* 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");
}
}

View file

@ -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<T extends Spell> implements Affine, SpellPredicate<
public static final SpellType<DispersableDisguiseSpell> CHANGELING_DISGUISE = register("disguise", Affinity.BAD, 0x19E48E, false, SpellTraits.EMPTY, DispersableDisguiseSpell::new);
public static final SpellType<RainboomAbilitySpell> RAINBOOM = register("rainboom", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, RainboomAbilitySpell::new);
public static final SpellType<RageAbilitySpell> RAGE = register("rage", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, RageAbilitySpell::new);
public static final SpellType<TimeControlAbilitySpell> TIME_CONTROL = register("time_control", Affinity.GOOD, 0xBDBDF9, false, SpellTraits.EMPTY, TimeControlAbilitySpell::new);
public static final SpellType<IceSpell> FROST = register("frost", Affinity.GOOD, 0xEABBFF, true, IceSpell.DEFAULT_TRAITS, IceSpell::new);

View file

@ -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) {

View file

@ -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<CasterView> clientWorld = Optional.of(() -> MinecraftClient.getInstance().world);
private final Int2ObjectMap<WeakReference<TickableSoundInstance>> playingSounds = new Int2ObjectOpenHashMap<>();
@Override
public Optional<CasterView> 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<TickableSoundInstance> soundSupplier) {
WeakReference<TickableSoundInstance> 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<LivingEntity> createTicker(int ticks) {
int[] ticker = new int[] {ticks};
return entity -> ticker[0]-- > 0;

View file

@ -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) {

View file

@ -89,8 +89,15 @@ public class TrinketsDelegateImpl implements TrinketsDelegate {
TrinketsApi.registerTrinket(item, new UnicopiaTrinket(item));
}
private Optional<TrinketComponent> getTrinketComponent(LivingEntity entity) {
try {
return TrinketsApi.getTrinketComponent(entity);
} catch (Throwable ingnored) {}
return Optional.empty();
}
public Optional<TrinketInventory> 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<TrinketInventory> 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<SlotGroup> getGroup(LivingEntity entity, Identifier slotId) {
return TrinketsApi.getTrinketComponent(entity)
return getTrinketComponent(entity)
.stream()
.map(component -> component.getGroups().get(slotId.getNamespace()))
.findFirst();

View file

@ -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<ToggleableAttribute> 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<EntityAttribute> attributes, Predicate<Pony> 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());
}
});
}
}
}

View file

@ -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<PlayerEntity> implements Copyable<Pony>, 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<PlayerEntity> implements Copyable<Pony>, 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<PlayerEntity> implements Copyable<Pony>, 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<PlayerEntity> implements Copyable<Pony>, Update
public Optional<Float> 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)

View file

@ -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<Boolean> 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<Boolean> info) {
if (EquinePredicates.PLAYER_KIRIN.test((Entity)(Object)this)) {
info.setReturnValue(true);
}
}
@Inject(method = "getMaxAir", at = @At("HEAD"), cancellable = true)
private void onGetMaxAir(CallbackInfoReturnable<Integer> info) {
if (EquinePredicates.PLAYER_KIRIN.test((Entity)(Object)this)) {
info.setReturnValue(150);
}
}
@Inject(method = "doesRenderOnFire", at = @At("HEAD"), cancellable = true)
private void onDoesRenderOnFire(CallbackInfoReturnable<Boolean> info) {
if (EquinePredicates.RAGING.test((Entity)(Object)this)) {
info.setReturnValue(true);
}
}

View file

@ -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<Boolean> info) {
if (Living.getOrEmpty(entity).filter(l -> l.getCompositeRace().includes(Race.KIRIN)).isPresent()) {
info.setReturnValue(false);
}
}
}

View file

@ -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",

View file

@ -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": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -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
}
}
]
}

View file

@ -43,6 +43,7 @@
"MixinStateManagerBuilder",
"MixinBlockState",
"MixinTargetPredicate",
"MixinWardenEntity",
"MixinWorld",
"MixinWorldChunk",
"trinkets.MixinTrinketSurvivalSlot",