diff --git a/src/main/java/com/minelittlepony/unicopia/Affinity.java b/src/main/java/com/minelittlepony/unicopia/Affinity.java index 4265c0f7..fe6e767a 100644 --- a/src/main/java/com/minelittlepony/unicopia/Affinity.java +++ b/src/main/java/com/minelittlepony/unicopia/Affinity.java @@ -2,8 +2,10 @@ package com.minelittlepony.unicopia; import java.util.Locale; +import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.StringIdentifiable; +import net.minecraft.util.Util; public enum Affinity implements StringIdentifiable { GOOD(Formatting.BLUE, -1, 0), @@ -20,10 +22,13 @@ public enum Affinity implements StringIdentifiable { public static final Affinity[] VALUES = values(); + private final String translationKey; + Affinity(Formatting color, int corruption, float alignment) { this.color = color; this.corruption = corruption; this.alignment = alignment; + this.translationKey = Util.createTranslationKey("affinity", Unicopia.id(name().toLowerCase(Locale.ROOT))); } @Override @@ -36,7 +41,11 @@ public enum Affinity implements StringIdentifiable { } public String getTranslationKey() { - return this == BAD ? "curse" : "spell"; + return translationKey; + } + + public Text getDisplayName() { + return Text.translatable(getTranslationKey()).formatted(getColor()); } public int getCorruption() { diff --git a/src/main/java/com/minelittlepony/unicopia/Race.java b/src/main/java/com/minelittlepony/unicopia/Race.java index 90644373..84994481 100644 --- a/src/main/java/com/minelittlepony/unicopia/Race.java +++ b/src/main/java/com/minelittlepony/unicopia/Race.java @@ -10,6 +10,7 @@ import com.google.common.base.Strings; import com.minelittlepony.unicopia.ability.Abilities; import com.minelittlepony.unicopia.ability.Ability; import com.minelittlepony.unicopia.ability.magic.Affine; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.util.RegistryUtils; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; @@ -44,6 +45,7 @@ public record Race ( 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 TrackableDataType TRACKABLE_TYPE = TrackableDataType.RACE; public static final RegistryKey> REGISTRY_KEY = REGISTRY.getKey(); private static final DynamicCommandExceptionType UNKNOWN_RACE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("commands.race.fail", id)); private static final Function COMPOSITES = Util.memoize(race -> new Composite(race, null, null)); diff --git a/src/main/java/com/minelittlepony/unicopia/UTags.java b/src/main/java/com/minelittlepony/unicopia/UTags.java index 0dc7cec4..7d16f2bc 100644 --- a/src/main/java/com/minelittlepony/unicopia/UTags.java +++ b/src/main/java/com/minelittlepony/unicopia/UTags.java @@ -107,6 +107,7 @@ public interface UTags { TagKey MIMIC_CHESTS = block("mimic_chests"); TagKey BUTTERFLIES_SPAWNABLE_ON = block("butterflies_spawn_on"); + TagKey ANGERS_GUARDIANS = block("angers_guardians"); private static TagKey block(String name) { return TagKey.of(RegistryKeys.BLOCK, Unicopia.id(name)); diff --git a/src/main/java/com/minelittlepony/unicopia/Unicopia.java b/src/main/java/com/minelittlepony/unicopia/Unicopia.java index 29c01867..8142560d 100644 --- a/src/main/java/com/minelittlepony/unicopia/Unicopia.java +++ b/src/main/java/com/minelittlepony/unicopia/Unicopia.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.minecraft.resource.ResourceType; import net.minecraft.util.Identifier; @@ -22,6 +23,7 @@ import com.minelittlepony.unicopia.container.UScreenHandlers; import com.minelittlepony.unicopia.diet.DietsLoader; import com.minelittlepony.unicopia.diet.affliction.AfflictionType; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; +import com.minelittlepony.unicopia.entity.effect.SeaponyGraceStatusEffect; import com.minelittlepony.unicopia.entity.effect.UPotions; import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.item.UItems; @@ -76,6 +78,7 @@ public class Unicopia implements ModInitializer { Debug.runTests(w); } }); + PlayerBlockBreakEvents.AFTER.register(SeaponyGraceStatusEffect::processBlockChange); NocturnalSleepManager.bootstrap(); registerServerDataReloaders(ResourceManagerHelper.get(ResourceType.SERVER_DATA)); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java index 457abd1c..93eacb89 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/AbilityDispatcher.java @@ -172,6 +172,16 @@ public class AbilityDispatcher implements Tickable, NbtSerialisable { } public void tick() { + Optional> activeAbility = getActiveAbility(); + + if (activeAbility.isEmpty()) { + if (warmup > 0) { + warmup--; + } + if (cooldown > 0) { + cooldown--; + } + } getActiveAbility().ifPresent(ability -> { if (warmup > 0) { warmup--; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java index 67aa667f..6f4cc3e0 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/ChangeFormAbility.java @@ -10,8 +10,10 @@ import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.advancement.UCriteria; +import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.FriendshipBraceletItem; +import com.minelittlepony.unicopia.item.UItems; import net.minecraft.particle.ParticleTypes; import net.minecraft.sound.SoundCategory; @@ -73,6 +75,11 @@ public class ChangeFormAbility implements Ability { List targets = getTargets(player).toList(); player.subtractEnergyCost(5 * targets.size()); + TrinketsDelegate.EquippedStack amulet = UItems.PEARL_NECKLACE.getForEntity(player.asEntity()); + if (!amulet.stack().isEmpty()) { + amulet.stack().damage(1, player.asEntity(), amulet.breakStatusSender()); + } + boolean isTransforming = player.getSuppressedRace().isUnset(); targets.forEach(target -> { Race supressed = target.getSuppressedRace(); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/ChangelingDisguiseAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/ChangelingDisguiseAbility.java index 6f3e2c60..b8c88516 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/ChangelingDisguiseAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/ChangelingDisguiseAbility.java @@ -51,7 +51,7 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility { player.getEntityWorld().playSound(null, player.getBlockPos(), USounds.ENTITY_PLAYER_CHANGELING_TRANSFORM, SoundCategory.PLAYERS, 1.4F, 0.4F); - Disguise currentDisguise = iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE, true) + Disguise currentDisguise = iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE) .orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE)); if (currentDisguise.isOf(looked)) { @@ -64,7 +64,6 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility { } player.calculateDimensions(); - iplayer.setDirty(); return true; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java index 78314ba7..de4c8be8 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyStompAbility.java @@ -6,9 +6,11 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.AwaitTickQueue; import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.UTags; import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; +import com.minelittlepony.unicopia.entity.LandingEventHandler; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.player.Pony; @@ -80,7 +82,9 @@ public class EarthPonyStompAbility implements Ability { @Override public Optional prepare(Pony player) { if (player.asEntity().getVelocity().y * player.getPhysics().getGravitySignum() < 0 - && !player.asEntity().getAbilities().flying) { + && !player.asEntity().getAbilities().flying + && !player.asEntity().isFallFlying() + && !player.asEntity().isUsingRiptide()) { thrustDownwards(player); return Hit.INSTANCE; } @@ -104,72 +108,88 @@ public class EarthPonyStompAbility implements Ability { @Override public boolean apply(Pony iplayer, Hit data) { - PlayerEntity player = iplayer.asEntity(); + final PlayerEntity player = iplayer.asEntity(); + final double initialY = player.getY() + 5; - Runnable r = () -> { - BlockPos center = PosHelper.findSolidGroundAt(player.getEntityWorld(), player.getBlockPos(), iplayer.getPhysics().getGravitySignum()); - - float heavyness = 1 + EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, player); - - iplayer.asWorld().getOtherEntities(player, areaOfEffect.offset(iplayer.getOriginVector())).forEach(i -> { - double dist = Math.sqrt(center.getSquaredDistance(i.getBlockPos())); - - if (dist <= rad + 3) { - double inertia = 2 / dist; - - if (i instanceof LivingEntity) { - inertia *= 1 + EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, (LivingEntity)i); - } - inertia /= heavyness; - - double liftAmount = Math.sin(Math.PI * dist / rad) * 12 * iplayer.getPhysics().getGravitySignum(); - - i.addVelocity( - -(player.getX() - i.getX()) / inertia, - -(player.getY() - i.getY() - liftAmount) / inertia + (dist < 1 ? dist : 0), - -(player.getZ() - i.getZ()) / inertia); - - double amount = (1.5F * player.getAttributeInstance(EntityAttributes.GENERIC_ATTACK_DAMAGE).getValue() + heavyness * 0.4) / (float)(dist * 1.3F); - - if (i instanceof PlayerEntity) { - Race.Composite race = Pony.of((PlayerEntity)i).getCompositeRace(); - if (race.canUseEarth()) { - amount /= 3; - } - - if (race.canFly()) { - amount *= 4; - } - } - - if (i instanceof LivingEntity) { - amount /= 1 + (EnchantmentHelper.getEquipmentLevel(UEnchantments.PADDED, (LivingEntity)i) / 6F); - } - - i.damage(iplayer.damageOf(UDamageTypes.SMASH, iplayer), (float)amount); - Living.updateVelocity(i); + var r = new LandingEventHandler.Callback() { + @Override + public float dispatch(float fallDistance) { + // fail if landing above the starting position + if (player.getY() > initialY) { + return fallDistance; } - }); - double radius = rad + heavyness * 0.3; + player.fallDistance = 0; + BlockPos center = PosHelper.findSolidGroundAt(player.getEntityWorld(), player.getBlockPos(), iplayer.getPhysics().getGravitySignum()); - spawnEffectAround(player, center, radius, rad); + float heavyness = 1 + EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, player); - ParticleUtils.spawnParticle(player.getWorld(), UParticles.GROUND_POUND, player.getX(), player.getY() - 1, player.getZ(), 0, 0, 0); - BlockState steppingState = player.getSteppingBlockState(); - if (steppingState.isIn(UTags.Blocks.KICKS_UP_DUST)) { - ParticleUtils.spawnParticle(player.getWorld(), new BlockStateParticleEffect(UParticles.DUST_CLOUD, steppingState), player.getBlockPos().down().toCenterPos(), Vec3d.ZERO); + iplayer.asWorld().getOtherEntities(player, areaOfEffect.offset(iplayer.getOriginVector())).forEach(i -> { + double dist = Math.sqrt(center.getSquaredDistance(i.getBlockPos())); + + if (dist <= rad + 3) { + double inertia = 2 / dist; + + if (i instanceof LivingEntity) { + inertia *= 1 + EnchantmentHelper.getEquipmentLevel(UEnchantments.HEAVY, (LivingEntity)i); + } + inertia /= heavyness; + + double liftAmount = Math.sin(Math.PI * dist / rad) * 12 * iplayer.getPhysics().getGravitySignum(); + + i.addVelocity( + -(player.getX() - i.getX()) / inertia, + -(player.getY() - i.getY() - liftAmount) / inertia + (dist < 1 ? dist : 0), + -(player.getZ() - i.getZ()) / inertia); + + double amount = (1.5F * player.getAttributeInstance(EntityAttributes.GENERIC_ATTACK_DAMAGE).getValue() + heavyness * 0.4) / (float)(dist * 1.3F); + + if (i instanceof PlayerEntity) { + Race.Composite race = Pony.of((PlayerEntity)i).getCompositeRace(); + if (race.canUseEarth()) { + amount /= 3; + } + + if (race.canFly()) { + amount *= 4; + } + } + + if (i instanceof LivingEntity) { + amount /= 1 + (EnchantmentHelper.getEquipmentLevel(UEnchantments.PADDED, (LivingEntity)i) / 6F); + } + + i.damage(iplayer.damageOf(UDamageTypes.SMASH, iplayer), (float)amount); + Living.updateVelocity(i); + } + }); + + double radius = rad + heavyness * 0.3; + + spawnEffectAround(player, center, radius, rad); + + ParticleUtils.spawnParticle(player.getWorld(), UParticles.GROUND_POUND, player.getX(), player.getY() - 1, player.getZ(), 0, 0, 0); + BlockState steppingState = player.getSteppingBlockState(); + if (steppingState.isIn(UTags.Blocks.KICKS_UP_DUST)) { + ParticleUtils.spawnParticle(player.getWorld(), new BlockStateParticleEffect(UParticles.DUST_CLOUD, steppingState), player.getBlockPos().down().toCenterPos(), Vec3d.ZERO); + } + + iplayer.subtractEnergyCost(rad); + iplayer.asEntity().addExhaustion(3); + return 0F; } - iplayer.subtractEnergyCost(rad); - iplayer.asEntity().addExhaustion(3); + @Override + public void onCancelled() { + iplayer.playSound(USounds.GUI_ABILITY_FAIL, 1F); + } }; if (iplayer.asEntity().isOnGround()) { iplayer.setAnimation(Animation.STOMP, Animation.Recipient.ANYONE, 10); iplayer.asEntity().jump(); iplayer.updateVelocity(); - AwaitTickQueue.scheduleTask(iplayer.asWorld(), w -> r.run(), 5); + AwaitTickQueue.scheduleTask(iplayer.asWorld(), w -> r.dispatch(0F), 5); } else { thrustDownwards(iplayer); iplayer.waitForFall(r); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java index b06df709..99e7d543 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/TimeChangeAbility.java @@ -57,9 +57,7 @@ public class TimeChangeAbility implements Ability { return false; } - if (player.getSpellSlot().contains(SpellType.TIME_CONTROL)) { - player.getSpellSlot().removeWhere(SpellType.TIME_CONTROL, true); - } else { + if (!player.getSpellSlot().removeWhere(SpellType.TIME_CONTROL)) { SpellType.TIME_CONTROL.withTraits().apply(player, CastingMethod.INNATE).update(player, data.applyTo(player)); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/ToggleFlightAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/ToggleFlightAbility.java index caa3c701..d1a45aa2 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/ToggleFlightAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/ToggleFlightAbility.java @@ -27,7 +27,7 @@ public class ToggleFlightAbility implements Ability { @Nullable @Override public Optional prepare(Pony player) { - return Hit.of(!player.asEntity().isCreative() && !player.getPhysics().getFlightType().isGrounded()); + return Hit.of(!player.asEntity().hasVehicle() && !player.asEntity().isCreative() && !player.getPhysics().getFlightType().isGrounded()); } @Override @@ -65,7 +65,6 @@ public class ToggleFlightAbility implements Ability { } else { player.getPhysics().cancelFlight(true); } - player.setDirty(); player.setAnimation(Animation.SPREAD_WINGS, Animation.Recipient.ANYONE); return true; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java index 64a0e4bf..64f70d7c 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java @@ -100,12 +100,21 @@ public class UnicornCastingAbility extends AbstractSpellCastingAbility { if (newSpell.getResult() != ActionResult.FAIL && canCast(newSpell.getValue().type())) { CustomisedSpellType spell = newSpell.getValue(); + if (newSpell.getResult() == ActionResult.CONSUME) { + CustomisedSpellType equippedType = player.getCharms().getEquippedSpell(player.getCharms().getHand()); + if (equippedType.type() == spell.type()) { + player.getCharms().equipSpell(player.getCharms().getHand(), spell); + } + } - boolean removed = player.getSpellSlot().removeWhere(s -> { - return s.findMatches(spell).findAny().isPresent() && (spell.isEmpty() || !SpellType.PLACED_SPELL.test(s)); - }, true); + if (spell.isEmpty()) { + return false; + } + + boolean has = !spell.isStackable() && player.getSpellSlot().contains(spell); + boolean removed = !spell.isStackable() && player.getSpellSlot().removeWhere(spell.type()); player.subtractEnergyCost(removed ? 2 : 4); - if (!removed) { + if (!has) { Spell s = spell.apply(player, CastingMethod.DIRECT); if (s == null) { player.spawnParticles(ParticleTypes.LARGE_SMOKE, 6); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java index 4eb6f47a..36c603c2 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java @@ -7,7 +7,6 @@ import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.entity.player.Pony; @@ -93,11 +92,7 @@ public class UnicornDispellAbility implements Ability { public boolean apply(Pony player, Pos data) { player.setAnimation(Animation.WOLOLO, Animation.Recipient.ANYONE); Caster.stream(VecHelper.findInRange(player.asEntity(), player.asWorld(), data.vec(), 3, EquinePredicates.IS_PLACED_SPELL).stream()).forEach(target -> { - target.getSpellSlot().forEach(spell -> { - spell.setDead(); - spell.tickDying(target); - return Operation.ofBoolean(!spell.isDead()); - }, true); + target.getSpellSlot().clear(false); }); return true; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java index e05038a9..09258a4a 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java @@ -24,7 +24,7 @@ import net.minecraft.util.TypedActionResult; public class UnicornProjectileAbility extends AbstractSpellCastingAbility { @Override public int getWarmupTime(Pony player) { - return 8; + return 1; } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java index dc6f4ec3..872aba90 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/Caster.java @@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.Ability; +import com.minelittlepony.unicopia.ability.magic.spell.effect.AreaProtectionSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.damage.UDamageSources; @@ -40,7 +41,7 @@ public interface Caster extends Physics getPhysics(); - SpellContainer getSpellSlot(); + SpellSlots getSpellSlot(); /** * Removes the desired amount of mana or health from this caster in exchange for a spell's benefits. @@ -111,7 +112,19 @@ public interface Caster extends } default boolean canCastAt(Vec3d pos) { - return !Ether.get(asWorld()).anyMatch(SpellType.ARCANE_PROTECTION, (spell, caster) -> spell.blocksMagicFor(caster, this, pos)); + return !Ether.get(asWorld()).anyMatch(SpellType.ARCANE_PROTECTION, entry -> { + var target = entry.entity.getTarget().orElse(null); + if (target != null && target.pos().distanceTo(pos) <= entry.getRadius()) { + Caster caster = entry.getCaster(); + if (caster != null) { + AreaProtectionSpell spell = entry.getSpell(); + if (spell != null) { + return spell.blocksMagicFor(caster, this, pos); + } + } + } + return false; + }); } default boolean canUse(Ability ability) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/Levelled.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/Levelled.java index c3094400..2ea5d1e4 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/Levelled.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/Levelled.java @@ -1,33 +1,32 @@ package com.minelittlepony.unicopia.ability.magic; +import java.util.function.IntConsumer; import java.util.function.IntSupplier; import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.math.MathHelper; /** * Object with levelling capabilities. */ public interface Levelled { - LevelStore EMPTY = fixed(0); + LevelStore ZERO = of(0, 1); - static LevelStore fixed(int level) { - return of(() -> level); - } - - static LevelStore of(IntSupplier supplier) { + static LevelStore of(IntSupplier getter, IntConsumer setter, IntSupplier max) { return new LevelStore() { @Override public int get() { - return supplier.getAsInt(); + return getter.getAsInt(); } @Override public void set(int level) { + setter.accept(level); } @Override public int getMax() { - return get(); + return max.getAsInt(); } }; } @@ -37,7 +36,9 @@ public interface Levelled { } static LevelStore fromNbt(NbtCompound compound) { - return of(compound.getInt("value"), compound.getInt("max")); + int max = Math.max(1, compound.getInt("max")); + int value = MathHelper.clamp(compound.getInt("value"), 0, max); + return of(value, max); } static LevelStore of(int level, int max) { @@ -70,6 +71,9 @@ public interface Levelled { void set(int level); default float getScaled(float max) { + if (getMax() == 0) { + return max; + } return ((float)get() / getMax()) * max; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/MultiSpellSlot.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/MultiSpellSlot.java new file mode 100644 index 00000000..e38184b7 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/MultiSpellSlot.java @@ -0,0 +1,191 @@ +package com.minelittlepony.unicopia.ability.magic; + +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.SpellReference; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.network.track.MsgTrackedValues; +import com.minelittlepony.unicopia.network.track.ObjectTracker; +import com.minelittlepony.unicopia.network.track.Trackable; +import com.minelittlepony.unicopia.network.track.TrackableObject; +import com.minelittlepony.unicopia.util.NbtSerialisable; +import com.minelittlepony.unicopia.util.serialization.PacketCodec; + +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; + +/** + * Container for multiple spells + * + * @param The owning entity + */ +class MultiSpellSlot implements SpellSlots, NbtSerialisable { + private final Caster owner; + private final ObjectTracker> tracker; + + public MultiSpellSlot(Caster owner) { + this.owner = owner; + this.tracker = Trackable.of(owner.asEntity()).getDataTrackers().checkoutTracker(() -> new Entry<>(owner)); + } + + public ObjectTracker getTracker() { + return tracker; + } + + @Override + public boolean contains(UUID id) { + return tracker.contains(id) + || tracker.values().stream().anyMatch(s -> s.spell.equalsOrContains(id)); + } + + @Override + public void put(@Nullable Spell effect) { + if (effect != null) { + tracker.add(effect.getUuid(), new Entry<>(owner, effect)); + } + } + + @Override + public void remove(UUID id, boolean force) { + tracker.remove(id, force); + } + + @Override + public Stream stream(@Nullable SpellPredicate type) { + return tracker.values().stream().flatMap(s -> s.spell.findMatches(type)); + } + + @Override + public boolean clear(boolean force) { + return tracker.clear(force); + } + + @Override + public void toNBT(NbtCompound compound) { + compound.put("spells", NbtSerialisable.writeMap(tracker.entries(), UUID::toString, entry -> entry.spell.toNBT())); + } + + @Override + public void fromNBT(NbtCompound compound) { + tracker.load(NbtSerialisable.readMap(compound.getCompound("spells"), key -> { + try { + return UUID.fromString(key); + } catch (Throwable ignore) {} + return null; + }, (key, nbt) -> { + try { + Entry entry = new Entry<>(owner); + entry.spell.fromNBT((NbtCompound)nbt); + return entry; + } catch (Throwable t) { + Unicopia.LOGGER.warn("Exception loading tracked object: {}", t.getMessage()); + } + return null; + })); + } + + static final class Entry implements TrackableObject> { + private final Caster owner; + final SpellReference spell = new SpellReference<>(); + private boolean hasValue; + + public Entry(Caster owner) { + this.owner = owner; + } + public Entry(Caster owner, T spell) { + this.owner = owner; + this.spell.set(spell); + if (owner instanceof UpdateCallback callback) { + callback.onSpellAdded(spell); + } + } + + @Override + public void discard(boolean immediate) { + if (immediate) { + spell.set(null, owner); + } else { + Spell s = spell.get(); + if (s != null) { + s.setDead(); + s.tickDying(owner); + } + } + } + + @Override + public Status getStatus() { + boolean hasValue = spell.get() != null; + if (hasValue != this.hasValue) { + this.hasValue = hasValue; + return hasValue ? Status.NEW : Status.REMOVED; + } + + return spell.hasDirtySpell() ? Status.UPDATED : Status.DEFAULT; + } + + @Override + public void readTrackedNbt(NbtCompound nbt) { + spell.fromNBT(nbt); + } + + @Override + public NbtCompound writeTrackedNbt() { + return spell.toNBT(); + } + + @Override + public void read(PacketByteBuf buffer) { + byte contentType = buffer.readByte(); + if (contentType == 1) { + readTrackedNbt(PacketCodec.COMPRESSED_NBT.read(buffer)); + } else { + T spell = this.spell.get(); + if (spell != null) { + spell.getDataTracker().load(new MsgTrackedValues.TrackerEntries(buffer)); + } + } + } + + @Override + public Optional write(Status status) { + if (status != Status.DEFAULT) { + PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()); + buffer.writeByte(1); + PacketCodec.COMPRESSED_NBT.write(buffer, spell.toNBT()); + return Optional.of(buffer); + } + @Nullable T spell = this.spell.get(); + if (spell == null) { + return Optional.empty(); + } + return spell.getDataTracker().getDirtyPairs().map(entries -> { + PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()); + buffer.writeByte(0); + entries.write(buffer); + return buffer; + }); + } + + @Override + public void copyTo(Entry destination) { + destination.spell.set(spell.get()); + } + } + + @Override + public void copyFrom(SpellSlots other, boolean alive) { + if (alive) { + other.stream().forEach(this::put); + } else { + other.stream().filter(SpellType.PLACE_CONTROL_SPELL).forEach(this::put); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SingleSpellSlot.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SingleSpellSlot.java new file mode 100644 index 00000000..94d6c664 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SingleSpellSlot.java @@ -0,0 +1,73 @@ +package com.minelittlepony.unicopia.ability.magic; + +import java.util.UUID; +import java.util.stream.Stream; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.network.track.Trackable; +import com.minelittlepony.unicopia.util.NbtSerialisable; +import net.minecraft.nbt.NbtCompound; + +/** + * Container for a single spell + * + * @param The owning entity + */ +class SingleSpellSlot implements SpellSlots, NbtSerialisable { + private final Caster owner; + private final MultiSpellSlot.Entry entry; + + public SingleSpellSlot(Caster owner) { + this.owner = owner; + this.entry = new MultiSpellSlot.Entry<>(owner); + Trackable.of(owner.asEntity()).getDataTrackers().getPrimaryTracker().startTracking(entry); + } + + @Override + public boolean contains(UUID id) { + return entry.spell.equalsOrContains(id); + } + + @Override + public void put(@Nullable Spell effect) { + entry.spell.set(effect, owner); + } + + @Override + public void remove(UUID id, boolean force) { + if (contains(id)) { + entry.discard(force); + } + } + + @Override + public Stream stream(@Nullable SpellPredicate type) { + return entry.spell.findMatches(type); + } + + @Override + public boolean clear(boolean force) { + if (entry.spell.get() != null) { + entry.discard(force); + return true; + } + return false; + } + + @Override + public void toNBT(NbtCompound compound) { + compound.put("effect", entry.spell.toNBT()); + } + + @Override + public void fromNBT(NbtCompound compound) { + entry.spell.fromNBT(compound.getCompound("effect")); + } + + @Override + public void copyFrom(SpellSlots other, boolean alive) { + other.get().ifPresent(this::put); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java deleted file mode 100644 index e703911d..00000000 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellContainer.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.minelittlepony.unicopia.ability.magic; - -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; -import org.jetbrains.annotations.Nullable; - -import com.minelittlepony.unicopia.ability.magic.spell.Spell; - -public interface SpellContainer { - /** - * Checks if a spell with the given uuid is present. - */ - boolean contains(UUID id); - - /** - * Checks if any matching spells are active. - */ - default boolean contains(@Nullable SpellPredicate type) { - return get(type, true).isPresent(); - } - - /** - * Gets the active effect for this caster updating it if needed. - */ - default Optional get(boolean update) { - return get(null, update); - } - - /** - * Gets the active effect for this caster updating it if needed. - */ - Optional get(@Nullable SpellPredicate type, boolean update); - - /** - * Sets the active effect. - */ - void put(@Nullable Spell effect); - - /** - * Cleanly removes a spell from this spell container. - * - * @param spellid ID of the spell to remove. - */ - void remove(UUID spellid); - - /** - * Removes all active effects that match or contain a matching effect. - * - * @return True if the collection was changed - */ - default boolean removeIf(Predicate test, boolean update) { - return removeWhere(spell -> spell.findMatches(test).findFirst().isPresent(), update); - } - - /** - * Removes all matching top level active effects. - * - * @return True if the collection was changed - */ - boolean removeWhere(Predicate test, boolean update); - - /** - * Iterates active spells and optionally removes matching ones. - * - * @return True if any matching spells remain active - */ - boolean forEach(Function action, boolean update); - - - /** - * Gets all active effects for this caster updating it if needed. - */ - Stream stream(boolean update); - - /** - * Gets all active effects for this caster that match the given type updating it if needed. - */ - Stream stream(@Nullable SpellPredicate type, boolean update); - - /** - * Removes all effects currently active in this slot. - */ - boolean clear(); - - public enum Operation { - SKIP, - KEEP, - REMOVE; - - public static Operation ofBoolean(boolean result) { - return result ? KEEP : REMOVE; - } - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellInventory.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellInventory.java new file mode 100644 index 00000000..c5fdfc19 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellInventory.java @@ -0,0 +1,75 @@ +package com.minelittlepony.unicopia.ability.magic; + +import java.util.function.Function; + +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; + +public class SpellInventory { + + private final Caster owner; + private final SpellSlots slots; + + public SpellInventory(Caster owner, SpellSlots slots) { + this.owner = owner; + this.slots = slots; + } + + public SpellSlots getSlots() { + return slots; + } + + public boolean tick(Situation situation) { + return tick(spell -> { + if (spell.isDying()) { + spell.tickDying(owner); + return Operation.ofBoolean(!spell.isDead()); + } + return Operation.ofBoolean(spell.tick(owner, situation)); + }); + } + + public boolean tick(Function tickAction) { + try { + return forEach(spell -> { + try { + return tickAction.apply(spell); + } catch (Throwable t) { + Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner, t); + } + return Operation.REMOVE; + }); + } catch (Exception e) { + Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner.asEntity(), e); + } + return false; + } + + /** + * Iterates active spells and optionally removes matching ones. + * + * @return True if any matching spells remain active + */ + public boolean forEach(Function test) { + return slots.reduce((initial, spell) -> { + Operation op = test.apply(spell); + if (op == Operation.REMOVE) { + slots.remove(spell.getUuid(), true); + } else { + initial |= op != Operation.SKIP; + } + return initial; + }); + } + + public enum Operation { + SKIP, + KEEP, + REMOVE; + + public static Operation ofBoolean(boolean result) { + return result ? KEEP : REMOVE; + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java index 3a7213c7..9a3ab3d2 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellPredicate.java @@ -7,19 +7,17 @@ import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.effect.MimicSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.ShieldSpell; - import net.minecraft.entity.Entity; public interface SpellPredicate extends Predicate { + SpellPredicate ALL = spell -> true; SpellPredicate CAN_SUPPRESS = s -> s instanceof IllusionarySpell; - SpellPredicate IS_PLACED = s -> s instanceof PlaceableSpell; SpellPredicate IS_DISGUISE = s -> s instanceof AbstractDisguiseSpell; SpellPredicate IS_MIMIC = s -> s instanceof MimicSpell; SpellPredicate IS_SHIELD_LIKE = spell -> spell instanceof ShieldSpell; SpellPredicate IS_TIMED = spell -> spell instanceof TimedSpell; SpellPredicate IS_ORIENTED = spell -> spell instanceof OrientedSpell; - SpellPredicate IS_NOT_PLACED = IS_PLACED.negate(); SpellPredicate IS_VISIBLE = spell -> spell != null && !spell.isHidden(); SpellPredicate IS_CORRUPTING = spell -> spell.getAffinity() == Affinity.BAD; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellSlots.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellSlots.java new file mode 100644 index 00000000..32a70f87 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/SpellSlots.java @@ -0,0 +1,124 @@ +package com.minelittlepony.unicopia.ability.magic; + +import java.util.Optional; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.stream.Stream; +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.util.Copyable; +import com.minelittlepony.unicopia.util.NbtSerialisable; + +public interface SpellSlots extends NbtSerialisable, Copyable { + static SpellInventory ofUnbounded(Caster caster) { + return new SpellInventory(caster, new MultiSpellSlot(caster)); + } + + static SpellInventory ofSingle(Caster caster) { + return new SpellInventory(caster, new SingleSpellSlot(caster)); + } + + /** + * Gets all active effects for this caster that match the given type. + */ + Stream stream(@Nullable SpellPredicate type); + + /** + * Sets the active effect. + */ + void put(@Nullable Spell effect); + + /** + * Removes all effects currently active in this slot. + */ + boolean clear(boolean force); + + /** + * Cleanly removes a spell from this spell container. + * + * @param spellid ID of the spell to remove. + */ + void remove(UUID spellid, boolean force); + + /** + * Checks if a spell with the given uuid is present. + */ + boolean contains(UUID id); + + /** + * Gets the active effect for this caster + */ + default Optional get() { + return get(null); + } + + /** + * Checks if any matching spells are active. + */ + default boolean contains(@Nullable SpellPredicate type) { + return get(type).isPresent(); + } + + /** + * Gets the active effect for this caster updating it if needed. + */ + default Optional get(@Nullable SpellPredicate type) { + return stream(type).findFirst(); + } + + /** + * Removes all effects currently active in this slot. + */ + default boolean clear() { + return clear(false); + } + + /** + * Cleanly removes a spell from this spell container. + * + * @param spellid ID of the spell to remove. + */ + default void remove(UUID spellid) { + remove(spellid, false); + } + + /** + * Removes all active effects that match or contain a matching effect. + * + * @return True if the collection was changed + */ + default boolean removeIf(SpellPredicate test) { + return removeWhere(spell -> spell.findMatches(test).findFirst().isPresent()); + } + + /** + * Removes all matching top level active effects. + * + * @return True if the collection was changed + */ + default boolean removeWhere(SpellPredicate test) { + return reduce((initial, spell) -> { + if (test.test(spell)) { + remove(spell.getUuid()); + return true; + } + return initial; + }); + } + + /** + * Gets all active effects for this caster updating it if needed. + */ + default Stream stream() { + return stream(null); + } + + default boolean reduce(BiFunction alteration) { + return stream().reduce(false, alteration, (a, b) -> b); + } + + public interface UpdateCallback { + void onSpellAdded(Spell spell); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractAreaEffectSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractAreaEffectSpell.java index c0f63418..1425a6ae 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractAreaEffectSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractAreaEffectSpell.java @@ -1,9 +1,17 @@ package com.minelittlepony.unicopia.ability.magic.spell; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.effect.*; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; public abstract class AbstractAreaEffectSpell extends AbstractSpell { + protected static final SpellAttribute RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> Math.max(0, 4 + power)); + public static final TooltipFactory TOOLTIP = RANGE; + protected AbstractAreaEffectSpell(CustomisedSpellType type) { super(type); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java index 68a006ed..78373b5a 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDelegatingSpell.java @@ -1,64 +1,60 @@ package com.minelittlepony.unicopia.ability.magic.spell; import java.util.*; -import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Stream; -import com.minelittlepony.unicopia.Affinity; +import com.google.common.base.MoreObjects; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; -import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; -import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; -import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; -import com.minelittlepony.unicopia.projectile.ProjectileDelegate; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.server.world.Ether; import net.minecraft.nbt.NbtCompound; -import net.minecraft.util.hit.BlockHitResult; -import net.minecraft.util.hit.EntityHitResult; - -public abstract class AbstractDelegatingSpell implements Spell, - ProjectileDelegate.ConfigurationListener, ProjectileDelegate.BlockHitListener, ProjectileDelegate.EntityHitListener { - - private boolean dirty; - private boolean hidden; - private boolean destroyed; +public abstract class AbstractDelegatingSpell implements Spell { private UUID uuid = UUID.randomUUID(); - private final SpellType type; + private final CustomisedSpellType type; + protected final SpellReference delegate = new SpellReference<>(); public AbstractDelegatingSpell(CustomisedSpellType type) { - this.type = type.type(); + this.type = type; } - public abstract Collection getDelegates(); + public AbstractDelegatingSpell(CustomisedSpellType type, Spell delegate) { + this.type = type; + this.delegate.set(delegate); + } + + public final Spell getDelegate() { + return delegate.get(); + } + + @Override + public final DataTracker getDataTracker() { + return getOrEmpty().getDataTracker(); + } + + private Spell getOrEmpty() { + return MoreObjects.firstNonNull(delegate.get(), EmptySpell.INSTANCE); + } @Override public boolean equalsOrContains(UUID id) { - return Spell.super.equalsOrContains(id) || getDelegates().stream().anyMatch(s -> s.equalsOrContains(id)); + return Spell.super.equalsOrContains(id) || delegate.equalsOrContains(id); } @Override - public Stream findMatches(Predicate predicate) { - return Stream.concat(Spell.super.findMatches(predicate), getDelegates().stream().flatMap(s -> s.findMatches(predicate))); + public Stream findMatches(SpellPredicate predicate) { + return Stream.concat(Spell.super.findMatches(predicate), delegate.findMatches(predicate)); } @Override - public Affinity getAffinity() { - return Affinity.NEUTRAL; - } - - @Override - public SpellType getType() { + public CustomisedSpellType getTypeAndTraits() { return type; } - @Override - public SpellTraits getTraits() { - return getDelegates().stream().map(Spell::getTraits).reduce(SpellTraits.EMPTY, SpellTraits::union); - } - @Override public final UUID getUuid() { return uuid; @@ -66,109 +62,80 @@ public abstract class AbstractDelegatingSpell implements Spell, @Override public void setDead() { - getDelegates().forEach(Spell::setDead); + getOrEmpty().setDead(); } @Override public void tickDying(Caster caster) { + getOrEmpty().tickDying(caster); } @Override public boolean isDead() { - return getDelegates().isEmpty() || getDelegates().stream().allMatch(Spell::isDead); + return getOrEmpty().isDead(); } @Override public boolean isDying() { - return false; + return getOrEmpty().isDying(); } + @Deprecated @Override public boolean isDirty() { - return dirty || getDelegates().stream().anyMatch(Spell::isDirty); + return delegate.hasDirtySpell(); } + @Deprecated @Override public void setDirty() { - dirty = true; + getOrEmpty().setDirty(); } @Override public boolean isHidden() { - return hidden || getDelegates().stream().allMatch(Spell::isHidden); + return getOrEmpty().isHidden(); } @Override public void setHidden(boolean hidden) { - this.hidden = hidden; + getOrEmpty().setHidden(hidden); } @Override public final void destroy(Caster caster) { - if (destroyed) { - return; + if (!caster.isClient()) { + Ether.get(caster.asWorld()).remove(this, caster); } - destroyed = true; - setDead(); - onDestroyed(caster); - } - - protected void onDestroyed(Caster caster) { - getDelegates().forEach(a -> a.destroy(caster)); + getOrEmpty().destroy(caster); } @Override public boolean tick(Caster source, Situation situation) { - return execute(getDelegates().stream(), a -> { - if (a.isDying()) { - a.tickDying(source); - return !a.isDead(); - } - return a.tick(source, situation); - }); - } - - @Override - public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) { - getDelegates(BlockHitListener.PREDICATE).forEach(a -> a.onImpact(projectile, hit)); - } - - @Override - public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) { - getDelegates(EntityHitListener.PREDICATE).forEach(a -> a.onImpact(projectile, hit)); - } - - @Override - public void configureProjectile(MagicProjectileEntity projectile, Caster caster) { - getDelegates(ConfigurationListener.PREDICATE).forEach(a -> a.configureProjectile(projectile, caster)); + Spell s = getOrEmpty(); + if (s.isDying()) { + s.tickDying(source); + return !s.isDead(); + } + return s.tick(source, situation); } @Override public void toNBT(NbtCompound compound) { compound.putUuid("uuid", uuid); - compound.putBoolean("hidden", hidden); - saveDelegates(compound); + compound.put("spell", delegate.toNBT()); } @Override public void fromNBT(NbtCompound compound) { - dirty = false; - hidden = compound.getBoolean("hidden"); if (compound.contains("uuid")) { uuid = compound.getUuid("uuid"); } - loadDelegates(compound); + delegate.fromNBT(compound.getCompound("spell")); } - protected abstract void loadDelegates(NbtCompound compound); - - protected abstract void saveDelegates(NbtCompound compound); - - protected Stream getDelegates(Function cast) { - return getDelegates().stream().map(cast).filter(Objects::nonNull); - } - - protected static boolean execute(Stream spells, Function action) { - return spells.reduce(false, (u, a) -> action.apply(a), (a, b) -> a || b); + @Override + public final String toString() { + return "Delegate{" + getTypeAndTraits() + "}[uuid=" + uuid + "][spell=" + delegate.get() + "]"; } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDisguiseSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDisguiseSpell.java index 1c34f82d..41e7c996 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDisguiseSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/AbstractDisguiseSpell.java @@ -28,10 +28,11 @@ public abstract class AbstractDisguiseSpell extends AbstractSpell implements Dis @Override protected void onDestroyed(Caster caster) { + super.onDestroyed(caster); caster.asEntity().calculateDimensions(); caster.asEntity().setInvisible(false); - if (caster instanceof Pony) { - ((Pony) caster).setInvisible(false); + if (caster instanceof Pony pony) { + pony.setInvisible(false); } disguise.remove(); } @@ -71,7 +72,7 @@ public abstract class AbstractDisguiseSpell extends AbstractSpell implements Dis public static Entity getAppearance(Entity e) { return e instanceof PlayerEntity ? Pony.of((PlayerEntity)e) .getSpellSlot() - .get(SpellPredicate.IS_DISGUISE, true) + .get(SpellPredicate.IS_DISGUISE) .map(AbstractDisguiseSpell::getDisguise) .map(EntityAppearance::getAppearance) .orElse(e) : e; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/DispersableDisguiseSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/DispersableDisguiseSpell.java index f52b0e4a..7f614bc6 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/DispersableDisguiseSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/DispersableDisguiseSpell.java @@ -9,6 +9,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellTyp import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.UParticles; import net.minecraft.entity.Entity; @@ -21,6 +23,7 @@ import net.minecraft.nbt.NbtCompound; */ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements IllusionarySpell { + private final DataTracker.Entry suppressed = dataTracker.startTracking(TrackableDataType.BOOLEAN, false); private int suppressionCounter; public DispersableDisguiseSpell(CustomisedSpellType type) { @@ -36,8 +39,8 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I @Override public void onSuppressed(Caster otherSource, float time) { time /= getTraits().getOrDefault(Trait.STRENGTH, 1); - suppressionCounter = (int)(100 * time); - setDirty(); + suppressionCounter = (int)time; + suppressed.set(true); } @Override @@ -64,7 +67,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I Entity appearance = getDisguise().getAppearance(); if (isSuppressed()) { - suppressionCounter--; + if (--suppressionCounter <= 0) { + suppressed.set(false); + } owner.setInvisible(false); if (source instanceof Pony) { @@ -92,6 +97,9 @@ public class DispersableDisguiseSpell extends AbstractDisguiseSpell implements I public void fromNBT(NbtCompound compound) { super.fromNBT(compound); suppressionCounter = compound.getInt("suppressionCounter"); + if (suppressionCounter > 0) { + suppressed.set(true); + } } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/EmptySpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/EmptySpell.java new file mode 100644 index 00000000..89b3f986 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/EmptySpell.java @@ -0,0 +1,83 @@ +package com.minelittlepony.unicopia.ability.magic.spell; + +import java.util.UUID; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.network.track.DataTracker; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.util.Util; + +public final class EmptySpell implements Spell { + public static final EmptySpell INSTANCE = new EmptySpell(); + + private EmptySpell() {} + + @Override + public void toNBT(NbtCompound compound) { } + + @Override + public void fromNBT(NbtCompound compound) { } + + @Override + public CustomisedSpellType getTypeAndTraits() { + return SpellType.EMPTY_KEY.withTraits(); + } + + @Override + public UUID getUuid() { + return Util.NIL_UUID; + } + + @Override + public void setDead() { } + + @Override + public boolean isDead() { + return true; + } + + @Override + public boolean isDying() { + return false; + } + + @Override + public boolean isDirty() { + return false; + } + + @Override + public boolean tick(Caster caster, Situation situation) { + return false; + } + + @Override + public void tickDying(Caster caster) { } + + @Override + public void setDirty() { } + + @Override + public boolean isHidden() { + return true; + } + + @Override + public void setHidden(boolean hidden) { } + + @Override + public void destroy(Caster caster) { } + + @Override + public String toString() { + return "EmptySpell{}"; + } + + @Override + public DataTracker getDataTracker() { + return null; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/OrientedSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/OrientedSpell.java index 38d35171..59a05850 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/OrientedSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/OrientedSpell.java @@ -1,11 +1,12 @@ package com.minelittlepony.unicopia.ability.magic.spell; import com.minelittlepony.unicopia.ability.data.Rot; +import com.minelittlepony.unicopia.ability.magic.Caster; public interface OrientedSpell extends Spell { - void setOrientation(float pitch, float yaw); + void setOrientation(Caster caster, float pitch, float yaw); - default void setOrientation(Rot rotation) { - setOrientation(rotation.pitch(), rotation.yaw()); + default void setOrientation(Caster caster, Rot rotation) { + setOrientation(caster, rotation.pitch(), rotation.yaw()); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java deleted file mode 100644 index 1eb59540..00000000 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java +++ /dev/null @@ -1,292 +0,0 @@ -package com.minelittlepony.unicopia.ability.magic.spell; - -import java.util.*; - -import org.jetbrains.annotations.Nullable; - -import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; -import com.minelittlepony.unicopia.entity.EntityReference; -import com.minelittlepony.unicopia.entity.EntityReference.EntityValues; -import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; -import com.minelittlepony.unicopia.entity.mob.UEntities; -import com.minelittlepony.unicopia.entity.player.Pony; -import com.minelittlepony.unicopia.network.Channel; -import com.minelittlepony.unicopia.network.MsgCasterLookRequest; -import com.minelittlepony.unicopia.server.world.Ether; -import com.minelittlepony.unicopia.util.NbtSerialisable; - -import net.minecraft.nbt.*; -import net.minecraft.registry.*; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.util.Identifier; -import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; - -/** - * A spell that can be attached to a specific location in the world. - *

- * The spell's effects are still powered by the casting player, so if the player dies or leaves the area, their - * spell loses affect until they return. - */ -public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedSpell { - /** - * Dimension the spell was originally cast in - */ - @Nullable - private RegistryKey dimension; - - /** - * ID of the placed counterpart of this spell. - */ - @Nullable - private UUID placedSpellId; - - /** - * The cast spell entity - */ - private final EntityReference castEntity = new EntityReference<>(); - - /** - * The spell being cast - */ - private final SpellReference spell = new SpellReference<>(); - - public float pitch; - public float yaw; - - private int prevAge; - private int age; - - private boolean dead; - private int prevDeathTicks; - private int deathTicks; - - private Optional position = Optional.empty(); - - public PlaceableSpell(CustomisedSpellType type) { - super(type); - } - - public PlaceableSpell setSpell(Spell spell) { - this.spell.set(spell); - return this; - } - - public float getAge(float tickDelta) { - return MathHelper.lerp(tickDelta, prevAge, age); - } - - public float getScale(float tickDelta) { - float add = MathHelper.clamp(getAge(tickDelta) / 25F, 0, 1); - float subtract = dead ? 1 - (MathHelper.lerp(tickDelta, prevDeathTicks, deathTicks) / 20F) : 0; - return MathHelper.clamp(add - subtract, 0, 1); - } - - @Override - public boolean isDying() { - return dead && deathTicks > 0; - } - - @Override - public void setDead() { - super.setDead(); - dead = true; - deathTicks = 20; - } - - @Override - public boolean isDead() { - return dead && deathTicks <= 0 && super.isDead(); - } - - @Override - public Collection getDelegates() { - Spell spell = this.spell.get(); - return spell == null ? List.of() : List.of(spell); - } - - @Override - public boolean tick(Caster source, Situation situation) { - if (situation == Situation.BODY) { - if (!source.isClient()) { - if (dimension == null) { - dimension = source.asWorld().getRegistryKey(); - if (source instanceof Pony) { - Channel.SERVER_REQUEST_PLAYER_LOOK.sendToPlayer(new MsgCasterLookRequest(getUuid()), (ServerPlayerEntity)source.asEntity()); - } - setDirty(); - } - - castEntity.getTarget().ifPresentOrElse( - target -> checkDetachment(source, target), - () -> spawnPlacedEntity(source) - ); - } - - return !isDead(); - } - - if (situation == Situation.GROUND_ENTITY) { - if (!source.isClient()) { - if (Ether.get(source.asWorld()).get(this, source) == null) { - setDead(); - return false; - } - } - - prevAge = age; - if (age < 25) { - age++; - } - - return super.tick(source, Situation.GROUND); - } - - return !isDead(); - } - - @Override - public void tickDying(Caster caster) { - prevDeathTicks = deathTicks; - deathTicks--; - } - - private void checkDetachment(Caster source, EntityValues target) { - if (getWorld(source).map(Ether::get).map(ether -> ether.get(getType(), target, placedSpellId)).isEmpty()) { - setDead(); - } - } - - private void spawnPlacedEntity(Caster source) { - CastSpellEntity entity = UEntities.CAST_SPELL.create(source.asWorld()); - Vec3d pos = getPosition().orElse(position.orElse(source.asEntity().getPos())); - entity.updatePositionAndAngles(pos.x, pos.y, pos.z, source.asEntity().getYaw(), source.asEntity().getPitch()); - PlaceableSpell copy = spell.get().toPlaceable(); - if (spell.get() instanceof PlacementDelegate delegate) { - delegate.onPlaced(source, copy, entity); - } - entity.getSpellSlot().put(copy); - entity.setCaster(source); - entity.getWorld().spawnEntity(entity); - placedSpellId = copy.getUuid(); - Ether.get(entity.getWorld()).getOrCreate(copy, entity); - - castEntity.set(entity); - setDirty(); - } - - @Override - public void setOrientation(float pitch, float yaw) { - this.pitch = -90 - pitch; - this.yaw = -yaw; - getDelegates(spell -> spell instanceof OrientedSpell o ? o : null) - .forEach(spell -> spell.setOrientation(pitch, yaw)); - setDirty(); - } - - public void setPosition(Caster source, Vec3d position) { - this.position = Optional.of(position); - this.dimension = source.asWorld().getRegistryKey(); - castEntity.ifPresent(source.asWorld(), entity -> { - entity.updatePositionAndAngles(position.x, position.y, position.z, entity.getYaw(), entity.getPitch()); - }); - getDelegates(spell -> spell instanceof PlaceableSpell o ? o : null) - .forEach(spell -> spell.setPosition(source, position)); - setDirty(); - } - - @Override - protected void onDestroyed(Caster source) { - if (!source.isClient()) { - castEntity.getTarget().ifPresent(target -> { - getWorld(source).map(Ether::get) - .ifPresent(ether -> ether.remove(getType(), target.uuid())); - }); - castEntity.set(null); - getSpellEntity(source).ifPresent(e -> { - castEntity.set(null); - }); - - if (source.asEntity() instanceof CastSpellEntity) { - Ether.get(source.asWorld()).remove(this, source); - } - } - super.onDestroyed(source); - } - - public Optional getSpellEntity(Caster source) { - return getWorld(source).map(castEntity::get); - } - - public Optional getPosition() { - return castEntity.getTarget().map(EntityValues::pos); - } - - protected Optional getWorld(Caster source) { - return Optional.ofNullable(dimension) - .map(dim -> source.asWorld().getServer().getWorld(dim)); - } - - @Override - public void toNBT(NbtCompound compound) { - super.toNBT(compound); - compound.putBoolean("dead", dead); - compound.putInt("deathTicks", deathTicks); - compound.putInt("age", age); - compound.putFloat("pitch", pitch); - compound.putFloat("yaw", yaw); - position.ifPresent(pos -> { - compound.put("position", NbtSerialisable.writeVector(pos)); - }); - if (placedSpellId != null) { - compound.putUuid("placedSpellId", placedSpellId); - } - if (dimension != null) { - compound.putString("dimension", dimension.getValue().toString()); - } - compound.put("castEntity", castEntity.toNBT()); - - } - - @Override - public void fromNBT(NbtCompound compound) { - super.fromNBT(compound); - dead = compound.getBoolean("dead"); - deathTicks = compound.getInt("deathTicks"); - age = compound.getInt("age"); - pitch = compound.getFloat("pitch"); - yaw = compound.getFloat("yaw"); - position = compound.contains("position") ? Optional.of(NbtSerialisable.readVector(compound.getList("position", NbtElement.FLOAT_TYPE))) : Optional.empty(); - placedSpellId = compound.containsUuid("placedSpellId") ? compound.getUuid("placedSpellId") : null; - if (compound.contains("dimension", NbtElement.STRING_TYPE)) { - Identifier id = Identifier.tryParse(compound.getString("dimension")); - if (id != null) { - dimension = RegistryKey.of(RegistryKeys.WORLD, id); - } - } - if (compound.contains("castEntity")) { - castEntity.fromNBT(compound.getCompound("castEntity")); - } - } - - @Override - protected void loadDelegates(NbtCompound compound) { - spell.fromNBT(compound.getCompound("spell")); - } - - @Override - protected void saveDelegates(NbtCompound compound) { - compound.put("spell", spell.toNBT()); - } - - @Override - public PlaceableSpell toPlaceable() { - return this; - } - - public interface PlacementDelegate { - void onPlaced(Caster source, PlaceableSpell parent, CastSpellEntity entity); - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlacementControlSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlacementControlSpell.java new file mode 100644 index 00000000..9eee830f --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlacementControlSpell.java @@ -0,0 +1,154 @@ +package com.minelittlepony.unicopia.ability.magic.spell; + +import java.util.Optional; +import java.util.UUID; + +import org.jetbrains.annotations.Nullable; +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.AbstractSpell; +import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; +import com.minelittlepony.unicopia.server.world.Ether; +import com.minelittlepony.unicopia.util.NbtSerialisable; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class PlacementControlSpell extends AbstractSpell implements OrientedSpell { + @Nullable + private UUID placedEntityId; + + private Optional> dimension = Optional.empty(); + private Optional position = Optional.empty(); + private Optional orientation = Optional.empty(); + + @Nullable + private Spell delegate; + + public PlacementControlSpell(CustomisedSpellType type) { + super(type); + } + + PlacementControlSpell(Spell delegate) { + this(SpellType.PLACE_CONTROL_SPELL.withTraits(delegate.getTypeAndTraits().traits())); + this.delegate = delegate; + } + + @Nullable + public Spell getDelegate() { + return delegate; + } + + public Optional getPosition() { + return position; + } + + public void setDimension(RegistryKey dimension) { + this.dimension = Optional.of(dimension); + setDirty(); + } + + public void setPosition(Vec3d position) { + this.position = Optional.of(position); + setDirty(); + } + + @Override + public void setOrientation(Caster caster, float pitch, float yaw) { + this.orientation = Optional.of(new Vec3d(pitch, yaw, 0)); + if (delegate instanceof OrientedSpell o) { + o.setOrientation(caster, pitch, yaw); + } + setDirty(); + if (!caster.isClient()) { + var entry = getConnection(caster); + if (entry != null) { + entry.setPitch(pitch); + entry.setYaw(yaw); + } + } + } + + @Override + public boolean apply(Caster caster) { + if (delegate == null) { + return false; + } + boolean result = super.apply(caster); + if (result) { + if (dimension.isEmpty()) { + setDimension(caster.asWorld().getRegistryKey()); + } + if (position.isEmpty()) { + setPosition(caster.asEntity().getPos()); + } + if (delegate instanceof PlacementDelegate) { + ((PlacementDelegate)delegate).onPlaced(caster, this); + } + + CastSpellEntity entity = new CastSpellEntity(caster.asWorld(), caster, this); + + Vec3d pos = position.get(); + Vec3d rot = orientation.orElse(Vec3d.ZERO); + + entity.updatePositionAndAngles(pos.x, pos.y, pos.z, (float)rot.y, (float)rot.x); + entity.getWorld().spawnEntity(entity); + + placedEntityId = entity.getUuid(); + } + return result; + } + + @Override + public boolean tick(Caster source, Situation situation) { + if (!source.isClient() && getConnection(source) == null) { + setDead(); + } + return !isDead(); + } + + @Nullable + private Ether.Entry getConnection(Caster source) { + return delegate == null || placedEntityId == null ? null : getWorld(source) + .map(world -> Ether.get(world).get(getDelegate().getTypeAndTraits().type(), placedEntityId, delegate.getUuid())) + .orElse(null); + } + + private Optional getWorld(Caster source) { + return dimension.map(source.asWorld().getServer()::getWorld); + } + + @Override + public void toNBT(NbtCompound compound) { + super.toNBT(compound); + compound.put("spell", Spell.writeNbt(delegate)); + position.ifPresent(pos -> compound.put("position", NbtSerialisable.writeVector(pos))); + orientation.ifPresent(o -> compound.put("orientation", NbtSerialisable.writeVector(o))); + dimension.ifPresent(d -> compound.putString("dimension", d.getValue().toString())); + if (placedEntityId != null) { + compound.putUuid("placedEntityId", placedEntityId); + } + } + + @Override + public void fromNBT(NbtCompound compound) { + super.fromNBT(compound); + delegate = Spell.readNbt(compound.getCompound("spell")); + placedEntityId = compound.containsUuid("placedEntityId") ? compound.getUuid("placedEntityId") : null; + position = compound.contains("position") ? Optional.of(NbtSerialisable.readVector(compound.getList("position", NbtElement.DOUBLE_TYPE))) : Optional.empty(); + orientation = compound.contains("orientation") ? Optional.of(NbtSerialisable.readVector(compound.getList("orientation", NbtElement.DOUBLE_TYPE))) : Optional.empty(); + if (compound.contains("dimension", NbtElement.STRING_TYPE)) { + dimension = Optional.ofNullable(Identifier.tryParse(compound.getString("dimension"))).map(id -> RegistryKey.of(RegistryKeys.WORLD, id)); + } + } + + public interface PlacementDelegate { + void onPlaced(Caster source, PlacementControlSpell parent); + } +} 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 index 62ef6a43..dbf98cf2 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RageAbilitySpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RageAbilitySpell.java @@ -54,7 +54,6 @@ public class RageAbilitySpell extends AbstractSpell { ticksExtenguishing++; source.playSound(USounds.Vanilla.ENTITY_GENERIC_EXTINGUISH_FIRE, 1); source.spawnParticles(ParticleTypes.CLOUD, 12); - setDirty(); } else { ticksExtenguishing = 0; } @@ -127,9 +126,6 @@ public class RageAbilitySpell extends AbstractSpell { age++; source.asEntity().setInvulnerable(age < 25); - - - setDirty(); return true; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java index 0c504041..aab10703 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/Spell.java @@ -1,17 +1,20 @@ package com.minelittlepony.unicopia.ability.magic.spell; import java.util.UUID; -import java.util.function.Predicate; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; import org.spongepowered.include.com.google.common.base.Objects; +import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; +import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; -import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.util.NbtSerialisable; import net.minecraft.nbt.NbtCompound; @@ -24,14 +27,20 @@ public interface Spell extends NbtSerialisable, Affine { Serializer SERIALIZER = Serializer.of(Spell::readNbt, Spell::writeNbt); /** - * Returns the registered type of this spell. + * Returns the full type that describes this spell. */ - SpellType getType(); + CustomisedSpellType getTypeAndTraits(); - /** - * Gets the traits of this spell. - */ - SpellTraits getTraits(); + DataTracker getDataTracker(); + + default boolean isOf(SpellType type) { + return getTypeAndTraits().type() == type; + } + + @Override + default Affinity getAffinity() { + return getTypeAndTraits().type().getAffinity(); + } /** * The unique id of this particular spell instance. @@ -48,8 +57,9 @@ public interface Spell extends NbtSerialisable, Affine { /** * Returns an optional containing the spell that matched the given predicate. */ - default Stream findMatches(Predicate predicate) { - return predicate.test(this) ? Stream.of(this) : Stream.empty(); + @SuppressWarnings("unchecked") + default Stream findMatches(SpellPredicate predicate) { + return predicate == null || predicate.test(this) ? Stream.of((T)this) : Stream.empty(); } /** @@ -67,14 +77,24 @@ public interface Spell extends NbtSerialisable, Affine { /** * Returns true if this effect has changes that need to be sent to the client. */ + @Deprecated boolean isDirty(); + /** + * Marks this effect as dirty. + */ + @Deprecated + void setDirty(); + /** * Applies this spell to the supplied caster. * @param caster The caster to apply the spell to */ default boolean apply(Caster caster) { caster.getSpellSlot().put(this); + if (!caster.isClient()) { + Ether.get(caster.asWorld()).getOrCreate(this, caster); + } return true; } @@ -100,11 +120,6 @@ public interface Spell extends NbtSerialisable, Affine { */ void tickDying(Caster caster); - /** - * Marks this effect as dirty. - */ - void setDirty(); - boolean isHidden(); void setHidden(boolean hidden); @@ -117,8 +132,8 @@ public interface Spell extends NbtSerialisable, Affine { /** * Converts this spell into a placeable spell. */ - default PlaceableSpell toPlaceable() { - return SpellType.PLACED_SPELL.withTraits().create().setSpell(this); + default PlacementControlSpell toPlaceable() { + return new PlacementControlSpell(this); } /** @@ -126,21 +141,14 @@ public interface Spell extends NbtSerialisable, Affine { * @return */ default ThrowableSpell toThrowable() { - return SpellType.THROWN_SPELL.withTraits().create().setSpell(this); + return new ThrowableSpell(this); } @Nullable static T readNbt(@Nullable NbtCompound compound) { try { - if (compound != null && compound.contains("effect_id")) { - @SuppressWarnings("unchecked") - T effect = (T)SpellType.getKey(compound).withTraits().create(); - - if (effect != null) { - effect.fromNBT(compound); - } - - return effect; + if (compound != null) { + return CustomisedSpellType.fromNBT(compound).create(compound); } } catch (Exception e) { Unicopia.LOGGER.fatal("Invalid spell nbt {}", e); @@ -153,9 +161,16 @@ public interface Spell extends NbtSerialisable, Affine { return compound == null || !compound.containsUuid("uuid") ? Util.NIL_UUID : compound.getUuid("uuid"); } - static NbtCompound writeNbt(Spell effect) { + static NbtCompound writeNbt(@Nullable Spell effect) { + if (effect == null) { + return new NbtCompound(); + } NbtCompound compound = effect.toNBT(); - effect.getType().toNbt(compound); + effect.getTypeAndTraits().toNbt(compound); return compound; } + + static Spell copy(T spell) { + return readNbt(writeNbt(spell)); + } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java index cca32178..4bab564c 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/SpellReference.java @@ -1,9 +1,13 @@ package com.minelittlepony.unicopia.ability.magic.spell; import java.util.Objects; +import java.util.UUID; +import java.util.stream.Stream; + import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.util.NbtSerialisable; import net.minecraft.nbt.NbtCompound; @@ -11,7 +15,6 @@ import net.minecraft.nbt.NbtCompound; public final class SpellReference implements NbtSerialisable { @Nullable private transient T spell; - private int nbtHash; @Nullable public T get() { @@ -22,6 +25,7 @@ public final class SpellReference implements NbtSerialisable { set(spell, null); } + @Deprecated public boolean hasDirtySpell() { return spell != null && spell.isDirty(); } @@ -33,36 +37,36 @@ public final class SpellReference implements NbtSerialisable { } T oldValue = this.spell; this.spell = spell; - nbtHash = 0; if (owner != null && oldValue != null && (spell == null || !oldValue.getUuid().equals(spell.getUuid()))) { oldValue.destroy(owner); } return true; } + public boolean equalsOrContains(UUID id) { + @Nullable T spell = get(); + return spell != null && spell.equalsOrContains(id); + } + + @SuppressWarnings("unchecked") + public Stream findMatches(SpellPredicate predicate) { + @Nullable T spell = get(); + return spell != null ? (predicate == null ? Stream.of((V)spell) : spell.findMatches(predicate)) : Stream.empty(); + } + @Override public void toNBT(NbtCompound compound) { if (spell != null && !spell.isDead()) { spell.toNBT(compound); - spell.getType().toNbt(compound); + spell.getTypeAndTraits().toNbt(compound); } } @Override public void fromNBT(NbtCompound compound) { - fromNBT(compound, true); - } - - public void fromNBT(NbtCompound compound, boolean force) { - final int hash = compound.hashCode(); - if (nbtHash == hash) { - return; - } - nbtHash = hash; - if (spell == null || !Objects.equals(Spell.getUuid(compound), spell.getUuid())) { spell = Spell.readNbt(compound); - } else if (force || !spell.isDirty()) { + } else { spell.fromNBT(compound); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java index 3e60cba0..d722147d 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/ThrowableSpell.java @@ -1,32 +1,28 @@ package com.minelittlepony.unicopia.ability.magic.spell; -import java.util.Collection; -import java.util.List; import java.util.Optional; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.projectile.MagicBeamEntity; -import net.minecraft.nbt.NbtCompound; +import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; +import com.minelittlepony.unicopia.projectile.ProjectileDelegate; + +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.EntityHitResult; import net.minecraft.world.World; -public final class ThrowableSpell extends AbstractDelegatingSpell { - - private final SpellReference spell = new SpellReference<>(); +public final class ThrowableSpell extends AbstractDelegatingSpell implements + ProjectileDelegate.ConfigurationListener, ProjectileDelegate.BlockHitListener, ProjectileDelegate.EntityHitListener { public ThrowableSpell(CustomisedSpellType type) { super(type); } - public ThrowableSpell setSpell(Spell spell) { - this.spell.set(spell); - return this; - } - - @Override - public Collection getDelegates() { - return List.of(spell.get()); + public ThrowableSpell(Spell delegate) { + super(SpellType.THROWN_SPELL.withTraits(delegate.getTypeAndTraits().traits()), delegate); } @Override @@ -57,7 +53,7 @@ public final class ThrowableSpell extends AbstractDelegatingSpell { return Optional.empty(); } - return Optional.ofNullable(spell.get().prepareForCast(caster, CastingMethod.STORED)).map(s -> { + return Optional.ofNullable(delegate.get().prepareForCast(caster, CastingMethod.STORED)).map(s -> { MagicBeamEntity projectile = new MagicBeamEntity(world, caster.asEntity(), divergance, s); configureProjectile(projectile, caster); @@ -67,16 +63,6 @@ public final class ThrowableSpell extends AbstractDelegatingSpell { }); } - @Override - protected void loadDelegates(NbtCompound compound) { - spell.fromNBT(compound.getCompound("spell")); - } - - @Override - protected void saveDelegates(NbtCompound compound) { - compound.put("spell", spell.toNBT()); - } - @Override public ThrowableSpell toThrowable() { return this; @@ -90,4 +76,25 @@ public final class ThrowableSpell extends AbstractDelegatingSpell { @Override public void setHidden(boolean hidden) { } + + @Override + public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) { + if (delegate.get() instanceof BlockHitListener listener) { + listener.onImpact(projectile, hit); + } + } + + @Override + public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) { + if (delegate.get() instanceof EntityHitListener listener) { + listener.onImpact(projectile, hit); + } + } + + @Override + public void configureProjectile(MagicProjectileEntity projectile, Caster caster) { + if (delegate.get() instanceof ConfigurationListener listener) { + listener.configureProjectile(projectile, caster); + } + } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java index d81e4f39..18546975 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/TimedSpell.java @@ -1,5 +1,9 @@ package com.minelittlepony.unicopia.ability.magic.spell; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.Tickable; @@ -10,6 +14,9 @@ import net.minecraft.util.math.MathHelper; * A magic effect with a set duration capable of reporting how long it has until it runs out. */ public interface TimedSpell extends Spell { + int BASE_DURATION = 120 * 20; + SpellAttribute TIME = SpellAttribute.create(SpellAttributeType.SOAPINESS, AttributeFormat.TIME, AttributeFormat.PERCENTAGE, Trait.FOCUS, focus -> BASE_DURATION + (int)(MathHelper.clamp(focus, 0, 160) * 19) * 20); + Timer getTimer(); class Timer implements Tickable, NbtSerialisable { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Affects.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Affects.java new file mode 100644 index 00000000..dbbe9916 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Affects.java @@ -0,0 +1,15 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +public enum Affects { + BOTH, + ENTITIES, + BLOCKS; + + public boolean allowsBlocks() { + return this == BOTH || this == BLOCKS; + } + + public boolean allowsEntities() { + return this == BOTH || this == ENTITIES; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/AttributeFormat.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/AttributeFormat.java new file mode 100644 index 00000000..2312e8f0 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/AttributeFormat.java @@ -0,0 +1,63 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.minelittlepony.unicopia.client.UnicopiaClient; +import com.minelittlepony.unicopia.client.gui.ItemTraitsTooltipRenderer; + +import net.minecraft.item.ItemStack; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.StringHelper; + +public enum AttributeFormat { + REGULAR { + @Override + public String formatValue(float value) { + return ItemStack.MODIFIER_FORMAT.format(value); + } + }, + TIME { + @Override + public String formatValue(float value) { + return StringHelper.formatTicks((int)Math.abs(value)); + } + }, + PERCENTAGE { + @Override + public String formatValue(float value) { + return ItemStack.MODIFIER_FORMAT.format((int)(Math.abs(value) * 100)) + "%"; + } + }; + + public abstract String formatValue(float value); + + public MutableText getBase(Text attributeName, float value, String comparison, Formatting color) { + return formatAttributeLine(Text.translatable("attribute.modifier." + comparison + ".0", formatValue(value), attributeName).formatted(color)); + } + + public Text get(Text attributeName, float value) { + return getBase(attributeName, value, "equals", Formatting.LIGHT_PURPLE); + } + + public Text getRelative(Text attributeName, float baseValue, float currentValue, boolean detrimental) { + float difference = currentValue - baseValue; + return Text.literal(" (" + (difference > 0 ? "+" : "-") + formatValue(this == PERCENTAGE ? difference / baseValue : difference) + ")").formatted((detrimental ? difference : -difference) < 0 ? Formatting.DARK_GREEN : Formatting.RED); + } + + static Text formatTraitDifference(Trait trait, float value) { + boolean known = ItemTraitsTooltipRenderer.isKnown(trait); + boolean canCast = UnicopiaClient.getClientPony() != null && UnicopiaClient.getClientPony().getObservedSpecies().canCast(); + Text name = canCast ? known + ? trait.getName() + : Text.translatable("spell_attribute.unicopia.added_trait.unknown").formatted(Formatting.YELLOW) + : trait.getName().copy().formatted(Formatting.OBFUSCATED, Formatting.YELLOW); + Text count = Text.literal(ItemStack.MODIFIER_FORMAT.format(value)); + return Text.translatable("spell_attribute.unicopia.added_trait." + ((value > 0) ? "plus" : "take"), name, count).formatted(Formatting.DARK_AQUA); + } + + public static MutableText formatAttributeLine(Text attributeName) { + return Text.literal(" ").append(attributeName).formatted(Formatting.LIGHT_PURPLE); + } + +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/CastOn.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/CastOn.java new file mode 100644 index 00000000..aded56d9 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/CastOn.java @@ -0,0 +1,6 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +public enum CastOn { + LOCATION, + SELF +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Permits.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Permits.java new file mode 100644 index 00000000..c63f1275 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/Permits.java @@ -0,0 +1,8 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +public enum Permits { + ITEMS, + PASSIVE, + HOSTILE, + PLAYER +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/SpellAttribute.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/SpellAttribute.java new file mode 100644 index 00000000..0baacd85 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/SpellAttribute.java @@ -0,0 +1,106 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +import java.util.List; +import java.util.Locale; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.jetbrains.annotations.NotNull; + +import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; +import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; +import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; + +import it.unimi.dsi.fastutil.floats.Float2ObjectFunction; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Util; + +public record SpellAttribute ( + Trait trait, + BiFunction valueGetter, + TooltipFactory tooltipFactory +) implements TooltipFactory { + @Override + public void appendTooltip(CustomisedSpellType type, List tooltip) { + tooltipFactory.appendTooltip(type, tooltip); + } + + public T get(SpellTraits traits) { + return valueGetter.apply(traits, traits.get(trait)); + } + + public static SpellAttribute create(SpellAttributeType id, AttributeFormat format, Trait trait, BiFunction valueGetter) { + return create(id, format, format, trait, valueGetter, false); + } + + public static SpellAttribute create(SpellAttributeType id, AttributeFormat format, Trait trait, Float2ObjectFunction<@NotNull T> valueGetter) { + return create(id, format, format, trait, valueGetter, false); + } + + public static SpellAttribute create(SpellAttributeType id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, Float2ObjectFunction<@NotNull T> valueGetter) { + return create(id, baseFormat, relativeFormat, trait, valueGetter, false); + } + + public static SpellAttribute create(SpellAttributeType id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, BiFunction valueGetter) { + return create(id, baseFormat, relativeFormat, trait, valueGetter, false); + } + + public static SpellAttribute create(SpellAttributeType id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, Float2ObjectFunction<@NotNull T> valueGetter, boolean detrimental) { + return create(id, baseFormat, relativeFormat, trait, (traits, value) -> valueGetter.get(value.floatValue()), detrimental); + } + + public static SpellAttribute create(SpellAttributeType id, AttributeFormat baseFormat, AttributeFormat relativeFormat, Trait trait, BiFunction valueGetter, boolean detrimental) { + return new SpellAttribute<>(trait, valueGetter, (CustomisedSpellType type, List tooltip) -> { + float traitAmount = type.traits().get(trait); + float traitDifference = type.relativeTraits().get(trait); + float value = valueGetter.apply(type.traits(), traitAmount).floatValue(); + + var b = baseFormat.getBase(id.name(), value, "equals", Formatting.LIGHT_PURPLE); + if (traitDifference != 0) { + tooltip.add(b.append(relativeFormat.getRelative(Text.empty(), valueGetter.apply(type.traits(), traitAmount - traitDifference).floatValue(), value, detrimental))); + tooltip.add(AttributeFormat.formatTraitDifference(trait, traitDifference)); + } else { + tooltip.add(b); + } + }); + } + + public static SpellAttribute createConditional(SpellAttributeType id, Trait trait, Float2ObjectFunction valueGetter) { + return createConditional(id, trait, (traits, value) -> valueGetter.get(value.floatValue())); + } + + public static SpellAttribute createConditional(SpellAttributeType id, Trait trait, BiFunction valueGetter) { + return new SpellAttribute<>(trait, valueGetter, (CustomisedSpellType type, List tooltip) -> { + float difference = type.relativeTraits().get(trait); + Text value = AttributeFormat.formatAttributeLine(id.name()); + if (!valueGetter.apply(type.traits(), type.traits().get(trait))) { + value = value.copy().formatted(Formatting.STRIKETHROUGH, Formatting.DARK_GRAY); + } + tooltip.add(value); + if (difference != 0) { + tooltip.add(AttributeFormat.formatTraitDifference(trait, difference)); + } + }); + } + + public static > SpellAttribute createEnumerated(SpellAttributeType id, Trait trait, Float2ObjectFunction valueGetter) { + return createEnumerated(id, trait, (traits, value) -> valueGetter.get(value.floatValue())); + } + + public static > SpellAttribute createEnumerated(SpellAttributeType id, Trait trait, BiFunction valueGetter) { + Function cache = Util.memoize(t -> Text.translatable(Util.createTranslationKey("spell_attribute", id.id().withPath(p -> p + "." + t.name().toLowerCase(Locale.ROOT))))); + return new SpellAttribute<>(trait, valueGetter, (CustomisedSpellType type, List tooltip) -> { + T t = valueGetter.apply(type.traits(), type.traits().get(trait)); + + if (t != null) { + int max = t.getClass().getEnumConstants().length; + tooltip.add(Text.translatable(" %s (%s/%s)", cache.apply(t), Text.literal("" + (t.ordinal() + 1)).formatted(Formatting.LIGHT_PURPLE), max).formatted(Formatting.DARK_PURPLE)); + } + float difference = type.relativeTraits().get(trait); + if (difference != 0) { + tooltip.add(AttributeFormat.formatTraitDifference(trait, difference)); + } + }); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/SpellAttributeType.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/SpellAttributeType.java new file mode 100644 index 00000000..51394786 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/SpellAttributeType.java @@ -0,0 +1,59 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +import java.util.ArrayList; +import java.util.List; + +import com.minelittlepony.unicopia.Unicopia; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; + +public record SpellAttributeType(Identifier id, Text name) { + public static final List REGISTRY = new ArrayList<>(); + + @Deprecated + public static final SpellAttributeType CAST_ON_LOCATION = register("cast_on.location"); + public static final SpellAttributeType FOLLOWS_TARGET = register("follows_target"); + + public static final SpellAttributeType PERMIT_ITEMS = register("permit_items"); + public static final SpellAttributeType PERMIT_PASSIVE = register("permit_passive"); + public static final SpellAttributeType PERMIT_HOSTILE = register("permit_hostile"); + public static final SpellAttributeType PERMIT_PLAYER = register("permit_player"); + + public static final SpellAttributeType FOCUSED_ENTITY = register("focused_entity"); + public static final SpellAttributeType RANGE = register("range"); + public static final SpellAttributeType DURATION = register("duration"); + public static final SpellAttributeType STRENGTH = register("strength"); + public static final SpellAttributeType VELOCITY = register("velocity"); + public static final SpellAttributeType VERTICAL_VELOCITY = register("vertical_velocity"); + public static final SpellAttributeType HANG_TIME = register("hang_time"); + public static final SpellAttributeType PUSHING_POWER = register("pushing_power"); + public static final SpellAttributeType CAUSES_LEVITATION = register("causes_levitation"); + public static final SpellAttributeType AFFECTS = register("affects"); + public static final SpellAttributeType DAMAGE_TO_TARGET = register("damage_to_target"); + public static final SpellAttributeType SIMULTANIOUS_TARGETS = register("simultanious_targets"); + public static final SpellAttributeType COST_PER_INDIVIDUAL = register("cost_per_individual"); + public static final SpellAttributeType EXPLOSION_STRENGTH = register("explosion_strength"); + public static final SpellAttributeType PROJECTILE_COUNT = register("projectile_count"); + public static final SpellAttributeType ORB_COUNT = register("orb_count"); + public static final SpellAttributeType WAVE_SIZE = register("wave_size"); + public static final SpellAttributeType FOLLOW_RANGE = register("follow_range"); + public static final SpellAttributeType LIGHT_TARGET = register("light_target"); + public static final SpellAttributeType STICK_TO_TARGET = register("stick_to_target"); + public static final SpellAttributeType SOAPINESS = register("soapiness"); + public static final SpellAttributeType CAST_ON = register("cast_on"); + + public static final SpellAttributeType TARGET_PREFERENCE = register("target_preference"); + public static final SpellAttributeType CASTER_PREFERENCE = register("caster_preference"); + public static final SpellAttributeType NEGATES_FALL_DAMAGE = register("negates_fall_damage"); + + public SpellAttributeType(Identifier id) { + this(id, Text.translatable(Util.createTranslationKey("spell_attribute", id))); + } + + public static SpellAttributeType register(String name) { + SpellAttributeType type = new SpellAttributeType(Unicopia.id(name)); + REGISTRY.add(type); + return type; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/TooltipFactory.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/TooltipFactory.java new file mode 100644 index 00000000..3be2ff4c --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/attribute/TooltipFactory.java @@ -0,0 +1,36 @@ +package com.minelittlepony.unicopia.ability.magic.spell.attribute; + +import java.util.List; +import java.util.function.Predicate; + +import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; +import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; + +import net.minecraft.text.Text; + +public interface TooltipFactory { + TooltipFactory EMPTY = (type, tooltip) -> {}; + + void appendTooltip(CustomisedSpellType type, List tooltip); + + static TooltipFactory of(TooltipFactory...lines) { + return (type, tooltip) -> { + for (var line : lines) { + line.appendTooltip(type, tooltip); + } + }; + } + + static TooltipFactory of(Text line) { + return (type, tooltip) -> tooltip.add(line); + } + + default TooltipFactory conditionally(Predicate condition) { + TooltipFactory self = this; + return (type, tooltip) -> { + if (condition.test(type.traits())) { + self.appendTooltip(type, tooltip); + } + }; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java index 1660c01e..6d08d6a9 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AbstractSpell.java @@ -2,69 +2,77 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import java.util.UUID; -import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; +import com.minelittlepony.unicopia.server.world.Ether; import net.minecraft.nbt.NbtCompound; public abstract class AbstractSpell implements Spell { - private boolean dead; - private boolean dying; - private boolean dirty; - private boolean hidden; - private boolean destroyed; - - private CustomisedSpellType type; - private UUID uuid = UUID.randomUUID(); + private final CustomisedSpellType type; + + protected final DataTracker dataTracker = new DataTracker(0); + + private final DataTracker.Entry dead = dataTracker.startTracking(TrackableDataType.BOOLEAN, false); + private final DataTracker.Entry dying = dataTracker.startTracking(TrackableDataType.BOOLEAN, false); + private boolean dirty; + private final DataTracker.Entry hidden = dataTracker.startTracking(TrackableDataType.BOOLEAN, false); + private boolean destroyed; protected AbstractSpell(CustomisedSpellType type) { this.type = type; } + @Override + public final DataTracker getDataTracker() { + return dataTracker; + } + @Override public final UUID getUuid() { return uuid; } - @Override - public final SpellType getType() { + protected final SpellType getType() { return type.type(); } + @Override public final CustomisedSpellType getTypeAndTraits() { return type; } - @Override - public final SpellTraits getTraits() { + protected final SpellTraits getTraits() { return type.traits(); } @Override public final void setDead() { - dying = true; - setDirty(); + dying.set(true); } @Override public final boolean isDead() { - return dead; + return dead.get(); } @Override public final boolean isDying() { - return dying; + return dying.get(); } + @Deprecated @Override public final boolean isDirty() { return dirty; } + @Deprecated @Override public final void setDirty() { dirty = true; @@ -72,25 +80,17 @@ public abstract class AbstractSpell implements Spell { @Override public final boolean isHidden() { - return hidden; + return hidden.get(); } @Override public final void setHidden(boolean hidden) { - this.hidden = hidden; - } - - @Override - public Affinity getAffinity() { - return getType().getAffinity(); - } - - protected void onDestroyed(Caster caster) { + this.hidden.set(hidden); } @Override public void tickDying(Caster caster) { - dead = true; + dead.set(true); } @Override @@ -103,11 +103,17 @@ public abstract class AbstractSpell implements Spell { onDestroyed(caster); } + protected void onDestroyed(Caster caster) { + if (!caster.isClient()) { + Ether.get(caster.asWorld()).remove(this, caster); + } + } + @Override public void toNBT(NbtCompound compound) { - compound.putBoolean("dying", dying); - compound.putBoolean("dead", dead); - compound.putBoolean("hidden", hidden); + compound.putBoolean("dying", dying.get()); + compound.putBoolean("dead", dead.get()); + compound.putBoolean("hidden", hidden.get()); compound.putUuid("uuid", uuid); compound.put("traits", getTraits().toNbt()); } @@ -118,12 +124,9 @@ public abstract class AbstractSpell implements Spell { if (compound.containsUuid("uuid")) { uuid = compound.getUuid("uuid"); } - dying = compound.getBoolean("dying"); - dead = compound.getBoolean("dead"); - hidden = compound.getBoolean("hidden"); - if (compound.contains("traits")) { - type = type.type().withTraits(SpellTraits.fromNbt(compound.getCompound("traits")).orElse(SpellTraits.EMPTY)); - } + dying.set(compound.getBoolean("dying")); + dead.set(compound.getBoolean("dead")); + hidden.set(compound.getBoolean("hidden")); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java index 920c5bd5..cdc9e012 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AreaProtectionSpell.java @@ -2,7 +2,13 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; +import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.CastOn; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.mob.UEntities; @@ -22,10 +28,19 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell { .with(Trait.STRENGTH, 30) .build(); + private static final SpellAttribute CAST_ON = SpellAttribute.createEnumerated(SpellAttributeType.CAST_ON, Trait.FOCUS, focus -> focus > 0 ? CastOn.SELF : CastOn.LOCATION); + + static final TooltipFactory TOOLTIP = TooltipFactory.of(CAST_ON, RANGE); + protected AreaProtectionSpell(CustomisedSpellType type) { super(type); } + @Override + public Spell prepareForCast(Caster caster, CastingMethod method) { + return method == CastingMethod.STAFF || CAST_ON.get(getTraits()) == CastOn.LOCATION ? toPlaceable() : this; + } + @Override public boolean tick(Caster source, Situation situation) { @@ -33,7 +48,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell { return false; } - float radius = (float)getDrawDropOffRange(source); + float radius = (float)getRange(source); if (source.isClient()) { Vec3d origin = source.getOriginVector(); @@ -44,7 +59,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell { } }); } else { - Ether.get(source.asWorld()).getOrCreate(this, source); + Ether.get(source.asWorld()).getOrCreate(this, source).setRadius(radius); } source.findAllSpellsInRange(radius, e -> isValidTarget(source, e)).filter(caster -> !caster.hasCommonOwner(source)).forEach(caster -> { @@ -54,17 +69,9 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell { return !isDead(); } - @Override - protected void onDestroyed(Caster caster) { - Ether.get(caster.asWorld()).remove(this, caster); - } - - /** - * Calculates the maximum radius of the shield. aka The area of effect. - */ - public double getDrawDropOffRange(Caster source) { + private double getRange(Caster source) { float multiplier = source instanceof Pony pony && pony.asEntity().isSneaking() ? 1 : 2; - float min = 4 + getTraits().get(Trait.POWER); + float min = RANGE.get(getTraits()); double range = (min + (source.getLevel().getScaled(4) * 2)) / multiplier; if (source instanceof Pony && range > 2) { range = Math.sqrt(range); @@ -74,7 +81,7 @@ public class AreaProtectionSpell extends AbstractAreaEffectSpell { public boolean blocksMagicFor(Caster source, Caster other, Vec3d position) { return !FriendshipBraceletItem.isComrade(other, other.asEntity()) - && source.getOriginVector().distanceTo(position) <= getDrawDropOffRange(source); + && source.getOriginVector().distanceTo(position) <= getRange(source); } protected boolean isValidTarget(Caster source, Entity entity) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractionUtils.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractionUtils.java index 76b2d29e..2f8833e9 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractionUtils.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractionUtils.java @@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.entity.Living; +import com.minelittlepony.unicopia.entity.effect.EffectUtils; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; @@ -61,12 +62,10 @@ public interface AttractionUtils { return Pony.of(entity).map(pony -> { double force = 0.75; - if (pony.getCompositeRace().canUseEarth()) { + if (EffectUtils.hasExtraDefenses(pony.asEntity())) { + force /= 12; + } else if (pony.getCompositeRace().canUseEarth()) { force /= 2; - - if (pony.asEntity().isSneaking()) { - force /= 6; - } } else if (pony.getCompositeRace().canFly()) { force *= 2; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java index 9af5cddb..7bf39425 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AttractiveSpell.java @@ -2,6 +2,9 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.Living; @@ -13,7 +16,6 @@ import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.entity.Entity; -import net.minecraft.entity.ItemEntity; import net.minecraft.nbt.NbtCompound; import net.minecraft.particle.ParticleTypes; import net.minecraft.util.hit.EntityHitResult; @@ -21,6 +23,10 @@ import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSpell, ProjectileDelegate.EntityHitListener { + static final SpellAttribute TARGET_FOCUSED_ENTITY = SpellAttribute.createConditional(SpellAttributeType.FOCUSED_ENTITY, Trait.ORDER, order -> order >= 20); + static final SpellAttribute STICK_TO_TARGET = SpellAttribute.createConditional(SpellAttributeType.STICK_TO_TARGET, Trait.CHAOS, chaos -> chaos > 0); + static final TooltipFactory TARGET = (type, tooltip) -> (TARGET_FOCUSED_ENTITY.get(type.traits()) ? TARGET_FOCUSED_ENTITY : ShieldSpell.TARGET).appendTooltip(type, tooltip); + static final TooltipFactory TOOLTIP = TooltipFactory.of(TIME, RANGE, TARGET, STICK_TO_TARGET, CAST_ON); private final EntityReference target = new EntityReference<>(); @@ -28,7 +34,8 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp protected AttractiveSpell(CustomisedSpellType type) { super(type); - timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); + timer = new Timer(TIME.get(getTraits())); + dataTracker.startTracking(target); } @Override @@ -38,14 +45,10 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp @Override public boolean tick(Caster caster, Situation situation) { - if (getType() != SpellType.DARK_VORTEX) { - timer.tick(); + timer.tick(); - if (timer.getTicksRemaining() <= 0) { - return false; - } - - setDirty(); + if (timer.getTicksRemaining() <= 0) { + return false; } target.getOrEmpty(caster.asWorld()) @@ -73,17 +76,9 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp }); } - @Override - public double getDrawDropOffRange(Caster caster) { - return 10 + (caster.getLevel().getScaled(8) * 2); - } - @Override protected boolean isValidTarget(Caster source, Entity entity) { - if (target.referenceEquals(entity)) { - return true; - } - return getTraits().get(Trait.KNOWLEDGE) > 10 ? entity instanceof ItemEntity : super.isValidTarget(source, entity); + return target.referenceEquals(entity) || super.isValidTarget(source, entity); } @Override @@ -133,7 +128,7 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp @Override public boolean setTarget(Entity target) { - if (getTraits().get(Trait.ORDER) >= 20) { + if (TARGET_FOCUSED_ENTITY.get(getTraits())) { this.target.set(target); target.setGlowing(true); return true; @@ -149,7 +144,7 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp @Override public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) { - if (!isDead() && getTraits().get(Trait.CHAOS) > 0) { + if (!isDead() && STICK_TO_TARGET.get(getTraits())) { setDead(); Caster.of(hit.getEntity()).ifPresent(caster -> getTypeAndTraits().apply(caster, CastingMethod.INDIRECT)); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AwkwardSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AwkwardSpell.java index 964fe1d4..dab51b1b 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AwkwardSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/AwkwardSpell.java @@ -40,8 +40,6 @@ public class AwkwardSpell extends AbstractSpell implements TimedSpell { if (timer.getTicksRemaining() <= 0) { return false; } - - setDirty(); } if (source.isClient()) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java index 63afe303..df1e9c92 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/BubbleSpell.java @@ -6,18 +6,24 @@ import java.util.UUID; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.*; +import com.minelittlepony.unicopia.entity.AttributeContainer; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.entity.Entity; -import net.minecraft.entity.LivingEntity; import net.minecraft.entity.attribute.*; import net.minecraft.entity.attribute.EntityAttributeModifier.Operation; import net.minecraft.nbt.NbtCompound; @@ -46,17 +52,21 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, .with(Trait.POWER, 1) .build(); + private static final SpellAttribute SOAPINESS = SpellAttribute.create(SpellAttributeType.SOAPINESS, AttributeFormat.REGULAR, Trait.POWER, power -> (int)(power * 2)); + + static final TooltipFactory TOOLTIP = TooltipFactory.of(TimedSpell.TIME, SOAPINESS); + private final Timer timer; - private int struggles; - private float prevRadius; - private float radius; + private DataTracker.Entry radius; + private DataTracker.Entry struggles; protected BubbleSpell(CustomisedSpellType type) { super(type); - timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); - struggles = (int)(getTraits().get(Trait.POWER) * 2); + timer = new Timer(TIME.get(getTraits())); + radius = dataTracker.startTracking(TrackableDataType.FLOAT, 0F); + struggles = dataTracker.startTracking(TrackableDataType.INT, SOAPINESS.get(getTraits())); } @Override @@ -65,27 +75,22 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, } public float getRadius(float tickDelta) { - return MathHelper.lerp(tickDelta, prevRadius, radius); + return MathHelper.lerp(tickDelta, prevRadius, radius.get()); } @Override public boolean apply(Caster source) { - if (getType().isOn(source)) { - source.getSpellSlot().removeWhere(getType(), true); + if (source.getSpellSlot().removeWhere(getType())) { return false; } Entity entity = source.asEntity(); - if (entity instanceof LivingEntity l) { - MODIFIERS.forEach((attribute, modifier) -> { - if (l.getAttributes().hasAttribute(attribute)) { - l.getAttributeInstance(attribute).addPersistentModifier(modifier); - } - }); + if (source instanceof AttributeContainer l) { + l.applyAttributeModifiers(MODIFIERS, false, true); } - radius = Math.max(entity.getHeight(), entity.getWidth()) * 1.2F; + radius.set(Math.max(entity.getHeight(), entity.getWidth()) * 1.2F); source.playSound(USounds.ENTITY_PLAYER_UNICORN_TELEPORT, 1); entity.addVelocity(0, 0.2F * source.getPhysics().getGravitySignum(), 0); Living.updateVelocity(entity); @@ -105,7 +110,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, boolean done = timer.getTicksRemaining() <= 0; - source.spawnParticles(source.getOriginVector().add(0, 1, 0), new Sphere(true, radius * (done ? 0.25F : 0.5F)), done ? 13 : 1, pos -> { + source.spawnParticles(source.getOriginVector().add(0, 1, 0), new Sphere(true, radius.get() * (done ? 0.25F : 0.5F)), done ? 13 : 1, pos -> { source.addParticle(done ? ParticleTypes.BUBBLE_POP : UParticles.BUBBLE, pos, Vec3d.ZERO); }); @@ -113,8 +118,6 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, return false; } - setDirty(); - source.asEntity().addVelocity( MathHelper.sin(source.asEntity().age / 6F) / 50F, MathHelper.sin(source.asEntity().age / 6F) / 50F, @@ -123,13 +126,14 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, source.asEntity().fallDistance = 0; - prevRadius = radius; + prevRadius = radius.get(); if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) { - setDirty(); - radius += 0.5F; + radius.set(radius.get() + 0.5F); source.playSound(USounds.SPELL_BUBBLE_DISTURB, 1); - if (struggles-- <= 0) { + int s = struggles.get() - 1; + struggles.set(s); + if (s <= 0) { setDead(); return false; } @@ -140,12 +144,9 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, @Override protected void onDestroyed(Caster source) { - if (source.asEntity() instanceof LivingEntity l) { - MODIFIERS.forEach((attribute, modifier) -> { - if (l.getAttributes().hasAttribute(attribute)) { - l.getAttributeInstance(attribute).removeModifier(modifier.getId()); - } - }); + super.onDestroyed(source); + if (source instanceof AttributeContainer l) { + l.applyAttributeModifiers(MODIFIERS, false, false); } source.playSound(USounds.ENTITY_PLAYER_UNICORN_TELEPORT, 1); } @@ -161,16 +162,16 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, @Override public void toNBT(NbtCompound compound) { super.toNBT(compound); - compound.putInt("struggles", struggles); - compound.putFloat("radius", radius); + compound.putInt("struggles", struggles.get()); + compound.putFloat("radius", radius.get()); timer.toNBT(compound); } @Override public void fromNBT(NbtCompound compound) { super.fromNBT(compound); - struggles = compound.getInt("struggles"); - radius = compound.getFloat("radius"); + struggles.set(compound.getInt("struggles")); + radius.set(compound.getFloat("radius")); timer.fromNBT(compound); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java index ce440570..036cc8f8 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CatapultSpell.java @@ -1,5 +1,6 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.List; import java.util.function.Consumer; import org.jetbrains.annotations.Nullable; @@ -7,6 +8,11 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.UTags; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.Affects; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity; @@ -17,10 +23,16 @@ import com.minelittlepony.unicopia.projectile.ProjectileDelegate; import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; import net.minecraft.entity.FallingBlockEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.text.Text; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.random.Random; import net.minecraft.world.World; /** @@ -37,20 +49,51 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B private static final float HORIZONTAL_VARIANCE = 0.25F; private static final float MAX_STRENGTH = 120; + private static final SpellAttribute LAUNCH_SPEED = SpellAttribute.create(SpellAttributeType.VERTICAL_VELOCITY, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.STRENGTH, strength -> 0.1F + (MathHelper.clamp(strength, -MAX_STRENGTH, MAX_STRENGTH) - 40) / 16F); + private static final SpellAttribute HANG_TIME = SpellAttribute.create(SpellAttributeType.HANG_TIME, AttributeFormat.TIME, AttributeFormat.PERCENTAGE, Trait.AIR, air -> 50 + (int)MathHelper.clamp(air, 0, 10) * 20F); + private static final SpellAttribute PUSHING_POWER = SpellAttribute.create(SpellAttributeType.PUSHING_POWER, AttributeFormat.REGULAR, Trait.POWER, power -> 1 + MathHelper.clamp(power, 0, 10) / 10F); + private static final SpellAttribute CAUSES_LEVITATION = SpellAttribute.createConditional(SpellAttributeType.CAUSES_LEVITATION, Trait.FOCUS, focus -> focus > 50); + private static final SpellAttribute AFFECTS = SpellAttribute.createEnumerated(SpellAttributeType.AFFECTS, Trait.ORDER, order -> { + if (order <= 0) { + return Affects.BOTH; + } else if (order <= 10) { + return Affects.ENTITIES; + } + return Affects.BLOCKS; + }); + static final TooltipFactory TOOLTIP = TooltipFactory.of(LAUNCH_SPEED, HANG_TIME, PUSHING_POWER, CAUSES_LEVITATION, AFFECTS); + + static void appendTooltip(CustomisedSpellType type, List tooltip) { + TOOLTIP.appendTooltip(type, tooltip); + } + protected CatapultSpell(CustomisedSpellType type) { super(type); } @Override public void onImpact(MagicProjectileEntity projectile, BlockHitResult hit) { + if (!AFFECTS.get(getTraits()).allowsBlocks()) { + return; + } + if (!projectile.isClient() && projectile instanceof MagicBeamEntity source && source.canModifyAt(hit.getBlockPos())) { - createBlockEntity(projectile.getWorld(), hit.getBlockPos(), e -> apply(source, e)); + createBlockEntity(projectile.getWorld(), hit.getBlockPos(), e -> { + e.setOnGround(true); + apply(source, e); + e.setOnGround(false); + }); } } @Override public void onImpact(MagicProjectileEntity projectile, EntityHitResult hit) { if (!projectile.isClient() && projectile instanceof MagicBeamEntity source) { + Entity e = hit.getEntity(); + if (!(e instanceof FallingBlockEntity) && !AFFECTS.get(getTraits()).allowsEntities()) { + return; + } + apply(source, hit.getEntity()); } } @@ -67,16 +110,40 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B } protected void apply(Caster caster, Entity e) { - Vec3d vel = caster.asEntity().getVelocity(); - if (Math.abs(e.getVelocity().y) > 0.5) { - e.setVelocity(caster.asEntity().getVelocity()); + + float power = 1 + getTraits().get(Trait.POWER, 0, 10) / 10F; + + if (!e.isOnGround()) { + e.setVelocity(caster.asEntity().getVelocity().multiply(power)); } else { + Random rng = caster.asWorld().random; e.addVelocity( - ((caster.asWorld().random.nextFloat() * HORIZONTAL_VARIANCE) - HORIZONTAL_VARIANCE + vel.x * 0.8F) * 0.1F, - 0.1F + (getTraits().get(Trait.STRENGTH, -MAX_STRENGTH, MAX_STRENGTH) - 40) / 16D, - ((caster.asWorld().random.nextFloat() * HORIZONTAL_VARIANCE) - HORIZONTAL_VARIANCE + vel.z * 0.8F) * 0.1F + rng.nextTriangular(0, HORIZONTAL_VARIANCE) * 0.1F, + LAUNCH_SPEED.get(getTraits()), + rng.nextTriangular(0, HORIZONTAL_VARIANCE) * 0.1F ); + + int hoverDuration = HANG_TIME.get(getTraits()).intValue(); + boolean noGravity = CAUSES_LEVITATION.get(getTraits()); + + if (e instanceof LivingEntity l) { + if (l.hasStatusEffect(StatusEffects.SLOW_FALLING)) { + l.removeStatusEffect(StatusEffects.SLOW_FALLING); + } + l.addStatusEffect(new StatusEffectInstance(StatusEffects.SLOW_FALLING, hoverDuration, 1)); + } + + if (noGravity || e instanceof FallingBlockEntity && (!e.getWorld().getBlockState(e.getBlockPos().up()).isReplaceable())) { + if (e instanceof LivingEntity l) { + l.addStatusEffect(new StatusEffectInstance(StatusEffects.LEVITATION, 200, 1)); + } else { + e.setNoGravity(true); + } + } } + + e.velocityDirty = true; + e.velocityModified = true; } static void createBlockEntity(World world, BlockPos bpos, @Nullable Consumer apply) { @@ -101,7 +168,5 @@ public class CatapultSpell extends AbstractSpell implements ProjectileDelegate.B apply.accept(e); } world.spawnEntity(e); - - e.updateVelocity(HORIZONTAL_VARIANCE, pos); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CustomisedSpellType.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CustomisedSpellType.java index 4c346401..48aac638 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CustomisedSpellType.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/CustomisedSpellType.java @@ -1,26 +1,45 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + import org.jetbrains.annotations.Nullable; +import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; +import com.minelittlepony.unicopia.client.TextHelper; +import com.minelittlepony.unicopia.entity.effect.EffectUtils; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import net.minecraft.util.TypedActionResult; public record CustomisedSpellType ( SpellType type, - SpellTraits traits + SpellTraits traits, + Supplier traitsDifferenceSupplier ) implements SpellPredicate { public boolean isEmpty() { return type.isEmpty(); } + public boolean isStackable() { + return type().isStackable(); + } + + public SpellTraits relativeTraits() { + return traitsDifferenceSupplier.get(); + } + public T create() { try { return type.getFactory().create(this); @@ -31,6 +50,14 @@ public record CustomisedSpellType ( return null; } + @Nullable + public T create(NbtCompound compound) { + T spell = create(); + if (spell != null) { + spell.fromNBT(compound); + } + return spell; + } @Nullable public T apply(Caster caster, CastingMethod method) { @@ -50,29 +77,47 @@ public record CustomisedSpellType ( } @Override - public boolean test(Spell spell) { - return type.test(spell) && spell.getTraits().equals(traits); + public boolean test(@Nullable Spell spell) { + return spell != null && spell.getTypeAndTraits().equals(this); } public ItemStack getDefaultStack() { return traits.applyTo(type.getDefualtStack()); } + public void appendTooltip(List lines) { + MutableText lore = Text.translatable(type().getTranslationKey() + ".lore").formatted(type().getAffinity().getColor()); + + if (!InteractionManager.getInstance().getClientSpecies().canCast()) { + lore = lore.formatted(Formatting.OBFUSCATED); + } + lines.addAll(TextHelper.wrap(lore, 180).toList()); + float corruption = ((int)traits().getCorruption() * 10) + type().getAffinity().getCorruption(); + List modifiers = new ArrayList<>(); + type.getTooltip().appendTooltip(this, modifiers); + if (corruption != 0) { + modifiers.add(EffectUtils.formatModifierChange("affinity.unicopia.corruption", corruption, true)); + } + if (!modifiers.isEmpty()) { + lines.add(Text.empty()); + lines.add(Text.translatable("affinity.unicopia.when_cast").formatted(Formatting.GRAY)); + lines.addAll(modifiers); + } + + } + public TypedActionResult> toAction() { return isEmpty() ? TypedActionResult.fail(this) : TypedActionResult.pass(this); } - public NbtCompound toNBT() { - NbtCompound tag = new NbtCompound(); - type.toNbt(tag); - tag.put("traits", traits.toNbt()); - return tag; + public NbtCompound toNbt(NbtCompound compound) { + type.toNbt(compound); + compound.put("traits", traits.toNbt()); + return compound; } - public static CustomisedSpellType fromNBT(NbtCompound compound) { - SpellType type = SpellType.getKey(compound); - SpellTraits traits = SpellTraits.fromNbt(compound.getCompound("traits")).orElse(type.getTraits()); - - return type.withTraits(traits); + public static CustomisedSpellType fromNBT(NbtCompound compound) { + SpellType type = SpellType.getKey(compound); + return type.withTraits(SpellTraits.fromNbt(compound.getCompound("traits")).orElse(type.getTraits())); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java index e46941c0..14eaa665 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DarkVortexSpell.java @@ -17,6 +17,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.ParticleUtils; @@ -56,7 +58,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate .with(Trait.DARKNESS, 100) .build(); - private float accumulatedMass = 0; + private final DataTracker.Entry accumulatedMass = this.dataTracker.startTracking(TrackableDataType.FLOAT, 0F); private final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget).setTargetowner(true).setTargetAllies(true); @@ -70,7 +72,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate // 3. force reaches 0 at distance of drawDropOffRange private double getMass() { - return 0.1F + accumulatedMass / 10F; + return 0.1F + accumulatedMass.get() / 10F; } public double getEventHorizonRadius() { @@ -174,11 +176,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate @Override public void tickDying(Caster source) { - accumulatedMass -= 0.8F; + float m = accumulatedMass.get() - 0.8F; + accumulatedMass.set(m); double mass = getMass() * 0.1; double logarithm = 1 - (1D / (1 + (mass * mass))); radius.update((float)Math.max(0.1, logarithm * source.asWorld().getGameRules().getInt(UGameRules.MAX_DARK_VORTEX_SIZE)), 200L); - if (accumulatedMass < 1) { + if (m < 1) { super.tickDying(source); } @@ -202,7 +205,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate @Override public boolean isFriendlyTogether(Affine other) { - return accumulatedMass < 4; + return accumulatedMass.get() < 4; } private boolean isValidTarget(Caster source, Entity entity) { @@ -274,8 +277,7 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate double massOfTarget = AttractionUtils.getMass(target); if (!source.isClient() && massOfTarget != 0) { - accumulatedMass += massOfTarget; - setDirty(); + accumulatedMass.set((float)(accumulatedMass.get() + massOfTarget)); } target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE); @@ -303,12 +305,12 @@ public class DarkVortexSpell extends AbstractSpell implements ProjectileDelegate @Override public void toNBT(NbtCompound compound) { super.toNBT(compound); - compound.putFloat("accumulatedMass", accumulatedMass); + compound.putFloat("accumulatedMass", accumulatedMass.get()); } @Override public void fromNBT(NbtCompound compound) { super.fromNBT(compound); - accumulatedMass = compound.getFloat("accumulatedMass"); + accumulatedMass.set(compound.getFloat("accumulatedMass")); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DispellEvilSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DispellEvilSpell.java index 869ef51d..137e21a1 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DispellEvilSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DispellEvilSpell.java @@ -2,6 +2,10 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; @@ -18,6 +22,10 @@ public class DispellEvilSpell extends AbstractSpell implements ProjectileDelegat .with(Trait.POWER, 1) .build(); + private static final SpellAttribute RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.TIME, AttributeFormat.PERCENTAGE, Trait.POWER, power -> (1 + power) * 10D); + + static final TooltipFactory TOOLTIP = RANGE; + protected DispellEvilSpell(CustomisedSpellType type) { super(type); } @@ -28,7 +36,7 @@ public class DispellEvilSpell extends AbstractSpell implements ProjectileDelegat return !isDead(); } - source.findAllEntitiesInRange(getTraits().get(Trait.POWER) * 10, e -> e.getType() == EntityType.PHANTOM).forEach(entity -> { + source.findAllEntitiesInRange(RANGE.get(getTraits()), e -> e.getType() == EntityType.PHANTOM).forEach(entity -> { entity.damage(entity.getDamageSources().magic(), 50); if (entity instanceof LivingEntity l) { double d = source.getOriginVector().getX() - entity.getX(); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java index 9affae3e..af56953e 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisperseIllusionSpell.java @@ -5,6 +5,10 @@ import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.util.shape.Sphere; @@ -15,6 +19,10 @@ import net.minecraft.util.math.Vec3d; * An area-effect spell that disperses illusions. */ public class DisperseIllusionSpell extends AbstractAreaEffectSpell { + private static final SpellAttribute RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> Math.max(0, 15 + power)); + private static final SpellAttribute DURATION = SpellAttribute.create(SpellAttributeType.DURATION, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.STRENGTH, strength -> (1 + (long)strength) * 100); + static final TooltipFactory TOOLTIP = TooltipFactory.of(RANGE, DURATION); + protected DisperseIllusionSpell(CustomisedSpellType type) { super(type); } @@ -22,7 +30,7 @@ public class DisperseIllusionSpell extends AbstractAreaEffectSpell { @Override public boolean tick(Caster source, Situation situation) { - float range = Math.max(0, 15 + getTraits().get(Trait.POWER)); + float range = RANGE.get(getTraits()); if (range == 0) { return false; @@ -38,10 +46,10 @@ public class DisperseIllusionSpell extends AbstractAreaEffectSpell { } source.findAllSpellsInRange(range).forEach(e -> { - e.getSpellSlot().get(SpellPredicate.CAN_SUPPRESS, false) + e.getSpellSlot().get(SpellPredicate.CAN_SUPPRESS) .filter(spell -> spell.isVulnerable(source, this)) .ifPresent(spell -> { - spell.onSuppressed(source, 1 + getTraits().get(Trait.STRENGTH)); + spell.onSuppressed(source, DURATION.get(getTraits())); e.playSound(USounds.SPELL_ILLUSION_DISPERSE, 0.2F, 0.5F); }); }); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java index a58b77de..1afef18f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/DisplacementSpell.java @@ -3,6 +3,10 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; @@ -16,6 +20,10 @@ import net.minecraft.util.math.Vec3d; public class DisplacementSpell extends AbstractSpell implements HomingSpell, ProjectileDelegate.EntityHitListener { + private static final SpellAttribute DAMAGE_TO_TARGET = SpellAttribute.create(SpellAttributeType.DAMAGE_TO_TARGET, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.BLOOD, blood -> blood); + + static final TooltipFactory TOOLTIP = DAMAGE_TO_TARGET; + private final EntityReference target = new EntityReference<>(); private int ticks = 10; @@ -71,7 +79,7 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pro entity.setGlowing(false); entity.playSound(USounds.SPELL_DISPLACEMENT_TELEPORT, 1, 1); - float damage = getTraits().get(Trait.BLOOD); + float damage = DAMAGE_TO_TARGET.get(getTraits()); if (damage > 0) { entity.damage(source.damageOf(UDamageTypes.EXHAUSTION, source), damage); } @@ -90,6 +98,7 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pro @Override protected void onDestroyed(Caster caster) { + super.onDestroyed(caster); caster.getOriginatingCaster().asEntity().setGlowing(false); target.ifPresent(caster.asWorld(), e -> e.setGlowing(false)); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java index 40fd8413..e90558d0 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FeatherFallSpell.java @@ -6,6 +6,10 @@ import java.util.stream.Stream; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.TimedSpell; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.item.FriendshipBraceletItem; @@ -28,6 +32,25 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell { private static final float POWERS_RANGE_WEIGHT = 0.3F; private static final float MAX_GENEROSITY_FACTOR = 19F; + private static final SpellAttribute DURATION = SpellAttribute.create(SpellAttributeType.DURATION, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.FOCUS, focus -> 10 + (int)(MathHelper.clamp(focus, 0, 160))); + private static final SpellAttribute STRENGTH = SpellAttribute.create(SpellAttributeType.STRENGTH, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.STRENGTH, strength -> MathHelper.clamp(strength, 2, 9)); + private static final SpellAttribute RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> MathHelper.clamp((power - 10) * POWERS_RANGE_WEIGHT, MIN_RANGE, MAX_RANGE)); + private static final SpellAttribute SIMULTANIOUS_TARGETS = SpellAttribute.create(SpellAttributeType.SIMULTANIOUS_TARGETS, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.GENEROSITY, (traits, generosity) -> { + return (long)(generosity + traits.get(Trait.FOCUS, MIN_TARGETS, MAX_TARGETS) * 2); + }); + private static final SpellAttribute COST_PER_INDIVIDUAL = SpellAttribute.create(SpellAttributeType.COST_PER_INDIVIDUAL, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, (traits, power) -> { + return MathHelper.clamp(((Math.max(power, 10) - 10) * POWERS_RANGE_WEIGHT) - ((Math.max(traits.get(Trait.FOCUS), 80) - 80) * FOCUS_RANGE_WEIGHT), 1, 7); + }); + private static final SpellAttribute TARGET_PREFERENCE = SpellAttribute.create(SpellAttributeType.TARGET_PREFERENCE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.GENEROSITY, generosity -> { + return MathHelper.clamp(generosity, 1, MAX_GENEROSITY_FACTOR) / MAX_GENEROSITY_FACTOR; + }); + private static final SpellAttribute CASTER_PREFERENCE = SpellAttribute.create(SpellAttributeType.CASTER_PREFERENCE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.GENEROSITY, (traits, generosity) -> { + return 1 - TARGET_PREFERENCE.get(traits); + }); + private static final SpellAttribute NEGATES_FALL_DAMAGE = SpellAttribute.createConditional(SpellAttributeType.NEGATES_FALL_DAMAGE, Trait.GENEROSITY, (generosity) -> generosity > 0.5F); + + static final TooltipFactory TOOLTIP = TooltipFactory.of(DURATION, STRENGTH, RANGE, SIMULTANIOUS_TARGETS, COST_PER_INDIVIDUAL, TARGET_PREFERENCE, CASTER_PREFERENCE, NEGATES_FALL_DAMAGE); + public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() .with(Trait.FOCUS, 80) .with(Trait.POWER, 10) @@ -40,7 +63,7 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell { protected FeatherFallSpell(CustomisedSpellType type) { super(type); - timer = new Timer(10 + (int)(getTraits().get(Trait.FOCUS, 0, 160))); + timer = new Timer(DURATION.get(getTraits())); } @Override @@ -56,26 +79,22 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell { return false; } - setDirty(); - List targets = getTargets(caster).toList(); if (targets.isEmpty()) { return true; } - final float strength = 1F / (getTraits().get(Trait.STRENGTH, 2, 9) / targets.size()); - final float generosity = getTraits().get(Trait.GENEROSITY, 1, MAX_GENEROSITY_FACTOR) / MAX_GENEROSITY_FACTOR; + final float strength = 1F / (STRENGTH.get(getTraits()) / targets.size()); + final float targetPreference = TARGET_PREFERENCE.get(getTraits()); + final float casterPreference = 1 - targetPreference; + final boolean negateFallDamage = NEGATES_FALL_DAMAGE.get(getTraits()); Entity entity = caster.asEntity(); Vec3d masterVelocity = entity.getVelocity().multiply(0.1); targets.forEach(target -> { if (target.getVelocity().y < 0) { - - boolean isSelf = caster.isOwnedBy(target) || target == entity; - float delta = strength * (isSelf ? (1F - generosity) : generosity); - - if (!isSelf || generosity < 0.5F) { + if (negateFallDamage) { target.verticalCollision = true; target.setOnGround(true); target.fallDistance = 0; @@ -83,6 +102,8 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell { if (target instanceof PlayerEntity) { ((PlayerEntity)target).getAbilities().flying = false; } + + float delta = strength * ((caster.isOwnedBy(target) || target == entity) ? casterPreference : targetPreference); target.setVelocity(target.getVelocity().multiply(1, delta, 1)); if (situation == Situation.PROJECTILE && target != entity) { target.addVelocity(masterVelocity.x, 0, masterVelocity.z); @@ -91,35 +112,16 @@ public class FeatherFallSpell extends AbstractSpell implements TimedSpell { ParticleUtils.spawnParticles(new MagicParticleEffect(getType().getColor()), target, 7); }); - return caster.subtractEnergyCost(timer.getTicksRemaining() % 50 == 0 ? getCostPerEntity() * targets.size() : 0); - } - - protected double getCostPerEntity() { - float focus = Math.max(getTraits().get(Trait.FOCUS), 80) - 80; - float power = Math.max(getTraits().get(Trait.POWER), 10) - 10; - - return MathHelper.clamp((power * POWERS_RANGE_WEIGHT) - (focus * FOCUS_RANGE_WEIGHT), 1, 7); - } - - protected double getEffectRange() { - float power = getTraits().get(Trait.POWER) - 10; - - return MathHelper.clamp(power * POWERS_RANGE_WEIGHT, MIN_RANGE, MAX_RANGE); - } - - protected long getMaxTargets() { - long generosity = (long)getTraits().get(Trait.GENEROSITY) * 2L; - long focus = (long)getTraits().get(Trait.FOCUS, MIN_TARGETS, MAX_TARGETS) * 2L; - return generosity + focus; + return caster.subtractEnergyCost(timer.getTicksRemaining() % 50 == 0 ? COST_PER_INDIVIDUAL.get(getTraits()) * targets.size() : 0); } protected Stream getTargets(Caster caster) { - return Stream.concat(Stream.of(caster.asEntity()), caster.findAllEntitiesInRange(getEffectRange()).sorted((a, b) -> { + return Stream.concat(Stream.of(caster.asEntity()), caster.findAllEntitiesInRange(RANGE.get(getTraits())).sorted((a, b) -> { return Integer.compare( FriendshipBraceletItem.isComrade(caster, a) ? 1 : 0, FriendshipBraceletItem.isComrade(caster, b) ? 1 : 0 ); - }).distinct()).limit(getMaxTargets()); + }).distinct()).limit(SIMULTANIOUS_TARGETS.get(getTraits())); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java index db63eaf4..346a652f 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireBoltSpell.java @@ -4,6 +4,10 @@ import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.HomingSpell; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.EntityReference; @@ -15,6 +19,7 @@ import net.minecraft.item.Items; import net.minecraft.nbt.NbtCompound; import net.minecraft.predicate.entity.EntityPredicates; import net.minecraft.util.hit.EntityHitResult; +import net.minecraft.util.math.MathHelper; public class FireBoltSpell extends AbstractSpell implements HomingSpell, ProjectileDelegate.ConfigurationListener, ProjectileDelegate.EntityHitListener { @@ -31,6 +36,15 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell, .with(Trait.FIRE, 60) .build(); + private static final SpellAttribute VELOCITY = SpellAttribute.create(SpellAttributeType.VELOCITY, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.STRENGTH, strength -> 1.3F + (strength / 11F)); + private static final SpellAttribute PROJECTILE_COUNT = SpellAttribute.create(SpellAttributeType.PROJECTILE_COUNT, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.EARTH, earth -> 11 + (int)earth * 3); + private static final SpellAttribute FOLLOWS_TARGET = SpellAttribute.createConditional(SpellAttributeType.FOLLOWS_TARGET, Trait.FOCUS, focus -> focus >= 50); + private static final SpellAttribute FOLLOW_RANGE = SpellAttribute.create(SpellAttributeType.FOLLOW_RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.FOCUS, focus -> Math.max(0F, focus - 49)); + private static final SpellAttribute MAX_EXPLOSION_STRENGTH = SpellAttribute.create(SpellAttributeType.EXPLOSION_STRENGTH, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.FOCUS, focus -> focus >= 50 ? 10F : 1F); + private static final SpellAttribute EXPLOSION_STRENGTH = SpellAttribute.create(SpellAttributeType.EXPLOSION_STRENGTH, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, (traits, focus) -> MathHelper.clamp(focus / 50, 0, MAX_EXPLOSION_STRENGTH.get(traits))); + + static final TooltipFactory TOOLTIP = TooltipFactory.of(MAX_EXPLOSION_STRENGTH, EXPLOSION_STRENGTH, VELOCITY, PROJECTILE_COUNT, FOLLOWS_TARGET, FOLLOW_RANGE.conditionally(FOLLOWS_TARGET::get)); + private final EntityReference target = new EntityReference<>(); protected FireBoltSpell(CustomisedSpellType type) { @@ -44,10 +58,12 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell, @Override public boolean tick(Caster caster, Situation situation) { + boolean followTarget = FOLLOWS_TARGET.get(getTraits()); + float followRage = FOLLOW_RANGE.get(getTraits()); if (situation == Situation.PROJECTILE) { - if (caster instanceof MagicProjectileEntity projectile && getTraits().get(Trait.FOCUS) >= 50) { + if (caster instanceof MagicProjectileEntity projectile && followTarget) { caster.findAllEntitiesInRange( - getTraits().get(Trait.FOCUS) - 49, + followRage, EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster)) ).findFirst().ifPresent(target -> projectile.setHomingTarget(target)); } @@ -55,9 +71,9 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell, return true; } - if (getTraits().get(Trait.FOCUS) >= 50 && target.getOrEmpty(caster.asWorld()).isEmpty()) { + if (followTarget && target.getOrEmpty(caster.asWorld()).isEmpty()) { target.set(caster.findAllEntitiesInRange( - getTraits().get(Trait.FOCUS) - 49, + followRage, EntityPredicates.VALID_LIVING_ENTITY.and(TargetSelecter.validTarget(this, caster)) ).findFirst().orElse(null)); } @@ -75,18 +91,18 @@ public class FireBoltSpell extends AbstractSpell implements HomingSpell, @Override public void configureProjectile(MagicProjectileEntity projectile, Caster caster) { projectile.setItem(Items.FIRE_CHARGE.getDefaultStack()); - projectile.addThrowDamage(getTraits().get(Trait.POWER, 0, getTraits().get(Trait.FOCUS) >= 50 ? 500 : 50) / 10F); + projectile.addThrowDamage(EXPLOSION_STRENGTH.get(getTraits())); projectile.setFireTicks(900000); - projectile.setVelocity(projectile.getVelocity().multiply(1.3 + getTraits().get(Trait.STRENGTH) / 11F)); + projectile.setVelocity(projectile.getVelocity().multiply(VELOCITY.get(getTraits()))); } protected int getNumberOfBalls(Caster caster) { - return 1 + caster.asWorld().random.nextInt(3) + (int)getTraits().get(Trait.EARTH) * 3; + return PROJECTILE_COUNT.get(getTraits()) + caster.asWorld().random.nextInt(3); } @Override public boolean setTarget(Entity target) { - if (getTraits().get(Trait.FOCUS) >= 50) { + if (FOLLOWS_TARGET.get(getTraits())) { this.target.set(target); return true; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java index 8dade44f..d7b9568c 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/FireSpell.java @@ -67,14 +67,14 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileDele generateParticles(source); } - return new Sphere(false, Math.max(0, 4 + getTraits().get(Trait.POWER))).translate(source.getOrigin()).getBlockPositions().reduce(false, + return new Sphere(false, RANGE.get(getTraits())).translate(source.getOrigin()).getBlockPositions().reduce(false, (r, i) -> source.canModifyAt(i) && applyBlocks(source.asWorld(), i), (a, b) -> a || b) || applyEntities(source, source.getOriginVector()); } protected void generateParticles(Caster source) { - source.spawnParticles(new Sphere(false, Math.max(0, 4 + getTraits().get(Trait.POWER))), (int)(1 + source.getLevel().getScaled(8)) * 6, pos -> { + source.spawnParticles(new Sphere(false, RANGE.get(getTraits())), (int)(1 + source.getLevel().getScaled(8)) * 6, pos -> { source.addParticle(ParticleTypes.LARGE_SMOKE, pos, Vec3d.ZERO); }); } @@ -121,8 +121,12 @@ public class FireSpell extends AbstractAreaEffectSpell implements ProjectileDele return false; } + protected float getEntityEffectRange() { + return Math.max(0, RANGE.get(getTraits()) - 1); + } + protected boolean applyEntities(Caster source, Vec3d pos) { - return source.findAllEntitiesInRange(Math.max(0, 3 + getTraits().get(Trait.POWER)), e -> { + return source.findAllEntitiesInRange(getEntityEffectRange(), e -> { LivingEntity master = source.getMaster(); return (!(e.equals(source.asEntity()) || e.equals(master)) || (master instanceof PlayerEntity && !EquinePredicates.PLAYER_UNICORN.test(master))) && !(e instanceof ItemEntity) diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java index ce21955f..cd6a198c 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/HydrophobicSpell.java @@ -8,6 +8,8 @@ import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.CastOn; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.advancement.UCriteria; @@ -35,6 +37,8 @@ public class HydrophobicSpell extends AbstractSpell { .with(Trait.KNOWLEDGE, 1) .build(); + static final TooltipFactory TOOLTIP = TooltipFactory.of(ShieldSpell.CAST_ON, ShieldSpell.RANGE); + private final TagKey affectedFluid; private final Set storedFluidPositions = new HashSet<>(); @@ -46,7 +50,7 @@ public class HydrophobicSpell extends AbstractSpell { @Override public Spell prepareForCast(Caster caster, CastingMethod method) { - if ((method == CastingMethod.DIRECT || method == CastingMethod.STAFF) && getTraits().get(Trait.GENEROSITY) > 0) { + if ((method == CastingMethod.DIRECT || method == CastingMethod.STAFF) && ShieldSpell.CAST_ON.get(getTraits()) == CastOn.LOCATION) { return toPlaceable(); } return this; @@ -92,8 +96,7 @@ public class HydrophobicSpell extends AbstractSpell { } double range = getRange(source); - var entry = Ether.get(source.asWorld()).getOrCreate(this, source); - entry.radius = (float)range; + Ether.get(source.asWorld()).getOrCreate(this, source).setRadius((float)range); source.spawnParticles(new Sphere(true, range), 10, pos -> { BlockPos bp = BlockPos.ofFloored(pos); @@ -116,7 +119,7 @@ public class HydrophobicSpell extends AbstractSpell { @Override protected void onDestroyed(Caster caster) { - Ether.get(caster.asWorld()).remove(this, caster); + super.onDestroyed(caster); storedFluidPositions.removeIf(entry -> { if (caster.canModifyAt(entry.pos())) { entry.restore(caster.asWorld()); @@ -142,7 +145,7 @@ public class HydrophobicSpell extends AbstractSpell { */ public double getRange(Caster source) { float multiplier = 1; - float min = (source instanceof Pony ? 4 : 6) + getTraits().get(Trait.POWER); + float min = (source instanceof Pony ? 0 : 2) + ShieldSpell.RANGE.get(getTraits()); boolean isLimitedRange = source instanceof Pony || source instanceof MagicProjectileEntity; double range = (min + (source.getLevel().getScaled(isLimitedRange ? 4 : 40) * (isLimitedRange ? 2 : 10))) / multiplier; return range; @@ -175,19 +178,21 @@ public class HydrophobicSpell extends AbstractSpell { } public boolean blocksFlow(Ether.Entry entry, Vec3d center, BlockPos pos, FluidState fluid) { - return fluid.isIn(affectedFluid) && pos.isWithinDistance(center, (double)entry.radius + 1); + return fluid.isIn(affectedFluid) && pos.isWithinDistance(center, (double)entry.getRadius() + 1); } public static boolean blocksFluidFlow(BlockView world, BlockPos pos, FluidState state) { - if (world instanceof ServerWorld sw) { - return Ether.get(sw).anyMatch(SpellType.HYDROPHOBIC, entry -> { - var spell = entry.getSpell(); - var target = entry.entity.getTarget().orElse(null); - return spell != null && target != null && spell.blocksFlow(entry, target.pos(), pos, state); - }); + if (!(world instanceof ServerWorld sw)) { + return false; } - return false; - + return Ether.get(sw).anyMatch(SpellType.HYDROPHOBIC, entry -> { + var target = entry.entity.getTarget().orElse(null); + if (target == null || !pos.isWithinDistance(target.pos(), entry.getRadius() + 1)) { + return false; + } + var spell = entry.getSpell(); + return spell != null && target != null && spell.blocksFlow(entry, target.pos(), pos, state); + }); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/IceSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/IceSpell.java index f5065ff2..679b8e59 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/IceSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/IceSpell.java @@ -5,6 +5,10 @@ import java.util.List; import com.minelittlepony.unicopia.Owned; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.block.state.StateMaps; @@ -12,7 +16,6 @@ import com.minelittlepony.unicopia.block.state.StatePredicate; import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.util.PosHelper; import com.minelittlepony.unicopia.util.VecHelper; -import com.minelittlepony.unicopia.util.shape.Shape; import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.block.*; @@ -33,8 +36,9 @@ public class IceSpell extends AbstractSpell { .with(Trait.ICE, 15) .build(); - private static final int RADIUS = 3; - private static final Shape OUTER_RANGE = new Sphere(false, RADIUS); + private static final SpellAttribute RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> Math.max(0, 3 + power)); + + static final TooltipFactory TOOLTIP = RANGE; protected IceSpell(CustomisedSpellType type) { super(type); @@ -43,11 +47,12 @@ public class IceSpell extends AbstractSpell { @Override public boolean tick(Caster source, Situation situation) { boolean submerged = source.asEntity().isSubmergedInWater() || source.asEntity().isSubmergedIn(FluidTags.LAVA); + float radius = RANGE.get(getTraits()); - long blocksAffected = OUTER_RANGE.translate(source.getOrigin()).getBlockPositions().filter(i -> { + long blocksAffected = new Sphere(false, radius).translate(source.getOrigin()).getBlockPositions().filter(i -> { if (source.canModifyAt(i) && applyBlockSingle(source.asEntity(), source.asWorld(), i, situation)) { - if (submerged & source.getOrigin().isWithinDistance(i, RADIUS - 1)) { + if (submerged & source.getOrigin().isWithinDistance(i, RANGE.get(getTraits()) - 1)) { BlockState state = source.asWorld().getBlockState(i); if (state.isIn(BlockTags.ICE) || state.isOf(Blocks.OBSIDIAN)) { source.asWorld().setBlockState(i, Blocks.AIR.getDefaultState(), Block.NOTIFY_NEIGHBORS); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java index 8bd9c813..2c14e4f8 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/LightSpell.java @@ -7,6 +7,10 @@ import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.TimedSpell; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.EntityReference; @@ -20,6 +24,7 @@ import com.minelittlepony.unicopia.util.VecHelper; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtList; +import net.minecraft.util.math.MathHelper; public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileDelegate.HitListener { public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() @@ -29,13 +34,17 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD .with(Trait.ORDER, 25) .build(); + private static final SpellAttribute ORB_COUNT = SpellAttribute.create(SpellAttributeType.ORB_COUNT, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.LIFE, life -> 2 + (int)(MathHelper.clamp(life, 10, 20) / 10F)); + + static final TooltipFactory TOOLTIP = TooltipFactory.of(TIME, ORB_COUNT); + private final Timer timer; private final List> lights = new ArrayList<>(); protected LightSpell(CustomisedSpellType type) { super(type); - timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); + timer = new Timer(TIME.get(getTraits())); } @Override @@ -56,11 +65,9 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD return false; } - setDirty(); - if (!caster.isClient()) { if (lights.isEmpty()) { - int size = 2 + caster.asWorld().random.nextInt(2) + (int)(getTraits().get(Trait.LIFE, 10, 20) - 10)/10; + int size = caster.asWorld().random.nextInt(2) + ORB_COUNT.get(getTraits()); while (lights.size() < size) { lights.add(new EntityReference<>()); } @@ -76,7 +83,6 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD entity.getWorld().spawnEntity(entity); ref.set(entity); - setDirty(); } }); } @@ -91,6 +97,7 @@ public class LightSpell extends AbstractSpell implements TimedSpell, ProjectileD @Override protected void onDestroyed(Caster caster) { + super.onDestroyed(caster); if (caster.isClient()) { return; } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java index b48866b7..57b984c1 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MimicSpell.java @@ -2,17 +2,20 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; -import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; + import net.minecraft.entity.Entity; import net.minecraft.nbt.NbtCompound; public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, TimedSpell { + static final TooltipFactory TOOLTIP = TimedSpell.TIME; + private final Timer timer; protected MimicSpell(CustomisedSpellType type) { super(type); - timer = new Timer((120 + (int)(getTraits().get(Trait.FOCUS, 0, 160) * 19)) * 20); + timer = new Timer(TIME.get(getTraits())); } @Override @@ -28,8 +31,6 @@ public class MimicSpell extends AbstractDisguiseSpell implements HomingSpell, Ti return false; } - setDirty(); - return super.tick(caster, situation); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MindSwapSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MindSwapSpell.java index 4b2f9cb0..eefb6ebb 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MindSwapSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/MindSwapSpell.java @@ -60,8 +60,8 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti LivingEntity master = caster.getMaster(); Caster other = Caster.of(e).get(); - other.getSpellSlot().removeIf(SpellType.MIMIC, true); - caster.getSpellSlot().removeIf(getType(), true); + other.getSpellSlot().removeIf(SpellType.MIMIC); + caster.getSpellSlot().removeIf(getType()); if (!isValidTarget(master) || !isValidTarget(e)) { master.damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE); @@ -135,7 +135,6 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti caster.playSound(USounds.SPELL_MINDSWAP_SWAP, 1); }); initialized = true; - setDirty(); } if (counterpart.isSet()) { @@ -143,13 +142,12 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti if (other == null) { caster.getOriginatingCaster().asEntity().damage(caster.asWorld().getDamageSources().magic(), Float.MAX_VALUE); - setDead(); + destroy(caster); return false; } if (!Caster.of(other).get().getSpellSlot().contains(SpellType.MIMIC)) { - onDestroyed(caster); - setDead(); + destroy(caster); return false; } } @@ -158,8 +156,7 @@ public class MindSwapSpell extends MimicSpell implements ProjectileDelegate.Enti counterpart.ifPresent(caster.asWorld(), e -> { e.damage(e.getDamageSources().magic(), Float.MAX_VALUE); }); - onDestroyed(caster); - setDead(); + destroy(caster); return false; } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java index dbf79bab..2ad5cce3 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/NecromancySpell.java @@ -8,6 +8,10 @@ import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; import com.minelittlepony.unicopia.ability.magic.spell.Situation; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.Creature; import com.minelittlepony.unicopia.entity.EntityReference; @@ -79,6 +83,9 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti return e -> e.getType() == type; } + static final SpellAttribute WAVE_SIZE = SpellAttribute.create(SpellAttributeType.WAVE_SIZE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.CHAOS, chaos -> 10 + (int)MathHelper.clamp(chaos, 0, 10)); + static final TooltipFactory TOOLTIP = TooltipFactory.of(RANGE, WAVE_SIZE); + private final List> summonedEntities = new ArrayList<>(); private int spawnCountdown; @@ -90,7 +97,7 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti @Override public boolean tick(Caster source, Situation situation) { - float radius = 4 + source.getLevel().getScaled(4) * 4 + getTraits().get(Trait.POWER); + float radius = source.getLevel().getScaled(4) * 4 + RANGE.get(getTraits()); if (radius <= 0) { return false; @@ -122,15 +129,16 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti return true; }).isEmpty()); - float additional = source.asWorld().getLocalDifficulty(source.getOrigin()).getLocalDifficulty() + getTraits().get(Trait.CHAOS, 0, 10); - - setDirty(); if (--spawnCountdown > 0 && !summonedEntities.isEmpty()) { return true; } + // TODO: refactory speed attribute + // TODO: weather resistant attribute spawnCountdown = 1200 + source.asWorld().random.nextInt(rainy ? 2000 : 1000); - if (summonedEntities.size() > 10 + additional) { + float additional = source.asWorld().getLocalDifficulty(source.getOrigin()).getLocalDifficulty() + WAVE_SIZE.get(getTraits()); + + if (summonedEntities.size() > additional) { return true; } @@ -153,6 +161,7 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti @Override protected void onDestroyed(Caster caster) { + super.onDestroyed(caster); if (caster.isClient()) { return; } @@ -205,7 +214,6 @@ public class NecromancySpell extends AbstractAreaEffectSpell implements Projecti source.asWorld().spawnEntity(minion); summonedEntities.add(new EntityReference<>(minion)); - setDirty(); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java index fea666b7..4277ff0c 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/PortalSpell.java @@ -1,19 +1,21 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; -import java.util.Optional; import java.util.UUID; import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.USounds; -import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.Living; -import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; +import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.network.Channel; +import com.minelittlepony.unicopia.network.MsgCasterLookRequest; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.particle.*; import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.util.shape.*; @@ -21,17 +23,18 @@ import com.minelittlepony.unicopia.util.shape.*; import net.minecraft.block.Block; import net.minecraft.block.Blocks; import net.minecraft.entity.Entity; -import net.minecraft.entity.LivingEntity; import net.minecraft.nbt.NbtCompound; import net.minecraft.network.packet.s2c.play.PositionFlag; import net.minecraft.particle.ParticleTypes; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Util; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.WorldEvents; -public class PortalSpell extends AbstractSpell implements PlaceableSpell.PlacementDelegate, OrientedSpell { +public class PortalSpell extends AbstractSpell implements PlacementControlSpell.PlacementDelegate, OrientedSpell { public static final SpellTraits DEFAULT_TRAITS = new SpellTraits.Builder() .with(Trait.LIFE, 10) .with(Trait.KNOWLEDGE, 1) @@ -40,15 +43,13 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme private static final Shape PARTICLE_AREA = new Sphere(true, 2, 1, 1, 0); @Nullable - private UUID targetPortalId; - private float targetPortalPitch; - private float targetPortalYaw; + private final DataTracker.Entry targetPortalId = dataTracker.startTracking(TrackableDataType.UUID, Util.NIL_UUID); + private final DataTracker.Entry targetPortalPitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F); + private final DataTracker.Entry targetPortalYaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F); private final EntityReference teleportationTarget = new EntityReference<>(); - private boolean publishedPosition; - - private float pitch; - private float yaw; + private final DataTracker.Entry pitch = dataTracker.startTracking(TrackableDataType.FLOAT, 0F); + private final DataTracker.Entry yaw = dataTracker.startTracking(TrackableDataType.FLOAT, 0F); private Shape particleArea = PARTICLE_AREA; @@ -56,45 +57,56 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme super(type); } - public boolean isLinked() { - return teleportationTarget.isSet(); - } - - public Optional> getTarget() { - return teleportationTarget.getTarget(); + public EntityReference getDestinationReference() { + return teleportationTarget; } public float getPitch() { - return pitch; + return pitch.get(); } public float getYaw() { - return yaw; + return yaw.get(); } public float getTargetPitch() { - return targetPortalPitch; + return targetPortalPitch.get(); } public float getTargetYaw() { - return targetPortalYaw; + return targetPortalYaw.get(); } public float getYawDifference() { - return MathHelper.wrapDegrees(180 + targetPortalYaw - yaw); + return MathHelper.wrapDegrees(180 + getTargetYaw() - getYaw()); } @SuppressWarnings("unchecked") - private Optional> getDestination(Caster source) { - return getTarget().map(target -> Ether.get(source.asWorld()).get((SpellType)getType(), target, targetPortalId)); + private Ether.Entry getDestination(Caster source) { + return Util.NIL_UUID.equals(targetPortalId.get()) ? null : getDestinationReference() + .getTarget() + .map(target -> Ether.get(source.asWorld()).get((SpellType)getType(), target.uuid(), targetPortalId.get())) + .filter(destination -> destination.isClaimedBy(getUuid())) + .orElse(null); } @Override public boolean apply(Caster caster) { - setOrientation(caster.asEntity().getPitch(), caster.asEntity().getYaw()); return toPlaceable().apply(caster); } + protected void setDestination(@Nullable Ether.Entry destination) { + if (destination == null) { + teleportationTarget.set(null); + targetPortalId.set(Util.NIL_UUID); + } else { + teleportationTarget.copyFrom(destination.entity); + targetPortalId.set(destination.getSpellId()); + targetPortalPitch.set(destination.getPitch()); + targetPortalYaw.set(destination.getYaw()); + } + } + @Override public boolean tick(Caster source, Situation situation) { if (situation == Situation.GROUND) { @@ -103,53 +115,56 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme source.addParticle(ParticleTypes.ELECTRIC_SPARK, pos, Vec3d.ZERO); }); } else { - teleportationTarget.getTarget().ifPresent(target -> { - if (Ether.get(source.asWorld()).get(getType(), target, targetPortalId) == null) { - Unicopia.LOGGER.debug("Lost sibling, breaking connection to " + target.uuid()); - teleportationTarget.set(null); - setDirty(); - source.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, source.getOrigin(), Block.getRawIdFromState(Blocks.GLASS.getDefaultState())); - } - }); + var ownEntry = Ether.get(source.asWorld()).get(this, source); + synchronized (ownEntry) { + var targetEntry = getDestination(source); - getDestination(source).ifPresentOrElse( - entry -> tickWithTargetLink(source, entry), - () -> findLink(source) - ); + if (targetEntry == null) { + if (teleportationTarget.isSet()) { + setDestination(null); + source.asWorld().syncWorldEvent(WorldEvents.BLOCK_BROKEN, source.getOrigin(), Block.getRawIdFromState(Blocks.GLASS.getDefaultState())); + } else { + Ether.get(source.asWorld()).anyMatch(getType(), entry -> { + if (entry.isAlive() && !entry.hasClaimant() && !entry.entityMatches(source.asEntity().getUuid())) { + entry.claim(getUuid()); + ownEntry.claim(entry.getSpellId()); + synchronized (entry) { + if (entry.getSpell() instanceof PortalSpell portal) { + portal.setDestination(ownEntry); + } + } + setDestination(entry); + } + return false; + }); + } + } else { + tickActive(source, targetEntry); + } + } } - Ether ether = Ether.get(source.asWorld()); - var entry = ether.getOrCreate(this, source); - entry.pitch = pitch; - entry.yaw = yaw; - ether.markDirty(); + var entry = Ether.get(source.asWorld()).getOrCreate(this, source); + entry.setPitch(pitch.get()); + entry.setYaw(yaw.get()); } return !isDead(); } - private void tickWithTargetLink(Caster source, Ether.Entry destination) { - - if (!MathHelper.approximatelyEquals(targetPortalPitch, destination.pitch)) { - targetPortalPitch = destination.pitch; - setDirty(); - } - if (!MathHelper.approximatelyEquals(targetPortalYaw, destination.yaw)) { - targetPortalYaw = destination.yaw; - setDirty(); - } - + private void tickActive(Caster source, Ether.Entry destination) { destination.entity.getTarget().ifPresent(target -> { source.findAllEntitiesInRange(1).forEach(entity -> { if (!entity.hasPortalCooldown()) { - float approachYaw = Math.abs(MathHelper.wrapDegrees(entity.getYaw() - this.yaw)); + float approachYaw = Math.abs(MathHelper.wrapDegrees(entity.getYaw() - this.yaw.get())); if (approachYaw > 80) { return; } - Vec3d offset = entity.getPos().subtract(source.getOriginVector()); - float yawDifference = pitch < 15 ? getYawDifference() : 0; + Vec3d offset = entity.getPos().subtract(source.asEntity().getPos()) + .add(new Vec3d(0, 0, -0.7F).rotateY(-getTargetYaw() * MathHelper.RADIANS_PER_DEGREE)); + float yawDifference = getYawDifference(); Vec3d dest = target.pos().add(offset.rotateY(yawDifference * MathHelper.RADIANS_PER_DEGREE)).add(0, 0.1, 0); if (entity.getWorld().isTopSolid(BlockPos.ofFloored(dest).up(), entity)) { @@ -165,7 +180,6 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1); entity.teleport((ServerWorld)entity.getWorld(), dest.x, dest.y, dest.z, PositionFlag.VALUES, yaw, entity.getPitch()); entity.getWorld().playSoundFromEntity(null, entity, USounds.ENTITY_PLAYER_UNICORN_TELEPORT, entity.getSoundCategory(), 1, 1); - setDirty(); Living.updateVelocity(entity); @@ -179,73 +193,61 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme }); } - private void findLink(Caster source) { - if (source.isClient()) { - return; - } - - Ether.get(source.asWorld()).anyMatch(getType(), entry -> { - if (!entry.entity.referenceEquals(source.asEntity()) && entry.claim()) { - teleportationTarget.copyFrom(entry.entity); - targetPortalId = entry.getSpellId(); - setDirty(); - } - return false; - }); - } - @Override - public void setOrientation(float pitch, float yaw) { - this.pitch = pitch; - this.yaw = yaw; + public void setOrientation(Caster caster, float pitch, float yaw) { + this.pitch.set(90 - pitch); + this.yaw.set(-yaw); particleArea = PARTICLE_AREA.rotate( - pitch * MathHelper.RADIANS_PER_DEGREE, - (180 - yaw) * MathHelper.RADIANS_PER_DEGREE + this.pitch.get() * MathHelper.RADIANS_PER_DEGREE, + (180 - this.yaw.get()) * MathHelper.RADIANS_PER_DEGREE ); - setDirty(); } @Override - public void onPlaced(Caster source, PlaceableSpell parent, CastSpellEntity entity) { - LivingEntity caster = source.getMaster(); + public void onPlaced(Caster source, PlacementControlSpell parent) { + Entity caster = source.asEntity(); Vec3d targetPos = caster.getRotationVector().multiply(3).add(caster.getEyePos()); - parent.setOrientation(pitch, yaw); - entity.setPos(targetPos.x, Math.abs(pitch) > 15 ? targetPos.y : caster.getPos().y, targetPos.z); + parent.setOrientation(source, -90 - source.asEntity().getPitch(), -source.asEntity().getYaw()); + parent.setPosition(new Vec3d(targetPos.x, caster.getPos().y, targetPos.z)); + if (source instanceof Pony pony) { + Channel.SERVER_REQUEST_PLAYER_LOOK.sendToPlayer(new MsgCasterLookRequest(parent.getUuid()), (ServerPlayerEntity)pony.asEntity()); + } } @Override protected void onDestroyed(Caster caster) { - Ether.get(caster.asWorld()).remove(getType(), caster); - getDestination(caster).ifPresent(Ether.Entry::release); + super.onDestroyed(caster); + if (!caster.isClient()) { + var destination = getDestination(caster); + if (destination != null) { + destination.release(getUuid()); + } + } } @Override public void toNBT(NbtCompound compound) { super.toNBT(compound); - if (targetPortalId != null) { - compound.putUuid("targetPortalId", targetPortalId); - } - compound.putBoolean("publishedPosition", publishedPosition); + compound.putUuid("targetPortalId", targetPortalId.get()); compound.put("teleportationTarget", teleportationTarget.toNBT()); - compound.putFloat("pitch", pitch); - compound.putFloat("yaw", yaw); - compound.putFloat("targetPortalPitch", targetPortalPitch); - compound.putFloat("targetPortalYaw", targetPortalYaw); + compound.putFloat("pitch", getPitch()); + compound.putFloat("yaw", getYaw()); + compound.putFloat("targetPortalPitch", getTargetPitch()); + compound.putFloat("targetPortalYaw", getTargetYaw()); } @Override public void fromNBT(NbtCompound compound) { super.fromNBT(compound); - targetPortalId = compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : null; - publishedPosition = compound.getBoolean("publishedPosition"); + targetPortalId.set(compound.containsUuid("targetPortalId") ? compound.getUuid("targetPortalId") : Util.NIL_UUID); teleportationTarget.fromNBT(compound.getCompound("teleportationTarget")); - pitch = compound.getFloat("pitch"); - yaw = compound.getFloat("yaw"); - targetPortalPitch = compound.getFloat("targetPortalPitch"); - targetPortalYaw = compound.getFloat("targetPortalYaw"); + pitch.set(compound.getFloat("pitch")); + yaw.set(compound.getFloat("yaw")); + targetPortalPitch.set(compound.getFloat("targetPortalPitch")); + targetPortalYaw.set(compound.getFloat("targetPortalYaw")); particleArea = PARTICLE_AREA.rotate( - pitch * MathHelper.RADIANS_PER_DEGREE, - (180 - yaw) * MathHelper.RADIANS_PER_DEGREE + pitch.get() * MathHelper.RADIANS_PER_DEGREE, + (180 - yaw.get()) * MathHelper.RADIANS_PER_DEGREE ); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ScorchSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ScorchSpell.java index 24c8a632..d4292c7c 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ScorchSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ScorchSpell.java @@ -36,7 +36,7 @@ public class ScorchSpell extends FireSpell implements ProjectileDelegate.Configu BlockPos pos = PosHelper.findSolidGroundAt(source.asWorld(), source.getOrigin(), source.getPhysics().getGravitySignum()); if (source.canModifyAt(pos) && StateMaps.FIRE_AFFECTED.convert(source.asWorld(), pos)) { - source.spawnParticles(new Sphere(false, Math.max(1, getTraits().get(Trait.POWER))), 5, p -> { + source.spawnParticles(new Sphere(false, RANGE.get(getTraits())), 5, p -> { source.addParticle(ParticleTypes.SMOKE, PosHelper.offset(p, pos), Vec3d.ZERO); }); } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java index 23ceb121..484e4473 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/ShieldSpell.java @@ -9,6 +9,11 @@ import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.AttributeFormat; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.CastOn; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttribute; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.SpellAttributeType; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; @@ -17,6 +22,7 @@ import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.projectile.ProjectileUtil; +import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.util.ColorHelper; import com.minelittlepony.unicopia.util.Lerp; import com.minelittlepony.unicopia.util.shape.Sphere; @@ -24,6 +30,7 @@ import com.minelittlepony.unicopia.util.shape.Sphere; import net.minecraft.entity.Entity; import net.minecraft.entity.EyeOfEnderEntity; import net.minecraft.entity.FallingBlockEntity; +import net.minecraft.entity.ItemEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.TntEntity; import net.minecraft.entity.Entity.RemovalReason; @@ -44,6 +51,19 @@ public class ShieldSpell extends AbstractSpell { .with(Trait.AIR, 9) .build(); + static final SpellAttribute RANGE = SpellAttribute.create(SpellAttributeType.RANGE, AttributeFormat.REGULAR, AttributeFormat.PERCENTAGE, Trait.POWER, power -> Math.max(0, 4 + power)); + protected static final SpellAttribute CAST_ON = SpellAttribute.createEnumerated(SpellAttributeType.CAST_ON, Trait.GENEROSITY, generosity -> generosity > 0 ? CastOn.LOCATION : CastOn.SELF); + + static final SpellAttribute TARGET_ITEMS = SpellAttribute.createConditional(SpellAttributeType.PERMIT_ITEMS, Trait.KNOWLEDGE, knowledge -> knowledge > 10); + static final SpellAttribute PERMIT_PASSIVE = SpellAttribute.createConditional(SpellAttributeType.PERMIT_PASSIVE, Trait.LIFE, l -> l > 0); + static final SpellAttribute PERMIT_HOSTILE = SpellAttribute.createConditional(SpellAttributeType.PERMIT_HOSTILE, Trait.BLOOD, l -> l > 0); + static final SpellAttribute PERMIT_PLAYER = SpellAttribute.createConditional(SpellAttributeType.PERMIT_PLAYER, Trait.ICE, l -> l > 0); + + static final TooltipFactory PERMIT_ENTITY = TooltipFactory.of(PERMIT_PASSIVE, PERMIT_HOSTILE, PERMIT_PLAYER); + static final TooltipFactory TARGET = (type, tooltip) -> (TARGET_ITEMS.get(type.traits()) ? TARGET_ITEMS : PERMIT_ENTITY).appendTooltip(type, tooltip); + + static final TooltipFactory TOOLTIP = TooltipFactory.of(RANGE, TARGET, CAST_ON); + protected final TargetSelecter targetSelecter = new TargetSelecter(this).setFilter(this::isValidTarget); private final Lerp radius = new Lerp(0); @@ -58,7 +78,7 @@ public class ShieldSpell extends AbstractSpell { @Override public Spell prepareForCast(Caster caster, CastingMethod method) { - return method == CastingMethod.STAFF || getTraits().get(Trait.GENEROSITY) > 0 ? toPlaceable() : this; + return method == CastingMethod.STAFF || CAST_ON.get(getTraits()) == CastOn.LOCATION ? toPlaceable() : this; } @Override @@ -90,6 +110,8 @@ public class ShieldSpell extends AbstractSpell { if (source.isClient()) { generateParticles(source); + } else { + Ether.get(source.asWorld()).getOrCreate(this, source).setRadius(radius.getValue()); } if (situation == Situation.PROJECTILE) { @@ -142,13 +164,18 @@ public class ShieldSpell extends AbstractSpell { * Calculates the maximum radius of the shield. aka The area of effect. */ public double getDrawDropOffRange(Caster source) { - float min = (source instanceof Pony ? 4 : 6) + getTraits().get(Trait.POWER); + float min = (source instanceof Pony ? 0 : 2) + RANGE.get(getTraits()); double range = (min + (source.getLevel().getScaled(source instanceof Pony ? 4 : 40) * (source instanceof Pony ? 2 : 10))) / rangeMultiplier.getValue(); return range; } protected boolean isValidTarget(Caster source, Entity entity) { + + if (TARGET_ITEMS.get(getTraits())) { + return entity instanceof ItemEntity; + } + boolean valid = (entity instanceof LivingEntity || entity instanceof TntEntity || entity instanceof FallingBlockEntity @@ -159,13 +186,13 @@ public class ShieldSpell extends AbstractSpell { || entity instanceof BoatEntity ); - if (getTraits().get(Trait.LIFE) > 0) { + if (PERMIT_PASSIVE.get(getTraits())) { valid &= !(entity instanceof PassiveEntity); } - if (getTraits().get(Trait.BLOOD) > 0) { + if (PERMIT_HOSTILE.get(getTraits())) { valid &= !(entity instanceof HostileEntity); } - if (getTraits().get(Trait.ICE) > 0) { + if (PERMIT_PLAYER.get(getTraits())) { valid &= !(entity instanceof PlayerEntity); } return valid; diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java index f6f45d05..d844c066 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/SiphoningSpell.java @@ -15,6 +15,8 @@ import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.UParticles; @@ -37,6 +39,7 @@ import net.minecraft.util.math.Vec3d; public class SiphoningSpell extends AbstractAreaEffectSpell { static final Predicate TARGET_PREDICATE = EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.and(EntityPredicates.VALID_LIVING_ENTITY); + private final DataTracker.Entry upset = dataTracker.startTracking(TrackableDataType.BOOLEAN, false); private int ticksUpset; protected SiphoningSpell(CustomisedSpellType type) { @@ -51,12 +54,12 @@ public class SiphoningSpell extends AbstractAreaEffectSpell { @Override public boolean tick(Caster source, Situation situation) { - if (ticksUpset > 0) { - ticksUpset--; + if (ticksUpset > 0 && --ticksUpset <= 0) { + upset.set(false); } if (source.isClient()) { - float radius = 4 + source.getLevel().getScaled(5); + float radius = source.getLevel().getScaled(5) + RANGE.get(getTraits()); int direction = isFriendlyTogether(source) ? 1 : -1; source.spawnParticles(new Sphere(true, radius, 1, 0, 1), 1, pos -> { @@ -102,7 +105,7 @@ public class SiphoningSpell extends AbstractAreaEffectSpell { } else { e.damage(damage, e.getHealth() / 4); ticksUpset = 100; - setDirty(); + upset.set(true); } } else { e.heal((float)Math.min(source.getLevel().getScaled(e.getHealth()) / 2F, maxHealthGain * 0.6)); @@ -168,5 +171,8 @@ public class SiphoningSpell extends AbstractAreaEffectSpell { public void fromNBT(NbtCompound compound) { super.fromNBT(compound); ticksUpset = compound.getInt("upset"); + if (ticksUpset > 0) { + upset.set(true); + } } } 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 e963c0e7..7937120a 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 @@ -2,18 +2,21 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; import org.jetbrains.annotations.Nullable; +import com.google.common.base.Suppliers; import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; +import com.minelittlepony.unicopia.ability.magic.spell.AbstractAreaEffectSpell; import com.minelittlepony.unicopia.ability.magic.spell.ChangelingFeedingSpell; 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.PlacementControlSpell; 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; +import com.minelittlepony.unicopia.ability.magic.spell.attribute.TooltipFactory; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.item.GemstoneItem; import com.minelittlepony.unicopia.item.UItems; @@ -34,47 +37,47 @@ import net.minecraft.server.command.ServerCommandSource; public final class SpellType implements Affine, SpellPredicate { public static final Identifier EMPTY_ID = Unicopia.id("none"); - public static final SpellType EMPTY_KEY = new SpellType<>(EMPTY_ID, Affinity.NEUTRAL, 0xFFFFFF, false, GemstoneItem.Shape.ROUND, SpellTraits.EMPTY, t -> null); + public static final SpellType EMPTY_KEY = builder(t -> null).affinity(Affinity.NEUTRAL).color(0xFFFFFF).unobtainable().build(EMPTY_ID); public static final Registry> REGISTRY = RegistryUtils.createSimple(Unicopia.id("spells")); public static final RegistryKey>> REGISTRY_KEY = REGISTRY.getKey(); private static final DynamicCommandExceptionType UNKNOWN_SPELL_TYPE_EXCEPTION = new DynamicCommandExceptionType(id -> Text.translatable("spell_type.unknown", id)); - public static final SpellType PLACED_SPELL = register("placed", Affinity.NEUTRAL, 0, false, GemstoneItem.Shape.DONUT, SpellTraits.EMPTY, PlaceableSpell::new); - public static final SpellType THROWN_SPELL = register("thrown", Affinity.NEUTRAL, 0, false, GemstoneItem.Shape.DONUT, SpellTraits.EMPTY, ThrowableSpell::new); + public static final SpellType PLACE_CONTROL_SPELL = register("place_controller", SpellType.builder(PlacementControlSpell::new).affinity(Affinity.NEUTRAL).unobtainable().stackable().shape(GemstoneItem.Shape.DONUT)); + public static final SpellType THROWN_SPELL = register("thrown", SpellType.builder(ThrowableSpell::new).affinity(Affinity.NEUTRAL).unobtainable().shape(GemstoneItem.Shape.DONUT)); - public static final SpellType CHANGELING_DISGUISE = register("disguise", Affinity.BAD, 0x19E48E, false, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, DispersableDisguiseSpell::new); - public static final SpellType FEED = register("feed", Affinity.BAD, 0xBDBDF9, false, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, ChangelingFeedingSpell::new); - public static final SpellType RAINBOOM = register("rainboom", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.ROCKET, SpellTraits.EMPTY, RainboomAbilitySpell::new); - public static final SpellType RAGE = register("rage", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.FLAME, SpellTraits.EMPTY, RageAbilitySpell::new); - public static final SpellType TIME_CONTROL = register("time_control", Affinity.GOOD, 0xBDBDF9, false, GemstoneItem.Shape.STAR, SpellTraits.EMPTY, TimeControlAbilitySpell::new); + public static final SpellType CHANGELING_DISGUISE = register("disguise", builder(DispersableDisguiseSpell::new).affinity(Affinity.BAD).color(0x19E48E).unobtainable().shape(GemstoneItem.Shape.ARROW)); + public static final SpellType FEED = register("feed", SpellType.builder(ChangelingFeedingSpell::new).affinity(Affinity.BAD).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.ARROW)); + public static final SpellType RAINBOOM = register("rainboom", builder(RainboomAbilitySpell::new).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.ROCKET)); + public static final SpellType RAGE = register("rage", builder(RageAbilitySpell::new).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.FLAME)); + public static final SpellType TIME_CONTROL = register("time_control", builder(TimeControlAbilitySpell::new).color(0xBDBDF9).unobtainable().shape(GemstoneItem.Shape.STAR)); - public static final SpellType FROST = register("frost", Affinity.GOOD, 0xEABBFF, true, GemstoneItem.Shape.TRIANGLE, IceSpell.DEFAULT_TRAITS, IceSpell::new); - public static final SpellType CHILLING_BREATH = register("chilling_breath", Affinity.NEUTRAL, 0xFFEAFF, true, GemstoneItem.Shape.TRIANGLE, ChillingBreathSpell.DEFAULT_TRAITS, ChillingBreathSpell::new); - public static final SpellType SCORCH = register("scorch", Affinity.BAD, 0xF8EC1F, true, GemstoneItem.Shape.FLAME, ScorchSpell.DEFAULT_TRAITS, ScorchSpell::new); - public static final SpellType FLAME = register("flame", Affinity.GOOD, 0xFFBB99, true, GemstoneItem.Shape.FLAME, FireSpell.DEFAULT_TRAITS, FireSpell::new); - public static final SpellType INFERNAL = register("infernal", Affinity.BAD, 0xFFAA00, true, GemstoneItem.Shape.FLAME, InfernoSpell.DEFAULT_TRAITS, InfernoSpell::new); - public static final SpellType SHIELD = register("shield", Affinity.NEUTRAL, 0x66CDAA, true, GemstoneItem.Shape.SHIELD, ShieldSpell.DEFAULT_TRAITS, ShieldSpell::new); - public static final SpellType ARCANE_PROTECTION = register("arcane_protection", Affinity.BAD, 0x99CDAA, true, GemstoneItem.Shape.SHIELD, AreaProtectionSpell.DEFAULT_TRAITS, AreaProtectionSpell::new); - public static final SpellType VORTEX = register("vortex", Affinity.NEUTRAL, 0xFFEA88, true, GemstoneItem.Shape.VORTEX, AttractiveSpell.DEFAULT_TRAITS, AttractiveSpell::new); - public static final SpellType DARK_VORTEX = register("dark_vortex", Affinity.BAD, 0xA33333, true, GemstoneItem.Shape.VORTEX, DarkVortexSpell.DEFAULT_TRAITS, DarkVortexSpell::new); - public static final SpellType NECROMANCY = register("necromancy", Affinity.BAD, 0xFA3A3A, true, GemstoneItem.Shape.SKULL, SpellTraits.EMPTY, NecromancySpell::new); - public static final SpellType SIPHONING = register("siphoning", Affinity.NEUTRAL, 0xFFA3AA, true, GemstoneItem.Shape.LAMBDA, SpellTraits.EMPTY, SiphoningSpell::new); - public static final SpellType REVEALING = register("reveal", Affinity.GOOD, 0xFFFFAF, true, GemstoneItem.Shape.CROSS, SpellTraits.EMPTY, DisperseIllusionSpell::new); - public static final SpellType AWKWARD = register("awkward", Affinity.GOOD, 0x3A59FF, true, GemstoneItem.Shape.ICE, SpellTraits.EMPTY, AwkwardSpell::new); - public static final SpellType TRANSFORMATION = register("transformation", Affinity.GOOD, 0x19E48E, true, GemstoneItem.Shape.BRUSH, SpellTraits.EMPTY, TransformationSpell::new); - public static final SpellType FEATHER_FALL = register("feather_fall", Affinity.GOOD, 0x00EEFF, true, GemstoneItem.Shape.LAMBDA, FeatherFallSpell.DEFAULT_TRAITS, FeatherFallSpell::new); - public static final SpellType CATAPULT = register("catapult", Affinity.GOOD, 0x22FF00, true, GemstoneItem.Shape.ROCKET, CatapultSpell.DEFAULT_TRAITS, CatapultSpell::new); - public static final SpellType FIRE_BOLT = register("fire_bolt", Affinity.GOOD, 0xFF8811, true, GemstoneItem.Shape.FLAME, FireBoltSpell.DEFAULT_TRAITS, FireBoltSpell::new); - public static final SpellType LIGHT = register("light", Affinity.GOOD, 0xEEFFAA, true, GemstoneItem.Shape.STAR, LightSpell.DEFAULT_TRAITS, LightSpell::new); - public static final SpellType DISPLACEMENT = register("displacement", Affinity.NEUTRAL, 0x9900FF, true, GemstoneItem.Shape.BRUSH, PortalSpell.DEFAULT_TRAITS, DisplacementSpell::new); - public static final SpellType PORTAL = register("portal", Affinity.GOOD, 0x99FFFF, true, GemstoneItem.Shape.RING, PortalSpell.DEFAULT_TRAITS, PortalSpell::new); - public static final SpellType MIMIC = register("mimic", Affinity.GOOD, 0xFFFF00, true, GemstoneItem.Shape.ARROW, SpellTraits.EMPTY, MimicSpell::new); - public static final SpellType MIND_SWAP = register("mind_swap", Affinity.BAD, 0xF9FF99, true, GemstoneItem.Shape.WAVE, SpellTraits.EMPTY, MindSwapSpell::new); - public static final SpellType HYDROPHOBIC = register("hydrophobic", Affinity.NEUTRAL, 0xF999FF, true, GemstoneItem.Shape.ROCKET, SpellTraits.EMPTY, s -> new HydrophobicSpell(s, FluidTags.WATER)); - public static final SpellType BUBBLE = register("bubble", Affinity.NEUTRAL, 0xF999FF, true, GemstoneItem.Shape.DONUT, BubbleSpell.DEFAULT_TRAITS, BubbleSpell::new); - public static final SpellType DISPEL_EVIL = register("dispel_evil", Affinity.GOOD, 0x00FF00, true, GemstoneItem.Shape.CROSS, DispellEvilSpell.DEFAULT_TRAITS, DispellEvilSpell::new); + public static final SpellType FROST = register("frost", builder(IceSpell::new).color(0xEABBFF).shape(GemstoneItem.Shape.TRIANGLE).traits(IceSpell.DEFAULT_TRAITS).tooltip(IceSpell.TOOLTIP)); + public static final SpellType CHILLING_BREATH = register("chilling_breath", builder(ChillingBreathSpell::new).affinity(Affinity.NEUTRAL).color(0xFFEAFF).shape(GemstoneItem.Shape.TRIANGLE).traits(ChillingBreathSpell.DEFAULT_TRAITS)); + public static final SpellType SCORCH = register("scorch", builder(ScorchSpell::new).affinity(Affinity.BAD).color(0xF8EC1F).stackable().shape(GemstoneItem.Shape.FLAME).traits(ScorchSpell.DEFAULT_TRAITS).tooltip(AbstractAreaEffectSpell.TOOLTIP)); + public static final SpellType FLAME = register("flame", builder(FireSpell::new).color(0xFFBB99).shape(GemstoneItem.Shape.FLAME).traits(FireSpell.DEFAULT_TRAITS).tooltip(AbstractAreaEffectSpell.TOOLTIP)); + public static final SpellType INFERNAL = register("infernal", builder(InfernoSpell::new).affinity(Affinity.BAD).color(0xFFAA00).shape(GemstoneItem.Shape.FLAME).traits(InfernoSpell.DEFAULT_TRAITS).tooltip(AbstractAreaEffectSpell.TOOLTIP)); + public static final SpellType SHIELD = register("shield", builder(ShieldSpell::new).affinity(Affinity.NEUTRAL).color(0x66CDAA).shape(GemstoneItem.Shape.SHIELD).traits(ShieldSpell.DEFAULT_TRAITS).tooltip(ShieldSpell.TOOLTIP)); + public static final SpellType ARCANE_PROTECTION = register("arcane_protection", builder(AreaProtectionSpell::new).affinity(Affinity.BAD).color(0x99CDAA).shape(GemstoneItem.Shape.SHIELD).traits(AreaProtectionSpell.DEFAULT_TRAITS).tooltip(AreaProtectionSpell.TOOLTIP)); + public static final SpellType VORTEX = register("vortex", builder(AttractiveSpell::new).affinity(Affinity.NEUTRAL).color(0xFFEA88).shape(GemstoneItem.Shape.VORTEX).traits(AttractiveSpell.DEFAULT_TRAITS).tooltip(AttractiveSpell.TOOLTIP)); + public static final SpellType DARK_VORTEX = register("dark_vortex", builder(DarkVortexSpell::new).affinity(Affinity.BAD).color(0xA33333).stackable().shape(GemstoneItem.Shape.VORTEX).traits(DarkVortexSpell.DEFAULT_TRAITS)); + public static final SpellType NECROMANCY = register("necromancy", builder(NecromancySpell::new).affinity(Affinity.BAD).color(0xFA3A3A).shape(GemstoneItem.Shape.SKULL).tooltip(NecromancySpell.TOOLTIP)); + public static final SpellType SIPHONING = register("siphoning", builder(SiphoningSpell::new).affinity(Affinity.NEUTRAL).color(0xFFA3AA).shape(GemstoneItem.Shape.LAMBDA).tooltip(AbstractAreaEffectSpell.TOOLTIP)); + public static final SpellType REVEALING = register("reveal", builder(DisperseIllusionSpell::new).color(0xFFFFAF).shape(GemstoneItem.Shape.CROSS).tooltip(DisperseIllusionSpell.TOOLTIP)); + public static final SpellType AWKWARD = register("awkward", builder(AwkwardSpell::new).affinity(Affinity.NEUTRAL).color(0x3A59FF).shape(GemstoneItem.Shape.ICE)); + public static final SpellType TRANSFORMATION = register("transformation", builder(TransformationSpell::new).color(0x19E48E).shape(GemstoneItem.Shape.BRUSH)); + public static final SpellType FEATHER_FALL = register("feather_fall", builder(FeatherFallSpell::new).color(0x00EEFF).shape(GemstoneItem.Shape.LAMBDA).traits(FeatherFallSpell.DEFAULT_TRAITS).tooltip(FeatherFallSpell.TOOLTIP)); + public static final SpellType CATAPULT = register("catapult", builder(CatapultSpell::new).color(0x22FF00).shape(GemstoneItem.Shape.ROCKET).traits(CatapultSpell.DEFAULT_TRAITS).tooltip(CatapultSpell.TOOLTIP)); + public static final SpellType FIRE_BOLT = register("fire_bolt", builder(FireBoltSpell::new).color(0xFF8811).shape(GemstoneItem.Shape.FLAME).traits(FireBoltSpell.DEFAULT_TRAITS).tooltip(FireBoltSpell.TOOLTIP)); + public static final SpellType LIGHT = register("light", builder(LightSpell::new).color(0xEEFFAA).shape(GemstoneItem.Shape.STAR).traits(LightSpell.DEFAULT_TRAITS).tooltip(LightSpell.TOOLTIP)); + public static final SpellType DISPLACEMENT = register("displacement", builder(DisplacementSpell::new).affinity(Affinity.NEUTRAL).color(0x9900FF).stackable().shape(GemstoneItem.Shape.BRUSH).traits(PortalSpell.DEFAULT_TRAITS).tooltip(DisplacementSpell.TOOLTIP)); + public static final SpellType PORTAL = register("portal", builder(PortalSpell::new).color(0x99FFFF).shape(GemstoneItem.Shape.RING).traits(PortalSpell.DEFAULT_TRAITS)); + public static final SpellType MIMIC = register("mimic", builder(MimicSpell::new).color(0xFFFF00).shape(GemstoneItem.Shape.ARROW).tooltip(MimicSpell.TOOLTIP)); + public static final SpellType MIND_SWAP = register("mind_swap", builder(MindSwapSpell::new).affinity(Affinity.BAD).color(0xF9FF99).shape(GemstoneItem.Shape.WAVE).tooltip(MimicSpell.TOOLTIP)); + public static final SpellType HYDROPHOBIC = register("hydrophobic", SpellType.builder(s -> new HydrophobicSpell(s, FluidTags.WATER)).affinity(Affinity.NEUTRAL).color(0xF999FF).stackable().shape(GemstoneItem.Shape.ROCKET).tooltip(HydrophobicSpell.TOOLTIP)); + public static final SpellType BUBBLE = register("bubble", builder(BubbleSpell::new).affinity(Affinity.NEUTRAL).color(0xF999FF).shape(GemstoneItem.Shape.DONUT).traits(BubbleSpell.DEFAULT_TRAITS).tooltip(BubbleSpell.TOOLTIP)); + public static final SpellType DISPEL_EVIL = register("dispel_evil", builder(DispellEvilSpell::new).color(0x00FF00).shape(GemstoneItem.Shape.CROSS).traits(DispellEvilSpell.DEFAULT_TRAITS).tooltip(DispellEvilSpell.TOOLTIP)); public static void bootstrap() {} @@ -82,6 +85,7 @@ public final class SpellType implements Affine, SpellPredicate< private final Affinity affinity; private final int color; private final boolean obtainable; + private final boolean stackable; private final GemstoneItem.Shape shape; private final Factory factory; @@ -94,15 +98,19 @@ public final class SpellType implements Affine, SpellPredicate< private final ItemStack defaultStack; - private SpellType(Identifier id, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory factory) { + private final TooltipFactory tooltipFunction; + + private SpellType(Identifier id, Affinity affinity, int color, boolean obtainable, boolean stackable, GemstoneItem.Shape shape, SpellTraits traits, TooltipFactory tooltipFunction, Factory factory) { this.id = id; this.affinity = affinity; this.color = color; this.obtainable = obtainable; this.shape = shape; + this.tooltipFunction = tooltipFunction; this.factory = factory; this.traits = traits; - traited = new CustomisedSpellType<>(this, traits); + this.stackable = stackable; + traited = new CustomisedSpellType<>(this, traits, SpellTraits::empty); defaultStack = UItems.GEMSTONE.getDefaultStack(this); } @@ -110,6 +118,10 @@ public final class SpellType implements Affine, SpellPredicate< return obtainable; } + public boolean isStackable() { + return stackable; + } + public Identifier getId() { return id; } @@ -154,16 +166,20 @@ public final class SpellType implements Affine, SpellPredicate< } public CustomisedSpellType withTraits(SpellTraits traits) { - return traits.isEmpty() ? withTraits() : new CustomisedSpellType<>(this, traits); + return traits.isEmpty() ? withTraits() : new CustomisedSpellType<>(this, traits, Suppliers.memoize(() -> traits.map((trait, value) -> value - getTraits().get(trait)))); } public Factory getFactory() { return factory; } + public TooltipFactory getTooltip() { + return tooltipFunction; + } + @Override public boolean test(@Nullable Spell spell) { - return spell != null && spell.getType() == this; + return spell != null && spell.getTypeAndTraits().type() == this; } public void toNbt(NbtCompound tag) { @@ -179,12 +195,12 @@ public final class SpellType implements Affine, SpellPredicate< return "SpellType[" + getTranslationKey() + "]"; } - public static SpellType register(String name, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory factory) { - return register(Unicopia.id(name), affinity, color, obtainable, shape, traits, factory); + public static SpellType register(String name, Builder builder) { + return register(Unicopia.id(name), builder); } - public static SpellType register(Identifier id, Affinity affinity, int color, boolean obtainable, GemstoneItem.Shape shape, SpellTraits traits, Factory factory) { - return Registry.register(REGISTRY, id, new SpellType<>(id, affinity, color, obtainable, shape, traits, factory)); + public static SpellType register(Identifier id, Builder builder) { + return Registry.register(REGISTRY, id, builder.build(id)); } @SuppressWarnings("unchecked") @@ -209,4 +225,62 @@ public final class SpellType implements Affine, SpellPredicate< public interface Factory { T create(CustomisedSpellType type); } + + public static Builder builder(Factory factory) { + return new Builder<>(factory); + } + + static class Builder { + private final Factory factory; + private Affinity affinity = Affinity.GOOD; + private int color; + private boolean obtainable = true; + private boolean stackable = false; + private GemstoneItem.Shape shape = GemstoneItem.Shape.ROUND; + private SpellTraits traits = SpellTraits.EMPTY; + private TooltipFactory tooltipFunction = TooltipFactory.EMPTY; + + Builder(Factory factory) { + this.factory = factory; + } + + public Builder affinity(Affinity affinity) { + this.affinity = affinity; + return this; + } + + public Builder color(int color) { + this.color = color; + return this; + } + + public Builder unobtainable() { + obtainable = false; + return this; + } + + public Builder stackable() { + stackable = true; + return this; + } + + public Builder shape(GemstoneItem.Shape shape) { + this.shape = shape; + return this; + } + + public Builder traits(SpellTraits traits) { + this.traits = traits; + return this; + } + + public Builder tooltip(TooltipFactory tooltipFunction) { + this.tooltipFunction = tooltipFunction; + return this; + } + + public SpellType build(Identifier id) { + return new SpellType<>(id, affinity, color, obtainable, stackable, shape, traits, tooltipFunction, factory); + } + } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java index 17cd9568..48cd1872 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/SpellTraits.java @@ -67,6 +67,10 @@ public final class SpellTraits implements Iterable> { }); } + public static SpellTraits empty() { + return EMPTY; + } + public static Map all() { return new HashMap<>(REGISTRY); } @@ -292,7 +296,8 @@ public final class SpellTraits implements Iterable> { return fromString(traits, " "); } - public static Optional fromString(String traits, String delimiter) { + @Deprecated + private static Optional fromString(String traits, String delimiter) { return fromEntries(Arrays.stream(traits.split(delimiter)).map(a -> a.split(":")).map(pair -> { Trait key = Trait.fromName(pair[0]).orElse(null); if (key == null) { diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/Trait.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/Trait.java index aaeeb999..d0abb545 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/Trait.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/trait/Trait.java @@ -165,7 +165,11 @@ public enum Trait implements CommandArgumentEnum { @Deprecated public static Optional fromName(String name) { - return Optional.ofNullable(CODEC.byId(name)); + Trait trait = CODEC.byId(name); + if (trait == null) { + Unicopia.LOGGER.error("Unknown trait: " + name); + } + return Optional.ofNullable(trait); } public static EnumArgumentType argument() { diff --git a/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java b/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java index 33c5f576..521281ac 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java +++ b/src/main/java/com/minelittlepony/unicopia/block/UBlocks.java @@ -112,9 +112,9 @@ public interface UBlocks { Block PALM_TRAPDOOR = register("palm_trapdoor", new TrapdoorBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).instrument(Instrument.BASS).strength(3).nonOpaque().allowsSpawning(BlockConstructionUtils::never).burnable(), UWoodTypes.PALM.setType()), ItemGroups.FUNCTIONAL); Block PALM_PRESSURE_PLATE = register("palm_pressure_plate", new PressurePlateBlock(PressurePlateBlock.ActivationRule.EVERYTHING, Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).noCollision().strength(0.5f).sounds(BlockSoundGroup.WOOD).pistonBehavior(PistonBehavior.DESTROY), UWoodTypes.PALM.setType()), ItemGroups.BUILDING_BLOCKS); Block PALM_BUTTON = register("palm_button", BlockConstructionUtils.woodenButton(), ItemGroups.BUILDING_BLOCKS); - Block PALM_SIGN = register("palm_sign", new SignBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).burnable().sounds(BlockSoundGroup.WOOD), UWoodTypes.PALM), ItemGroups.FUNCTIONAL); + Block PALM_SIGN = register("palm_sign", new SignBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).burnable().sounds(BlockSoundGroup.WOOD), UWoodTypes.PALM)); Block PALM_WALL_SIGN = register("palm_wall_sign", new WallSignBlock(Settings.create().mapColor(PALM_PLANKS.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).dropsLike(PALM_SIGN).burnable(), UWoodTypes.PALM)); - Block PALM_HANGING_SIGN = register("palm_hanging_sign", new HangingSignBlock(Settings.create().mapColor(PALM_LOG.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).burnable(), UWoodTypes.PALM), ItemGroups.FUNCTIONAL); + Block PALM_HANGING_SIGN = register("palm_hanging_sign", new HangingSignBlock(Settings.create().mapColor(PALM_LOG.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1).burnable(), UWoodTypes.PALM)); Block PALM_WALL_HANGING_SIGN = register("palm_wall_hanging_sign", new WallHangingSignBlock(Settings.create().mapColor(PALM_LOG.getDefaultMapColor()).solid().instrument(Instrument.BASS).noCollision().strength(1.0f).burnable().dropsLike(PALM_HANGING_SIGN), UWoodTypes.PALM)); Block PALM_LEAVES = register("palm_leaves", BlockConstructionUtils.createLeavesBlock(BlockSoundGroup.GRASS), ItemGroups.BUILDING_BLOCKS); diff --git a/src/main/java/com/minelittlepony/unicopia/block/cloud/UnstableCloudBlock.java b/src/main/java/com/minelittlepony/unicopia/block/cloud/UnstableCloudBlock.java index 30bf49c4..b64c03d6 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/cloud/UnstableCloudBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/block/cloud/UnstableCloudBlock.java @@ -2,9 +2,9 @@ package com.minelittlepony.unicopia.block.cloud; import java.util.Optional; -import com.minelittlepony.unicopia.entity.mob.StormCloudEntity; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.ParticleUtils; +import com.minelittlepony.unicopia.util.PosHelper; import net.minecraft.block.Block; import net.minecraft.block.BlockState; @@ -82,7 +82,7 @@ public class UnstableCloudBlock extends CloudBlock { ); BlockPos shockPosition = lightningRodPos.or(() -> { return sw.getOtherEntities(entity, new Box(pos.down()).expand(5, 0, 5).stretch(0, -10, 0)).stream().findAny().map(Entity::getBlockPos); - }).orElseGet(() -> StormCloudEntity.findSurfaceBelow(sw, pos.add(world.random.nextInt(10) - 5, -world.random.nextInt(10), world.random.nextInt(10) - 5)).toImmutable()); + }).orElseGet(() -> PosHelper.findNearestSurface(sw, pos.add(world.random.nextInt(10) - 5, -world.random.nextInt(10), world.random.nextInt(10) - 5)).toImmutable()); ParticleUtils.spawnParticle(world, new LightningBoltParticleEffect(false, 10, 6, 0.3F, Optional.of(shockPosition.toCenterPos())), diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index ffb412b5..617041bd 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -56,7 +56,6 @@ import net.minecraft.client.particle.SpriteProvider; import net.minecraft.client.render.*; import net.minecraft.client.render.VertexConsumerProvider.Immediate; import net.minecraft.client.render.block.entity.BlockEntityRendererFactories; -import net.minecraft.client.render.entity.EmptyEntityRenderer; import net.minecraft.client.render.entity.FlyingItemEntityRenderer; import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.model.json.ModelTransformationMode; @@ -115,7 +114,7 @@ public interface URenderers { EntityRendererRegistry.register(UEntities.LOOT_BUG, LootBugEntityRenderer::new); EntityRendererRegistry.register(UEntities.TENTACLE, TentacleEntityRenderer::new); EntityRendererRegistry.register(UEntities.IGNOMINIOUS_BULB, IgnominiousBulbEntityRenderer::new); - EntityRendererRegistry.register(UEntities.SPECTER, EmptyEntityRenderer::new); + EntityRendererRegistry.register(UEntities.SPECTER, SpecterEntityRenderer::new); EntityRendererRegistry.register(UEntities.MIMIC, MimicEntityRenderer::new); BlockEntityRendererFactories.register(UBlockEntities.WEATHER_VANE, WeatherVaneBlockEntityRenderer::new); diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java b/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java index 8361a045..cddf13b9 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/DismissSpellScreen.java @@ -2,14 +2,13 @@ package com.minelittlepony.unicopia.client.gui; import java.util.ArrayList; import java.util.List; - import org.joml.Vector4f; +import com.google.common.base.MoreObjects; import com.minelittlepony.common.client.gui.GameGui; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.*; -import com.minelittlepony.unicopia.client.TextHelper; import com.minelittlepony.unicopia.client.render.model.SphereModel; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.UItems; @@ -41,11 +40,11 @@ public class DismissSpellScreen extends GameGui { double azimuth = 0; double ring = 2; - List placeableSpells = new ArrayList<>(); + List placeableSpells = new ArrayList<>(); - for (Spell spell : pony.getSpellSlot().stream(true).filter(SpellPredicate.IS_VISIBLE).toList()) { + for (Spell spell : pony.getSpellSlot().stream().filter(SpellPredicate.IS_VISIBLE).toList()) { - if (spell instanceof PlaceableSpell placeable) { + if (spell instanceof PlacementControlSpell placeable) { if (placeable.getPosition().isPresent()) { placeableSpells.add(placeable); continue; @@ -58,15 +57,16 @@ public class DismissSpellScreen extends GameGui { } double minimalDistance = 75 * (ring - 1) - 25; - Vec3d origin = pony.getOriginVector(); + Vec3d origin = pony.asEntity().getPos(); placeableSpells.forEach(placeable -> { placeable.getPosition().ifPresent(position -> { - Vec3d relativePos = position.subtract(origin); + Vec3d relativePos = position.subtract(origin).multiply(1, 0, 1); + float yaw = client.gameRenderer.getCamera().getYaw(); Vec3d cartesian = relativePos .normalize() - .multiply(minimalDistance + relativePos.length()) - .rotateY((pony.asEntity().getYaw() - 180) * MathHelper.RADIANS_PER_DEGREE); + .multiply(minimalDistance + relativePos.horizontalLength()) + .rotateY((180 + yaw) * MathHelper.RADIANS_PER_DEGREE); addDrawableChild(new Entry(placeable).ofCartesian(cartesian)); }); }); @@ -84,8 +84,8 @@ public class DismissSpellScreen extends GameGui { matrices.push(); matrices.translate(width - mouseX, height - mouseY, 0); DrawableUtil.drawLine(matrices, 0, 0, relativeMouseX, relativeMouseY, 0xFFFFFF88); - DrawableUtil.drawArc(matrices, 40, 80, 0, DrawableUtil.TAU, 0x00000010, false); - DrawableUtil.drawArc(matrices, 160, 1600, 0, DrawableUtil.TAU, 0x00000020, false); + DrawableUtil.drawArc(matrices, 40, 80, 0, DrawableUtil.TAU, 0x00000010); + DrawableUtil.drawArc(matrices, 160, 1600, 0, DrawableUtil.TAU, 0x00000020); super.render(context, mouseX, mouseY, delta); DrawableUtil.renderRaceIcon(context, pony.getObservedSpecies(), 0, 0, 16); @@ -137,17 +137,16 @@ public class DismissSpellScreen extends GameGui { } private Spell getActualSpell() { - if (spell instanceof AbstractDelegatingSpell) { - return ((AbstractDelegatingSpell)spell).getDelegates().stream().findFirst().orElse(spell); - } - return spell; + return spell instanceof AbstractDelegatingSpell s ? MoreObjects.firstNonNull(s.getDelegate(), s) + : spell instanceof PlacementControlSpell s ? MoreObjects.firstNonNull(s.getDelegate(), s) + : spell; } @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (isMouseOver(relativeMouseX, relativeMouseY)) { remove(this); - pony.getSpellSlot().removeIf(spell -> spell == this.spell, true); + pony.getSpellSlot().removeIf(spell -> spell == this.spell); Channel.REMOVE_SPELL.sendToServer(new MsgRemoveSpell(spell)); playClickEffect(); return true; @@ -164,38 +163,45 @@ public class DismissSpellScreen extends GameGui { public void render(DrawContext context, int mouseX, int mouseY, float tickDelta) { MatrixStack matrices = context.getMatrices(); - var type = actualSpell.getType().withTraits(actualSpell.getTraits()); + var type = actualSpell.getTypeAndTraits(); copy.set(mouseX - width * 0.5F - x * 0.5F, mouseY - height * 0.5F - y * 0.5F, 0, 0); DrawableUtil.drawLine(matrices, 0, 0, (int)x, (int)y, actualSpell.getAffinity().getColor().getColorValue()); - DrawableUtil.renderItemIcon(context, actualSpell.isDead() ? UItems.BOTCHED_GEM.getDefaultStack() : type.getDefaultStack(), - x - 8 - copy.x * 0.2F, - y - 8 - copy.y * 0.2F, - 1 - ); - int color = actualSpell.getType().getColor() << 2; + + int color = type.type().getColor() << 2; matrices.push(); matrices.translate(x, y, 0); - DrawableUtil.drawArc(matrices, 7, 8, 0, DrawableUtil.TAU, color | 0x00000088, false); + matrices.push(); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(((MinecraftClient.getInstance().player.age + tickDelta) * 2) % 360)); + DrawableUtil.renderItemIcon(context, actualSpell.isDead() ? UItems.BOTCHED_GEM.getDefaultStack() : type.getDefaultStack(), + -8, + -8, + 1 + ); + matrices.pop(); - if (isMouseOver(relativeMouseX, relativeMouseY)) { - DrawableUtil.drawArc(matrices, 0, 8, 0, DrawableUtil.TAU, color | 0x000000FF, false); + boolean hovered = isMouseOver(relativeMouseX, relativeMouseY); + double radius = (hovered ? 9 + MathHelper.sin((MinecraftClient.getInstance().player.age + tickDelta) / 9F) : 7); + + DrawableUtil.drawArc(matrices, radius, radius + 1, 0, DrawableUtil.TAU, color | 0x00000088); + + if (hovered) { + DrawableUtil.drawArc(matrices, 0, 8, 0, DrawableUtil.TAU, color | 0x000000FF); List tooltip = new ArrayList<>(); - MutableText name = actualSpell.getType().getName().copy(); - color = actualSpell.getType().getColor(); + MutableText name = type.type().getName().copy(); + color = type.type().getColor(); name.setStyle(name.getStyle().withColor(color == 0 ? 0xFFAAAAAA : color)); tooltip.add(Text.translatable("gui.unicopia.dispell_screen.spell_type", name)); - actualSpell.getType().getTraits().appendTooltip(tooltip); + type.traits().appendTooltip(tooltip); tooltip.add(ScreenTexts.EMPTY); - tooltip.add(Text.translatable("gui.unicopia.dispell_screen.affinity", actualSpell.getAffinity().name()).formatted(actualSpell.getAffinity().getColor())); + type.appendTooltip(tooltip); tooltip.add(ScreenTexts.EMPTY); - tooltip.addAll(TextHelper.wrap(Text.translatable(actualSpell.getType().getTranslationKey() + ".lore").formatted(actualSpell.getAffinity().getColor()), 180).toList()); if (spell instanceof TimedSpell timed) { tooltip.add(ScreenTexts.EMPTY); tooltip.add(Text.translatable("gui.unicopia.dispell_screen.time_left", StringHelper.formatTicks(timed.getTimer().getTicksRemaining()))); diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/DrawableUtil.java b/src/main/java/com/minelittlepony/unicopia/client/gui/DrawableUtil.java index 82cd1958..24f5141c 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/DrawableUtil.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/DrawableUtil.java @@ -71,27 +71,41 @@ public interface DrawableUtil { RenderSystem.disableBlend(); } + /** + * Renders a colored arc with notches. + * + * @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides. + */ + static void drawNotchedArc(MatrixStack matrices, double innerRadius, double outerRadius, double startAngle, double arcAngle, double notchAngle, double notchSpacing, int color) { + double notchBegin = startAngle; + double endAngle = startAngle + arcAngle; + while (notchBegin < endAngle) { + double notchEnd = Math.min(notchBegin + notchAngle, endAngle); + if (notchEnd <= notchBegin) { + return; + } + drawArc(matrices, innerRadius, outerRadius, notchBegin, notchEnd - notchBegin, color); + notchBegin += notchAngle + notchSpacing; + } + + } + /** * Renders a colored arc. * * @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides. */ - static void drawArc(MatrixStack matrices, double innerRadius, double outerRadius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) { + static void drawArc(MatrixStack matrices, double innerRadius, double outerRadius, double startAngle, double arcAngle, int color) { + if (arcAngle < INCREMENT) { + return; + } float r = (color >> 24 & 255) / 255F; float g = (color >> 16 & 255) / 255F; float b = (color >> 8 & 255) / 255F; float k = (color & 255) / 255F; - if (arcAngle < INCREMENT) { - return; - } - final double maxAngle = MathHelper.clamp(startAngle + arcAngle, 0, TAU - INCREMENT); - if (!mirrorHorizontally) { - startAngle = -startAngle; - } - RenderSystem.setShaderColor(1, 1, 1, 1); RenderSystem.setShader(GameRenderer::getPositionColorProgram); RenderSystem.enableBlend(); @@ -102,7 +116,7 @@ public interface DrawableUtil { BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_COLOR); - for (double angle = startAngle; angle >= -maxAngle; angle -= INCREMENT) { + for (double angle = -startAngle; angle >= -maxAngle; angle -= INCREMENT) { // center cylendricalVertex(bufferBuilder, model, innerRadius, angle, r, g, b, k); // point one @@ -116,70 +130,6 @@ public interface DrawableUtil { BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); } - /** - * Renders hollow circle - * - * @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides. - */ - static void drawArc(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) { - drawCircle(matrices, radius, startAngle, arcAngle, color, mirrorHorizontally, VertexFormat.DrawMode.DEBUG_LINES); - } - - /** - * Renders a filled circle. - * - * @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides. - */ - static void drawCircle(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) { - drawCircle(matrices, radius, startAngle, arcAngle, color, mirrorHorizontally, VertexFormat.DrawMode.QUADS); - } - - private static void drawCircle(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally, VertexFormat.DrawMode mode) { - float r = (color >> 24 & 255) / 255F; - float g = (color >> 16 & 255) / 255F; - float b = (color >> 8 & 255) / 255F; - float k = (color & 255) / 255F; - - if (arcAngle < INCREMENT) { - return; - } - - final double maxAngle = MathHelper.clamp(startAngle + arcAngle, 0, TAU - INCREMENT); - - if (!mirrorHorizontally) { - startAngle = -startAngle; - } - - RenderSystem.setShaderColor(1, 1, 1, 1); - RenderSystem.setShader(GameRenderer::getPositionColorProgram); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - - Matrix4f model = matrices.peek().getPositionMatrix(); - - BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer(); - bufferBuilder.begin(mode, VertexFormats.POSITION_COLOR); - - boolean joinEnds = mode == VertexFormat.DrawMode.QUADS; - - // center - - for (double angle = startAngle; angle >= -maxAngle; angle -= INCREMENT) { - if (joinEnds) { - bufferBuilder.vertex(model, 0, 0, 0).color(r, g, b, k).next(); - } - // point one - cylendricalVertex(bufferBuilder, model, radius, angle, r, g, b, k); - // point two - cylendricalVertex(bufferBuilder, model, radius, angle + INCREMENT, r, g, b, k); - if (joinEnds) { - bufferBuilder.vertex(model, 0, 0, 0).color(r, g, b, k).next(); - } - } - - BufferRenderer.drawWithGlobalProgram(bufferBuilder.end()); - } - private static void cylendricalVertex(BufferBuilder bufferBuilder, Matrix4f model, double radius, double angle, float r, float g, float b, float k) { bufferBuilder.vertex(model, (float)(radius * MathHelper.sin((float)angle)), diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/HudEffects.java b/src/main/java/com/minelittlepony/unicopia/client/gui/HudEffects.java index 2188245d..4d0bd016 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/HudEffects.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/HudEffects.java @@ -7,7 +7,6 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.entity.duck.EntityDuck; -import com.minelittlepony.unicopia.entity.effect.EffectUtils; import com.minelittlepony.unicopia.entity.effect.UEffects; import com.minelittlepony.unicopia.entity.player.Pony; @@ -31,7 +30,7 @@ public class HudEffects { private static void apply(Pony pony, float tickDelta, boolean on) { if (on) { - if (!pony.asEntity().hasStatusEffect(StatusEffects.HUNGER) && EffectUtils.getAmplifier(pony.asEntity(), UEffects.FOOD_POISONING) > 0) { + if (!pony.asEntity().hasStatusEffect(StatusEffects.HUNGER) && pony.asEntity().hasStatusEffect(UEffects.FOOD_POISONING)) { addedHunger = true; pony.asEntity().addStatusEffect(new StatusEffectInstance(StatusEffects.HUNGER, 1, 1, false, false)); } diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/ManaRingSlot.java b/src/main/java/com/minelittlepony/unicopia/client/gui/ManaRingSlot.java index 73ef3bdb..fa9f3d05 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/ManaRingSlot.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/ManaRingSlot.java @@ -1,6 +1,7 @@ package com.minelittlepony.unicopia.client.gui; import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.ability.Abilities; import com.minelittlepony.unicopia.ability.AbilityDispatcher; import com.minelittlepony.unicopia.ability.AbilitySlot; import com.minelittlepony.unicopia.entity.player.MagicReserves; @@ -21,19 +22,42 @@ class ManaRingSlot extends Slot { @Override protected void renderContents(DrawContext context, AbilityDispatcher abilities, boolean bSwap, float tickDelta) { MatrixStack matrices = context.getMatrices(); + matrices.push(); - matrices.translate(24.5, 25.5, 0); + matrices.translate(24.125, 24.75, 0); Pony pony = Pony.of(uHud.client.player); MagicReserves mana = pony.getMagicalReserves(); - double arcBegin = 0; + boolean canUseSuper = Abilities.RAGE.canUse(pony.getCompositeRace()) || Abilities.RAINBOOM.canUse(pony.getCompositeRace()); - arcBegin = renderRing(matrices, 17, 13, 0, mana.getMana(), 0xFF88FF99, tickDelta); + double maxManaBarSize = canUseSuper ? DrawableUtil.PI : DrawableUtil.TAU; + double arcBegin = renderRing(matrices, 17, 13, 0, maxManaBarSize, mana.getMana(), 0xFF88FF99, tickDelta); + renderRing(matrices, 17, 13, 0, maxManaBarSize, mana.getExhaustion(), 0xFF002299, tickDelta); if (!uHud.client.player.isCreative()) { - renderRing(matrices, 13, 9, 0, mana.getXp(), 0x88880099, tickDelta); - renderRing(matrices, 9, 0, 0, mana.getCharge(), 0x0000FF99, tickDelta); + + int level = pony.getLevel().get(); + int seconds = level % 16; + int minutes = (level / 16) % 16; + int hours = level / 32; + + DrawableUtil.drawNotchedArc(matrices, 10, 13, 0, hours * 0.2, 0.1, 0.1, 0x00AA88FF); + DrawableUtil.drawNotchedArc(matrices, 10, 13, hours * 0.2, minutes * 0.2, 0.1, 0.1, 0x008888AA); + DrawableUtil.drawNotchedArc(matrices, 10, 13, (hours + minutes) * 0.2, seconds * 0.2, 0.1, 0.1, 0x88880099); + + level = pony.getCorruption().get(); + seconds = level % 16; + minutes = (level / 16) % 16; + hours = level / 32; + + DrawableUtil.drawNotchedArc(matrices, 7, 10, DrawableUtil.PI, hours * 0.2, 0.1, 0.1, 0x000088FF); + DrawableUtil.drawNotchedArc(matrices, 7, 10, hours * 0.2 + DrawableUtil.PI, minutes * 0.2, 0.1, 0.1, 0x000088AA); + DrawableUtil.drawNotchedArc(matrices, 7, 10, (hours + minutes) * 0.2 + DrawableUtil.PI, seconds * 0.2, 0.1, 0.1, 0x00008899); + + if (canUseSuper) { + renderRing(matrices, 17, 13, Math.min(arcBegin, DrawableUtil.PI), Math.max(DrawableUtil.PI, DrawableUtil.TAU - arcBegin), mana.getCharge(), 0x88FF9999, tickDelta); + } double cost = abilities.getStats().stream() .mapToDouble(s -> s.getCost(Unicopia.getConfig().hudPage.get())) @@ -53,28 +77,25 @@ class ManaRingSlot extends Slot { double angle = cost * Math.PI * 2; - DrawableUtil.drawArc(matrices, 13, 17, arcBegin - angle, angle, color, false); + DrawableUtil.drawArc(matrices, 13, 17, arcBegin - angle, angle, color); } } - arcBegin = renderRing(matrices, 17, 13, arcBegin, mana.getExhaustion(), 0xFF002299, tickDelta); - matrices.pop(); - super.renderContents(context, abilities, bSwap, tickDelta); } - private double renderRing(MatrixStack matrices, double outerRadius, double innerRadius, double offsetAngle, Bar bar, int color, float tickDelta) { - double fill = bar.getPercentFill(tickDelta) * DrawableUtil.TAU; - double shadow = bar.getShadowFill(tickDelta) * DrawableUtil.TAU; + private double renderRing(MatrixStack matrices, double outerRadius, double innerRadius, double offsetAngle, double maxAngle, Bar bar, int color, float tickDelta) { + double fill = bar.getPercentFill(tickDelta) * maxAngle; + double shadow = bar.getShadowFill(tickDelta) * maxAngle; - DrawableUtil.drawArc(matrices, innerRadius, outerRadius, offsetAngle, fill, color, true); + DrawableUtil.drawArc(matrices, innerRadius, outerRadius, offsetAngle, fill, color); if (shadow > fill) { color = (color & 0xFFFFFF00) | ((color & 0x000000FF) / 2); - DrawableUtil.drawArc(matrices, innerRadius, outerRadius, offsetAngle + fill, shadow - fill, color, false); + DrawableUtil.drawArc(matrices, innerRadius, outerRadius, offsetAngle + fill, shadow - fill, color); } return offsetAngle + fill; } diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/Slot.java b/src/main/java/com/minelittlepony/unicopia/client/gui/Slot.java index 4d1e0ec0..80b7ef78 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/Slot.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/Slot.java @@ -89,10 +89,15 @@ class Slot { bSwap &= abilities.isFilled(bSlot); } + int page = Unicopia.getConfig().hudPage.get(); AbilityDispatcher.Stat stat = abilities.getStat(bSwap ? bSlot : aSlot); - if (stat.getAbility(Unicopia.getConfig().hudPage.get()).isEmpty()) { - return; + if (stat.getAbility(page).isEmpty()) { + if (aSlot != AbilitySlot.PRIMARY + || (!abilities.getStat(AbilitySlot.SECONDARY).getAbility(page).isEmpty() + && !abilities.getStat(AbilitySlot.TERTIARY).getAbility(page).isEmpty())) { + return; + } } RenderSystem.setShaderColor(1, 1, 1, 1); @@ -104,8 +109,6 @@ class Slot { // background context.drawTexture(UHud.HUD_TEXTURE, 0, 0, backgroundU, backgroundV, size, size, 128, 128); - - int iconPosition = ((size - iconSize + slotPadding + 1) / 2); int sz = iconSize - slotPadding; uHud.renderAbilityIcon(context, stat, iconPosition, iconPosition, sz, sz, sz, sz); diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/SpellIconRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/gui/SpellIconRenderer.java index 77a54548..57c383d7 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/SpellIconRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/SpellIconRenderer.java @@ -33,13 +33,13 @@ public interface SpellIconRenderer { int color = spell.type().getColor() | 0x000000FF; double radius = (1.5F + Math.sin(client.player.age / 9D) / 4) * ringScale; - DrawableUtil.drawArc(modelStack, radius, radius + 3, 0, DrawableUtil.TAU, color & 0xFFFFFF2F, false); - DrawableUtil.drawArc(modelStack, radius + 3, radius + 4, 0, DrawableUtil.TAU, color & 0xFFFFFFAF, false); - pony.getSpellSlot().get(spell.and(SpellPredicate.IS_TIMED), false).map(TimedSpell::getTimer).ifPresent(timer -> { - DrawableUtil.drawArc(modelStack, radius, radius + 3, 0, DrawableUtil.TAU * timer.getPercentTimeRemaining(client.getTickDelta()), 0xFFFFFFFF, false); + DrawableUtil.drawArc(modelStack, radius, radius + 3, 0, DrawableUtil.TAU, color & 0xFFFFFF2F); + DrawableUtil.drawArc(modelStack, radius + 3, radius + 4, 0, DrawableUtil.TAU, color & 0xFFFFFFAF); + pony.getSpellSlot().get(spell.and(SpellPredicate.IS_TIMED)).map(TimedSpell::getTimer).ifPresent(timer -> { + DrawableUtil.drawArc(modelStack, radius, radius + 3, 0, DrawableUtil.TAU * timer.getPercentTimeRemaining(client.getTickDelta()), 0xFFFFFFFF); }); - long count = pony.getSpellSlot().stream(spell, false).count(); + long count = pony.getSpellSlot().stream(spell).count(); if (count > 1) { modelStack.push(); modelStack.translate(1, 1, 900); 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 f24dd0e5..1dd0ae28 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java @@ -101,7 +101,7 @@ public class UHud { float flapCooldown = pony.getPhysics().getFlapCooldown(tickDelta); if (flapCooldown > 0) { float angle = MathHelper.TAU * flapCooldown; - DrawableUtil.drawArc(context.getMatrices(), 3, 6, -angle / 2F, angle, 0x888888AF, false); + DrawableUtil.drawArc(context.getMatrices(), 3, 6, -angle / 2F, angle, 0x888888AF); } matrices.pop(); @@ -205,7 +205,7 @@ public class UHud { context.fill(RenderLayers.getEndPortal(), 0, 0, scaledWidth, scaledHeight, 0); context.getMatrices().push(); context.getMatrices().translate(scaledWidth / 2, scaledHeight / 2, 0); - DrawableUtil.drawArc(context.getMatrices(), 0, 20, 0, MathHelper.TAU, 0x000000FF, false); + DrawableUtil.drawArc(context.getMatrices(), 0, 20, 0, MathHelper.TAU, 0x000000FF); context.getMatrices().pop(); return; } else if (vortexDistortion > 0) { @@ -214,8 +214,8 @@ public class UHud { boolean hasEffect = client.player.hasStatusEffect(UEffects.SUN_BLINDNESS); - ItemStack glasses = GlassesItem.getForEntity(client.player); - boolean hasSunglasses = glasses.getItem() == UItems.SUNGLASSES; + ItemStack glasses = GlassesItem.getForEntity(client.player).stack(); + boolean hasSunglasses = glasses.isOf(UItems.SUNGLASSES); if (hasEffect || (!hasSunglasses && pony.getObservedSpecies() == Race.BAT && SunBlindnessStatusEffect.hasSunExposure(client.player))) { float i = hasEffect ? (client.player.getStatusEffect(UEffects.SUN_BLINDNESS).getDuration() - tickDelta) / SunBlindnessStatusEffect.MAX_DURATION : 0; diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/IngredientTree.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/IngredientTree.java index 831ea7ca..fb4f6337 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/IngredientTree.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/IngredientTree.java @@ -18,6 +18,8 @@ import net.minecraft.client.gui.DrawContext; import net.minecraft.client.item.TooltipContext; import net.minecraft.client.render.*; import com.minelittlepony.unicopia.client.render.RenderLayers; +import com.minelittlepony.unicopia.container.SpellbookState; + import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.model.json.ModelTransformationMode; import net.minecraft.client.util.math.MatrixStack; @@ -324,7 +326,7 @@ public class IngredientTree implements SpellbookRecipe.CraftingTreeBuilder { @Override public void onClick() { if (MinecraftClient.getInstance().currentScreen instanceof SpellbookScreen spellbook) { - spellbook.getState().setCurrentPageId(SpellbookChapterList.TRAIT_DEX_ID); + spellbook.getState().setCurrentPageId(SpellbookState.TRAIT_DEX_ID); spellbook.getTraitDex().pageTo(spellbook, trait); } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookChapterList.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookChapterList.java index 3e91b7a1..6b206793 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookChapterList.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookChapterList.java @@ -6,16 +6,10 @@ import java.util.stream.Stream; import com.minelittlepony.common.client.gui.IViewRoot; import com.minelittlepony.unicopia.Debug; -import com.minelittlepony.unicopia.Unicopia; - import net.minecraft.client.gui.DrawContext; import net.minecraft.util.Identifier; public class SpellbookChapterList { - public static final Identifier CRAFTING_ID = Unicopia.id("crafting"); - public static final Identifier PROFILE_ID = Unicopia.id("profile"); - public static final Identifier TRAIT_DEX_ID = Unicopia.id("traits"); - private final SpellbookScreen screen; private final Chapter craftingChapter; @@ -73,10 +67,6 @@ public class SpellbookChapterList { default void copyStateFrom(Content old) {} - default boolean showInventory() { - return false; - } - default Identifier getIcon(Chapter chapter, Identifier icon) { return icon; } diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookCraftingPageContent.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookCraftingPageContent.java index c23705a6..e50765fa 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookCraftingPageContent.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookCraftingPageContent.java @@ -59,11 +59,6 @@ public class SpellbookCraftingPageContent extends ScrollContainer implements Spe init(this::initPageContent); } - @Override - public boolean showInventory() { - return state.getOffset() == 0; - } - private void initPageContent() { getContentPadding().setVertical(10); getContentPadding().bottom = 30; diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookProfilePageContent.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookProfilePageContent.java index 65c6ebe2..b0fe5fda 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookProfilePageContent.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookProfilePageContent.java @@ -58,11 +58,6 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content } } - @Override - public boolean showInventory() { - return true; - } - @Override public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) { @@ -110,13 +105,13 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content int color = 0x10404000 | alpha; int xpColor = 0xAA0040FF | ((int)((0.3F + 0.7F * xpPercentage) * 0xFF) & 0xFF) << 16; int manaColor = 0xFF00F040; - if (pony.getSpellSlot().get(SpellPredicate.IS_CORRUPTING, false).isPresent()) { + if (pony.getSpellSlot().get(SpellPredicate.IS_CORRUPTING).isPresent()) { manaColor = ColorHelper.lerp(Math.abs(MathHelper.sin(pony.asEntity().age / 15F)), manaColor, 0xFF0030F0); } manaColor |= (int)((0.3F + 0.7F * alphaF) * 0x40) << 16; - DrawableUtil.drawArc(matrices, 0, radius + 24, 0, DrawableUtil.TAU, color, false); - DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, DrawableUtil.TAU, color, false); + DrawableUtil.drawArc(matrices, 0, radius + 24, 0, DrawableUtil.TAU, color); + DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, DrawableUtil.TAU, color); if (currentLevel >= pony.getLevel().getMax()) { int rayCount = 6; @@ -134,14 +129,14 @@ public class SpellbookProfilePageContent implements SpellbookChapterList.Content double rad = (radius + glowSize) * 0.8F + growth - (i % 2) * 5; float rot = (rotate + raySeparation * i) % MathHelper.TAU; - DrawableUtil.drawArc(matrices, 0, rad, rot, 0.2F, bandAColor, false); - DrawableUtil.drawArc(matrices, 0, rad + 0.3F, rot + 0.37F, 0.25F, bandBColor, false); + DrawableUtil.drawArc(matrices, 0, rad, rot, 0.2F, bandAColor); + DrawableUtil.drawArc(matrices, 0, rad + 0.3F, rot + 0.37F, 0.25F, bandBColor); } } - DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, xpPercentage * DrawableUtil.TAU, xpColor, false); + DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, xpPercentage * DrawableUtil.TAU, xpColor); radius += 8; - DrawableUtil.drawArc(matrices, radius, radius + 6 + growth, 0, manaPercentage * DrawableUtil.TAU, manaColor, false); + DrawableUtil.drawArc(matrices, radius, radius + 6 + growth, 0, manaPercentage * DrawableUtil.TAU, manaColor); String manaString = (int)reserves.getMana().get() + "/" + (int)reserves.getMana().getMax(); diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookScreen.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookScreen.java index e60e8393..0f275bcd 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookScreen.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookScreen.java @@ -14,7 +14,6 @@ import com.minelittlepony.unicopia.Debug; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; -import com.minelittlepony.unicopia.client.TextHelper; import com.minelittlepony.unicopia.client.gui.*; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.*; import com.minelittlepony.unicopia.compat.trinkets.TrinketSlotBackSprites; @@ -52,12 +51,11 @@ public class SpellbookScreen extends HandledScreen imple private final RecipeBookWidget recipeBook = new RecipeBookWidget(); - private final Chapter craftingChapter; private final SpellbookTraitDexPageContent traitDex = new SpellbookTraitDexPageContent(this); private final SpellbookChapterList chapters = new SpellbookChapterList(this, - craftingChapter = new Chapter(SpellbookChapterList.CRAFTING_ID, TabSide.LEFT, 0, 0, Optional.of(new SpellbookCraftingPageContent(this))), - new Chapter(SpellbookChapterList.PROFILE_ID, TabSide.LEFT, 1, 0, Optional.of(new SpellbookProfilePageContent(this))), - new Chapter(SpellbookChapterList.TRAIT_DEX_ID, TabSide.LEFT, 3, 0, Optional.of(traitDex)) + new Chapter(SpellbookState.CRAFTING_ID, TabSide.LEFT, 0, 0, Optional.of(new SpellbookCraftingPageContent(this))), + new Chapter(SpellbookState.PROFILE_ID, TabSide.LEFT, 1, 0, Optional.of(new SpellbookProfilePageContent(this))), + new Chapter(SpellbookState.TRAIT_DEX_ID, TabSide.LEFT, 3, 0, Optional.of(traitDex)) ); private final SpellbookTabBar tabs = new SpellbookTabBar(this, chapters); @@ -68,13 +66,6 @@ public class SpellbookScreen extends HandledScreen imple backgroundWidth = 405; backgroundHeight = 219; contentBounds = new Bounds(CONTENT_PADDING, CONTENT_PADDING, backgroundWidth - CONTENT_PADDING * 2, backgroundHeight - CONTENT_PADDING * 3 - 2); - - handler.addSlotShowingCondition(slotType -> { - if (slotType == SlotType.INVENTORY) { - return chapters.getCurrentChapter().content().filter(Content::showInventory).isPresent(); - } - return chapters.getCurrentChapter() == craftingChapter; - }); handler.getSpellbookState().setSynchronizer(state -> { Channel.CLIENT_SPELLBOOK_UPDATE.sendToServer(MsgSpellbookStateChanged.create(handler, state)); }); @@ -218,8 +209,7 @@ public class SpellbookScreen extends HandledScreen imple List tooltip = new ArrayList<>(); tooltip.add(spell.type().getName()); - tooltip.addAll(TextHelper.wrap(Text.translatable(spell.type().getTranslationKey() + ".lore").formatted(spell.type().getAffinity().getColor()), 180).toList()); - + spell.appendTooltip(tooltip); context.drawTooltip(textRenderer, tooltip, x, y); context.getMatrices().pop(); diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookTraitDexPageContent.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookTraitDexPageContent.java index c5f7f347..6322bf1c 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookTraitDexPageContent.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/SpellbookTraitDexPageContent.java @@ -73,7 +73,7 @@ public class SpellbookTraitDexPageContent implements SpellbookChapterList.Conten return; } page /= 2; - state = screen.getState().getState(SpellbookChapterList.TRAIT_DEX_ID); + state = screen.getState().getState(SpellbookState.TRAIT_DEX_ID); state.setOffset(page); leftPage.verticalScrollbar.scrollBy(leftPage.verticalScrollbar.getScrubber().getPosition()); rightPage.verticalScrollbar.scrollBy(rightPage.verticalScrollbar.getScrubber().getPosition()); @@ -84,7 +84,7 @@ public class SpellbookTraitDexPageContent implements SpellbookChapterList.Conten @Override public void onRecipesChanged() { - init(screen, SpellbookChapterList.TRAIT_DEX_ID); + init(screen, SpellbookState.TRAIT_DEX_ID); } private final class DexPage extends ScrollContainer { diff --git a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/AmuletGear.java b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/AmuletGear.java index 59199f8d..de89085e 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/AmuletGear.java +++ b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/AmuletGear.java @@ -30,7 +30,7 @@ class AmuletGear extends AmuletModel implements Gear { @Override public boolean canRender(PonyModel model, Entity entity) { - return entity instanceof LivingEntity living && !AmuletItem.getForEntity(living).isEmpty(); + return entity instanceof LivingEntity living && !AmuletItem.get(living).stack().isEmpty(); } @Override @@ -40,7 +40,7 @@ class AmuletGear extends AmuletModel implements Gear { @Override public Identifier getTexture(T entity, Context context) { - return textures.computeIfAbsent(Registries.ITEM.getId(AmuletItem.getForEntity((LivingEntity)entity).getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); + return textures.computeIfAbsent(Registries.ITEM.getId(AmuletItem.get((LivingEntity)entity).stack().getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/BangleGear.java b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/BangleGear.java index d150614f..d2c945a2 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/BangleGear.java +++ b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/BangleGear.java @@ -67,8 +67,8 @@ class BangleGear implements Gear { public void pose(PonyModel model, Entity entity, boolean rainboom, UUID interpolatorId, float move, float swing, float bodySwing, float ticks) { alex = entity instanceof ClientPlayerEntity && ((ClientPlayerEntity)entity).getSkinTextures().model() == Model.SLIM; FriendshipBraceletItem.getWornBangles((LivingEntity)entity, slot).findFirst().ifPresent(bracelet -> { - color = ((DyeableItem)bracelet.getItem()).getColor(bracelet); - glowing = ((GlowableItem)bracelet.getItem()).isGlowing(bracelet); + color = ((DyeableItem)bracelet.stack().getItem()).getColor(bracelet.stack()); + glowing = ((GlowableItem)bracelet.stack().getItem()).isGlowing(bracelet.stack()); }); BraceletModel m = alex ? alexModel : steveModel; @@ -76,7 +76,7 @@ class BangleGear implements Gear { m.setAngles(biped); } Arm mainArm = ((LivingEntity)entity).getMainArm(); - m.setVisible(slot == TrinketsDelegate.MAINHAND ? mainArm : mainArm.getOpposite()); + m.setVisible(slot == TrinketsDelegate.MAIN_GLOVE ? mainArm : mainArm.getOpposite()); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/GlassesGear.java b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/GlassesGear.java index 2381b2ab..cce63359 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/GlassesGear.java +++ b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/GlassesGear.java @@ -28,7 +28,7 @@ class GlassesGear extends GlassesModel implements Gear { @Override public boolean canRender(PonyModel model, Entity entity) { - return entity instanceof LivingEntity living && !GlassesItem.getForEntity(living).isEmpty(); + return entity instanceof LivingEntity living && !GlassesItem.getForEntity(living).stack().isEmpty(); } @Override @@ -38,7 +38,7 @@ class GlassesGear extends GlassesModel implements Gear { @Override public Identifier getTexture(T entity, Context context) { - return textures.computeIfAbsent(Registries.ITEM.getId(GlassesItem.getForEntity((LivingEntity)entity).getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); + return textures.computeIfAbsent(Registries.ITEM.getId(GlassesItem.getForEntity((LivingEntity)entity).stack().getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java index 0f2c8e0f..399a8cea 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java +++ b/src/main/java/com/minelittlepony/unicopia/client/minelittlepony/Main.java @@ -43,8 +43,8 @@ public class Main extends MineLPDelegate implements ClientModInitializer { public void onInitializeClient() { INSTANCE = this; PonyModelPrepareCallback.EVENT.register(this::onPonyModelPrepared); - Gear.register(() -> new BangleGear(TrinketsDelegate.MAINHAND)); - Gear.register(() -> new BangleGear(TrinketsDelegate.OFFHAND)); + Gear.register(() -> new BangleGear(TrinketsDelegate.MAIN_GLOVE)); + Gear.register(() -> new BangleGear(TrinketsDelegate.SECONDARY_GLOVE)); Gear.register(HeldEntityGear::new); Gear.register(BodyPartGear::pegasusWings); Gear.register(BodyPartGear::batWings); diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java index 245c2b06..38700671 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/WindParticle.java @@ -89,7 +89,8 @@ public class WindParticle extends AbstractBillboardParticle { trail.update(new Vec3d(x + cos, y + sin, z - cos)); } else { if (target != null && target.isAlive()) { - trail.update(target.getPos().add(offset).add(cos, sin, -cos)); + + trail.update(target.getPos().add(target.getRotationVecClient().multiply(-7)).add(offset).add(cos, sin, -cos)); if (attachmentTicks > 0 && --attachmentTicks <= 0) { target = null; diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/AmuletFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/AmuletFeatureRenderer.java index f2a2c54f..99bce334 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/AmuletFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/AmuletFeatureRenderer.java @@ -42,7 +42,7 @@ public class AmuletFeatureRenderer implements AccessoryF @Override public void render(MatrixStack matrices, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) { - ItemStack stack = AmuletItem.getForEntity(entity); + ItemStack stack = AmuletItem.get(entity).stack(); if (!stack.isEmpty()) { Identifier texture = textures.computeIfAbsent(Registries.ITEM.getId(stack.getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/BraceletFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/BraceletFeatureRenderer.java index 4a3ca930..09a53fe6 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/BraceletFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/BraceletFeatureRenderer.java @@ -49,11 +49,11 @@ public class BraceletFeatureRenderer implements Accessor @Override public void render(MatrixStack stack, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) { - FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.MAINHAND).findFirst().ifPresent(bangle -> { - renderBangleThirdPerson(bangle, stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm()); + FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.MAIN_GLOVE).findFirst().ifPresent(bangle -> { + renderBangleThirdPerson(bangle.stack(), stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm()); }); - FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.OFFHAND).findFirst().ifPresent(bangle -> { - renderBangleThirdPerson(bangle, stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm().getOpposite()); + FriendshipBraceletItem.getWornBangles(entity, TrinketsDelegate.SECONDARY_GLOVE).findFirst().ifPresent(bangle -> { + renderBangleThirdPerson(bangle.stack(), stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch, entity.getMainArm().getOpposite()); }); } @@ -83,14 +83,14 @@ public class BraceletFeatureRenderer implements Accessor @Override public void renderArm(MatrixStack stack, VertexConsumerProvider renderContext, int lightUv, E entity, ModelPart armModel, Arm side) { - FriendshipBraceletItem.getWornBangles(entity, side == entity.getMainArm() ? TrinketsDelegate.MAINHAND : TrinketsDelegate.OFFHAND).findFirst().ifPresent(item -> { - int j = ((DyeableItem)item.getItem()).getColor(item); + FriendshipBraceletItem.getWornBangles(entity, side == entity.getMainArm() ? TrinketsDelegate.MAIN_GLOVE : TrinketsDelegate.SECONDARY_GLOVE).findFirst().ifPresent(item -> { + int j = ((DyeableItem)item.stack().getItem()).getColor(item.stack()); boolean alex = entity instanceof ClientPlayerEntity && ((ClientPlayerEntity)entity).getSkinTextures().model() == SkinTextures.Model.SLIM; BraceletModel model = alex ? alexModel : steveModel; - boolean glowing = ((GlowableItem)item.getItem()).isGlowing(item); + boolean glowing = ((GlowableItem)item.stack().getItem()).isGlowing(item.stack()); if (MineLPDelegate.getInstance().getPlayerPonyRace((ClientPlayerEntity)entity).isEquine()) { stack.translate(side == Arm.LEFT ? 0.06 : -0.06, 0.3, 0); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java index c130779c..6ace7501 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/DisguisedArmsFeatureRenderer.java @@ -114,7 +114,7 @@ public class DisguisedArmsFeatureRenderer implements Acc } private Entity getAppearance(E entity) { - return Caster.of(entity).flatMap(caster -> caster.getSpellSlot().get(SpellPredicate.IS_DISGUISE, false)).map(Disguise.class::cast) + return Caster.of(entity).flatMap(caster -> caster.getSpellSlot().get(SpellPredicate.IS_DISGUISE)).map(Disguise.class::cast) .flatMap(Disguise::getAppearance) .map(EntityAppearance::getAppearance) .orElse(null); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java index 40a83c9d..94941723 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/EntityDisguiseRenderer.java @@ -9,20 +9,19 @@ import com.minelittlepony.unicopia.entity.behaviour.Disguise; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.mixin.MixinBlockEntity; -import net.minecraft.block.BlockState; +import net.minecraft.block.BlockRenderType; import net.minecraft.block.entity.BlockEntity; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.block.entity.BlockEntityRenderer; +import net.minecraft.client.render.entity.EntityRenderDispatcher; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.Entity; -import net.minecraft.state.property.Properties; -import net.minecraft.util.math.Direction; +import net.minecraft.entity.FallingBlockEntity; import net.minecraft.util.math.MathHelper; -import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.Vec3d; class EntityDisguiseRenderer { @@ -37,7 +36,9 @@ class EntityDisguiseRenderer { double x, double y, double z, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) { int fireTicks = pony.asEntity().doesRenderOnFire() ? 1 : 0; - disguise.update(pony, false); + if (!delegate.client.isPaused()) { + disguise.update(pony, false); + } EntityAppearance ve = disguise.getDisguise(); Entity e = ve.getAppearance(); @@ -53,11 +54,11 @@ class EntityDisguiseRenderer { } render(ve, e, x, y, z, fireTicks, tickDelta, matrices, vertices, light); - ve.getAttachments().forEach(ee -> { - PehkUtil.copyScale(pony.asEntity(), ee); - Vec3d difference = ee.getPos().subtract(e.getPos()); - render(ve, ee, x + difference.x, y + difference.y, z + difference.z, fireTicks, tickDelta, matrices, vertices, light); - PehkUtil.clearScale(ee); + ve.getAttachments().forEach(attachment -> { + PehkUtil.copyScale(pony.asEntity(), attachment.entity()); + Vec3d difference = attachment.entity().getPos().subtract(e.getPos()); + render(ve, attachment.entity(), x + difference.x, y + difference.y, z + difference.z, fireTicks, tickDelta, matrices, vertices, light); + PehkUtil.clearScale(attachment.entity()); }); matrices.push(); @@ -70,6 +71,7 @@ class EntityDisguiseRenderer { return true; } + @SuppressWarnings("deprecation") private void render(EntityAppearance ve, Entity e, double x, double y, double z, int fireTicks, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) { @@ -88,24 +90,22 @@ class EntityDisguiseRenderer { BlockEntityRenderer r = MinecraftClient.getInstance().getBlockEntityRenderDispatcher().get(blockEntity); if (r != null) { ((MixinBlockEntity)blockEntity).setPos(e.getBlockPos()); + if (e instanceof FallingBlockEntity fbe) { + blockEntity.setCachedState(fbe.getBlockState()); + } blockEntity.setWorld(e.getWorld()); matrices.push(); - - BlockState state = blockEntity.getCachedState(); - Direction direction = state.contains(Properties.HORIZONTAL_FACING) ? state.get(Properties.HORIZONTAL_FACING) : Direction.UP; - matrices.translate(x, y, z); - - matrices.multiply(direction.getRotationQuaternion()); - matrices.multiply(RotationAxis.NEGATIVE_X.rotationDegrees(90)); - matrices.translate(-0.5, 0, -0.5); r.render(blockEntity, 1, matrices, vertexConsumers, light, OverlayTexture.DEFAULT_UV); matrices.pop(); - blockEntity.setWorld(null); - return; + + BlockRenderType type = blockEntity.getCachedState().getRenderType(); + if (type == BlockRenderType.ENTITYBLOCK_ANIMATED) { + return; + } } } @@ -116,7 +116,15 @@ class EntityDisguiseRenderer { } e.setFireTicks(fireTicks); - delegate.client.getEntityRenderDispatcher().render(e, x, y, z, e.getYaw(), tickDelta, matrices, vertexConsumers, light); + + EntityRenderDispatcher dispatcher = delegate.client.getEntityRenderDispatcher(); + if (e instanceof FallingBlockEntity) { + dispatcher.setRenderShadows(false); + } + dispatcher.render(e, x, y, z, e.getYaw(), tickDelta, matrices, vertexConsumers, light); + if (e instanceof FallingBlockEntity) { + dispatcher.setRenderShadows(true); + } e.setFireTicks(0); if (model != null) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/EntityReplacementManager.java b/src/main/java/com/minelittlepony/unicopia/client/render/EntityReplacementManager.java index c1a9d8c7..8b759a18 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/EntityReplacementManager.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/EntityReplacementManager.java @@ -51,7 +51,7 @@ class EntityReplacementManager implements Disguise { return Optional.of(this); } - return caster.getSpellSlot().get(SpellPredicate.IS_DISGUISE, false).map(Disguise.class::cast); + return caster.getSpellSlot().get(SpellPredicate.IS_DISGUISE).map(Disguise.class::cast); } private List> getMobTypePool(EntityType type) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/GlassesFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/GlassesFeatureRenderer.java index 40771191..7a0ca70d 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/GlassesFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/GlassesFeatureRenderer.java @@ -36,7 +36,7 @@ public class GlassesFeatureRenderer implements Accessory @Override public void render(MatrixStack matrices, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) { - ItemStack stack = GlassesItem.getForEntity(entity); + ItemStack stack = GlassesItem.getForEntity(entity).stack(); if (!stack.isEmpty()) { Identifier texture = textures.computeIfAbsent(Registries.ITEM.getId(stack.getItem()), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/HornFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/HornFeatureRenderer.java index 068263e5..8b430d57 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/HornFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/HornFeatureRenderer.java @@ -3,7 +3,7 @@ package com.minelittlepony.unicopia.client.render; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.ability.AbilityDispatcher.Stat; -import com.minelittlepony.unicopia.ability.magic.SpellPredicate; +import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import net.minecraft.client.model.Dilation; import net.minecraft.client.model.Model; @@ -55,7 +55,9 @@ public class HornFeatureRenderer implements AccessoryFea return pony.getAbilities().getActiveStat() .flatMap(Stat::getActiveAbility) .map(ability -> ability.getColor(pony)) - .filter(i -> i != -1).or(() -> pony.getSpellSlot().get(SpellPredicate.IS_NOT_PLACED, false).map(spell -> spell.getType().getColor())); + .filter(i -> i != -1).or(() -> pony.getSpellSlot() + .get(SpellType.PLACE_CONTROL_SPELL.negate()) + .map(spell -> spell.getTypeAndTraits().type().getColor())); }).ifPresent(color -> { model.setState(true); model.render(stack, ItemRenderer.getArmorGlintConsumer(renderContext, RenderLayers.getMagicColored((0x99 << 24) | color), false, false), lightUv, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/PlayerPoser.java b/src/main/java/com/minelittlepony/unicopia/client/render/PlayerPoser.java index d1e69b94..e58e1078 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/PlayerPoser.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/PlayerPoser.java @@ -43,7 +43,7 @@ public class PlayerPoser { boolean liftLeftArm = mainArm == Arm.LEFT || !ponyRace.isEquine(); boolean liftRightArm = mainArm == Arm.RIGHT || !ponyRace.isEquine(); - ItemStack glasses = GlassesItem.getForEntity(player); + ItemStack glasses = GlassesItem.getForEntity(player).stack(); ModelPart head = model.getHead(); if (context == Context.THIRD_PERSON && !player.isSneaking()) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CastSpellEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CastSpellEntityRenderer.java index ece6638b..086d3b48 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/CastSpellEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/CastSpellEntityRenderer.java @@ -1,16 +1,35 @@ package com.minelittlepony.unicopia.client.render.entity; +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.common.util.Color; +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.client.render.model.PlaneModel; import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher; +import com.minelittlepony.unicopia.client.render.spell.SpellRenderer; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.EntityRendererFactory; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.screen.PlayerScreenHandler; import net.minecraft.util.Identifier; +import net.minecraft.util.math.RotationAxis; public class CastSpellEntityRenderer extends EntityRenderer { + private static final Identifier[] TEXTURES = new Identifier[] { + Unicopia.id("textures/particles/runes_0.png"), + Unicopia.id("textures/particles/runes_1.png"), + Unicopia.id("textures/particles/runes_2.png"), + Unicopia.id("textures/particles/runes_3.png"), + Unicopia.id("textures/particles/runes_4.png"), + Unicopia.id("textures/particles/runes_5.png") + }; + public CastSpellEntityRenderer(EntityRendererFactory.Context ctx) { super(ctx); } @@ -21,11 +40,62 @@ public class CastSpellEntityRenderer extends EntityRenderer { } @Override - public void render(CastSpellEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) { - SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertexConsumers, light, entity, 0, 0, tickDelta, getAnimationProgress(entity, tickDelta), yaw, 0); + public void render(CastSpellEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertices, int light) { + matrices.push(); + matrices.translate(0, 0.001, 0); + final float height = entity.getHeight(); + final float pitch = entity.getPitch(tickDelta); + matrices.translate(0, (-pitch / 90F) * height * 0.5F, 0); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(yaw)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-pitch)); + + float animationProgress = getAnimationProgress(entity, tickDelta); + renderAmbientEffects(matrices, vertices, entity, entity.getSpellSlot().get().orElse(null), light, animationProgress, tickDelta); + SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertices, light, entity, entity.getScale(tickDelta), 0, tickDelta, animationProgress, yaw, pitch); + + matrices.pop(); } protected float getAnimationProgress(CastSpellEntity entity, float tickDelta) { return entity.age + tickDelta; } + + protected void renderAmbientEffects(MatrixStack matrices, VertexConsumerProvider vertices, CastSpellEntity entity, @Nullable Spell spell, int light, float animationProgress, float tickDelta) { + matrices.push(); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90)); + + float scale = entity.getScale(tickDelta) * 3; + matrices.scale(scale, scale, scale); + + float angle = (animationProgress / 9F) % 360; + + int color = spell == null ? 0 : spell.getTypeAndTraits().type().getColor(); + + float red = Color.r(color); + float green = Color.g(color); + float blue = Color.b(color); + + @Nullable + SpellRenderer renderer = spell == null ? null : SpellEffectsRenderDispatcher.INSTANCE.getRenderer(spell); + + for (int i = 0; i < TEXTURES.length; i++) { + if (renderer != null && !renderer.shouldRenderEffectPass(i)) { + continue; + } + VertexConsumer buffer = vertices.getBuffer(RenderLayer.getEntityTranslucent(TEXTURES[i])); + + for (int dim = 0; dim < 3; dim++) { + float ringSpeed = (i % 2 == 0 ? i : -1) * i; + + matrices.push(); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(angle * ringSpeed)); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(angle * ringSpeed * dim)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(angle * ringSpeed * dim)); + PlaneModel.INSTANCE.render(matrices, buffer, light, 0, 1, red, green, blue, scale / ((float)(dim * 3) + 1)); + matrices.pop(); + } + } + + matrices.pop(); + } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/MagicBeamEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/MagicBeamEntityRenderer.java index 26e2297a..3310d883 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/MagicBeamEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/MagicBeamEntityRenderer.java @@ -56,8 +56,8 @@ public class MagicBeamEntityRenderer extends EntityRenderer { -entity.getPitch(tickDelta) * MathHelper.RADIANS_PER_DEGREE ); - RenderLayer layer = entity.getSpellSlot().get(true) - .map(spell -> (0x99 << 24) | spell.getType().getColor()) + RenderLayer layer = entity.getSpellSlot().get() + .map(spell -> (0x99 << 24) | spell.getTypeAndTraits().type().getColor()) .map(RenderLayers::getMagicColored) .orElseGet(RenderLayers::getMagicColored); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java index 1195e6c1..7656c7af 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/entity/MimicEntityRenderer.java @@ -1,6 +1,7 @@ package com.minelittlepony.unicopia.client.render.entity; import com.minelittlepony.unicopia.entity.mob.MimicEntity; +import com.minelittlepony.unicopia.mixin.MixinBlockEntity; import net.minecraft.block.ChestBlock; import net.minecraft.block.entity.BlockEntity; @@ -74,6 +75,8 @@ public class MimicEntityRenderer extends MobEntityRenderer { + + public SpecterEntityRenderer(Context context) { + super(context, new SpecterEntityModel(context.getPart(EntityModelLayers.PLAYER)), 0); + addFeature(new ArmorFeatureRenderer<>(this, + new BipedEntityModel<>(context.getPart(EntityModelLayers.PLAYER_INNER_ARMOR)), + new BipedEntityModel<>(context.getPart(EntityModelLayers.PLAYER_OUTER_ARMOR)), + context.getModelManager())); + } + + @Override + public Identifier getTexture(SpecterEntity entity) { + return PlayerScreenHandler.BLOCK_ATLAS_TEXTURE; + } + + static class SpecterEntityModel extends BipedEntityModel { + public SpecterEntityModel(ModelPart root) { + super(root); + } + + @Override + public void render(MatrixStack matrices, VertexConsumer vertices, int light, int overlay, float red, float green, float blue, float alpha) { + // noop + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/AllSpells.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/AllSpells.java deleted file mode 100644 index c6c5c2ff..00000000 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/AllSpells.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.minelittlepony.unicopia.client.render.spell; - -import com.minelittlepony.unicopia.ability.magic.SpellPredicate; -import com.minelittlepony.unicopia.ability.magic.spell.Spell; - -public interface AllSpells extends SpellPredicate { - AllSpells INSTANCE = spell -> true; -} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java deleted file mode 100644 index fbdfce47..00000000 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.minelittlepony.unicopia.client.render.spell; - -import com.minelittlepony.common.util.Color; -import com.minelittlepony.unicopia.Unicopia; -import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell; -import com.minelittlepony.unicopia.ability.magic.spell.Spell; -import com.minelittlepony.unicopia.client.render.model.PlaneModel; -import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; - -import net.minecraft.client.render.RenderLayer; -import net.minecraft.client.render.VertexConsumer; -import net.minecraft.client.render.VertexConsumerProvider; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.util.Identifier; -import net.minecraft.util.math.RotationAxis; - -public class PlacedSpellRenderer extends SpellRenderer { - private static final Identifier[] TEXTURES = new Identifier[] { - Unicopia.id("textures/particles/runes_0.png"), - Unicopia.id("textures/particles/runes_1.png"), - Unicopia.id("textures/particles/runes_2.png"), - Unicopia.id("textures/particles/runes_3.png"), - Unicopia.id("textures/particles/runes_4.png"), - Unicopia.id("textures/particles/runes_5.png") - }; - - @Override - public void render(MatrixStack matrices, VertexConsumerProvider vertices, PlaceableSpell spell, Caster caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { - if (!(caster.asEntity() instanceof CastSpellEntity castSpell)) { - return; - } - - matrices.push(); - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(-castSpell.getYaw())); - - - for (Spell delegate : spell.getDelegates()) { - renderAmbientEffects(matrices, vertices, spell, delegate, caster, light, animationProgress, tickDelta); - - matrices.push(); - float height = caster.asEntity().getHeight(); - matrices.translate(0, (-spell.pitch / 90F) * height * 0.5F, 0); - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-spell.pitch)); - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180 - spell.yaw)); - SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertices, delegate, caster, light, spell.getScale(tickDelta), limbDistance, tickDelta, animationProgress, headYaw, headPitch); - matrices.pop(); - } - - matrices.pop(); - } - - protected void renderAmbientEffects(MatrixStack matrices, VertexConsumerProvider vertices, PlaceableSpell spell, Spell delegate, Caster caster, int light, float animationProgress, float tickDelta) { - matrices.push(); - matrices.translate(0, 0.001, 0); - - float height = caster.asEntity().getHeight(); - matrices.translate(0, (-spell.pitch / 90F) * height * 0.5F, 0); - - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-spell.pitch)); - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(180 - spell.yaw)); - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90)); - - float scale = spell.getScale(tickDelta) * 3; - matrices.scale(scale, scale, scale); - - float angle = (animationProgress / 9F) % 360; - - int color = delegate.getType().getColor(); - - float red = Color.r(color); - float green = Color.g(color); - float blue = Color.b(color); - - SpellRenderer renderer = SpellEffectsRenderDispatcher.INSTANCE.getRenderer(delegate); - - for (int i = 0; i < TEXTURES.length; i++) { - if (!renderer.shouldRenderEffectPass(i)) { - continue; - } - VertexConsumer buffer = vertices.getBuffer(RenderLayer.getEntityTranslucent(TEXTURES[i])); - - for (int dim = 0; dim < 3; dim++) { - float ringSpeed = (i % 2 == 0 ? i : -1) * i; - - matrices.push(); - matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(angle * ringSpeed)); - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(angle * ringSpeed * dim)); - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(angle * ringSpeed * dim)); - PlaneModel.INSTANCE.render(matrices, buffer, light, 0, 1, red, green, blue, scale / ((float)(dim * 3) + 1)); - matrices.pop(); - } - } - - matrices.pop(); - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalFrameBuffer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalFrameBuffer.java index e594b647..7a558eef 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalFrameBuffer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalFrameBuffer.java @@ -159,13 +159,13 @@ class PortalFrameBuffer implements AutoCloseable { Camera camera = client.gameRenderer.getCamera(); Entity cameraEntity = UEntities.CAST_SPELL.create(caster.asWorld()); - Vec3d offset = new Vec3d(0, -0.2F, -0.2F).rotateY(-spell.getTargetYaw() * MathHelper.RADIANS_PER_DEGREE); + Vec3d offset = new Vec3d(0, 0, -0.1F).rotateY(-spell.getTargetYaw() * MathHelper.RADIANS_PER_DEGREE); float yaw = spell.getTargetYaw() + camera.getYaw() - spell.getYaw() + 180; float pitch = spell.getTargetPitch() + (camera.getPitch() - spell.getPitch()) * 1.65F; cameraEntity.setPosition(target.pos().add(offset)); - cameraEntity.setPitch(pitch); + cameraEntity.setPitch(90 + pitch); cameraEntity.setYaw(yaw); drawWorld(cameraEntity, 400, 400); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalSpellRenderer.java index a75c3f72..bb65f063 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PortalSpellRenderer.java @@ -10,6 +10,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; import net.minecraft.util.math.RotationAxis; public class PortalSpellRenderer extends SpellRenderer { @@ -29,7 +30,9 @@ public class PortalSpellRenderer extends SpellRenderer { SphereModel.DISK.render(matrices, buff, light, 0, 2F * strength, 1, 1, 1, 1); matrices.pop(); - if (Unicopia.getConfig().simplifiedPortals.get() || !spell.isLinked()) { + EntityReference destination = spell.getDestinationReference(); + + if (Unicopia.getConfig().simplifiedPortals.get() || !destination.isSet()) { matrices.push(); matrices.translate(0, -0.02, 0); matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(180)); @@ -52,7 +55,7 @@ public class PortalSpellRenderer extends SpellRenderer { matrices.push(); matrices.scale(strength, strength, strength); - spell.getTarget().ifPresent(target -> { + destination.getTarget().ifPresent(target -> { float grown = Math.min(caster.asEntity().age, 20) / 20F; matrices.push(); matrices.translate(0, -0.01, 0); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java index 1fc202ad..70fd70f3 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java @@ -27,7 +27,7 @@ public class ShieldSpellRenderer extends SpellRenderer { double height = caster.asEntity().getEyeY() - caster.getOriginVector().y; matrices.translate(0, height, 0); - int typeColor = spell.getType().getColor(); + int typeColor = spell.getTypeAndTraits().type().getColor(); int ponyColor = MineLPDelegate.getInstance().getMagicColor(caster.getOriginatingCaster().asEntity()); int color = ColorHelper.lerp(caster.getCorruption().getScaled(1) * (tickDelta / (1 + caster.asWorld().random.nextFloat())), diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java index c87430da..d80d55b6 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellEffectsRenderDispatcher.java @@ -10,7 +10,7 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.entity.Living; @@ -47,7 +47,6 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader } static { - register(SpellType.PLACED_SPELL, PlacedSpellRenderer::new); register(SpellType.SHIELD, ShieldSpellRenderer::new); register(SpellType.DARK_VORTEX, DarkVortexSpellRenderer::new); register(SpellType.BUBBLE, BubbleSpellRenderer::new); @@ -68,7 +67,7 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader @SuppressWarnings("unchecked") public SpellRenderer getRenderer(S spell) { - return (SpellRenderer)renderers.getOrDefault(spell.getType(), SpellRenderer.DEFAULT); + return (SpellRenderer)renderers.getOrDefault(spell.getTypeAndTraits().type(), SpellRenderer.DEFAULT); } public void render(MatrixStack matrices, VertexConsumerProvider vertices, Spell spell, Caster caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { @@ -86,10 +85,9 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader return; } - caster.getSpellSlot().forEach(spell -> { + caster.getSpellSlot().stream().forEach(spell -> { render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); - return Operation.SKIP; - }, false); + }); if (client.getEntityRenderDispatcher().shouldRenderHitboxes() && !client.hasReducedDebugInfo() @@ -124,11 +122,11 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader caster.asEntity().getDisplayName().copy().append(" (" + Registries.ENTITY_TYPE.getId(caster.asEntity().getType()) + ")"), caster.getMaster() != null ? Text.literal("Master: ").append(caster.getMaster().getDisplayName()) : Text.empty() ), - caster.getSpellSlot().stream(AllSpells.INSTANCE, false).flatMap(spell -> + caster.getSpellSlot().stream(SpellPredicate.ALL).flatMap(spell -> Stream.of( Text.literal("UUID: " + spell.getUuid()), - Text.literal("|>Type: ").append(Text.literal(spell.getType().getId().toString()).styled(s -> s.withColor(spell.getType().getColor()))), - Text.of("|>Traits: " + spell.getTraits()), + Text.literal("|>Type: ").append(Text.literal(spell.getTypeAndTraits().type().getId().toString()).styled(s -> s.withColor(spell.getTypeAndTraits().type().getColor()))), + Text.of("|>Traits: " + spell.getTypeAndTraits().traits()), Text.literal("|>HasRenderer: ").append(Text.literal((getRenderer(spell) != null) + "").formatted(getRenderer(spell) != null ? Formatting.GREEN : Formatting.RED)) ) ) diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRenderer.java index cdaf9f79..a1171b7c 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/SpellRenderer.java @@ -42,7 +42,7 @@ public class SpellRenderer { matrices.push(); matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(animationProgress)); - client.getItemRenderer().renderItem(spell.getType().withTraits(spell.getTraits()).getDefaultStack(), ModelTransformationMode.FIXED, light, 0, matrices, vertices, caster.asWorld(), 0); + client.getItemRenderer().renderItem(spell.getTypeAndTraits().getDefaultStack(), ModelTransformationMode.FIXED, light, 0, matrices, vertices, caster.asWorld(), 0); matrices.pop(); if (spell instanceof TimedSpell timed) { @@ -58,8 +58,7 @@ public class SpellRenderer { float timeRemaining = spell.getTimer().getPercentTimeRemaining(tickDelta); DrawableUtil.drawArc(matrices, radius, radius + 0.3F, 0, DrawableUtil.TAU * timeRemaining, - ColorHelper.lerp(MathHelper.clamp(timeRemaining * 4, 0, 1), 0xFF0000FF, 0xFFFFFFFF), - false + ColorHelper.lerp(MathHelper.clamp(timeRemaining * 4, 0, 1), 0xFF0000FF, 0xFFFFFFFF) ); } diff --git a/src/main/java/com/minelittlepony/unicopia/command/CastCommand.java b/src/main/java/com/minelittlepony/unicopia/command/CastCommand.java index 1983c550..e3c63072 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/CastCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/CastCommand.java @@ -4,7 +4,7 @@ import java.util.Optional; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; -import com.minelittlepony.unicopia.ability.magic.spell.PlaceableSpell; +import com.minelittlepony.unicopia.ability.magic.spell.PlacementControlSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; @@ -98,11 +98,11 @@ public class CastCommand { private static int placed(CommandContext source, TraitsFunc traits, Optional position, Vec2f rotation) throws CommandSyntaxException { ServerPlayerEntity player = source.getSource().getPlayerOrThrow(); - PlaceableSpell spell = getSpell(source, traits).create().toPlaceable(); + PlacementControlSpell spell = getSpell(source, traits).create().toPlaceable(); Caster caster = Caster.of(player).orElseThrow(); - spell.setOrientation(rotation.x, rotation.y); - position.ifPresent(pos -> spell.setPosition(caster, pos)); + spell.setOrientation(caster, rotation.x, rotation.y); + position.ifPresent(spell::setPosition); spell.apply(caster); return 0; diff --git a/src/main/java/com/minelittlepony/unicopia/command/DisguiseCommand.java b/src/main/java/com/minelittlepony/unicopia/command/DisguiseCommand.java index 31306fad..d735c89a 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/DisguiseCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/DisguiseCommand.java @@ -80,7 +80,7 @@ public class DisguiseCommand { } Pony iplayer = Pony.of(player); - iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE, true) + iplayer.getSpellSlot().get(SpellType.CHANGELING_DISGUISE) .orElseGet(() -> SpellType.CHANGELING_DISGUISE.withTraits().apply(iplayer, CastingMethod.INNATE)) .setDisguise(entity); @@ -109,7 +109,7 @@ public class DisguiseCommand { static int reveal(ServerCommandSource source, PlayerEntity player) { Pony iplayer = Pony.of(player); - iplayer.getSpellSlot().removeIf(SpellPredicate.IS_DISGUISE, true); + iplayer.getSpellSlot().removeIf(SpellPredicate.IS_DISGUISE); if (source.getEntity() == player) { source.sendFeedback(() -> Text.translatable("commands.disguise.removed.self"), true); diff --git a/src/main/java/com/minelittlepony/unicopia/command/GravityCommand.java b/src/main/java/com/minelittlepony/unicopia/command/GravityCommand.java index c5249dcb..a97272e9 100644 --- a/src/main/java/com/minelittlepony/unicopia/command/GravityCommand.java +++ b/src/main/java/com/minelittlepony/unicopia/command/GravityCommand.java @@ -48,7 +48,6 @@ class GravityCommand { static int set(ServerCommandSource source, Collection targets, float gravity, boolean isSelf) { List affected = targets.stream().map(Living::living).filter(Objects::nonNull).map(l -> { l.getPhysics().setBaseGravityModifier(gravity); - l.setDirty(); if (l.asEntity() instanceof PlayerEntity player) { if (source.getEntity() == player) { player.sendMessage(Text.translatable("commands.gravity.set.self", gravity)); diff --git a/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegate.java b/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegate.java index c9e17eb8..f19d592a 100644 --- a/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegate.java +++ b/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegate.java @@ -1,6 +1,8 @@ package com.minelittlepony.unicopia.compat.trinkets; import java.util.*; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -16,16 +18,17 @@ import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.registry.tag.TagKey; import net.minecraft.screen.slot.Slot; import net.minecraft.util.Identifier; public interface TrinketsDelegate { - Identifier MAINHAND = new Identifier("hand:glove"); - Identifier OFFHAND = new Identifier("offhand:glove"); + Identifier MAIN_GLOVE = new Identifier("hand:glove"); + Identifier SECONDARY_GLOVE = new Identifier("offhand:glove"); Identifier NECKLACE = new Identifier("chest:necklace"); Identifier FACE = new Identifier("head:face"); - Set ALL = new TreeSet<>(List.of(MAINHAND, OFFHAND, NECKLACE, FACE)); + Set ALL = new TreeSet<>(List.of(MAIN_GLOVE, SECONDARY_GLOVE, NECKLACE, FACE)); TrinketsDelegate EMPTY = new TrinketsDelegate() {}; @@ -61,8 +64,8 @@ public interface TrinketsDelegate { default void setEquippedStack(LivingEntity entity, Identifier slot, ItemStack stack) { EquipmentSlot eq = slot == FACE ? EquipmentSlot.HEAD : slot == NECKLACE ? EquipmentSlot.CHEST - : slot == MAINHAND ? EquipmentSlot.CHEST - : slot == OFFHAND ? EquipmentSlot.OFFHAND + : slot == MAIN_GLOVE ? EquipmentSlot.CHEST + : slot == SECONDARY_GLOVE ? EquipmentSlot.OFFHAND : null; if (eq != null) { entity.equipStack(eq, stack); @@ -70,19 +73,27 @@ public interface TrinketsDelegate { } default Set getAvailableTrinketSlots(LivingEntity entity, Set probedSlots) { - return probedSlots.stream().filter(slot -> getEquipped(entity, slot).anyMatch(ItemStack::isEmpty)).collect(Collectors.toSet()); + return probedSlots.stream().filter(slot -> getEquipped(entity, slot).map(EquippedStack::stack).anyMatch(ItemStack::isEmpty)).collect(Collectors.toSet()); } - default Stream getEquipped(LivingEntity entity, Identifier slot) { + default Stream getEquipped(LivingEntity entity, Identifier slot, TagKey tag) { + return getEquipped(entity, slot, stack -> stack.isIn(tag)); + } - if (slot == FACE) { - return Stream.of(entity.getEquippedStack(EquipmentSlot.HEAD)); + default Stream getEquipped(LivingEntity entity, Identifier slot) { + return getEquipped(entity, slot, (Predicate)null); + } + + default Stream getEquipped(LivingEntity entity, Identifier slot, @Nullable Predicate predicate) { + + if (slot == FACE && (predicate == null || predicate.test(entity.getEquippedStack(EquipmentSlot.HEAD)))) { + return Stream.of(new EquippedStack(entity, EquipmentSlot.HEAD)); } - if (slot == NECKLACE || slot == MAINHAND) { - return Stream.of(entity.getEquippedStack(EquipmentSlot.CHEST)); + if ((slot == NECKLACE || slot == MAIN_GLOVE) && (predicate == null || predicate.test(entity.getEquippedStack(EquipmentSlot.CHEST)))) { + return Stream.of(new EquippedStack(entity, EquipmentSlot.CHEST)); } - if (slot == OFFHAND) { - return Stream.of(entity.getOffHandStack()); + if (slot == SECONDARY_GLOVE && (predicate == null || predicate.test(entity.getEquippedStack(EquipmentSlot.OFFHAND)))) { + return Stream.of(new EquippedStack(entity, EquipmentSlot.OFFHAND)); } return Stream.empty(); @@ -102,16 +113,24 @@ public interface TrinketsDelegate { interface Inventory extends EntityConvertable { - default Stream getEquippedStacks(Identifier slot) { + default Stream getEquippedStacks(Identifier slot) { return TrinketsDelegate.getInstance(asEntity()).getEquipped(asEntity(), slot); } - default ItemStack getEquippedStack(Identifier slot) { - return getEquippedStacks(slot).findFirst().orElse(ItemStack.EMPTY); + default EquippedStack getEquippedStack(Identifier slot) { + return getEquippedStacks(slot).findFirst().orElse(EquippedStack.EMPTY); } default void equipStack(Identifier slot, ItemStack stack) { TrinketsDelegate.getInstance(asEntity()).setEquippedStack(asEntity(), slot, stack); } } + + record EquippedStack(ItemStack stack, Runnable sendUpdate, Consumer breakStatusSender) { + public static EquippedStack EMPTY = new EquippedStack(ItemStack.EMPTY, () -> {}, l -> {}); + + EquippedStack(LivingEntity entity, EquipmentSlot slot) { + this(entity.getEquippedStack(slot), () -> {}, l -> l.sendEquipmentBreakStatus(slot)); + } + } } 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 f8f0ec24..edbe0274 100644 --- a/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegateImpl.java +++ b/src/main/java/com/minelittlepony/unicopia/compat/trinkets/TrinketsDelegateImpl.java @@ -1,11 +1,16 @@ package com.minelittlepony.unicopia.compat.trinkets; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jetbrains.annotations.Nullable; + import com.minelittlepony.unicopia.container.SpellbookScreenHandler; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; +import com.minelittlepony.unicopia.network.Channel; +import com.minelittlepony.unicopia.network.MsgTrinketBroken; import com.minelittlepony.unicopia.util.InventoryUtil; import dev.emi.trinkets.TrinketSlot; import dev.emi.trinkets.api.*; @@ -80,8 +85,16 @@ public class TrinketsDelegateImpl implements TrinketsDelegate { } @Override - public Stream getEquipped(LivingEntity entity, Identifier slot) { - return getInventory(entity, slot).stream().flatMap(InventoryUtil::stream).filter(s -> !s.isEmpty()); + public Stream getEquipped(LivingEntity entity, Identifier slot, @Nullable Predicate predicate) { + return getInventory(entity, slot).stream().flatMap(inventory -> { + return InventoryUtil.stream(inventory).filter(s -> !s.isEmpty() && (predicate == null || predicate.test(s))).map(stack -> { + ItemStack oldStack = stack.copy(); + return new EquippedStack(stack, inventory::markUpdate, l -> { + inventory.markUpdate(); + Channel.SERVER_TRINKET_BROKEN.sendToSurroundingPlayers(new MsgTrinketBroken(oldStack, l.getId()), l); + }); + }); + }); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreenHandler.java b/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreenHandler.java index 649f5d9f..4e53efe2 100644 --- a/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreenHandler.java +++ b/src/main/java/com/minelittlepony/unicopia/container/SpellbookScreenHandler.java @@ -1,8 +1,6 @@ package com.minelittlepony.unicopia.container; import java.util.*; -import java.util.function.Predicate; - import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe; @@ -56,8 +54,6 @@ public class SpellbookScreenHandler extends ScreenHandler { private final ScreenHandlerContext context; - private Predicate canShowSlots; - private final SpellbookState state; @Nullable @@ -152,8 +148,8 @@ public class SpellbookScreenHandler extends ScreenHandler { TrinketsDelegate.getInstance(inv.player).createSlot(this, inv.player, TrinketsDelegate.FACE, 0, rightHandX, inventoryY + slotSpacing * 6).ifPresent(this::addSlot); TrinketsDelegate.getInstance(inv.player).createSlot(this, inv.player, TrinketsDelegate.NECKLACE, 0, leftHandX, equipmentY + slotSpacing).ifPresent(this::addSlot); - TrinketsDelegate.getInstance(inv.player).createSlot(this, inv.player, TrinketsDelegate.MAINHAND, 0, leftHandX, equipmentY).ifPresent(this::addSlot); - TrinketsDelegate.getInstance(inv.player).createSlot(this, inv.player, TrinketsDelegate.OFFHAND, 0, rightHandX, equipmentY).ifPresent(this::addSlot); + TrinketsDelegate.getInstance(inv.player).createSlot(this, inv.player, TrinketsDelegate.MAIN_GLOVE, 0, leftHandX, equipmentY).ifPresent(this::addSlot); + TrinketsDelegate.getInstance(inv.player).createSlot(this, inv.player, TrinketsDelegate.SECONDARY_GLOVE, 0, rightHandX, equipmentY).ifPresent(this::addSlot); addSlot(outputSlot = new OutputSlot(this, inventory.player, input, result, 0, gemPos.get(0))); @@ -167,12 +163,13 @@ public class SpellbookScreenHandler extends ScreenHandler { return state; } - public void addSlotShowingCondition(Predicate canShowSlots) { - this.canShowSlots = canShowSlots; - } - public boolean canShowSlots(SlotType type) { - return canShowSlots == null || canShowSlots.test(type); + Identifier pageId = state.getCurrentPageId().orElse(null); + boolean isCraftingPage = SpellbookState.CRAFTING_ID.equals(pageId); + return switch (type) { + case INVENTORY -> isCraftingPage ? state.getState(pageId).getOffset() == 0 : SpellbookState.PROFILE_ID.equals(pageId); + case CRAFTING -> isCraftingPage; + }; } public int getOutputSlotId() { @@ -214,11 +211,16 @@ public class SpellbookScreenHandler extends ScreenHandler { return ItemStack.EMPTY; } + @Override + public boolean canInsertIntoSlot(ItemStack stack, Slot slot) { + return slot != null && slot.canInsert(stack) && slot.isEnabled(); + } + @Override public ItemStack quickMove(PlayerEntity player, int index) { Slot sourceSlot = slots.get(index); - if (sourceSlot == null || !sourceSlot.hasStack()) { + if (sourceSlot == null || !sourceSlot.hasStack() || (sourceSlot instanceof SpellSlot)) { return ItemStack.EMPTY; } @@ -230,35 +232,69 @@ public class SpellbookScreenHandler extends ScreenHandler { } if (index >= HOTBAR_START && !(sourceSlot instanceof OutputSlot || sourceSlot instanceof InputSlot)) { - if (!gemSlot.hasStack() && gemSlot.canInsert(stack)) { - if (insertItem(transferredStack, GEM_SLOT_INDEX, GEM_SLOT_INDEX + 1, false)) { + // hotbar or inventory -> crafting grid + if (canShowSlots(SlotType.CRAFTING)) { + if (!gemSlot.hasStack() && gemSlot.canInsert(stack)) { + if (insertItem(transferredStack, GEM_SLOT_INDEX, GEM_SLOT_INDEX + 1, false)) { + onContentChanged(input); + return ItemStack.EMPTY; + } + } + + if (insertItem(transferredStack, 0, GEM_SLOT_INDEX, false)) { + sourceSlot.onQuickTransfer(transferredStack, stack); onContentChanged(input); return ItemStack.EMPTY; } } - if (insertItem(transferredStack, 0, GEM_SLOT_INDEX, false)) { - sourceSlot.onQuickTransfer(transferredStack, stack); - onContentChanged(input); - return ItemStack.EMPTY; + if (index < HOTBAR_END) { + if (canShowSlots(SlotType.INVENTORY)) { + // hotbar -> inventory + // insert into inventory - armor + if (insertItem(transferredStack, HOTBAR_END + 27, HOTBAR_END + 27 + 4, false)) { + sourceSlot.onQuickTransfer(transferredStack, stack); + onContentChanged(input); + return ItemStack.EMPTY; + } + + // insert into inventory - inventory + if (insertItem(transferredStack, HOTBAR_END, HOTBAR_END + 27, false)) { + sourceSlot.onQuickTransfer(transferredStack, stack); + onContentChanged(input); + return ItemStack.EMPTY; + } + } + } else { + // inventory -> hotbar + if (insertItem(transferredStack, HOTBAR_START, HOTBAR_END, true)) { + sourceSlot.onQuickTransfer(transferredStack, stack); + onContentChanged(input); + return ItemStack.EMPTY; + } } } else { + // crafting grid -> hotbar if (insertItem(transferredStack, HOTBAR_START, HOTBAR_END, true)) { sourceSlot.onQuickTransfer(transferredStack, stack); onContentChanged(input); return ItemStack.EMPTY; } - if (insertItem(transferredStack, HOTBAR_END + 27, HOTBAR_END + 27 + 4, false)) { - sourceSlot.onQuickTransfer(transferredStack, stack); - onContentChanged(input); - return ItemStack.EMPTY; - } + if (canShowSlots(SlotType.INVENTORY)) { + // crafting grid -> armor + if (insertItem(transferredStack, HOTBAR_END + 27, HOTBAR_END + 27 + 4, false)) { + sourceSlot.onQuickTransfer(transferredStack, stack); + onContentChanged(input); + return ItemStack.EMPTY; + } - if (insertItem(transferredStack, HOTBAR_END, HOTBAR_END + 27, false)) { - sourceSlot.onQuickTransfer(transferredStack, stack); - onContentChanged(input); - return ItemStack.EMPTY; + // crafting grid -> inventory + if (insertItem(transferredStack, HOTBAR_END, HOTBAR_END + 27, false)) { + sourceSlot.onQuickTransfer(transferredStack, stack); + onContentChanged(input); + return ItemStack.EMPTY; + } } } diff --git a/src/main/java/com/minelittlepony/unicopia/container/SpellbookState.java b/src/main/java/com/minelittlepony/unicopia/container/SpellbookState.java index c616a8a1..9fedcffc 100644 --- a/src/main/java/com/minelittlepony/unicopia/container/SpellbookState.java +++ b/src/main/java/com/minelittlepony/unicopia/container/SpellbookState.java @@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.container; import java.util.*; import java.util.function.Consumer; +import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.network.datasync.Synchronizable; import com.minelittlepony.unicopia.util.NbtSerialisable; @@ -13,6 +14,10 @@ import net.minecraft.util.Identifier; import net.minecraft.util.math.MathHelper; public class SpellbookState extends Synchronizable implements NbtSerialisable { + public static final Identifier CRAFTING_ID = Unicopia.id("crafting"); + public static final Identifier PROFILE_ID = Unicopia.id("profile"); + public static final Identifier TRAIT_DEX_ID = Unicopia.id("traits"); + private Optional currentPageId = Optional.empty(); private boolean dirty; diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/FoodGroupsGenerator.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/FoodGroupsGenerator.java index 3959da20..a30de498 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/FoodGroupsGenerator.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/FoodGroupsGenerator.java @@ -39,7 +39,7 @@ public class FoodGroupsGenerator { )))); exporter.accept(Unicopia.id("nuts_and_seeds"), new FoodGroupEffects.Builder() .tag(UConventionalTags.Items.GRAIN).tag(UConventionalTags.Items.NUTS).tag(UConventionalTags.Items.SEEDS) - .food(UFoodComponents.BANANA) + .food(UFoodComponents.SEEDS) ); exporter.accept(Unicopia.id("pinecone"), new FoodGroupEffects.Builder().tag(UConventionalTags.Items.PINECONES).food(UFoodComponents.PINECONE).ailment(new HealingAffliction(1))); @@ -57,36 +57,36 @@ public class FoodGroupsGenerator { FoodComponents.COOKED_BEEF, FoodComponents.BEEF, exporter); exporter.accept(Unicopia.id("foraging/blinding"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_BLINDING).food(4, 0.2F).ailment(CompoundAffliction.of( - new StatusEffectAffliction(StatusEffects.BLINDNESS, Range.of(30), Range.of(0), 50), + new StatusEffectAffliction(StatusEffects.BLINDNESS, Range.of(30), Range.of(0), 10), new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 12) ))); exporter.accept(Unicopia.id("foraging/dangerous"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_DANGEROUS).food(3, 0.3F).ailment(new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(250), Range.of(2), 4))); - exporter.accept(Unicopia.id("foraging/edible_filling"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_FILLING).food(17, 0.6F)); + exporter.accept(Unicopia.id("foraging/edible_filling"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_FILLING).food(7, 0.1F)); exporter.accept(Unicopia.id("foraging/edible"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_SAFE).food(2, 1)); - exporter.accept(Unicopia.id("foraging/leafy_greens"), new FoodGroupEffects.Builder().tag(ItemTags.LEAVES).food(1, 1.4F)); + exporter.accept(Unicopia.id("foraging/leafy_greens"), new FoodGroupEffects.Builder().tag(ItemTags.LEAVES).food(1, 0)); exporter.accept(Unicopia.id("foraging/nauseating"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_NAUSEATING).food(5, 0.5F).ailment(CompoundAffliction.of( new StatusEffectAffliction(StatusEffects.WEAKNESS, Range.of(200), Range.of(1), 30), new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(200), Range.of(2), 0) ))); exporter.accept(Unicopia.id("foraging/prickly"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_PRICKLY).food(0, 1.5F).ailment(CompoundAffliction.of( - new StatusEffectAffliction(StatusEffects.INSTANT_DAMAGE, Range.of(1), Range.of(0), 30) + new StatusEffectAffliction(StatusEffects.INSTANT_DAMAGE, Range.of(1), Range.of(0), 12) ))); exporter.accept(Unicopia.id("foraging/glowing"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_GLOWING).food(1, 1.6F).ailment(CompoundAffliction.of( - new StatusEffectAffliction(StatusEffects.GLOWING, Range.of(30), Range.of(0), 30), - new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 0) + new StatusEffectAffliction(StatusEffects.GLOWING, Range.of(30), Range.of(0), 0), + new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 30) ))); exporter.accept(Unicopia.id("foraging/risky"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_RISKY).food(9, 1.1F).ailment(new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 80))); exporter.accept(Unicopia.id("foraging/severely_nauseating"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_SEVERE_NAUSEATING).food(3, 0.9F).ailment(CompoundAffliction.of( new StatusEffectAffliction(StatusEffects.WEAKNESS, Range.of(200), Range.of(1), 0), - new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 80) + new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 7) ))); exporter.accept(Unicopia.id("foraging/severely_prickly"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_SEVERE_PRICKLY).food(2, 0.9F).ailment(CompoundAffliction.of( new StatusEffectAffliction(StatusEffects.INSTANT_DAMAGE, Range.of(1), Range.of(0), 0), - new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 80) + new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 50) ))); exporter.accept(Unicopia.id("foraging/strengthening"), new FoodGroupEffects.Builder().tag(UTags.Items.FORAGE_STRENGHENING).food(4, 0.2F).ailment(CompoundAffliction.of( new StatusEffectAffliction(StatusEffects.STRENGTH, Range.of(1300), Range.of(0), 0), - new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 70) + new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 30) ))); } @@ -94,14 +94,15 @@ public class FoodGroupsGenerator { TagKey cookedTag, TagKey rawTag, TagKey rottenTag, FoodComponent cooked, FoodComponent raw, FoodComponent rotten, BiConsumer exporter) { - exporter.accept(Unicopia.id(name + "/cooked"), new FoodGroupEffects.Builder().tag(cookedTag).food(cooked).ailment(new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 25))); + exporter.accept(Unicopia.id(name + "/cooked"), new FoodGroupEffects.Builder().tag(cookedTag).food(cooked).ailment( + new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(10, 100), Range.of(1, 2), 120))); exporter.accept(Unicopia.id(name + "/raw"), new FoodGroupEffects.Builder().tag(rawTag).food(raw).ailment(CompoundAffliction.of( - new StatusEffectAffliction(StatusEffects.POISON, Range.of(45), Range.of(2), 80), - new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 65) + new StatusEffectAffliction(StatusEffects.POISON, Range.of(25, 50), Range.of(1, 2), 30), + new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(90, 100), Range.of(1, 2), 25) ))); exporter.accept(Unicopia.id(name + "/rotten"), new FoodGroupEffects.Builder().tag(rottenTag).food(rotten).ailment(CompoundAffliction.of( - new StatusEffectAffliction(StatusEffects.POISON, Range.of(45), Range.of(2), 80), - new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(100), Range.of(2), 95) + new StatusEffectAffliction(StatusEffects.POISON, Range.of(45, 70), Range.of(1, 2), 8), + new StatusEffectAffliction(UEffects.FOOD_POISONING, Range.of(20, 130), Range.of(1, 2), 5) ))); } diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/loot/UBlockLootTableProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/loot/UBlockLootTableProvider.java index 9c625a1d..c39e781c 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/loot/UBlockLootTableProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/loot/UBlockLootTableProvider.java @@ -55,7 +55,8 @@ import net.minecraft.util.StringIdentifiable; public class UBlockLootTableProvider extends FabricBlockLootTableProvider { - private static final ConditionalLootFunction.Builder FORTUNE_BONUS = ApplyBonusLootFunction.binomialWithBonusCount(Enchantments.FORTUNE, 0.5714286F, 3); + private static final ConditionalLootFunction.Builder BASE_PRESERVING_FORTUNE_BONUS = ApplyBonusLootFunction.binomialWithBonusCount(Enchantments.FORTUNE, 0.8714286F, 0); + private static final ConditionalLootFunction.Builder CROPS_FORTUNE_BONUS = ApplyBonusLootFunction.binomialWithBonusCount(Enchantments.FORTUNE, 0.5714286F, 3); public UBlockLootTableProvider(FabricDataOutput output) { super(output); @@ -108,8 +109,8 @@ public class UBlockLootTableProvider extends FabricBlockLootTableProvider { }); addDrop(UBlocks.GOLDEN_APPLE, LootTable.builder().pool(LootPool.builder() .rolls(exactly(1)) - .with(applyStateCondition(UBlocks.GOLDEN_APPLE, EnchantedFruitBlock.ENCHANTED, false, applyExplosionDecay(UBlocks.GOLDEN_APPLE, ItemEntry.builder(Items.GOLDEN_APPLE))).apply(FORTUNE_BONUS)) - .with(applyStateCondition(UBlocks.GOLDEN_APPLE, EnchantedFruitBlock.ENCHANTED, true, applyExplosionDecay(UBlocks.GOLDEN_APPLE, ItemEntry.builder(Items.ENCHANTED_GOLDEN_APPLE))).apply(FORTUNE_BONUS)) + .with(applyStateCondition(UBlocks.GOLDEN_APPLE, EnchantedFruitBlock.ENCHANTED, false, applyExplosionDecay(UBlocks.GOLDEN_APPLE, ItemEntry.builder(Items.GOLDEN_APPLE))).apply(BASE_PRESERVING_FORTUNE_BONUS)) + .with(applyStateCondition(UBlocks.GOLDEN_APPLE, EnchantedFruitBlock.ENCHANTED, true, applyExplosionDecay(UBlocks.GOLDEN_APPLE, ItemEntry.builder(Items.ENCHANTED_GOLDEN_APPLE))).apply(BASE_PRESERVING_FORTUNE_BONUS)) )); List.of(UBlocks.GREEN_APPLE_LEAVES, UBlocks.SOUR_APPLE_LEAVES, UBlocks.SWEET_APPLE_LEAVES, UBlocks.GOLDEN_OAK_LEAVES).forEach(block -> addDrop(block, this::fruitLeavesDrops)); addDrop(UBlocks.MANGO_LEAVES, block -> leavesDrops(block, UTreeGen.MANGO_TREE.sapling().get(), 0.025F, 0.027777778F, 0.03125F, 0.041666668F, 0.1F)); // same chance as jungle @@ -177,13 +178,13 @@ public class UBlockLootTableProvider extends FabricBlockLootTableProvider { addDrop(UBlocks.BANANAS, LootTable.builder() .pool(addSurvivesExplosionCondition(UBlocks.BANANAS, LootPool.builder() .rolls(exactly(1)) - .with(item(UItems.BANANA, between(6, 12F)).apply(FORTUNE_BONUS)) + .with(item(UItems.BANANA, between(6, 12F)).apply(CROPS_FORTUNE_BONUS)) ))); addDrop(UBlocks.PINEAPPLE, LootTable.builder() .pool(addSurvivesExplosionCondition(UBlocks.PINEAPPLE, LootPool.builder() .rolls(exactly(1)) .with(item(UItems.PINEAPPLE, between(6, 12F)) - .apply(FORTUNE_BONUS) + .apply(BASE_PRESERVING_FORTUNE_BONUS) .conditionally(BlockStatePropertyLootCondition.builder(UBlocks.PINEAPPLE).properties(StatePredicate.Builder.create() .exactMatch(Properties.BLOCK_HALF, BlockHalf.TOP) .exactMatch(Properties.AGE_7, Properties.AGE_7_MAX)))) @@ -191,8 +192,8 @@ public class UBlockLootTableProvider extends FabricBlockLootTableProvider { addDrop(UBlocks.ROCKS, applyExplosionDecay(UBlocks.ROCKS, LootTable.builder() .pool(applyStateCondition(UBlocks.ROCKS, Properties.AGE_7, Properties.AGE_7_MAX, LootPool.builder() .rolls(exactly(1)) - .with(ItemEntry.builder(UItems.WEIRD_ROCK).conditionally(RandomChanceLootCondition.builder(0.25F)).apply(FORTUNE_BONUS)) - .with(ItemEntry.builder(UItems.ROCK).apply(FORTUNE_BONUS)))) + .with(ItemEntry.builder(UItems.WEIRD_ROCK).conditionally(RandomChanceLootCondition.builder(0.25F)).apply(CROPS_FORTUNE_BONUS)) + .with(ItemEntry.builder(UItems.ROCK).apply(CROPS_FORTUNE_BONUS)))) .pool(LootPool.builder() .rolls(exactly(1)) .with(ItemEntry.builder(UItems.PEBBLES))) @@ -200,16 +201,15 @@ public class UBlockLootTableProvider extends FabricBlockLootTableProvider { addDrop(UBlocks.GOLD_ROOT, applyExplosionDecay(UBlocks.GOLD_ROOT, LootTable.builder() .pool(LootPool.builder().with(ItemEntry.builder(Items.GOLDEN_CARROT))) .pool(applyStateCondition(UBlocks.GOLD_ROOT, CarrotsBlock.AGE, 7, LootPool.builder()) - .with(ItemEntry.builder(Items.GOLDEN_CARROT).apply(FORTUNE_BONUS))))); + .with(ItemEntry.builder(Items.GOLDEN_CARROT).apply(CROPS_FORTUNE_BONUS))))); + /* addDrop(UBlocks.PLUNDER_VINE, applyExplosionDecay(UBlocks.PLUNDER_VINE, LootTable.builder() - .pool(LootPool.builder().rolls(exactly(4)) + .pool(LootPool.builder().rolls(exactly(1)).conditionally(RandomChanceLootCondition.builder(0.25F)) .with(ItemEntry.builder(Items.STICK)) - .with(ItemEntry.builder(Items.DEAD_BUSH))) - .pool(LootPool.builder().rolls(exactly(1)) - .with(ItemEntry.builder(Items.STICK)) - .with(ItemEntry.builder(Items.DEAD_BUSH)) - .with(ItemEntry.builder(UItems.GRYPHON_FEATHER))) + .with(ItemEntry.builder(Items.DEAD_BUSH)) + .with(ItemEntry.builder(UItems.GRYPHON_FEATHER).conditionally(RandomChanceLootCondition.builder(0.2F)))) )); + */ // hay addDrop(UBlocks.HAY_BLOCK, b -> edibleBlockDrops(b, Items.WHEAT)); @@ -231,7 +231,7 @@ public class UBlockLootTableProvider extends FabricBlockLootTableProvider { .with(ItemEntry.builder(baseCrop.getSeedsItem())))) .pool(applyStateCondition(baseCrop, baseCrop.getAgeProperty(), baseCrop.getMaxAge(), LootPool.builder() .rolls(exactly(1)) - .with(ItemEntry.builder(baseCrop.getSeedsItem()).apply(FORTUNE_BONUS))))); + .with(ItemEntry.builder(baseCrop.getSeedsItem()).apply(CROPS_FORTUNE_BONUS))))); SegmentedCropBlock stage = baseCrop; while ((stage = stage.getNext()) != null) { @@ -295,14 +295,14 @@ public class UBlockLootTableProvider extends FabricBlockLootTableProvider { .rolls(exactly(1)) .with(ItemEntry.builder(shell)) .apply(ShellsBlock.COUNT.getValues(), count -> applyStateCondition(block, ShellsBlock.COUNT, count, SetCountLootFunction.builder(exactly(count)))) - .apply(FORTUNE_BONUS))); + .apply(BASE_PRESERVING_FORTUNE_BONUS))); } public LootTable.Builder fortuneBonusDrops(ItemConvertible drop) { return LootTable.builder().pool(addSurvivesExplosionCondition(drop, LootPool.builder() .rolls(exactly(1)) - .with(ItemEntry.builder(drop).apply(FORTUNE_BONUS)))); + .with(ItemEntry.builder(drop).apply(BASE_PRESERVING_FORTUNE_BONUS)))); } public static ConstantLootNumberProvider exactly(float n) { diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/loot/UChestAdditionsLootTableProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/loot/UChestAdditionsLootTableProvider.java index 0d6beac9..2c6b18bd 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/loot/UChestAdditionsLootTableProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/loot/UChestAdditionsLootTableProvider.java @@ -16,6 +16,7 @@ import net.minecraft.loot.context.LootContextTypes; import net.minecraft.loot.entry.ItemEntry; import net.minecraft.loot.entry.TagEntry; import net.minecraft.loot.function.SetCountLootFunction; +import net.minecraft.loot.provider.number.ConstantLootNumberProvider; import net.minecraft.loot.provider.number.UniformLootNumberProvider; import net.minecraft.util.Identifier; @@ -115,34 +116,28 @@ public class UChestAdditionsLootTableProvider extends SimpleFabricLootTableProvi .with(ItemEntry.builder(UItems.MUSIC_DISC_CRUSADE).weight(1)) )); exporter.accept(LootTables.OCEAN_RUIN_WARM_ARCHAEOLOGY, LootTable.builder().pool(LootPool.builder() - .rolls(UniformLootNumberProvider.create(1, 4)) - .with(TagEntry.expandBuilder(UTags.Items.SHELLS).weight(1)) + .rolls(UniformLootNumberProvider.create(1, 2)) + .with(TagEntry.expandBuilder(UTags.Items.SHELLS).weight(2)) .with(ItemEntry.builder(UItems.PEARL_NECKLACE).weight(1)) - .with(TagEntry.expandBuilder(UConventionalTags.Items.ROTTEN_FISH).weight(1)) + .with(TagEntry.expandBuilder(UConventionalTags.Items.ROTTEN_FISH).weight(2)) )); exporter.accept(LootTables.FISHING_GAMEPLAY, LootTable.builder().pool(LootPool.builder() - .rolls(UniformLootNumberProvider.create(1, 4)) + .rolls(UniformLootNumberProvider.create(1, 2)) .with(TagEntry.expandBuilder(UTags.Items.SHELLS).weight(2)) .with(TagEntry.expandBuilder(UConventionalTags.Items.ROTTEN_FISH).weight(1)) )); exporter.accept(LootTables.FISHING_JUNK_GAMEPLAY, LootTable.builder().pool(LootPool.builder() - .rolls(UniformLootNumberProvider.create(1, 4)) + .rolls(UniformLootNumberProvider.create(1, 2)) .with(ItemEntry.builder(UItems.BROKEN_SUNGLASSES).weight(2)) .with(ItemEntry.builder(UItems.WHEAT_WORMS).weight(2)) .with(TagEntry.expandBuilder(UConventionalTags.Items.ROTTEN_FISH).weight(1)) .with(ItemEntry.builder(UItems.BOTCHED_GEM).weight(4)) )); - exporter.accept(LootTables.FISHING_TREASURE_GAMEPLAY, LootTable.builder().pool(LootPool.builder() - .rolls(UniformLootNumberProvider.create(1, 4)) - .with(ItemEntry.builder(UItems.PEARL_NECKLACE).weight(1)) - .with(ItemEntry.builder(UItems.SHELLY).weight(1)) - )); - exporter.accept(LootTables.HERO_OF_THE_VILLAGE_FISHERMAN_GIFT_GAMEPLAY, LootTable.builder().pool(LootPool.builder() - .rolls(UniformLootNumberProvider.create(1, 4)) + .rolls(ConstantLootNumberProvider.create(1)) .with(ItemEntry.builder(UItems.PEARL_NECKLACE).weight(1)) .with(ItemEntry.builder(UItems.SHELLY).weight(1)) )); diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/CuttingBoardRecipeJsonBuilder.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/CuttingBoardRecipeJsonBuilder.java new file mode 100644 index 00000000..cf373884 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/CuttingBoardRecipeJsonBuilder.java @@ -0,0 +1,142 @@ +package com.minelittlepony.unicopia.datagen.providers.recipe; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import org.spongepowered.include.com.google.common.base.Preconditions; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import net.minecraft.advancement.Advancement; +import net.minecraft.advancement.AdvancementCriterion; +import net.minecraft.advancement.AdvancementEntry; +import net.minecraft.advancement.AdvancementRequirements; +import net.minecraft.advancement.AdvancementRewards; +import net.minecraft.advancement.criterion.RecipeUnlockedCriterion; +import net.minecraft.data.server.recipe.RecipeExporter; +import net.minecraft.data.server.recipe.RecipeJsonProvider; +import net.minecraft.item.Item; +import net.minecraft.item.ItemConvertible; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.registry.Registries; +import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; + +public class CuttingBoardRecipeJsonBuilder { + private final Map> criterions = new LinkedHashMap<>(); + + private final ItemConvertible output; + private final TagKey tool; + + private final List> results = new ArrayList<>(); + private final List ingredients = new ArrayList<>(); + + public static CuttingBoardRecipeJsonBuilder create(ItemConvertible output, TagKey tool) { + return new CuttingBoardRecipeJsonBuilder(output, tool); + } + + protected CuttingBoardRecipeJsonBuilder(ItemConvertible output, TagKey tool) { + this.output = output; + this.tool = tool; + result(output); + } + + public CuttingBoardRecipeJsonBuilder input(ItemConvertible input) { + ingredients.add(Ingredient.ofItems(input)); + return this; + } + + public CuttingBoardRecipeJsonBuilder result(ItemConvertible result) { + results.add(() -> Registries.ITEM.getId(result.asItem())); + return this; + } + + public CuttingBoardRecipeJsonBuilder result(Identifier result) { + results.add(() -> result); + return this; + } + + public CuttingBoardRecipeJsonBuilder criterion(String name, AdvancementCriterion condition) { + criterions.put(name, condition); + return this; + } + + public void offerTo(RecipeExporter exporter, Identifier id) { + id = id.withPrefixedPath("cutting/"); + Preconditions.checkState(!criterions.isEmpty(), "No way of obtaining recipe " + id); + Advancement.Builder advancementBuilder = exporter.getAdvancementBuilder() + .criterion("has_the_recipe", RecipeUnlockedCriterion.create(id)) + .rewards(AdvancementRewards.Builder.recipe(id)) + .criteriaMerger(AdvancementRequirements.CriterionMerger.OR); + exporter.accept(new JsonProvider(id, advancementBuilder.build(id.withPrefixedPath("recipes/")))); + } + + public void offerTo(RecipeExporter exporter) { + offerTo(exporter, Registries.ITEM.getId(output.asItem())); + } + + public void offerTo(RecipeExporter exporter, String recipePath) { + Identifier recipeId = new Identifier(recipePath); + if (recipeId.equals(Registries.ITEM.getId(output.asItem()))) { + throw new IllegalStateException("Recipe " + recipePath + " should remove its 'save' argument as it is equal to default one"); + } + offerTo(exporter, recipeId); + } + + private class JsonProvider implements RecipeJsonProvider { + private final Identifier recipeId; + private final AdvancementEntry advancement; + public JsonProvider(Identifier recipeId, AdvancementEntry advancement) { + this.recipeId = recipeId; + this.advancement = advancement; + } + + @Override + public JsonObject toJson() { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("type", "farmersdelight:cutting"); + serialize(jsonObject); + return jsonObject; + } + + @Override + public void serialize(JsonObject json) { + JsonArray ingredientsJson = new JsonArray(); + for (var ingredient : ingredients) { + ingredientsJson.add(ingredient.toJson(false)); + } + json.add("ingredients", ingredientsJson); + JsonObject toolJson = new JsonObject(); + toolJson.addProperty("type", "farmersdelight:tool"); + toolJson.addProperty("tag", tool.id().toString()); + json.add("tool", toolJson); + JsonArray resultJson = new JsonArray(); + for (var result : results) { + JsonObject o = new JsonObject(); + o.addProperty("item", result.get().toString()); + resultJson.add(o); + } + json.add("result", resultJson); + } + + @Override + public Identifier id() { + return recipeId; + } + + @Override + public RecipeSerializer serializer() { + return RecipeSerializer.SHAPELESS; + } + + @Override + public AdvancementEntry advancement() { + return advancement; + } + + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java index 22a48d35..b3c904cb 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/recipe/URecipeProvider.java @@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.datagen.providers.recipe; import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.Map; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; @@ -23,12 +24,14 @@ import com.mojang.datafixers.util.Either; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider; +import net.fabricmc.fabric.api.resource.conditions.v1.DefaultResourceConditions; import net.fabricmc.fabric.api.tag.convention.v1.ConventionalItemTags; import net.minecraft.advancement.AdvancementCriterion; import net.minecraft.advancement.criterion.Criteria; import net.minecraft.advancement.criterion.InventoryChangedCriterion; import net.minecraft.block.Block; import net.minecraft.block.Blocks; +import net.minecraft.data.family.BlockFamily.Variant; import net.minecraft.data.server.recipe.ComplexRecipeJsonBuilder; import net.minecraft.data.server.recipe.RecipeExporter; import net.minecraft.data.server.recipe.RecipeProvider; @@ -44,6 +47,7 @@ import net.minecraft.recipe.book.RecipeCategory; import net.minecraft.registry.Registries; import net.minecraft.registry.tag.ItemTags; import net.minecraft.registry.tag.TagKey; +import net.minecraft.util.Identifier; public class URecipeProvider extends FabricRecipeProvider { private static final List WOOLS = List.of(Items.BLACK_WOOL, Items.BLUE_WOOL, Items.BROWN_WOOL, Items.CYAN_WOOL, Items.GRAY_WOOL, Items.GREEN_WOOL, Items.LIGHT_BLUE_WOOL, Items.LIGHT_GRAY_WOOL, Items.LIME_WOOL, Items.MAGENTA_WOOL, Items.ORANGE_WOOL, Items.PINK_WOOL, Items.PURPLE_WOOL, Items.RED_WOOL, Items.YELLOW_WOOL, Items.WHITE_WOOL); @@ -78,6 +82,9 @@ public class URecipeProvider extends FabricRecipeProvider { .input(ConventionalItemTags.GLASS_BLOCKS) .input(UItems.SUNGLASSES).criterion("has_broken_sunglasses", conditionsFromItem(UItems.BROKEN_SUNGLASSES)) .offerTo(exporter, convertBetween(UItems.SUNGLASSES, UItems.BROKEN_SUNGLASSES)); + + // farmers delight + offerFarmersDelightCuttingRecipes(withConditions(exporter, DefaultResourceConditions.allModsLoaded("farmersdelight"))); } private void generateVanillaRecipeExtensions(RecipeExporter exporter) { @@ -682,6 +689,42 @@ public class URecipeProvider extends FabricRecipeProvider { PatternTemplate.SEVEN_COLOR.offerTo(exporter, UItems.RAINBOW_BED_SHEETS, UItems.RAINBOW_BED_SHEETS, Items.LIGHT_BLUE_WOOL, Items.RED_WOOL, Items.ORANGE_WOOL, Items.YELLOW_WOOL, Items.BLUE_WOOL, Items.GREEN_WOOL, Items.PURPLE_WOOL); } + private void offerFarmersDelightCuttingRecipes(RecipeExporter exporter) { + + // unwaxing + UBlockFamilies.WAXED_ZAP.getVariants().forEach((variant, waxed) -> { + if (variant == Variant.WALL_SIGN) return; + var unwaxed = UBlockFamilies.ZAP.getVariant(variant); + CuttingBoardRecipeJsonBuilder.create(unwaxed, ItemTags.AXES) + .input(waxed).criterion(hasItem(waxed), conditionsFromItem(waxed)) + .result(Items.HONEYCOMB) + .offerTo(exporter, getItemPath(unwaxed) + "_from_waxed"); + }); + List.of(UBlockFamilies.ZAP, UBlockFamilies.PALM).forEach(family -> { + family.getVariants().forEach((variant, block) -> { + if (variant == Variant.WALL_SIGN) return; + CuttingBoardRecipeJsonBuilder.create(family.getBaseBlock(), ItemTags.AXES) + .input(block).criterion(hasItem(block), conditionsFromItem(block)) + .offerTo(exporter, getItemPath(block)); + }); + }); + CuttingBoardRecipeJsonBuilder.create(UBlocks.PALM_PLANKS, ItemTags.AXES) + .input(UBlocks.PALM_HANGING_SIGN).criterion(hasItem(UBlocks.PALM_HANGING_SIGN), conditionsFromItem(UBlocks.PALM_HANGING_SIGN)) + .offerTo(exporter); + + Map.of( + UBlocks.PALM_LOG, UBlocks.STRIPPED_PALM_LOG, + UBlocks.PALM_WOOD, UBlocks.STRIPPED_PALM_WOOD, + UBlocks.ZAP_LOG, UBlocks.STRIPPED_ZAP_LOG, + UBlocks.ZAP_WOOD, UBlocks.STRIPPED_ZAP_WOOD + ).forEach((unstripped, stripped) -> { + CuttingBoardRecipeJsonBuilder.create(stripped, ItemTags.AXES) + .input(unstripped).criterion(hasItem(unstripped), conditionsFromItem(unstripped)) + .result(new Identifier("farmersdelight:tree_bark")) + .offerTo(exporter, convertBetween(stripped, unstripped)); + }); + } + public static void offerCompactingRecipe(RecipeExporter exporter, RecipeCategory category, ItemConvertible output, ItemConvertible input, int resultCount) { offerCompactingRecipe(exporter, category, output, input, hasItem(input), resultCount); } diff --git a/src/main/java/com/minelittlepony/unicopia/datagen/providers/tag/UBlockTagProvider.java b/src/main/java/com/minelittlepony/unicopia/datagen/providers/tag/UBlockTagProvider.java index a8ce0bbb..66ea54f0 100644 --- a/src/main/java/com/minelittlepony/unicopia/datagen/providers/tag/UBlockTagProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/datagen/providers/tag/UBlockTagProvider.java @@ -49,6 +49,12 @@ public class UBlockTagProvider extends FabricTagProvider.BlockTagProvider { Blocks.COMMAND_BLOCK, Blocks.CHAIN_COMMAND_BLOCK, Blocks.REPEATING_COMMAND_BLOCK, Blocks.LIGHT, Blocks.JIGSAW, Blocks.BARRIER, Blocks.BEDROCK ).forceAddTag(BlockTags.DOORS).forceAddTag(BlockTags.TRAPDOORS); + getOrCreateTagBuilder(UTags.Blocks.ANGERS_GUARDIANS).add( + Blocks.PRISMARINE, Blocks.PRISMARINE_SLAB, Blocks.PRISMARINE_STAIRS, Blocks.PRISMARINE_WALL, + Blocks.PRISMARINE_BRICKS, Blocks.PRISMARINE_BRICK_SLAB, Blocks.PRISMARINE_BRICK_STAIRS, + Blocks.DARK_PRISMARINE, Blocks.DARK_PRISMARINE_SLAB, Blocks.DARK_PRISMARINE_STAIRS, + Blocks.SEA_LANTERN, Blocks.SPONGE + ); getOrCreateTagBuilder(UTags.Blocks.BUTTERFLIES_SPAWNABLE_ON).forceAddTag(BlockTags.ANIMALS_SPAWNABLE_ON).forceAddTag(BlockTags.LEAVES).forceAddTag(BlockTags.FLOWERS).forceAddTag(BlockTags.FLOWER_POTS); getOrCreateTagBuilder(UTags.Blocks.JARS).add(UBlocks.JAR, UBlocks.CLOUD_JAR, UBlocks.STORM_JAR, UBlocks.LIGHTNING_JAR, UBlocks.ZAP_JAR); getOrCreateTagBuilder(BlockTags.CROPS).add(crops); @@ -60,6 +66,7 @@ public class UBlockTagProvider extends FabricTagProvider.BlockTagProvider { getOrCreateTagBuilder(BlockTags.FIRE).add(UBlocks.SPECTRAL_FIRE); getOrCreateTagBuilder(BlockTags.HOE_MINEABLE).add(UBlocks.HAY_BLOCK).addOptional(Unicopia.id("rice_block")).addOptional(Unicopia.id("straw_block")); getOrCreateTagBuilder(BlockTags.SHOVEL_MINEABLE).add(UBlocks.WORM_BLOCK); + getOrCreateTagBuilder(BlockTags.REPLACEABLE_BY_TREES).add(UBlocks.GREEN_APPLE, UBlocks.SOUR_APPLE, UBlocks.GOLDEN_APPLE, UBlocks.SWEET_APPLE, UBlocks.ZAP_APPLE, UBlocks.ZAP_BULB); addZapWoodset(); addPalmWoodset(); diff --git a/src/main/java/com/minelittlepony/unicopia/diet/affliction/ClearLoveSicknessAffliction.java b/src/main/java/com/minelittlepony/unicopia/diet/affliction/ClearLoveSicknessAffliction.java index ab8786a4..4a2105f1 100644 --- a/src/main/java/com/minelittlepony/unicopia/diet/affliction/ClearLoveSicknessAffliction.java +++ b/src/main/java/com/minelittlepony/unicopia/diet/affliction/ClearLoveSicknessAffliction.java @@ -19,6 +19,9 @@ public final class ClearLoveSicknessAffliction implements Affliction { @Override public void afflict(PlayerEntity player, ItemStack stack) { player.heal(stack.isFood() ? stack.getItem().getFoodComponent().getHunger() : 1); + if (player.getWorld().isClient) { + return; + } player.removeStatusEffect(StatusEffects.NAUSEA); player.removeStatusEffect(UEffects.FOOD_POISONING); player.removeStatusEffect(StatusEffects.WEAKNESS); diff --git a/src/main/java/com/minelittlepony/unicopia/diet/affliction/StatusEffectAffliction.java b/src/main/java/com/minelittlepony/unicopia/diet/affliction/StatusEffectAffliction.java index 9bc7adff..c6bdfebd 100644 --- a/src/main/java/com/minelittlepony/unicopia/diet/affliction/StatusEffectAffliction.java +++ b/src/main/java/com/minelittlepony/unicopia/diet/affliction/StatusEffectAffliction.java @@ -42,6 +42,9 @@ public record StatusEffectAffliction(StatusEffect effect, Range seconds, Range a @Override public void afflict(PlayerEntity player, ItemStack stack) { + if (player.getWorld().isClient) { + return; + } if (chance > 0 && player.getWorld().random.nextInt(chance) > 0) { return; } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/AttributeContainer.java b/src/main/java/com/minelittlepony/unicopia/entity/AttributeContainer.java new file mode 100644 index 00000000..8c2ba874 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/AttributeContainer.java @@ -0,0 +1,69 @@ +package com.minelittlepony.unicopia.entity; + +import java.util.Map; +import java.util.UUID; + +import org.jetbrains.annotations.Nullable; + +import it.unimi.dsi.fastutil.floats.Float2ObjectFunction; +import net.minecraft.entity.attribute.EntityAttribute; +import net.minecraft.entity.attribute.EntityAttributeInstance; +import net.minecraft.entity.attribute.EntityAttributeModifier; +import net.minecraft.util.math.MathHelper; + +public interface AttributeContainer { + @Nullable + EntityAttributeInstance getAttributeInstance(EntityAttribute attribute); + + default void updateAttributeModifier(UUID id, EntityAttribute attribute, float desiredValue, Float2ObjectFunction modifierSupplier, boolean permanent) { + @Nullable + EntityAttributeInstance instance = getAttributeInstance(attribute); + if (instance == null) { + return; + } + + @Nullable + EntityAttributeModifier modifier = instance.getModifier(id); + + if (!MathHelper.approximatelyEquals(desiredValue, modifier == null ? 0 : modifier.getValue())) { + instance.tryRemoveModifier(id); + + if (desiredValue != 0) { + if (permanent) { + instance.addPersistentModifier(modifierSupplier.get(desiredValue)); + } else { + instance.addTemporaryModifier(modifierSupplier.get(desiredValue)); + } + } + } + } + + default void applyAttributeModifiers(Map modifiers, boolean permanent, boolean apply) { + modifiers.forEach((attribute, modifier) -> { + applyAttributeModifier(attribute, modifier, permanent, apply); + }); + } + + default void applyAttributeModifier(EntityAttribute attribute, EntityAttributeModifier modifier, boolean permanent, boolean apply) { + @Nullable + EntityAttributeInstance instance = getAttributeInstance(attribute); + if (instance == null) { + return; + } + + @Nullable + boolean present = instance.hasModifier(modifier); + + if (present != apply) { + if (apply) { + if (permanent) { + instance.addPersistentModifier(modifier); + } else { + instance.addTemporaryModifier(modifier); + } + } else { + instance.removeModifier(modifier.getId()); + } + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java index cd0277dd..c319f104 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Creature.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Creature.java @@ -17,8 +17,12 @@ import com.minelittlepony.unicopia.entity.ai.BreakHeartGoal; import com.minelittlepony.unicopia.entity.ai.DynamicTargetGoal; import com.minelittlepony.unicopia.entity.ai.EatMuffinGoal; import com.minelittlepony.unicopia.entity.ai.FleeExplosionGoal; +import com.minelittlepony.unicopia.entity.ai.PrioritizedActiveTargetGoal; +import com.minelittlepony.unicopia.entity.ai.TargettingUtil; import com.minelittlepony.unicopia.entity.ai.WantItTakeItGoal; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import net.minecraft.entity.EntityType; import net.minecraft.entity.LivingEntity; @@ -26,9 +30,6 @@ import net.minecraft.entity.SpawnGroup; import net.minecraft.entity.ai.goal.*; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.attribute.EntityAttributes; -import net.minecraft.entity.data.DataTracker; -import net.minecraft.entity.data.TrackedData; -import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.mob.*; import net.minecraft.entity.passive.*; import net.minecraft.entity.player.PlayerEntity; @@ -37,13 +38,6 @@ import net.minecraft.nbt.NbtElement; import net.minecraft.util.math.MathHelper; public class Creature extends Living implements WeaklyOwned.Mutable { - private static final TrackedData EFFECT = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.NBT_COMPOUND); - private static final TrackedData MASTER = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.NBT_COMPOUND); - public static final TrackedData GRAVITY = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.FLOAT); - private static final TrackedData EATING = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.INTEGER); - private static final TrackedData DISCORDED = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.BOOLEAN); - private static final TrackedData SMITTEN = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.BOOLEAN); - public static void boostrap() {} private final EntityPhysics physics; @@ -67,26 +61,25 @@ public class Creature extends Living implements WeaklyOwned.Mutabl .isEmpty(); }); + protected final DataTracker.Entry eating; + protected final DataTracker.Entry discorded; + protected final DataTracker.Entry smitten; + public Creature(LivingEntity entity) { - super(entity, EFFECT); - physics = new EntityPhysics<>(entity, GRAVITY); + super(entity); + physics = new EntityPhysics<>(entity); addTicker(physics); addTicker(this::updateConsumption); - } - @Override - public void initDataTracker() { - super.initDataTracker(); - entity.getDataTracker().startTracking(MASTER, owner.toNBT()); - entity.getDataTracker().startTracking(EATING, 0); - entity.getDataTracker().startTracking(DISCORDED, false); - entity.getDataTracker().startTracking(SMITTEN, false); + tracker.startTracking(owner); + eating = tracker.startTracking(TrackableDataType.INT, 0); + discorded = tracker.startTracking(TrackableDataType.BOOLEAN, false); + smitten = tracker.startTracking(TrackableDataType.BOOLEAN, false); } @Override public void setMaster(LivingEntity owner) { this.owner.set(owner); - entity.getDataTracker().set(MASTER, this.owner.toNBT()); if (owner != null) { targets.ifPresent(this::initMinionAi); } @@ -97,20 +90,20 @@ public class Creature extends Living implements WeaklyOwned.Mutabl } public boolean isDiscorded() { - return entity.getDataTracker().get(DISCORDED); + return discorded.get(); } public boolean isSmitten() { - return entity.getDataTracker().get(SMITTEN); + return smitten.get(); } public void setSmitten(boolean smitten) { smittenTicks = smitten ? 20 : 0; - entity.getDataTracker().set(SMITTEN, smitten); + this.smitten.set(smitten); } public void setDiscorded(boolean discorded) { - entity.getDataTracker().set(DISCORDED, discorded); + this.discorded.set(discorded); discordedChanged = true; } @@ -122,10 +115,6 @@ public class Creature extends Living implements WeaklyOwned.Mutabl @Override public EntityReference getMasterReference() { - if (entity.getDataTracker().containsKey(MASTER)) { - NbtCompound data = entity.getDataTracker().get(MASTER); - owner.fromNBT(data); - } return owner; } @@ -152,6 +141,9 @@ public class Creature extends Living implements WeaklyOwned.Mutabl } if (entity.getType().getSpawnGroup() == SpawnGroup.MONSTER) { goals.add(3, new BreakHeartGoal((MobEntity)entity, targetter)); + if (entity instanceof AbstractSkeletonEntity) { + targets.add(1, new PrioritizedActiveTargetGoal<>((MobEntity)entity, PlayerEntity.class, TargettingUtil.FLYING_PREFERRED, true)); + } } if (entity instanceof PigEntity pig) { eatMuffinGoal = new EatMuffinGoal(pig, targetter); @@ -234,10 +226,10 @@ public class Creature extends Living implements WeaklyOwned.Mutabl private void updateConsumption() { if (isClient()) { - eatTimer = entity.getDataTracker().get(EATING); + eatTimer = eating.get(); } else if (eatMuffinGoal != null) { eatTimer = eatMuffinGoal.getTimer(); - entity.getDataTracker().set(EATING, eatTimer); + eating.set(eatTimer); } } @@ -281,12 +273,12 @@ public class Creature extends Living implements WeaklyOwned.Mutabl @Override public LevelStore getLevel() { - return Levelled.EMPTY; + return Levelled.ZERO; } @Override public LevelStore getCorruption() { - return Levelled.EMPTY; + return Levelled.ZERO; } @Override @@ -310,7 +302,7 @@ public class Creature extends Living implements WeaklyOwned.Mutabl @Override public void toNBT(NbtCompound compound) { super.toNBT(compound); - getSpellSlot().get(true).ifPresent(effect -> { + getSpellSlot().get().ifPresent(effect -> { compound.put("effect", Spell.writeNbt(effect)); }); compound.put("master", getMasterReference().toNBT()); @@ -326,9 +318,6 @@ public class Creature extends Living implements WeaklyOwned.Mutabl } if (compound.contains("master", NbtElement.COMPOUND_TYPE)) { owner.fromNBT(compound.getCompound("master")); - if (entity.getDataTracker().containsKey(MASTER)) { - entity.getDataTracker().set(MASTER, owner.toNBT()); - } if (owner.isSet()) { targets.ifPresent(this::initMinionAi); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/EntityPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/EntityPhysics.java index 749ee3cf..c50d740f 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/EntityPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/EntityPhysics.java @@ -1,6 +1,9 @@ package com.minelittlepony.unicopia.entity; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.Trackable; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.util.Copyable; import com.minelittlepony.unicopia.util.Tickable; @@ -8,7 +11,6 @@ import net.minecraft.block.BlockState; import net.minecraft.block.FenceGateBlock; import net.minecraft.entity.Entity; import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.data.TrackedData; import net.minecraft.entity.mob.MobEntity; import net.minecraft.nbt.NbtCompound; import net.minecraft.registry.tag.BlockTags; @@ -18,15 +20,17 @@ import net.minecraft.util.math.Vec3d; public class EntityPhysics implements Physics, Copyable>, Tickable { - private final TrackedData gravity; - protected final T entity; private float lastGravity = 1; - public EntityPhysics(T entity, TrackedData gravity) { + private final DataTracker tracker; + protected final DataTracker.Entry gravity; + + public EntityPhysics(T entity) { this.entity = entity; - this.gravity = gravity; + this.tracker = Trackable.of(entity).getDataTrackers().getPrimaryTracker(); + gravity = tracker.startTracking(TrackableDataType.FLOAT, 1F); } @Override @@ -91,12 +95,12 @@ public class EntityPhysics implements Physics, Copyable The type of the entity this reference points to. */ -public class EntityReference implements NbtSerialisable { +public class EntityReference implements NbtSerialisable, TrackableObject> { private static final Serializer SERIALIZER = Serializer.of(EntityReference::new); @SuppressWarnings("unchecked") @@ -42,6 +43,8 @@ public class EntityReference implements NbtSerialisable { private WeakReference directReference = new WeakReference<>(null); + private boolean dirty = true; + public EntityReference() {} public EntityReference(T entity) { @@ -61,13 +64,14 @@ public class EntityReference implements NbtSerialisable { public boolean set(@Nullable T entity) { this.directReference = new WeakReference<>(entity); this.reference = entity == null ? null : new EntityValues<>(entity); + this.dirty = true; return entity != null; } public Optional> getTarget() { T value = directReference.get(); if (value != null) { - set(value); + this.reference = new EntityValues<>(value); } return Optional.ofNullable(reference); } @@ -113,6 +117,7 @@ public class EntityReference implements NbtSerialisable { @Override public void fromNBT(NbtCompound tag) { this.reference = tag.contains("uuid") ? new EntityValues<>(tag) : null; + this.dirty = true; } @Override @@ -120,6 +125,36 @@ public class EntityReference implements NbtSerialisable { return getTarget().map(EntityValues::uuid).orElse(Util.NIL_UUID).hashCode(); } + @Override + public Status getStatus() { + if (dirty) { + dirty = false; + return Status.UPDATED; + } + return Status.DEFAULT; + } + + @Override + public NbtCompound writeTrackedNbt() { + return toNBT(); + } + + @Override + public void readTrackedNbt(NbtCompound compound) { + fromNBT(compound); + } + + @Override + public void copyTo(EntityReference destination) { + destination.reference = reference; + destination.directReference = directReference; + } + + @Override + public void discard(boolean immediate) { + set(null); + } + public record EntityValues( UUID uuid, Vec3d pos, @@ -134,8 +169,8 @@ public class EntityReference implements NbtSerialisable { entity.getPos(), entity.getId(), entity instanceof PlayerEntity, !entity.isAlive(), - Caster.of(entity).map(Caster::getLevel).map(Levelled::copyOf).orElse(Levelled.EMPTY), - Caster.of(entity).map(Caster::getCorruption).map(Levelled::copyOf).orElse(Levelled.EMPTY) + Caster.of(entity).map(Caster::getLevel).map(Levelled::copyOf).orElse(Levelled.ZERO), + Caster.of(entity).map(Caster::getCorruption).map(Levelled::copyOf).orElse(Levelled.ZERO) ); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Equine.java b/src/main/java/com/minelittlepony/unicopia/entity/Equine.java index 691bbfcf..a9cf3707 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Equine.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Equine.java @@ -19,8 +19,6 @@ public interface Equine extends NbtSerialisable, Tickable, Pro void setSpecies(Race race); - void initDataTracker(); - /** * Called at the beginning of an update cycle. */ diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java b/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java index 27e8ca7f..ce04e0d9 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/ItemImpl.java @@ -5,16 +5,15 @@ import java.util.List; import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.item.enchantment.WantItNeedItEnchantment; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.DataTrackerManager; +import com.minelittlepony.unicopia.network.track.Trackable; import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.util.VecHelper; - import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.*; -import net.minecraft.entity.data.DataTracker; -import net.minecraft.entity.data.TrackedData; -import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.decoration.ItemFrameEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.ProjectileEntity; @@ -29,24 +28,22 @@ import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.random.Random; public class ItemImpl implements Equine { - private static final TrackedData ITEM_RACE = DataTracker.registerData(ItemEntity.class, TrackedDataHandlerRegistry.STRING); - static final TrackedData ITEM_GRAVITY = DataTracker.registerData(ItemEntity.class, TrackedDataHandlerRegistry.FLOAT); - private final ItemEntity entity; private final ItemPhysics physics; - private Race serverRace; + private final DataTrackerManager trackers; + protected final DataTracker tracker; + + private final DataTracker.Entry race; public ItemImpl(ItemEntity owner) { this.entity = owner; + this.trackers = Trackable.of(entity).getDataTrackers(); + this.tracker = trackers.getPrimaryTracker(); this.physics = new ItemPhysics(owner); - } - @Override - public void initDataTracker() { - entity.getDataTracker().startTracking(ITEM_GRAVITY, 1F); - entity.getDataTracker().startTracking(ITEM_RACE, Race.REGISTRY.getId(Race.HUMAN).toString()); + race = tracker.startTracking(Race.TRACKABLE_TYPE, Race.HUMAN); } @Override @@ -58,13 +55,6 @@ public class ItemImpl implements Equine { public boolean beforeUpdate() { if (!entity.getWorld().isClient) { - Race race = getSpecies(); - if (race != serverRace) { - serverRace = race; - setSpecies(Race.HUMAN); - setSpecies(race); - } - if (WantItNeedItEnchantment.getLevel(entity) > 0) { var random = entity.getWorld().random; @@ -150,12 +140,12 @@ public class ItemImpl implements Equine { @Override public Race getSpecies() { - return Race.fromName(entity.getDataTracker().get(ITEM_RACE), Race.HUMAN); + return race.get(); } @Override public void setSpecies(Race race) { - entity.getDataTracker().set(ITEM_RACE, Race.REGISTRY.getId(race).toString()); + this.race.set(race); } @Override @@ -165,7 +155,7 @@ public class ItemImpl implements Equine { @Override public void toNBT(NbtCompound compound) { - compound.putString("owner_race", Race.REGISTRY.getId(getSpecies()).toString()); + compound.putString("owner_race", getSpecies().getId().toString()); physics.toNBT(compound); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ItemPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/ItemPhysics.java index b747dd0a..77af67da 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/ItemPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/ItemPhysics.java @@ -4,7 +4,7 @@ import net.minecraft.entity.ItemEntity; class ItemPhysics extends EntityPhysics { public ItemPhysics(ItemEntity entity) { - super(entity, ItemImpl.ITEM_GRAVITY); + super(entity); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/entity/LandingEventHandler.java b/src/main/java/com/minelittlepony/unicopia/entity/LandingEventHandler.java new file mode 100644 index 00000000..41516581 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/LandingEventHandler.java @@ -0,0 +1,68 @@ +package com.minelittlepony.unicopia.entity; + +import java.util.concurrent.atomic.AtomicReference; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.util.Tickable; + +public class LandingEventHandler implements Tickable { + + private final Living living; + + @Nullable + private final AtomicReference callback = new AtomicReference<>(); + private double prevY; + private float prevFallDistance; + + public LandingEventHandler(Living living) { + this.living = living; + } + + public void setCallback(LandingEventHandler.Callback callback) { + if (living.asEntity().isOnGround()) { + callback.dispatch(0F); + } else { + updateCallback(callback); + } + } + + public void beforeTick() { + + } + + @Override + public void tick() { + if (living.asEntity().getY() > prevY) { + discard(); + } + prevY = living.asEntity().getY(); + + if (living.asEntity().isOnGround() && living.landedChanged()) { + fire(prevFallDistance); + } + prevFallDistance = living.asEntity().fallDistance; + } + + float fire(float fallDistance) { + var event = callback.getAndSet(null); + return event == null ? fallDistance : event.dispatch(fallDistance); + } + + void discard() { + updateCallback(null); + } + + void updateCallback(@Nullable Callback callback) { + var event = this.callback.getAndSet(callback); + if (event != null) { + event.onCancelled(); + } + } + + public interface Callback { + float dispatch(float fallDistance); + + void onCancelled(); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Living.java b/src/main/java/com/minelittlepony/unicopia/entity/Living.java index 46aca94f..756a4491 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/Living.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/Living.java @@ -12,7 +12,8 @@ import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.Abilities; import com.minelittlepony.unicopia.ability.Ability; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; +import com.minelittlepony.unicopia.ability.magic.SpellInventory; +import com.minelittlepony.unicopia.ability.magic.SpellSlots; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.Situation; @@ -23,7 +24,6 @@ import com.minelittlepony.unicopia.entity.behaviour.Guest; import com.minelittlepony.unicopia.entity.damage.MagicalDamageSource; import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck; import com.minelittlepony.unicopia.entity.effect.CorruptInfluenceStatusEffect; -import com.minelittlepony.unicopia.entity.effect.EffectUtils; import com.minelittlepony.unicopia.entity.effect.UEffects; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.input.Heuristic; @@ -31,14 +31,15 @@ import com.minelittlepony.unicopia.input.Interactable; import com.minelittlepony.unicopia.item.GlassesItem; import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; -import com.minelittlepony.unicopia.network.datasync.EffectSync; -import com.minelittlepony.unicopia.network.datasync.Transmittable; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.DataTrackerManager; +import com.minelittlepony.unicopia.network.track.Trackable; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.projectile.ProjectileImpactListener; import com.minelittlepony.unicopia.server.world.DragonBreathStore; import com.minelittlepony.unicopia.util.*; -import it.unimi.dsi.fastutil.floats.Float2ObjectFunction; import net.fabricmc.fabric.api.util.TriState; import net.minecraft.block.BlockState; import net.minecraft.enchantment.Enchantment; @@ -46,10 +47,8 @@ import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.*; import net.minecraft.entity.attribute.EntityAttribute; import net.minecraft.entity.attribute.EntityAttributeInstance; -import net.minecraft.entity.attribute.EntityAttributeModifier; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageTypes; -import net.minecraft.entity.data.*; import net.minecraft.entity.mob.HostileEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.ProjectileEntity; @@ -65,26 +64,22 @@ import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.BlockSoundGroup; import net.minecraft.sound.SoundCategory; import net.minecraft.util.Hand; +import net.minecraft.util.Util; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; -public abstract class Living implements Equine, Caster, Transmittable { - private static final TrackedData> CARRIER_ID = DataTracker.registerData(LivingEntity.class, TrackedDataHandlerRegistry.OPTIONAL_UUID); - +public abstract class Living implements Equine, Caster, AttributeContainer { protected final T entity; - private final EffectSync effectDelegate; + private final SpellInventory spells; private final Interactable sneakingHeuristic; private final Interactable landedHeuristic; private final Interactable jumpingHeuristic; - @Nullable - private Runnable landEvent; - private boolean invisible = false; @Nullable @@ -98,24 +93,26 @@ public abstract class Living implements Equine, Caste private final List tickers = new ArrayList<>(); + private final LandingEventHandler landEvent = addTicker(new LandingEventHandler(this)); private final Enchantments enchants = addTicker(new Enchantments(this)); private final ItemTracker armour = addTicker(new ItemTracker(this)); - //private final Transportation transportation = new Transportation<>(this); private final Transportation transportation = new Transportation<>(this); - protected Living(T entity, TrackedData effect) { + protected final DataTrackerManager trackers; + protected final DataTracker tracker; + + protected final DataTracker.Entry carrierId; + + protected Living(T entity) { this.entity = entity; - this.effectDelegate = new EffectSync(this, effect); + this.trackers = Trackable.of(entity).getDataTrackers(); + this.tracker = trackers.getPrimaryTracker(); + this.spells = SpellSlots.ofUnbounded(this); this.sneakingHeuristic = addTicker(new Interactable(entity::isSneaking)); this.landedHeuristic = addTicker(new Interactable(entity::isOnGround)); this.jumpingHeuristic = addTicker(new Interactable(((LivingEntityDuck)entity)::isJumping)); - } - @Override - public void initDataTracker() { - effectDelegate.initDataTracker(); - entity.getDataTracker().startTracking(Creature.GRAVITY, 1F); - entity.getDataTracker().startTracking(CARRIER_ID, Optional.empty()); + carrierId = tracker.startTracking(TrackableDataType.UUID, Util.NIL_UUID); } public Q addTicker(Q tickable) { @@ -131,12 +128,8 @@ public abstract class Living implements Equine, Caste this.invisible = invisible; } - public void waitForFall(Runnable action) { - if (entity.isOnGround()) { - action.run(); - } else { - landEvent = action; - } + public void waitForFall(LandingEventHandler.Callback callback) { + landEvent.setCallback(callback); } public boolean sneakingChanged() { @@ -152,8 +145,8 @@ public abstract class Living implements Equine, Caste } @Override - public SpellContainer getSpellSlot() { - return effectDelegate; + public SpellSlots getSpellSlot() { + return spells.getSlots(); } public Enchantments getEnchants() { @@ -174,15 +167,16 @@ public abstract class Living implements Equine, Caste } public Optional getCarrierId() { - return entity.getDataTracker().get(CARRIER_ID); + UUID carrierId = this.carrierId.get(); + return carrierId == Util.NIL_UUID ? Optional.empty() : Optional.of(carrierId); } public void setCarrier(UUID carrier) { - entity.getDataTracker().set(CARRIER_ID, Optional.ofNullable(carrier)); + carrierId.set(carrier == null ? Util.NIL_UUID : carrier); } public void setCarrier(Entity carrier) { - entity.getDataTracker().set(CARRIER_ID, Optional.ofNullable(carrier).map(Entity::getUuid)); + setCarrier(carrier == null ? Util.NIL_UUID : carrier.getUuid()); } @Nullable @@ -206,7 +200,8 @@ public abstract class Living implements Equine, Caste @Override public boolean beforeUpdate() { - if (EffectUtils.getAmplifier(entity, UEffects.PARALYSIS) > 1 && entity.getVelocity().horizontalLengthSquared() > 0) { + landEvent.beforeTick(); + if (entity.hasStatusEffect(UEffects.PARALYSIS) && entity.getVelocity().horizontalLengthSquared() > 0) { entity.setVelocity(entity.getVelocity().multiply(0, 1, 0)); updateVelocity(); } @@ -217,7 +212,7 @@ public abstract class Living implements Equine, Caste @Override public void tick() { tickers.forEach(Tickable::tick); - effectDelegate.tick(Situation.BODY); + spells.tick(Situation.BODY); if (!(entity instanceof PlayerEntity)) { if (!entity.hasVehicle() && getCarrierId().isPresent() && !asWorld().isClient && entity.age % 10 == 0) { @@ -236,11 +231,6 @@ public abstract class Living implements Equine, Caste invinsibilityTicks--; } - if (landEvent != null && entity.isOnGround() && landedChanged()) { - landEvent.run(); - landEvent = null; - } - if (entity.hasStatusEffect(UEffects.PARALYSIS) && entity.getVelocity().horizontalLengthSquared() > 0) { entity.setVelocity(entity.getVelocity().multiply(0, 1, 0)); updateVelocity(); @@ -261,33 +251,15 @@ public abstract class Living implements Equine, Caste transportation.tick(); } - public void updateAttributeModifier(UUID id, EntityAttribute attribute, float desiredValue, Float2ObjectFunction modifierSupplier, boolean permanent) { - @Nullable - EntityAttributeInstance instance = asEntity().getAttributeInstance(attribute); - if (instance == null) { - return; - } - - @Nullable - EntityAttributeModifier modifier = instance.getModifier(id); - - if (!MathHelper.approximatelyEquals(desiredValue, modifier == null ? 0 : modifier.getValue())) { - instance.removeModifier(id); - - if (desiredValue != 0) { - if (permanent) { - instance.addPersistentModifier(modifierSupplier.get(desiredValue)); - } else { - instance.addTemporaryModifier(modifierSupplier.get(desiredValue)); - } - } - } + @Override + public final @Nullable EntityAttributeInstance getAttributeInstance(EntityAttribute attribute) { + return asEntity().getAttributeInstance(attribute); } public boolean canBeSeenBy(Entity entity) { return !isInvisible() && getSpellSlot() - .get(SpellPredicate.IS_DISGUISE, true) + .get(SpellPredicate.IS_DISGUISE) .filter(spell -> spell.getDisguise().getAppearance() == entity) .isEmpty(); } @@ -399,8 +371,8 @@ public abstract class Living implements Equine, Caste } if (magical.isIn(UTags.DamageTypes.BREAKS_SUNGLASSES)) { - ItemStack glasses = GlassesItem.getForEntity(entity); - if (glasses.getItem() == UItems.SUNGLASSES) { + ItemStack glasses = GlassesItem.getForEntity(entity).stack(); + if (glasses.isOf(UItems.SUNGLASSES)) { ItemStack broken = UItems.BROKEN_SUNGLASSES.getDefaultStack(); broken.setNbt(glasses.getNbt()); TrinketsDelegate.getInstance(entity).setEquippedStack(entity, TrinketsDelegate.FACE, broken); @@ -421,7 +393,7 @@ public abstract class Living implements Equine, Caste } public Optional chooseClimbingPos() { - return getSpellSlot().get(SpellPredicate.IS_DISGUISE, false) + return getSpellSlot().get(SpellPredicate.IS_DISGUISE) .map(AbstractDisguiseSpell::getDisguise) .filter(EntityAppearance::canClimbWalls) .map(v -> entity.getBlockPos()); @@ -445,7 +417,7 @@ public abstract class Living implements Equine, Caste return StreamSupport.stream(entity.getArmorItems().spliterator(), false); } return Stream.concat( - TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.NECKLACE), + TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.NECKLACE).map(TrinketsDelegate.EquippedStack::stack), StreamSupport.stream(entity.getArmorItems().spliterator(), false) ); } @@ -456,17 +428,24 @@ public abstract class Living implements Equine, Caste @Override public boolean onProjectileImpact(ProjectileEntity projectile) { - return getSpellSlot().get(true) + return getSpellSlot().get() .filter(effect -> !effect.isDead() && effect instanceof ProjectileImpactListener && ((ProjectileImpactListener)effect).onProjectileImpact(projectile)) .isPresent(); } - protected void handleFall(float distance, float damageMultiplier, DamageSource cause) { - getSpellSlot().get(SpellPredicate.IS_DISGUISE, false).ifPresent(spell -> { - spell.getDisguise().onImpact(this, distance, damageMultiplier, cause); + public float onImpact(float distance, float damageMultiplier, DamageSource cause) { + float fallDistance = landEvent.fire(getEffectiveFallDistance(distance)); + + getSpellSlot().get(SpellPredicate.IS_DISGUISE).ifPresent(spell -> { + spell.getDisguise().onImpact(this, fallDistance, damageMultiplier, cause); }); + return fallDistance; + } + + protected float getEffectiveFallDistance(float distance) { + return distance; } @Override @@ -477,33 +456,28 @@ public abstract class Living implements Equine, Caste return MathHelper.clamp(level / (float)maxLevel, 0, 1); } - @Override - public void setDirty() {} - @Override public void toNBT(NbtCompound compound) { enchants.toNBT(compound); - effectDelegate.toNBT(compound); + spells.getSlots().toNBT(compound); + getCarrierId().ifPresent(id -> compound.putUuid("carrier", id)); toSyncronisedNbt(compound); } @Override public void fromNBT(NbtCompound compound) { enchants.fromNBT(compound); - effectDelegate.fromNBT(compound); + spells.getSlots().fromNBT(compound); + setCarrier(compound.containsUuid("carrier") ? compound.getUuid("carrier") : null); fromSynchronizedNbt(compound); } - @Override public void toSyncronisedNbt(NbtCompound compound) { compound.put("armour", armour.toNBT()); - getCarrierId().ifPresent(id -> compound.putUuid("carrier", id)); } - @Override public void fromSynchronizedNbt(NbtCompound compound) { armour.fromNBT(compound.getCompound("armour")); - setCarrier(compound.containsUuid("carrier") ? compound.getUuid("carrier") : null); } public void updateVelocity() { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ai/PrioritizedActiveTargetGoal.java b/src/main/java/com/minelittlepony/unicopia/entity/ai/PrioritizedActiveTargetGoal.java new file mode 100644 index 00000000..67ea7796 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/ai/PrioritizedActiveTargetGoal.java @@ -0,0 +1,35 @@ +package com.minelittlepony.unicopia.entity.ai; + +import java.util.Comparator; +import java.util.function.Predicate; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.goal.ActiveTargetGoal; +import net.minecraft.entity.mob.MobEntity; + +public class PrioritizedActiveTargetGoal extends ActiveTargetGoal { + + private final Comparator prioritySorting; + + public PrioritizedActiveTargetGoal(MobEntity mob, Class targetClass, Comparator prioritySorting, boolean checkVisibility) { + super(mob, targetClass, checkVisibility); + this.prioritySorting = prioritySorting; + } + + public PrioritizedActiveTargetGoal(MobEntity mob, Class targetClass, Comparator prioritySorting, boolean checkVisibility, Predicate targetPredicate) { + super(mob, targetClass, 10, checkVisibility, false, targetPredicate); + this.prioritySorting = prioritySorting; + } + + public PrioritizedActiveTargetGoal(MobEntity mob, Class targetClass, Comparator prioritySorting, boolean checkVisibility, boolean checkCanNavigate) { + super(mob, targetClass, 10, checkVisibility, checkCanNavigate, null); + this.prioritySorting = prioritySorting; + } + + @Override + protected void findClosestTarget() { + targetEntity = TargettingUtil.getTargets(targetClass, targetPredicate, mob, getSearchBox(getFollowRange())) + .sorted(prioritySorting.thenComparing(TargettingUtil.nearestTo(mob))) + .findFirst() + .orElse(null); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/ai/TargettingUtil.java b/src/main/java/com/minelittlepony/unicopia/entity/ai/TargettingUtil.java new file mode 100644 index 00000000..b165579f --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/ai/TargettingUtil.java @@ -0,0 +1,39 @@ +package com.minelittlepony.unicopia.entity.ai; + +import java.util.Comparator; +import java.util.stream.Stream; + +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.ai.TargetPredicate; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Vec3d; + +public interface TargettingUtil { + Comparator FLYING_PREFERRED = Comparator.comparing(e -> Pony.of(e).getPhysics().isFlying() ? 0 : 1); + + @SuppressWarnings("unchecked") + static Stream getTargets(Class type, TargetPredicate predicate, LivingEntity subject, Box searchArea) { + if (type == PlayerEntity.class || type == ServerPlayerEntity.class) { + return (Stream)subject.getWorld().getPlayers(predicate, subject, searchArea).stream(); + } + return subject.getWorld().getTargets(type, predicate, subject, searchArea).stream(); + } + + static Comparator nearestTo(LivingEntity subject) { + Vec3d fromPos = subject.getEyePos(); + return Comparator.comparing(e -> fromPos.distanceTo(e.getPos())); + } + + static Vec3d getProjectedPos(LivingEntity entity) { + if (entity instanceof PlayerEntity player) { + Vec3d velocity = Pony.of(player).getPhysics().getClientVelocity(); + return entity.getEyePos().add(velocity.multiply(1.5)).add(0, -1, 0); + } + return entity.getEyePos().add(entity.getVelocity()); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java index 7041b6e7..b34606f7 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/Disguise.java @@ -64,11 +64,10 @@ public interface Disguise extends FlightType.Provider, PlayerDimensions.Provider @SuppressWarnings("unchecked") default boolean update(Caster caster, boolean tick) { - if (!(caster instanceof Living)) { + if (!(caster instanceof Living source)) { return false; } - Living source = (Living)caster; LivingEntity owner = source.asEntity(); if (owner == null) { @@ -89,8 +88,8 @@ public interface Disguise extends FlightType.Provider, PlayerDimensions.Provider entity.noClip = true; - if (entity instanceof MobEntity) { - ((MobEntity)entity).setAiDisabled(true); + if (entity instanceof MobEntity mob) { + mob.setAiDisabled(true); } entity.setInvisible(false); @@ -117,9 +116,7 @@ public interface Disguise extends FlightType.Provider, PlayerDimensions.Provider behaviour.update(source, entity, this); - if (source instanceof Pony) { - Pony player = (Pony)source; - + if (source instanceof Pony player) { source.asEntity().setInvisible(true); player.setInvisible(true); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java index 11cddaa1..6c272cca 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityAppearance.java @@ -48,21 +48,22 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.ShulkerBulletEntity; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; +import net.minecraft.util.math.Vec3d; import net.minecraft.util.shape.VoxelShape; public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provider, FlightType.Provider, EntityCollisions.ComplexCollidable { private static final Optional BLOCK_HEIGHT = Optional.of(0.5F); @NotNull - private String entityId = ""; + private transient String entityId = ""; @Nullable - private Entity entity; + private transient Entity entity; @Nullable - private BlockEntity blockEntity; + private transient BlockEntity blockEntity; - private List attachments = new ArrayList<>(); + private transient List attachments = new ArrayList<>(); private Optional dimensions = Optional.empty(); @@ -71,7 +72,7 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi * This is not serialized, so should only be used for server-side data. */ @Nullable - private NbtCompound tag; + private transient NbtCompound tag; @Nullable private NbtCompound entityNbt; @@ -86,16 +87,21 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi return blockEntity; } - public List getAttachments() { + public List getAttachments() { return attachments; } - public void addBlockEntity(BlockEntity blockEntity) { + public record Attachment(Vec3d offset, Entity entity) {} + + public void setBlockEntity(@Nullable BlockEntity blockEntity) { + if (this.blockEntity != null) { + this.blockEntity.markRemoved(); + } this.blockEntity = blockEntity; } - public void attachExtraEntity(Entity entity) { - attachments.add(entity); + public void attachExtraEntity(Vec3d offset, Entity entity) { + attachments.add(new Attachment(offset, entity)); } public void setAppearance(@Nullable Entity entity) { @@ -383,7 +389,7 @@ public class EntityAppearance implements NbtSerialisable, PlayerDimensions.Provi @Override public void getCollissionShapes(ShapeContext context, Consumer output) { EntityCollisions.getCollissionShapes(getAppearance(), context, output); - getAttachments().forEach(e -> EntityCollisions.getCollissionShapes(e, context, output)); + getAttachments().forEach(e -> EntityCollisions.getCollissionShapes(e.entity(), context, output)); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java index a04113e6..09511f47 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/EntityBehaviour.java @@ -34,7 +34,6 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.projectile.LlamaSpitEntity; import net.minecraft.entity.projectile.thrown.SnowballEntity; import net.minecraft.item.ItemStack; -import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Vec3d; import net.minecraft.registry.Registry; @@ -138,12 +137,6 @@ public class EntityBehaviour { double y = positionOffset.y + Math.floor(from.getY()); double z = positionOffset.z + Math.floor(from.getZ()) + 0.5; - BlockPos pos = BlockPos.ofFloored(x, y, z); - - if (!from.getWorld().isAir(pos) && !from.getWorld().isWater(pos)) { - y++; - } - to.prevX = x; to.prevY = y; to.prevZ = z; diff --git a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java index d932e0cc..1ed88f8f 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/behaviour/FallingBlockBehaviour.java @@ -1,6 +1,5 @@ package com.minelittlepony.unicopia.entity.behaviour; -import java.util.List; import java.util.Optional; import com.minelittlepony.unicopia.ability.magic.Caster; @@ -11,28 +10,26 @@ import com.minelittlepony.unicopia.mixin.MixinFallingBlock; import com.minelittlepony.unicopia.mixin.MixinFallingBlockEntity; import com.minelittlepony.unicopia.util.Tickable; +import net.minecraft.block.BedBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockEntityProvider; import net.minecraft.block.BlockState; -import net.minecraft.block.DoorBlock; import net.minecraft.block.FallingBlock; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.ChestBlockEntity; import net.minecraft.block.entity.EnderChestBlockEntity; +import net.minecraft.block.enums.BedPart; +import net.minecraft.block.enums.ChestType; import net.minecraft.block.enums.DoubleBlockHalf; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityDimensions; import net.minecraft.entity.FallingBlockEntity; import net.minecraft.entity.damage.DamageSource; import net.minecraft.state.property.Properties; -import net.minecraft.registry.tag.BlockTags; -import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.Vec3i; public class FallingBlockBehaviour extends EntityBehaviour { - - private static final Vec3d UP = Vec3d.of(Direction.UP.getVector()); - private static final Optional FULL_BLOCK = Optional.of(EntityDimensions.changing(0.6F, 0.9F)); @Override @@ -71,22 +68,27 @@ public class FallingBlockBehaviour extends EntityBehaviour { public FallingBlockEntity onCreate(FallingBlockEntity entity, EntityAppearance context, boolean replaceOld) { super.onCreate(entity, context, replaceOld); - BlockState state = entity.getBlockState(); + BlockState state = entity.getBlockState() + .withIfExists(Properties.CHEST_TYPE, ChestType.SINGLE) + .withIfExists(Properties.BED_PART, BedPart.HEAD) + .withIfExists(Properties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.LOWER); Block block = state.getBlock(); + context.setBlockEntity(block instanceof BlockEntityProvider bep ? bep.createBlockEntity(entity.getBlockPos(), state) : null); - if (state.isIn(BlockTags.DOORS) && block instanceof DoorBlock) { - BlockState lowerState = state.with(DoorBlock.HALF, DoubleBlockHalf.LOWER); - BlockState upperState = state.with(DoorBlock.HALF, DoubleBlockHalf.UPPER); - - context.attachExtraEntity(configure(MixinFallingBlockEntity.createInstance(entity.getWorld(), entity.getX(), entity.getY(), entity.getZ(), upperState), block)); - - return configure(MixinFallingBlockEntity.createInstance(entity.getWorld(), entity.getX(), entity.getY() + 1, entity.getZ(), lowerState), block); + if (state.contains(Properties.BED_PART)) { + Vec3i offset = BedBlock.getOppositePartDirection(state).getVector(); + BlockState foot = state.with(Properties.BED_PART, BedPart.FOOT); + context.attachExtraEntity(Vec3d.of(offset), configure(MixinFallingBlockEntity.createInstance(entity.getWorld(), entity.getX() + offset.getX(), entity.getY() + offset.getY(), entity.getZ() + offset.getZ(), foot), block)); } - if (block instanceof BlockEntityProvider bep) { - context.addBlockEntity(bep.createBlockEntity(entity.getBlockPos(), state)); + if (state.contains(Properties.DOUBLE_BLOCK_HALF)) { + BlockState upperState = state.with(Properties.DOUBLE_BLOCK_HALF, DoubleBlockHalf.UPPER); + context.attachExtraEntity(new Vec3d(0, 1, 0), configure(MixinFallingBlockEntity.createInstance(entity.getWorld(), entity.getX(), entity.getY() + 1, entity.getZ(), upperState), block)); } + if (state != entity.getBlockState()) { + entity = MixinFallingBlockEntity.createInstance(entity.getWorld(), entity.getX(), entity.getY(), entity.getZ(), state); + } return configure(entity, block); } @@ -100,15 +102,10 @@ public class FallingBlockBehaviour extends EntityBehaviour { if (state.get(Properties.WATERLOGGED) != logged) { entity = MixinFallingBlockEntity.createInstance(entity.getWorld(), entity.getX(), entity.getY(), entity.getZ(), state.with(Properties.WATERLOGGED, logged)); spell.getDisguise().setAppearance(entity); - return; } } EntityAppearance disguise = spell.getDisguise(); - List attachments = disguise.getAttachments(); - if (attachments.size() > 0) { - copyBaseAttributes(source.asEntity(), attachments.get(0), UP); - } BlockEntity be = disguise.getBlockEntity(); @@ -122,5 +119,9 @@ public class FallingBlockBehaviour extends EntityBehaviour { ceb.tick(); be.setWorld(null); } + + for (var attachment : disguise.getAttachments()) { + copyBaseAttributes(source.asEntity(), attachment.entity(), attachment.offset()); + } } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/collision/EntityCollisions.java b/src/main/java/com/minelittlepony/unicopia/entity/collision/EntityCollisions.java index d2d65f3e..b4454d66 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/collision/EntityCollisions.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/collision/EntityCollisions.java @@ -44,7 +44,7 @@ public class EntityCollisions { ShapeContext ctx = entity == null ? ShapeContext.absent() : ShapeContext.of(entity); return collectCollisionBoxes(box, collector -> { world.getOtherEntities(entity, box.expand(50), e -> { - Caster.of(e).flatMap(c -> c.getSpellSlot().get(SpellPredicate.IS_DISGUISE, false)).ifPresent(p -> { + Caster.of(e).flatMap(c -> c.getSpellSlot().get(SpellPredicate.IS_DISGUISE)).ifPresent(p -> { p.getDisguise().getCollissionShapes(ctx, collector); }); if (e instanceof ComplexCollidable collidable) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java b/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java index 14372955..3a462183 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/effect/EffectUtils.java @@ -2,17 +2,73 @@ package com.minelittlepony.unicopia.entity.effect; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.StringHelper; public interface EffectUtils { static boolean isPoisoned(LivingEntity entity) { - return getAmplifier(entity, UEffects.FOOD_POISONING) > 2; + return getAmplifier(entity, UEffects.FOOD_POISONING) > 1; + } + + static boolean hasABrokenWing(LivingEntity entity) { + return entity.hasStatusEffect(UEffects.BROKEN_WINGS); + } + + static boolean hasBothBrokenWing(LivingEntity entity) { + return getAmplifier(entity, UEffects.BROKEN_WINGS) > 1; } static int getAmplifier(LivingEntity entity, StatusEffect effect) { - return entity.hasStatusEffect(effect) ? entity.getStatusEffect(effect).getAmplifier() : 0; + return entity.hasStatusEffect(effect) ? entity.getStatusEffect(effect).getAmplifier() + 1 : 0; } static boolean isChangingRace(LivingEntity entity) { return entity.getStatusEffects().stream().anyMatch(effect -> effect.getEffectType() instanceof RaceChangeStatusEffect); } + + static boolean hasExtraDefenses(LivingEntity entity) { + return entity.hasStatusEffect(UEffects.FORTIFICATION); + } + + static boolean applyStatusEffect(LivingEntity entity, StatusEffect effect, boolean apply) { + if (entity.getWorld().isClient) { + return false; + } + boolean has = entity.hasStatusEffect(effect); + if (has != apply) { + if (has) { + if (entity.getStatusEffect(effect).getDuration() == StatusEffectInstance.INFINITE) { + entity.removeStatusEffect(effect); + } + } else { + entity.addStatusEffect(new StatusEffectInstance(effect, StatusEffectInstance.INFINITE, 0, false, false, true)); + } + return true; + } + return false; + } + + static Text formatModifierChange(String modifierName, int time, boolean isDetrimental) { + return Text.literal(" ").append(Text.translatable("attribute.modifier." + (time > 0 ? "plus" : "take") + ".0", + StringHelper.formatTicks(Math.abs(time)), + Text.translatable(modifierName) + ).formatted((isDetrimental ? time : -time) < 0 ? Formatting.DARK_GREEN : Formatting.RED)); + } + + static Text formatModifierChange(String modifierName, float change, boolean isDetrimental) { + return Text.literal(" ").append(Text.translatable("attribute.modifier." + (change > 0 ? "plus" : "take") + ".0", + ItemStack.MODIFIER_FORMAT.format(Math.abs(change)), + Text.translatable(modifierName) + ).formatted((isDetrimental ? change : -change) < 0 ? Formatting.DARK_GREEN : Formatting.RED)); + } + + static Text formatModifierChange(Text modifierName, float change, boolean isDetrimental) { + return Text.literal(" ").append(Text.translatable("attribute.modifier." + (change > 0 ? "plus" : "take") + ".0", + ItemStack.MODIFIER_FORMAT.format(Math.abs(change)), + modifierName + ).formatted((isDetrimental ? change : -change) < 0 ? Formatting.DARK_GREEN : Formatting.RED)); + } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/effect/FoodPoisoningStatusEffect.java b/src/main/java/com/minelittlepony/unicopia/entity/effect/FoodPoisoningStatusEffect.java index f216cc67..6d90a953 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/effect/FoodPoisoningStatusEffect.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/effect/FoodPoisoningStatusEffect.java @@ -24,16 +24,18 @@ public class FoodPoisoningStatusEffect extends StatusEffect { @Override public void applyUpdateEffect(LivingEntity entity, int amplifier) { + if (entity.getWorld().isClient) { + return; + } boolean showParticles = entity.getStatusEffect(this).shouldShowParticles(); if (!entity.hasStatusEffect(StatusEffects.NAUSEA) && entity.getRandom().nextInt(12) == 0) { - entity.addStatusEffect(new StatusEffectInstance(StatusEffects.NAUSEA, 100, 1, true, showParticles, false)); } - if (entity instanceof PlayerEntity) { - ((PlayerEntity)entity).getHungerManager().addExhaustion(0.5F); + if (entity instanceof PlayerEntity player) { + player.getHungerManager().addExhaustion(0.5F); } if (EffectUtils.isPoisoned(entity) && entity.getRandom().nextInt(12) == 0 && !entity.hasStatusEffect(StatusEffects.POISON)) { @@ -63,7 +65,9 @@ public class FoodPoisoningStatusEffect extends StatusEffect { user.getWorld().playSound(null, user.getX(), user.getY(), user.getZ(), USounds.Vanilla.ENTITY_PLAYER_BURP, SoundCategory.NEUTRAL, 1, 1 + (user.getWorld().random.nextFloat() - user.getWorld().random.nextFloat()) * 0.4f); - user.addStatusEffect(new StatusEffectInstance(StatusEffects.NAUSEA, 100, 1, true, false, false)); + if (!user.getWorld().isClient) { + user.addStatusEffect(new StatusEffectInstance(StatusEffects.NAUSEA, 100, 1, true, false, false)); + } return TypedActionResult.fail(stack); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/effect/SeaponyGraceStatusEffect.java b/src/main/java/com/minelittlepony/unicopia/entity/effect/SeaponyGraceStatusEffect.java new file mode 100644 index 00000000..46eee096 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/effect/SeaponyGraceStatusEffect.java @@ -0,0 +1,85 @@ +package com.minelittlepony.unicopia.entity.effect; + +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.EquinePredicates; +import com.minelittlepony.unicopia.USounds; +import com.minelittlepony.unicopia.UTags; + +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.entity.mob.GuardianEntity; +import net.minecraft.entity.mob.MobEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundCategory; +import net.minecraft.structure.StructureStart; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraft.world.gen.structure.StructureKeys; + +public class SeaponyGraceStatusEffect { + + public static void update(LivingEntity entity) { + if (entity.getWorld().isClient) { + return; + } + if (EquinePredicates.PLAYER_SEAPONY.test(entity)) { + if (!entity.hasStatusEffect(UEffects.SEAPONYS_GRACE) && !entity.hasStatusEffect(UEffects.SEAPONYS_IRE)) { + entity.addStatusEffect(new StatusEffectInstance(UEffects.SEAPONYS_GRACE, StatusEffectInstance.INFINITE, 0, false, true)); + } + } else if (entity.hasStatusEffect(UEffects.SEAPONYS_GRACE)) { + entity.removeStatusEffect(UEffects.SEAPONYS_GRACE); + } + } + + public static boolean hasIre(LivingEntity entity, MobEntity enemy) { + return enemy.getAttacker() == entity || hasIre(entity); + } + + public static boolean hasGrace(LivingEntity entity) { + boolean isSeapony = EquinePredicates.PLAYER_SEAPONY.test(entity); + return isSeapony && entity.hasStatusEffect(UEffects.SEAPONYS_GRACE) && !entity.hasStatusEffect(UEffects.SEAPONYS_IRE); + } + + public static boolean hasIre(LivingEntity entity) { + return !EquinePredicates.PLAYER_SEAPONY.test(entity) || entity.hasStatusEffect(UEffects.SEAPONYS_IRE); + } + + public static void processBlockChange(World world, PlayerEntity player, BlockPos pos, BlockState stateBroken, @Nullable BlockEntity blockEntity) { + + if (!(world instanceof ServerWorld sw)) { + return; + } + + if (!hasGrace(player)) { + return; + } + + if (!stateBroken.isIn(UTags.Blocks.ANGERS_GUARDIANS)) { + return; + } + StructureStart start = sw.getStructureAccessor().getStructureContaining(pos, StructureKeys.MONUMENT); + if (start.getStructure() == null) { + return; + } + + List guardians = sw.getEntitiesByClass(GuardianEntity.class, player.getBoundingBox().expand(10), EntityPredicates.VALID_LIVING_ENTITY); + + if (guardians.size() > 0) { + guardians.forEach(guardian -> { + guardian.setTarget(player); + guardian.playAmbientSound(); + }); + + player.removeStatusEffect(UEffects.SEAPONYS_GRACE); + player.addStatusEffect(new StatusEffectInstance(UEffects.SEAPONYS_IRE, 90000, 0, false, true)); + world.playSound(null, pos, USounds.Vanilla.ENTITY_ELDER_GUARDIAN_CURSE, SoundCategory.PLAYERS, 1, 1); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/effect/SimpleStatusEffect.java b/src/main/java/com/minelittlepony/unicopia/entity/effect/SimpleStatusEffect.java new file mode 100644 index 00000000..7adc3654 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/effect/SimpleStatusEffect.java @@ -0,0 +1,33 @@ +package com.minelittlepony.unicopia.entity.effect; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.entity.effect.StatusEffectCategory; + +public class SimpleStatusEffect extends StatusEffect { + + private final boolean instant; + + public SimpleStatusEffect(StatusEffectCategory category, int color, boolean instant) { + super(category, color); + this.instant = instant; + } + + @Override + public void applyUpdateEffect(LivingEntity entity, int amplifier) { + + } + + @Override + public void applyInstantEffect(@Nullable Entity source, @Nullable Entity attacker, LivingEntity target, int amplifier, double proximity) { + + } + + @Override + public final boolean isInstant() { + return instant; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/effect/SunBlindnessStatusEffect.java b/src/main/java/com/minelittlepony/unicopia/entity/effect/SunBlindnessStatusEffect.java index 10e607a9..57f91ed4 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/effect/SunBlindnessStatusEffect.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/effect/SunBlindnessStatusEffect.java @@ -11,7 +11,6 @@ import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.util.MeteorlogicalUtil; import net.minecraft.entity.Entity; -import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffect; import net.minecraft.entity.effect.StatusEffectInstance; @@ -72,9 +71,7 @@ public class SunBlindnessStatusEffect extends StatusEffect { return true; } - if (entity.getEquippedStack(EquipmentSlot.HEAD).isIn(UTags.Items.SHADES) - || TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.FACE).anyMatch(i -> i.isIn(UTags.Items.SHADES)) - || entity.isSubmergedInWater()) { + if (entity.isSubmergedInWater() || TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.FACE, i -> i.isIn(UTags.Items.SHADES)).findAny().isPresent()) { return false; } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/effect/UEffects.java b/src/main/java/com/minelittlepony/unicopia/entity/effect/UEffects.java index 00b51dd6..798a7289 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/effect/UEffects.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/effect/UEffects.java @@ -15,13 +15,18 @@ public interface UEffects { * When affecting an entity, will give them a random chance to reproduce or duplicate themselves when they die. */ StatusEffect CORRUPT_INFLUENCE = register("corrupt_influence", new CorruptInfluenceStatusEffect(0x00FF00)); - StatusEffect PARALYSIS = register("paralysis", new StatusEffect(StatusEffectCategory.HARMFUL, 0) {}); + StatusEffect PARALYSIS = register("paralysis", new SimpleStatusEffect(StatusEffectCategory.HARMFUL, 0, false)); + StatusEffect FORTIFICATION = register("fortification", new SimpleStatusEffect(StatusEffectCategory.BENEFICIAL, 0x000077, false)); + StatusEffect BROKEN_WINGS = register("broken_wings", new SimpleStatusEffect(StatusEffectCategory.BENEFICIAL, 0xEEAA00, false)); /** * Side-effect of wearing the alicorn amulet. * Causes the player to lose grip on whatever item they're holding. */ StatusEffect BUTTER_FINGERS = register("butter_fingers", new ButterfingersStatusEffect(0x888800)); + StatusEffect SEAPONYS_GRACE = register("seaponys_grace", new SimpleStatusEffect(StatusEffectCategory.BENEFICIAL, 0x0000EE, false)); + StatusEffect SEAPONYS_IRE = register("seaponys_ire", new SimpleStatusEffect(StatusEffectCategory.HARMFUL, 0xEE00EE, false)); + private static StatusEffect register(String name, StatusEffect effect) { return Registry.register(Registries.STATUS_EFFECT, Unicopia.id(name), effect); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java index 92b1a909..fbca955c 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java @@ -1,9 +1,14 @@ package com.minelittlepony.unicopia.entity.mob; +import java.util.UUID; + import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Levelled; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; +import com.minelittlepony.unicopia.ability.magic.SpellInventory; +import com.minelittlepony.unicopia.ability.magic.SpellPredicate; +import com.minelittlepony.unicopia.ability.magic.SpellSlots; +import com.minelittlepony.unicopia.ability.magic.spell.PlacementControlSpell; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; @@ -11,7 +16,8 @@ import com.minelittlepony.unicopia.entity.EntityPhysics; import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.MagicImmune; import com.minelittlepony.unicopia.entity.Physics; -import com.minelittlepony.unicopia.network.datasync.EffectSync; +import com.minelittlepony.unicopia.network.track.Trackable; +import com.minelittlepony.unicopia.server.world.Ether; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityDimensions; @@ -23,29 +29,84 @@ import net.minecraft.entity.data.TrackedData; import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.nbt.NbtCompound; import net.minecraft.text.Text; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; public class CastSpellEntity extends LightEmittingEntity implements Caster, WeaklyOwned.Mutable, MagicImmune { - private static final TrackedData GRAVITY = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.FLOAT); - private static final TrackedData EFFECT = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.NBT_COMPOUND); + private static final TrackedData LEVEL = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.INTEGER); + private static final TrackedData MAX_LEVEL = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.INTEGER); + private static final TrackedData CORRUPTION = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.INTEGER); + private static final TrackedData MAX_CORRUPTION = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.INTEGER); - private final EntityPhysics physics = new EntityPhysics<>(this, GRAVITY); + private static final TrackedData DEAD = DataTracker.registerData(CastSpellEntity.class, TrackedDataHandlerRegistry.BOOLEAN); - private final EffectSync effectDelegate = new EffectSync(this, EFFECT); + private final EntityPhysics physics = new EntityPhysics<>(this); + + private final SpellInventory spells = SpellSlots.ofSingle(this); private final EntityReference owner = new EntityReference<>(); - private LevelStore level = Levelled.EMPTY; - private LevelStore corruption = Levelled.EMPTY; + private final LevelStore level = Levelled.of( + () -> dataTracker.get(LEVEL), + l -> dataTracker.set(LEVEL, l), + () -> dataTracker.get(MAX_LEVEL) + ); + private final LevelStore corruption = Levelled.of( + () -> dataTracker.get(CORRUPTION), + l -> dataTracker.set(CORRUPTION, l), + () -> dataTracker.get(MAX_CORRUPTION) + ); + + private UUID controllingEntityUuid; + private UUID controllingSpellUuid; + + private int prevAge; + + private int prevDeathTicks; + private int deathTicks; + + public CastSpellEntity(World world, Caster caster, PlacementControlSpell control) { + this(UEntities.CAST_SPELL, world); + this.controllingEntityUuid = caster.asEntity().getUuid(); + this.controllingSpellUuid = control.getUuid(); + setCaster(caster); + Spell spell = Spell.copy(control.getDelegate()); + spells.getSlots().put(spell); + } public CastSpellEntity(EntityType type, World world) { super(type, world); ignoreCameraFrustum = true; + Trackable.of(this).getDataTrackers().getPrimaryTracker().startTracking(owner); } @Override protected void initDataTracker() { - getDataTracker().startTracking(EFFECT, new NbtCompound()); + dataTracker.startTracking(LEVEL, 0); + dataTracker.startTracking(CORRUPTION, 0); + dataTracker.startTracking(MAX_LEVEL, 1); + dataTracker.startTracking(MAX_CORRUPTION, 1); + dataTracker.startTracking(DEAD, false); + } + + @Override + public void updatePositionAndAngles(double x, double y, double z, float yaw, float pitch) { + super.updatePositionAndAngles(x, y, z, yaw, pitch); + spells.getSlots().stream(SpellPredicate.IS_ORIENTED).forEach(spell -> spell.setOrientation(this, pitch, yaw)); + } + + private boolean checkConnection() { + return Ether.get(getWorld()).get(SpellType.PLACE_CONTROL_SPELL, controllingEntityUuid, controllingSpellUuid) != null; + } + + public float getAge(float tickDelta) { + return MathHelper.lerp(tickDelta, prevAge, age); + } + + public float getScale(float tickDelta) { + float add = MathHelper.clamp(getAge(tickDelta) / 25F, 0, 1); + float subtract = MathHelper.clamp(MathHelper.lerp(tickDelta, prevDeathTicks, deathTicks) / 20F, 0, 1); + return MathHelper.clamp(add - subtract, 0, 1); } @Override @@ -63,21 +124,50 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster { + Ether.get(getWorld()).getOrCreate(spell, this); + }); + } else if (isDead()) { + spells.getSlots().clear(); } - if (!effectDelegate.tick(Situation.GROUND_ENTITY)) { - discard(); + prevDeathTicks = deathTicks; + + if (!spells.tick(Situation.GROUND) && deathTicks++ > 40) { + remove(Entity.RemovalReason.KILLED); + } + } + + @Override + public void kill() { + setDead(true); + } + + public boolean isDead() { + return dataTracker.get(DEAD); + } + + public void setDead(boolean dead) { + dataTracker.set(DEAD, dead); + if (dead) { + spells.getSlots().clear(); } } @Override public EntityDimensions getDimensions(EntityPose pose) { - return super.getDimensions(pose).scaled(getSpellSlot().get(SpellType.IS_PLACED, false).map(spell -> spell.getScale(1)).orElse(1F)); + return super.getDimensions(pose).scaled(getScale(1)); } @Override @@ -91,8 +181,10 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster caster) { - this.level = Levelled.copyOf(caster.getLevel()); - this.corruption = Levelled.copyOf(caster.getCorruption()); + dataTracker.set(LEVEL, caster.getLevel().get()); + dataTracker.set(MAX_LEVEL, caster.getLevel().getMax()); + dataTracker.set(CORRUPTION, caster.getCorruption().get()); + dataTracker.set(MAX_CORRUPTION, caster.getCorruption().getMax()); setMaster(caster); } @@ -108,7 +200,7 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster { - tag.put("effect", Spell.writeNbt(effect)); - }); + + if (controllingEntityUuid != null) { + tag.putUuid("owningEntity", controllingEntityUuid); + } + if (controllingSpellUuid != null) { + tag.putUuid("owningSpell", controllingSpellUuid); + } + + spells.getSlots().toNBT(tag); + tag.putInt("age", age); + tag.putInt("prevAge", prevAge); + tag.putBoolean("dead", isDead()); + tag.put("owner", owner.toNBT()); } @Override protected void readCustomDataFromNbt(NbtCompound tag) { + var level = Levelled.fromNbt(tag.getCompound("level")); + dataTracker.set(MAX_LEVEL, level.getMax()); + dataTracker.set(LEVEL, level.get()); + var corruption = Levelled.fromNbt(tag.getCompound("corruption")); + dataTracker.set(MAX_CORRUPTION, corruption.getMax()); + dataTracker.set(CORRUPTION, corruption.get()); + + controllingEntityUuid = tag.containsUuid("owningEntity") ? tag.getUuid("owningEntity") : null; + controllingSpellUuid = tag.containsUuid("owningSpell") ? tag.getUuid("owningSpell") : null; + + spells.getSlots().fromNBT(tag); + age = tag.getInt("age"); + prevAge = tag.getInt("prevAge"); + setDead(tag.getBoolean("dead")); + if (tag.contains("owner")) { owner.fromNBT(tag.getCompound("owner")); } - if (tag.contains("effect")) { - getSpellSlot().put(Spell.readNbt(tag.getCompound("effect"))); - } - level = Levelled.fromNbt(tag.getCompound("level")); - corruption = Levelled.fromNbt(tag.getCompound("corruption")); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/FloatingArtefactEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/FloatingArtefactEntity.java index 26fe7e5d..f653d96d 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/FloatingArtefactEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/FloatingArtefactEntity.java @@ -13,6 +13,7 @@ import net.minecraft.entity.data.TrackedData; import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; +import net.minecraft.text.Text; import net.minecraft.util.ActionResult; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -48,6 +49,11 @@ public class FloatingArtefactEntity extends StationaryObjectEntity { positionSeed = (float)(Math.random() * Math.PI * 2); } + @Override + protected Text getDefaultName() { + return getStack().getName(); + } + @Override protected void initDataTracker() { super.initDataTracker(); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/SombraEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/SombraEntity.java index f3906ba2..3a0a9bac 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/SombraEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/SombraEntity.java @@ -11,11 +11,11 @@ import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; import com.minelittlepony.unicopia.advancement.UCriteria; +import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate; import com.minelittlepony.unicopia.entity.AmuletSelectors; import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.ai.ArenaAttackGoal; import com.minelittlepony.unicopia.entity.player.Pony; -import com.minelittlepony.unicopia.item.AmuletItem; import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.ParticleSource; @@ -63,7 +63,6 @@ import net.minecraft.entity.mob.HostileEntity; import net.minecraft.entity.passive.IronGolemEntity; import net.minecraft.entity.passive.MerchantEntity; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtHelper; @@ -484,9 +483,10 @@ public class SombraEntity extends HostileEntity implements ArenaCombatant, Parti player.sendMessage(Text.translatable("entity.unicopia.sombra.taunt")); } } - ItemStack amulet = AmuletItem.getForEntity(player); - if (amulet.isOf(UItems.ALICORN_AMULET)) { - amulet.decrement(1); + TrinketsDelegate.EquippedStack amulet = UItems.ALICORN_AMULET.getForEntity(player); + if (!amulet.stack().isEmpty()) { + amulet.stack().decrement(1); + amulet.sendUpdate(); } } boolean damaged = super.damage(source, amount); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/SpecterEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/SpecterEntity.java index 91f9b407..7d99db13 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/SpecterEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/SpecterEntity.java @@ -2,11 +2,15 @@ package com.minelittlepony.unicopia.entity.mob; import org.jetbrains.annotations.Nullable; +import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.particle.FootprintParticleEffect; import com.minelittlepony.unicopia.particle.ParticleUtils; +import net.minecraft.entity.EntityData; import net.minecraft.entity.EntityType; +import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.SpawnReason; import net.minecraft.entity.ai.goal.ActiveTargetGoal; import net.minecraft.entity.ai.goal.LookAroundGoal; import net.minecraft.entity.ai.goal.LookAtEntityGoal; @@ -19,12 +23,18 @@ import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.mob.HostileEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Items; +import net.minecraft.nbt.NbtCompound; import net.minecraft.particle.BlockStateParticleEffect; import net.minecraft.particle.ParticleTypes; import net.minecraft.registry.tag.BlockTags; import net.minecraft.sound.SoundEvent; import net.minecraft.util.math.Vec3d; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.Difficulty; import net.minecraft.world.GameRules; +import net.minecraft.world.LocalDifficulty; +import net.minecraft.world.ServerWorldAccess; import net.minecraft.world.World; public class SpecterEntity extends HostileEntity { @@ -44,21 +54,21 @@ public class SpecterEntity extends HostileEntity { @Override protected void initGoals() { - this.goalSelector.add(1, new SwimGoal(this)); - this.goalSelector.add(4, new MeleeAttackGoal(this, 1.0, true)); - this.goalSelector.add(5, new WanderAroundFarGoal(this, 0.8)); - this.goalSelector.add(6, new LookAtEntityGoal(this, PlayerEntity.class, 8.0f)); - this.goalSelector.add(6, new LookAroundGoal(this)); - this.targetSelector.add(1, new RevengeGoal(this)); - this.targetSelector.add(2, new TargetGoal<>(this, PlayerEntity.class)); + goalSelector.add(1, new SwimGoal(this)); + goalSelector.add(4, new MeleeAttackGoal(this, 1.0, true)); + goalSelector.add(5, new WanderAroundFarGoal(this, 0.8)); + goalSelector.add(6, new LookAtEntityGoal(this, PlayerEntity.class, 8.0f)); + goalSelector.add(6, new LookAroundGoal(this)); + targetSelector.add(1, new RevengeGoal(this)); + //this.targetSelector.add(2, new TargetGoal<>(this, PlayerEntity.class)); } - @SuppressWarnings("deprecation") @Override public void tick() { Vec3d prevPosition = getPos(); super.tick(); - if (getBrightnessAtEyes() < 0.5F || getTarget() != null) { + + if (getTarget() != null) { ParticleUtils.spawnParticles(ParticleTypes.AMBIENT_ENTITY_EFFECT, this, 6); if (getWorld().getGameRules().getBoolean(GameRules.DO_MOB_GRIEFING)) { @@ -66,6 +76,8 @@ public class SpecterEntity extends HostileEntity { getWorld().breakBlock(getBlockPos(), true); } } + + } if (!hasVehicle() && isOnGround()) { @@ -83,6 +95,23 @@ public class SpecterEntity extends HostileEntity { } } + @Override + public void setAttacker(@Nullable LivingEntity attacker) { + if (!getWorld().isClient && attacker != null) { + getWorld().getEntitiesByClass(SpecterEntity.class, this.getBoundingBox().expand(5), e -> e != this && e.getTarget() == null).forEach(specter -> { + specter.notifyPartyAttacker(this, attacker); + }); + } + super.setAttacker(attacker); + } + + private void notifyPartyAttacker(SpecterEntity sender, @Nullable LivingEntity attacker) { + super.setAttacker(attacker); + getNavigation().stop(); + getNavigation().startMovingTo(sender, 3); + playSound(USounds.Vanilla.ENTITY_VEX_HURT, 1, 0.5F); + } + @Override public float getSoundPitch() { return super.getSoundPitch() * 0.3F; @@ -104,6 +133,27 @@ public class SpecterEntity extends HostileEntity { } + @Nullable + @Override + public EntityData initialize(ServerWorldAccess world, LocalDifficulty difficulty, SpawnReason spawnReason, @Nullable EntityData data, @Nullable NbtCompound entityNbt) { + data = super.initialize(world, difficulty, spawnReason, data, entityNbt); + Random random = world.getRandom(); + float diff = difficulty.getClampedLocalDifficulty(); + setCanPickUpLoot(random.nextFloat() < 0.55F * diff); + initEquipment(random, difficulty); + return data; + } + + @Override + protected void initEquipment(Random random, LocalDifficulty localDifficulty) { + if (random.nextFloat() < (getWorld().getDifficulty() == Difficulty.HARD ? 0.05F : 0.01F)) { + if (random.nextFloat() < (getWorld().getDifficulty() == Difficulty.HARD ? 0.5F : 0.1F)) { + super.initEquipment(random, localDifficulty); + } + equipStack(EquipmentSlot.MAINHAND, (random.nextInt(3) == 0 ? Items.STONE_SWORD : Items.WOODEN_SWORD).getDefaultStack()); + } + } + static class TargetGoal extends ActiveTargetGoal { public TargetGoal(SpecterEntity specter, Class targetEntityClass) { super(specter, targetEntityClass, true); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/SpellbookEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/SpellbookEntity.java index 5808aea4..41180bda 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/SpellbookEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/SpellbookEntity.java @@ -75,6 +75,7 @@ public class SpellbookEntity extends MobEntity implements MagicImmune { super(type, world); setPersistent(); setAltered(world.random.nextInt(3) == 0); + state.setCurrentPageId(SpellbookState.CRAFTING_ID); if (!world.isClient) { state.setSynchronizer(state -> { getWorld().getPlayers().forEach(player -> { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/StormCloudEntity.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/StormCloudEntity.java index d7f1054f..6bad71e2 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/StormCloudEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/StormCloudEntity.java @@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.entity.MagicImmune; import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.server.world.WeatherConditions; +import com.minelittlepony.unicopia.util.PosHelper; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityStatuses; @@ -38,7 +39,6 @@ import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.GameRules; @@ -148,7 +148,7 @@ public class StormCloudEntity extends Entity implements MagicImmune { } if (isLogicalSideForUpdatingMovement()) { - float groundY = findSurfaceBelow(getWorld(), getBlockPos()).getY(); + float groundY = PosHelper.findNearestSurface(getWorld(), getBlockPos()).getY(); float targetY = isStormy() ? STORMY_TARGET_ALTITUDE : CLEAR_TARGET_ALTITUDE; float cloudY = (float)getY() - targetY; @@ -271,24 +271,10 @@ public class StormCloudEntity extends Entity implements MagicImmune { private void pickRandomPoints(int count, Consumer action) { BlockPos.iterateRandomly(random, 3, getBlockPos(), getSizeInBlocks()).forEach(pos -> { - action.accept(findSurfaceBelow(getWorld(), pos)); + action.accept(PosHelper.findNearestSurface(getWorld(), pos)); }); } - public static BlockPos findSurfaceBelow(World world, BlockPos pos) { - BlockPos.Mutable mutable = new BlockPos.Mutable(); - mutable.set(pos); - while (mutable.getY() > world.getBottomY() && world.isAir(mutable)) { - mutable.move(Direction.DOWN); - } - while (world.isInBuildLimit(mutable) && !world.isAir(mutable)) { - mutable.move(Direction.UP); - } - mutable.move(Direction.DOWN); - - return mutable; - } - private void spawnLightningStrike(BlockPos pos, boolean cosmetic, boolean infect) { if (infect) { if (!CrystalShardsEntity.infestBlock((ServerWorld)getWorld(), pos)) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java b/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java index e3f3d9c8..a048bdae 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/UEntities.java @@ -50,7 +50,7 @@ public interface UEntities { .trackRangeBlocks(200) .disableSummon() .dimensions(EntityDimensions.fixed(1, 1))); - EntityType CAST_SPELL = register("cast_spell", FabricEntityTypeBuilder.create(SpawnGroup.MISC, CastSpellEntity::new) + EntityType CAST_SPELL = register("cast_spell", FabricEntityTypeBuilder.create(SpawnGroup.MISC, CastSpellEntity::new) .trackRangeBlocks(200) .disableSummon() .dimensions(EntityDimensions.changing(4, 4))); @@ -86,13 +86,13 @@ public interface UEntities { .trackRangeChunks(8) .dimensions(EntityDimensions.fixed(3, 2))); EntityType SPECTER = register("specter", FabricEntityTypeBuilder.createMob().spawnGroup(SpawnGroup.MONSTER).entityFactory(SpecterEntity::new) - .spawnRestriction(Location.ON_GROUND, Type.MOTION_BLOCKING_NO_LEAVES, HostileEntity::canSpawnIgnoreLightLevel) + .spawnRestriction(Location.ON_GROUND, Type.MOTION_BLOCKING_NO_LEAVES, HostileEntity::canSpawnInDark) .fireImmune() .spawnableFarFromPlayer() .dimensions(EntityDimensions.fixed(1, 2))); EntityType MIMIC = register("mimic", FabricEntityTypeBuilder.create(SpawnGroup.MONSTER, MimicEntity::new) .fireImmune() - //.disableSummon() + .disableSummon() .dimensions(EntityDimensions.changing(0.875F, 0.875F))); static EntityType register(String name, FabricEntityTypeBuilder builder) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Acrobatics.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Acrobatics.java index 37009bb5..e86b61c8 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Acrobatics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Acrobatics.java @@ -7,16 +7,14 @@ import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation.Recipient; import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck; -import com.minelittlepony.unicopia.entity.mob.StormCloudEntity; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.util.NbtSerialisable; +import com.minelittlepony.unicopia.util.PosHelper; import com.minelittlepony.unicopia.util.Tickable; - import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.block.SideShapeType; -import net.minecraft.entity.data.DataTracker; -import net.minecraft.entity.data.TrackedData; -import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtCompound; import net.minecraft.registry.tag.BlockTags; @@ -28,8 +26,6 @@ import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; public class Acrobatics implements Tickable, NbtSerialisable { - static final TrackedData> HANGING_POSITION = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.OPTIONAL_BLOCK_POS); - private int ticksHanging; private Direction attachDirection; @@ -38,16 +34,15 @@ public class Acrobatics implements Tickable, NbtSerialisable { private final Pony pony; private final PlayerEntity entity; - public Acrobatics(Pony pony) { + private final DataTracker.Entry> hangingPos; + + public Acrobatics(Pony pony, DataTracker tracker) { this.pony = pony; this.entity = pony.asEntity(); + this.hangingPos = tracker.startTracking(TrackableDataType.OPTIONAL_POS, Optional.empty()); pony.addTicker(this::checkDislodge); } - public void initDataTracker() { - entity.getDataTracker().startTracking(HANGING_POSITION, Optional.empty()); - } - public boolean isImmobile() { return isFloppy() && entity.isOnGround(); } @@ -56,7 +51,7 @@ public class Acrobatics implements Tickable, NbtSerialisable { if (entity.isCreative() && entity.getAbilities().flying) { return false; } - return pony.getCompositeRace().any(Race::isFish) && !entity.isTouchingWater() && !entity.getWorld().isWater(StormCloudEntity.findSurfaceBelow(entity.getWorld(), entity.getBlockPos())); + return pony.getCompositeRace().any(Race::isFish) && !entity.isTouchingWater() && !entity.getWorld().isWater(PosHelper.findNearestSurface(entity.getWorld(), entity.getBlockPos())); } @Override @@ -147,7 +142,7 @@ public class Acrobatics implements Tickable, NbtSerialisable { } public Optional getHangingPosition() { - return entity.getDataTracker().get(HANGING_POSITION); + return hangingPos.get(); } public boolean isHanging() { @@ -155,13 +150,13 @@ public class Acrobatics implements Tickable, NbtSerialisable { } public void stopHanging() { - entity.getDataTracker().set(HANGING_POSITION, Optional.empty()); + hangingPos.set(Optional.empty()); entity.calculateDimensions(); ticksHanging = 0; } public void startHanging(BlockPos pos) { - entity.getDataTracker().set(HANGING_POSITION, Optional.of(pos)); + hangingPos.set(Optional.of(pos)); entity.teleport(pos.getX() + 0.5, pos.getY() - 1, pos.getZ() + 0.5); entity.setVelocity(Vec3d.ZERO); entity.setSneaking(false); @@ -202,6 +197,6 @@ public class Acrobatics implements Tickable, NbtSerialisable { @Override public void fromNBT(NbtCompound compound) { ticksHanging = compound.getInt("ticksHanging"); - pony.asEntity().getDataTracker().set(HANGING_POSITION, NbtSerialisable.BLOCK_POS.readOptional("hangingPosition", compound)); + hangingPos.set(NbtSerialisable.BLOCK_POS.readOptional("hangingPosition", compound)); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/CorruptionHandler.java b/src/main/java/com/minelittlepony/unicopia/entity/player/CorruptionHandler.java index d3cbc40d..489bf5b7 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/CorruptionHandler.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/CorruptionHandler.java @@ -22,7 +22,7 @@ public class CorruptionHandler implements Tickable { } public boolean hasCorruptingMagic() { - return pony.getSpellSlot().get(SpellPredicate.IS_CORRUPTING, false).isPresent() || UItems.ALICORN_AMULET.isApplicable(pony.asEntity()); + return pony.getSpellSlot().get(SpellPredicate.IS_CORRUPTING).isPresent() || UItems.ALICORN_AMULET.isApplicable(pony.asEntity()); } @Override @@ -38,12 +38,10 @@ public class CorruptionHandler implements Tickable { if (entity.age % (10 * ItemTracker.SECONDS) == 0) { if (random.nextInt(100) == 0) { pony.getCorruption().add(-1); - pony.setDirty(); } if (entity.getHealth() >= entity.getMaxHealth() - 1 && !entity.getHungerManager().isNotFull()) { pony.getCorruption().add(-random.nextInt(4)); - pony.setDirty(); } } } @@ -79,6 +77,5 @@ public class CorruptionHandler implements Tickable { MagicReserves reserves = pony.getMagicalReserves(); reserves.getExertion().addPercent(10); reserves.getEnergy().add(10); - pony.setDirty(); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/FlightStuntUtil.java b/src/main/java/com/minelittlepony/unicopia/entity/player/FlightStuntUtil.java index c5975dbe..9ff96023 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/FlightStuntUtil.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/FlightStuntUtil.java @@ -1,7 +1,7 @@ package com.minelittlepony.unicopia.entity.player; -import com.minelittlepony.unicopia.entity.mob.StormCloudEntity; import com.minelittlepony.unicopia.util.MutableVector; +import com.minelittlepony.unicopia.util.PosHelper; import net.minecraft.util.math.BlockPos; @@ -14,6 +14,6 @@ public class FlightStuntUtil { public static boolean isFlyingLow(Pony pony, MutableVector velocity) { BlockPos pos = pony.asEntity().getBlockPos(); - return velocity.horizontalLengthSquared() > 0.005F && (pos.getY() - StormCloudEntity.findSurfaceBelow(pony.asWorld(), pos).getY()) < 6; + return velocity.horizontalLengthSquared() > 0.005F && (pos.getY() - PosHelper.findNearestSurface(pony.asWorld(), pos).getY()) < 6; } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/ManaContainer.java b/src/main/java/com/minelittlepony/unicopia/entity/player/ManaContainer.java index c527430d..15bcf34f 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/ManaContainer.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/ManaContainer.java @@ -3,11 +3,11 @@ package com.minelittlepony.unicopia.entity.player; import java.util.HashMap; import java.util.Map; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import com.minelittlepony.unicopia.util.Copyable; import com.minelittlepony.unicopia.util.NbtSerialisable; import com.minelittlepony.unicopia.util.Tickable; - -import net.minecraft.entity.data.TrackedData; import net.minecraft.nbt.NbtCompound; import net.minecraft.util.math.MathHelper; @@ -23,14 +23,14 @@ class ManaContainer implements MagicReserves, Tickable, NbtSerialisable, Copyabl private final BarInst xp; private final BarInst charge; - public ManaContainer(Pony pony) { + public ManaContainer(Pony pony, DataTracker tracker) { this.pony = pony; - this.energy = addBar("energy", new BarInst(Pony.ENERGY, 100F, 0)); - this.exhaustion = addBar("exhaustion", new BarInst(Pony.EXHAUSTION, 100F, 0)); - this.exertion = addBar("exertion", new BarInst(Pony.EXERTION, 10F, 0)); - this.xp = addBar("xp", new BarInst(Pony.XP, 1F, 0)); - this.mana = addBar("mana", new XpCollectingBar(Pony.MANA, 100F, 1)); - this.charge = addBar("charge", new BarInst(Pony.CHARGE, 10F, 0) { + this.energy = addBar("energy", new BarInst(tracker, 100F, 0)); + this.exhaustion = addBar("exhaustion", new BarInst(tracker, 100F, 0)); + this.exertion = addBar("exertion", new BarInst(tracker, 10F, 0)); + this.xp = addBar("xp", new BarInst(tracker, 1F, 0)); + this.mana = addBar("mana", new XpCollectingBar(tracker, 100F, 1)); + this.charge = addBar("charge", new BarInst(tracker, 10F, 0) { @Override protected float applyLimits(float value) { return Math.max(0, value); @@ -38,10 +38,6 @@ class ManaContainer implements MagicReserves, Tickable, NbtSerialisable, Copyabl }); } - public void initDataTracker() { - bars.values().forEach(BarInst::initDataTracker); - } - protected BarInst addBar(String name, BarInst bar) { bars.put(name, bar); return bar; @@ -99,13 +95,13 @@ class ManaContainer implements MagicReserves, Tickable, NbtSerialisable, Copyabl energy.addPercent(-1); } - if (pony.getCompositeRace().canFly() && !pony.getPhysics().isFlying()) { - exhaustion.multiply(0.8F); + if (pony.getCompositeRace().canFly() && !pony.getPhysics().isFlying() && pony.asEntity().isOnGround()) { + exhaustion.multiply(0.99F); } else { exhaustion.addPercent(-1); } - if (!pony.getCompositeRace().canFly() || !pony.getPhysics().isFlying()) { + if (!pony.getCompositeRace().canFly() || (!pony.getPhysics().isFlying() && pony.asEntity().isOnGround())) { if (mana.getPercentFill() < 1 && mana.getShadowFill(1) <= mana.getPercentFill(1)) { mana.addPercent(MathHelper.clamp(1 + pony.getLevel().get(), 1, 50) / 4F); } @@ -130,9 +126,8 @@ class ManaContainer implements MagicReserves, Tickable, NbtSerialisable, Copyabl } class XpCollectingBar extends BarInst { - - XpCollectingBar(TrackedData marker, float max, float initial) { - super(marker, max, initial); + XpCollectingBar(DataTracker tracker, float max, float initial) { + super(tracker, max, initial); } @Override @@ -156,29 +151,24 @@ class ManaContainer implements MagicReserves, Tickable, NbtSerialisable, Copyabl } class BarInst implements Bar, NbtSerialisable { - - private final TrackedData marker; + private final DataTracker.Entry marker; private final float max; private float trailingValue; private float prevTrailingValue; private float prevValue; - BarInst(TrackedData marker, float max, float initial) { - this.marker = marker; + BarInst(DataTracker tracker, float max, float initial) { this.max = max; this.trailingValue = initial; this.prevTrailingValue = initial; this.prevValue = initial; - } - - public void initDataTracker() { - pony.asEntity().getDataTracker().startTracking(marker, max * trailingValue); + this.marker = tracker.startTracking(TrackableDataType.FLOAT, max * trailingValue); } @Override public float get() { - return applyLimits(pony.asEntity().getDataTracker().get(marker)); + return applyLimits(marker.get()); } @Override @@ -197,7 +187,7 @@ class ManaContainer implements MagicReserves, Tickable, NbtSerialisable, Copyabl } private void load(float value) { - pony.asEntity().getDataTracker().set(marker, value); + marker.set(value); } protected float getInitial(float initial) { 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 ad647427..8fd84dcf 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerAttributes.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerAttributes.java @@ -6,6 +6,8 @@ import java.util.function.Predicate; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.entity.effect.EffectUtils; +import com.minelittlepony.unicopia.entity.effect.UEffects; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; import com.minelittlepony.unicopia.util.Tickable; @@ -87,6 +89,7 @@ public class PlayerAttributes implements Tickable { @Override public void tick() { ATTRIBUTES.forEach(attribute -> attribute.update(pony)); + EffectUtils.applyStatusEffect(pony.asEntity(), UEffects.FORTIFICATION, pony.getCompositeRace().canUseEarth() && pony.asEntity().isSneaking()); } record ToggleableAttribute(EntityAttributeModifier modifier, List attributes, Predicate test) { diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCamera.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCamera.java index 61effece..792caec6 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCamera.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCamera.java @@ -53,7 +53,7 @@ public class PlayerCamera extends MotionCompositor { public Optional calculateDistance(double distance) { return player.getSpellSlot() - .get(SpellPredicate.IS_DISGUISE, false) + .get(SpellPredicate.IS_DISGUISE) .map(AbstractDisguiseSpell::getDisguise) .flatMap(d -> d.getDistance(player)) .map(d -> distance * d); diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCharmTracker.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCharmTracker.java index aeaf4d73..fbb29e1c 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCharmTracker.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCharmTracker.java @@ -7,6 +7,7 @@ import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.item.EnchantableItem; +import com.minelittlepony.unicopia.util.Copyable; import com.minelittlepony.unicopia.util.NbtSerialisable; import net.minecraft.nbt.NbtCompound; @@ -15,7 +16,7 @@ import net.minecraft.nbt.NbtList; import net.minecraft.util.Hand; import net.minecraft.util.TypedActionResult; -public class PlayerCharmTracker implements NbtSerialisable { +public class PlayerCharmTracker implements NbtSerialisable, Copyable { private final Pony pony; @@ -62,11 +63,18 @@ public class PlayerCharmTracker implements NbtSerialisable { return previous; } + @Override + public void copyFrom(PlayerCharmTracker old, boolean alive) { + for (int i = 0; i < handSpells.length; i++) { + handSpells[i] = old.handSpells[i]; + } + } + @Override public void toNBT(NbtCompound compound) { NbtList equippedSpells = new NbtList(); for (CustomisedSpellType spell : handSpells) { - equippedSpells.add(spell.toNBT()); + equippedSpells.add(spell.toNbt(new NbtCompound())); } compound.put("handSpells", equippedSpells); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerDimensions.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerDimensions.java index a2e5a329..491a66fd 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerDimensions.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerDimensions.java @@ -39,7 +39,7 @@ public final class PlayerDimensions { } Optional getPredicate() { - return pony.getSpellSlot().get(true) + return pony.getSpellSlot().get() .filter(effect -> !effect.isDead() && effect instanceof Provider) .map(effect -> (Provider)effect); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerLevelStore.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerLevelStore.java index f883529a..decc9b12 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerLevelStore.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerLevelStore.java @@ -1,8 +1,8 @@ package com.minelittlepony.unicopia.entity.player; import com.minelittlepony.unicopia.ability.magic.Levelled; - -import net.minecraft.entity.data.TrackedData; +import com.minelittlepony.unicopia.network.track.DataTracker; +import com.minelittlepony.unicopia.network.track.TrackableDataType; import net.minecraft.sound.*; import net.minecraft.util.math.MathHelper; @@ -10,15 +10,15 @@ class PlayerLevelStore implements Levelled.LevelStore { private final Pony pony; - private final TrackedData dataEntry; + private final DataTracker.Entry dataEntry; private final boolean upgradeMana; private final SoundEvent levelUpSound; - PlayerLevelStore(Pony pony, TrackedData dataEntry, boolean upgradeMana, SoundEvent levelUpSound) { + PlayerLevelStore(Pony pony, DataTracker tracker, boolean upgradeMana, SoundEvent levelUpSound) { this.pony = pony; - this.dataEntry = dataEntry; + this.dataEntry = tracker.startTracking(TrackableDataType.INT, 0); this.upgradeMana = upgradeMana; this.levelUpSound = levelUpSound; } @@ -41,11 +41,11 @@ class PlayerLevelStore implements Levelled.LevelStore { @Override public int get() { - return pony.asEntity().getDataTracker().get(dataEntry); + return dataEntry.get(); } @Override public void set(int level) { - pony.asEntity().getDataTracker().set(dataEntry, MathHelper.clamp(level, 0, getMax())); + dataEntry.set(MathHelper.clamp(level, 0, getMax())); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java index 8aed9af4..c2789be6 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java @@ -13,9 +13,11 @@ import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.compat.ad_astra.OxygenApi; +import com.minelittlepony.unicopia.compat.trinkets.TrinketsDelegate; import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck; +import com.minelittlepony.unicopia.entity.effect.EffectUtils; import com.minelittlepony.unicopia.entity.player.MagicReserves.Bar; import com.minelittlepony.unicopia.input.Heuristic; import com.minelittlepony.unicopia.item.AmuletItem; @@ -24,6 +26,7 @@ import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.MsgPlayerFlightControlsInput; +import com.minelittlepony.unicopia.network.track.DataTracker; import com.minelittlepony.unicopia.particle.*; import com.minelittlepony.unicopia.projectile.ProjectileUtil; import com.minelittlepony.unicopia.server.world.BlockDestructionManager; @@ -36,12 +39,10 @@ import net.fabricmc.fabric.api.tag.convention.v1.ConventionalBlockTags; import net.minecraft.block.*; import net.minecraft.enchantment.EnchantmentHelper; import net.minecraft.entity.EntityType; -import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LightningEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.damage.DamageSource; -import net.minecraft.item.ItemStack; import net.minecraft.nbt.NbtCompound; import net.minecraft.particle.ParticleTypes; import net.minecraft.predicate.entity.EntityPredicates; @@ -96,8 +97,8 @@ public class PlayerPhysics extends EntityPhysics implements Tickab private Lerp updraft = new Lerp(0); private Lerp windStrength = new Lerp(0); - public PlayerPhysics(Pony pony) { - super(pony.asEntity(), Creature.GRAVITY); + public PlayerPhysics(Pony pony, DataTracker tracker) { + super(pony.asEntity()); this.pony = pony; dimensions = new PlayerDimensions(pony, this); } @@ -208,7 +209,7 @@ public class PlayerPhysics extends EntityPhysics implements Tickab return FlightType.ARTIFICIAL; } - return pony.getSpellSlot().get(true) + return pony.getSpellSlot().get() .filter(effect -> !effect.isDead() && effect instanceof FlightType.Provider) .map(effect -> ((FlightType.Provider)effect).getFlightType()) .filter(FlightType::isPresent) @@ -229,6 +230,8 @@ public class PlayerPhysics extends EntityPhysics implements Tickab if (wasFlying) { entity.calculateDimensions(); } + + pony.setDirty(); } public double getHorizontalMotion() { @@ -285,6 +288,18 @@ public class PlayerPhysics extends EntityPhysics implements Tickab cancelFlight(false); } + if (!pony.isClient()) { + if (type.canFly() + && isFlying() + && EffectUtils.hasBothBrokenWing(entity) + && ticksInAir > 90) { + + entity.getWorld().playSoundFromEntity(null, entity, USounds.Vanilla.ENTITY_PLAYER_BIG_FALL, SoundCategory.PLAYERS, 2, 1F); + entity.damage(entity.getDamageSources().generic(), 3); + cancelFlight(true); + } + } + if (entity.isOnGround()) { isCancelled = false; } @@ -393,7 +408,9 @@ public class PlayerPhysics extends EntityPhysics implements Tickab private void tickGrounded() { prevStrafe = 0; strafe = 0; - ticksInAir = 0; + if (entity.isOnGround()) { + ticksInAir = 0; + } wallHitCooldown = MAX_WALL_HIT_CALLDOWN; soundPlaying = false; descentRate = 0; @@ -439,7 +456,12 @@ public class PlayerPhysics extends EntityPhysics implements Tickab entity.fallDistance = 0; - applyThrust(velocity); + if (!EffectUtils.hasABrokenWing(entity) || entity.age % 50 < 25) { + applyThrust(velocity); + } else if (entity.getWorld().random.nextInt(40) == 0) { + entity.getWorld().playSoundFromEntity(null, entity, USounds.Vanilla.ENTITY_PLAYER_BIG_FALL, SoundCategory.PLAYERS, 2, 1.5F); + entity.damage(entity.getDamageSources().generic(), 0.5F); + } if (type.isAvian()) { if (pony.getObservedSpecies() != Race.BAT && entity.getWorld().random.nextInt(9000) == 0) { @@ -476,8 +498,8 @@ public class PlayerPhysics extends EntityPhysics implements Tickab private void tickArtificialFlight(MutableVector velocity) { if (ticksInAir % 10 == 0 && !entity.getWorld().isClient) { - ItemStack stack = AmuletItem.getForEntity(entity); - if (ChargeableItem.getEnergy(stack) < 9) { + TrinketsDelegate.EquippedStack stack = AmuletItem.get(entity); + if (ChargeableItem.getEnergy(stack.stack()) < 9) { playSound(USounds.ITEM_ICARUS_WINGS_WARN, 0.13F, 0.5F); } @@ -494,10 +516,10 @@ public class PlayerPhysics extends EntityPhysics implements Tickab minDamage *= 3; } - ChargeableItem.consumeEnergy(stack, energyConsumed); + ChargeableItem.consumeEnergy(stack.stack(), energyConsumed); if (entity.getWorld().random.nextInt(damageInterval) == 0) { - stack.damage(minDamage + entity.getWorld().random.nextInt(50), entity, e -> e.sendEquipmentBreakStatus(EquipmentSlot.CHEST)); + stack.stack().damage(minDamage + entity.getWorld().random.nextInt(50), (LivingEntity)entity, stack.breakStatusSender()); } if (!lastFlightType.canFly()) { @@ -524,9 +546,15 @@ public class PlayerPhysics extends EntityPhysics implements Tickab mana.add(MathHelper.clamp(cost, -100, 0)); - if (mana.getPercentFill() < 0.2) { - pony.getMagicalReserves().getExertion().addPercent(2); - pony.getMagicalReserves().getExhaustion().add(2 + (int)(getHorizontalMotion() * 50)); + boolean overVoid = PosHelper.isOverVoid(pony.asWorld(), pony.getOrigin(), getGravitySignum()); + + if (overVoid) { + mana.addPercent(-2); + } + + if (mana.getPercentFill() < (overVoid ? 0.4F : 0.2F)) { + pony.getMagicalReserves().getExertion().addPercent(overVoid ? 4 : 2); + pony.getMagicalReserves().getExhaustion().add((overVoid ? 4 : 0) + 2 + (int)(getHorizontalMotion() * 50)); if (mana.getPercentFill() < 0.1 && ticksInAir % 10 == 0) { float exhaustion = (0.3F * ticksInAir) / 70; @@ -537,10 +565,10 @@ public class PlayerPhysics extends EntityPhysics implements Tickab entity.addExhaustion(exhaustion); } - if (pony.getMagicalReserves().getExhaustion().get() > 99 && ticksInAir % 25 == 0) { - entity.damage(pony.damageOf(UDamageTypes.EXHAUSTION), 2); + if (pony.getMagicalReserves().getExhaustion().getPercentFill() > 0.99F && ticksInAir % 25 == 0 && !pony.isClient()) { + entity.damage(pony.damageOf(UDamageTypes.EXHAUSTION), entity.getWorld().random.nextBetween(2, 4)); - if (entity.getWorld().random.nextInt(110) == 1 && !pony.isClient()) { + if (entity.getWorld().random.nextInt(110) == 1) { pony.getLevel().add(1); if (Abilities.RAINBOOM.canUse(pony.getCompositeRace())) { pony.getMagicalReserves().getCharge().addPercent(4); @@ -589,6 +617,7 @@ public class PlayerPhysics extends EntityPhysics implements Tickab thrustScale = 0; descentRate = 0; entity.calculateDimensions(); + pony.setDirty(); if (entity.isOnGround() || !force) { //BlockState steppingState = pony.asEntity().getSteppingBlockState(); 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 aa174dd4..cfe236a9 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -11,6 +11,7 @@ import com.minelittlepony.unicopia.client.render.PlayerPoser.AnimationInstance; import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.*; import com.minelittlepony.unicopia.ability.magic.*; +import com.minelittlepony.unicopia.ability.magic.SpellSlots.UpdateCallback; import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.RageAbilitySpell; @@ -21,7 +22,9 @@ import com.minelittlepony.unicopia.advancement.UCriteria; import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; import com.minelittlepony.unicopia.entity.duck.LivingEntityDuck; +import com.minelittlepony.unicopia.entity.effect.EffectUtils; import com.minelittlepony.unicopia.entity.effect.MetamorphosisStatusEffect; +import com.minelittlepony.unicopia.entity.effect.SeaponyGraceStatusEffect; import com.minelittlepony.unicopia.entity.effect.SunBlindnessStatusEffect; import com.minelittlepony.unicopia.entity.effect.UEffects; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; @@ -32,7 +35,7 @@ import com.minelittlepony.unicopia.item.enchantment.EnchantmentUtil; import com.minelittlepony.unicopia.item.enchantment.UEnchantments; import com.minelittlepony.unicopia.util.*; import com.minelittlepony.unicopia.network.*; -import com.minelittlepony.unicopia.network.datasync.EffectSync.UpdateCallback; +import com.minelittlepony.unicopia.network.track.DataTracker; import com.minelittlepony.unicopia.server.world.UGameRules; import com.minelittlepony.common.util.animation.LinearInterpolator; import com.google.common.collect.Streams; @@ -45,9 +48,6 @@ import net.minecraft.entity.*; import net.minecraft.entity.attribute.DefaultAttributeContainer; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.damage.DamageTypes; -import net.minecraft.entity.data.DataTracker; -import net.minecraft.entity.data.TrackedData; -import net.minecraft.entity.data.TrackedDataHandlerRegistry; import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.entity.mob.HostileEntity; import net.minecraft.entity.player.PlayerEntity; @@ -64,34 +64,19 @@ import net.minecraft.server.world.ServerWorld; import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; import net.minecraft.util.ActionResult; -import net.minecraft.util.Hand; import net.minecraft.util.math.*; import net.minecraft.world.GameMode; 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); - static final TrackedData EXERTION = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT); - static final TrackedData MANA = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT); - static final TrackedData XP = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT); - static final TrackedData CHARGE = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT); - static final TrackedData LEVEL = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER); - static final TrackedData CORRUPTION = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER); - static final int INITIAL_SUN_IMMUNITY = 20; - private static final TrackedData EFFECT = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.NBT_COMPOUND); - private final AbilityDispatcher powers = new AbilityDispatcher(this); - private final PlayerPhysics gravity = addTicker(new PlayerPhysics(this)); + private final PlayerPhysics gravity = addTicker(new PlayerPhysics(this, tracker)); private final PlayerCharmTracker charms = new PlayerCharmTracker(this); private final PlayerCamera camera = new PlayerCamera(this); private final TraitDiscovery discoveries = new TraitDiscovery(this); - private final Acrobatics acrobatics = new Acrobatics(this); + private final Acrobatics acrobatics = new Acrobatics(this, tracker); private final CorruptionHandler corruptionHandler = new CorruptionHandler(this); private final Map advancementProgress = new HashMap<>(); @@ -120,11 +105,23 @@ public class Pony extends Living implements Copyable, Update private int animationMaxDuration; private int animationDuration; + private DataTracker.Entry race; + private DataTracker.Entry suppressedRace; + public Pony(PlayerEntity player) { - super(player, EFFECT); - this.levels = new PlayerLevelStore(this, LEVEL, true, USounds.Vanilla.ENTITY_PLAYER_LEVELUP); - this.corruption = new PlayerLevelStore(this, CORRUPTION, false, USounds.ENTITY_PLAYER_CORRUPTION); - this.mana = addTicker(new ManaContainer(this)); + super(player); + trackers.addPacketEmitter((sender, initial) -> { + if (initial || dirty) { + dirty = false; + sender.accept(Channel.SERVER_PLAYER_CAPABILITIES.toPacket(new MsgPlayerCapabilities(this))); + } + }); + + race = this.tracker.startTracking(Race.TRACKABLE_TYPE, Race.UNSET); + suppressedRace = this.tracker.startTracking(Race.TRACKABLE_TYPE, Race.UNSET); + this.levels = new PlayerLevelStore(this, tracker, true, USounds.Vanilla.ENTITY_PLAYER_LEVELUP); + this.corruption = new PlayerLevelStore(this, tracker, false, USounds.ENTITY_PLAYER_CORRUPTION); + this.mana = addTicker(new ManaContainer(this, tracker)); addTicker(this::updateAnimations); addTicker(this::updateBatPonyAbilities); @@ -133,17 +130,6 @@ public class Pony extends Living implements Copyable, Update addTicker(corruptionHandler); } - @Override - public void initDataTracker() { - super.initDataTracker(); - acrobatics.initDataTracker(); - mana.initDataTracker(); - entity.getDataTracker().startTracking(LEVEL, 0); - entity.getDataTracker().startTracking(CORRUPTION, 0); - entity.getDataTracker().startTracking(RACE, Race.DEFAULT_ID); - entity.getDataTracker().startTracking(SUPPRESSED_RACE, Race.DEFAULT_ID); - } - public static void registerAttributes(DefaultAttributeContainer.Builder builder) { builder.add(UEntityAttributes.EXTRA_MINING_SPEED); builder.add(UEntityAttributes.ENTITY_GRAVITY_MODIFIER); @@ -190,6 +176,7 @@ public class Pony extends Living implements Copyable, Update animation.animation().getSound().ifPresent(sound -> { playSound(sound, sound == USounds.ENTITY_PLAYER_WOLOLO ? 0.1F : 0.9F, 1); }); + setDirty(); } } @@ -217,7 +204,7 @@ public class Pony extends Living implements Copyable, Update */ @Override public Race getSpecies() { - return Race.fromName(entity.getDataTracker().get(RACE), Race.HUMAN); + return race.get(); } /** @@ -241,7 +228,7 @@ public class Pony extends Living implements Copyable, Update public void setSpecies(Race race) { race = race.validate(entity); Race current = getSpecies(); - entity.getDataTracker().set(RACE, race.getId().toString()); + this.race.set(race); if (race != current) { clearSuppressedRace(); } @@ -254,7 +241,7 @@ public class Pony extends Living implements Copyable, Update } public void setSuppressedRace(Race race) { - entity.getDataTracker().set(SUPPRESSED_RACE, race.validate(entity).getId().toString()); + suppressedRace.set(race.validate(entity)); } public void clearSuppressedRace() { @@ -262,7 +249,7 @@ public class Pony extends Living implements Copyable, Update } public Race getSuppressedRace() { - return Race.fromName(entity.getDataTracker().get(SUPPRESSED_RACE), Race.UNSET); + return suppressedRace.get(); } public TraitDiscovery getDiscoveries() { @@ -329,24 +316,11 @@ public class Pony extends Living implements Copyable, Update return getSpecies().getAffinity(); } - @Override + @Deprecated public void setDirty() { dirty = true; } - private void sendCapabilities() { - if (!dirty) { - return; - } - dirty = false; - - if (entity instanceof ServerPlayerEntity) { - MsgOtherPlayerCapabilities packet = new MsgOtherPlayerCapabilities(this); - Channel.SERVER_PLAYER_CAPABILITIES.sendToPlayer(packet, (ServerPlayerEntity)entity); - Channel.SERVER_OTHER_PLAYER_CAPABILITIES.sendToSurroundingPlayers(packet, entity); - } - } - public AbilityDispatcher getAbilities() { return powers; } @@ -441,6 +415,8 @@ public class Pony extends Living implements Copyable, Update powers.tick(); acrobatics.tick(); + SeaponyGraceStatusEffect.update(entity); + if (getObservedSpecies() == Race.KIRIN) { var charge = getMagicalReserves().getCharge(); @@ -499,7 +475,7 @@ public class Pony extends Living implements Copyable, Update Race intrinsicRace = getSpecies(); Race suppressedRace = getSuppressedRace(); compositeRace = MetamorphosisStatusEffect.getEffectiveRace(entity, getSpellSlot() - .get(SpellPredicate.IS_MIMIC, true) + .get(SpellPredicate.IS_MIMIC) .map(AbstractDisguiseSpell::getDisguise) .map(EntityAppearance::getAppearance) .flatMap(Pony::of) @@ -515,7 +491,7 @@ public class Pony extends Living implements Copyable, Update @Override public Optional chooseClimbingPos() { - if (getObservedSpecies() == Race.CHANGELING && getSpellSlot().get(SpellPredicate.IS_DISGUISE, false).isEmpty()) { + if (getObservedSpecies() == Race.CHANGELING && getSpellSlot().get(SpellPredicate.IS_DISGUISE).isEmpty()) { if (acrobatics.isFaceClimbable(entity.getWorld(), entity.getBlockPos(), entity.getHorizontalFacing()) || acrobatics.canHangAt(entity.getBlockPos())) { return Optional.of(entity.getBlockPos()); } @@ -553,7 +529,7 @@ public class Pony extends Living implements Copyable, Update } if (getObservedSpecies() == Race.BAT && !entity.hasPortalCooldown()) { - boolean hasShades = TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.FACE).anyMatch(s -> s.isIn(UTags.Items.SHADES)); + boolean hasShades = TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.FACE).anyMatch(s -> s.stack().isIn(UTags.Items.SHADES)); if (!this.hasShades && hasShades && getObservedSpecies() == Race.BAT) { UCriteria.WEAR_SHADES.trigger(entity); } @@ -602,8 +578,6 @@ public class Pony extends Living implements Copyable, Update setSpecies(newRace); } } - - sendCapabilities(); } @Override @@ -700,18 +674,17 @@ public class Pony extends Living implements Copyable, Update } } - if (!cause.isIn(DamageTypeTags.BYPASSES_SHIELD) + if (EffectUtils.hasExtraDefenses(entity) + && !cause.isIn(DamageTypeTags.BYPASSES_SHIELD) && !cause.isOf(DamageTypes.MAGIC) && !cause.isIn(DamageTypeTags.IS_FIRE) && !cause.isIn(DamageTypeTags.BYPASSES_INVULNERABILITY) && !cause.isOf(DamageTypes.THORNS) && !cause.isOf(DamageTypes.FREEZE)) { - if (getCompositeRace().canUseEarth() && entity.isSneaking()) { - amount /= (cause.isOf(DamageTypes.MOB_PROJECTILE) ? 3 : 2) * (entity.getHealth() < 5 ? 3 : 1); + amount /= (cause.isOf(DamageTypes.MOB_PROJECTILE) ? 3 : 2) * (entity.getHealth() < 5 ? 3 : 1); - return Optional.of(amount); - } + return Optional.of(amount); } return Optional.empty(); } @@ -723,10 +696,23 @@ public class Pony extends Living implements Copyable, Update }); } - public Optional onImpact(float distance, float damageMultiplier, DamageSource cause) { - float originalDistance = distance; + @Override + public float onImpact(float distance, float damageMultiplier, DamageSource cause) { + distance = super.onImpact(distance, damageMultiplier, cause); - boolean extraProtection = getSpellSlot().get(SpellType.SHIELD, false).isPresent(); + if (EffectUtils.hasExtraDefenses(entity)) { + double radius = distance / 10; + if (radius > 0) { + EarthPonyStompAbility.spawnEffectAround(entity, entity.getSteppingPos(), radius, radius); + } + } + + return distance; + } + + @Override + protected float getEffectiveFallDistance(float distance) { + boolean extraProtection = getSpellSlot().get(SpellType.SHIELD).isPresent(); if (!entity.isCreative() && !entity.isSpectator()) { @@ -737,17 +723,12 @@ public class Pony extends Living implements Copyable, Update } } - if (getCompositeRace().canFly() || (getCompositeRace().canUseEarth() && entity.isSneaking())) { + if (getCompositeRace().canFly() || EffectUtils.hasExtraDefenses(entity)) { distance -= 5; } - distance = Math.max(0, distance); } - handleFall(distance, damageMultiplier, cause); - if (distance != originalDistance) { - return Optional.of(distance); - } - return Optional.empty(); + return Math.max(0, distance); } public void onEat(ItemStack stack) { @@ -758,20 +739,7 @@ public class Pony extends Living implements Copyable, Update if (getObservedSpecies() == Race.KIRIN && (stack.isIn(UTags.Items.COOLS_OFF_KIRINS) || PotionUtil.getPotion(stack) == Potions.WATER)) { getMagicalReserves().getCharge().multiply(0.5F); - getSpellSlot().get(SpellType.RAGE, false).ifPresent(RageAbilitySpell::setExtenguishing); - } - } - - @SuppressWarnings("deprecation") - @Override - protected void handleFall(float distance, float damageMultiplier, DamageSource cause) { - super.handleFall(distance, damageMultiplier, cause); - - if (getCompositeRace().canUseEarth() && entity.isSneaking()) { - double radius = distance / 10; - if (radius > 0) { - EarthPonyStompAbility.spawnEffectAround(entity, entity.getLandingPos(), radius, radius); - } + getSpellSlot().get(SpellType.RAGE).ifPresent(RageAbilitySpell::setExtenguishing); } } @@ -784,7 +752,7 @@ public class Pony extends Living implements Copyable, Update @Override public boolean subtractEnergyCost(double foodSubtract) { - if (getSpellSlot().get(SpellPredicate.IS_CORRUPTING, false).isPresent()) { + if (getSpellSlot().get(SpellPredicate.IS_CORRUPTING).isPresent()) { int corruptionTaken = (int)(foodSubtract * (AmuletSelectors.ALICORN_AMULET.test(entity) ? 0.9F : 0.5F)); foodSubtract -= corruptionTaken; getCorruption().add(corruptionTaken); @@ -850,6 +818,22 @@ public class Pony extends Living implements Copyable, Update return getArmour().contains(UItems.ALICORN_AMULET) || super.isEnemy(other); } + @Override + public void toNBT(NbtCompound compound) { + compound.put("mana", mana.toNBT()); + compound.putInt("levels", levels.get()); + compound.putInt("corruption", corruption.get()); + super.toNBT(compound); + } + + @Override + public void fromNBT(NbtCompound compound) { + levels.set(compound.getInt("levels")); + corruption.set(compound.getInt("corruption")); + mana.fromNBT(compound.getCompound("mana")); + super.fromNBT(compound); + } + @Override public void toSyncronisedNbt(NbtCompound compound) { super.toSyncronisedNbt(compound); @@ -863,12 +847,8 @@ public class Pony extends Living implements Copyable, Update compound.put("gravity", gravity.toNBT()); compound.put("charms", charms.toNBT()); compound.put("discoveries", discoveries.toNBT()); - compound.put("mana", mana.toNBT()); - compound.putInt("levels", levels.get()); - compound.putInt("corruption", corruption.get()); compound.putInt("ticksInvulnerable", ticksInvulnerable); compound.putInt("ticksMetamorphising", ticksMetamorphising); - NbtCompound progress = new NbtCompound(); advancementProgress.forEach((key, count) -> { progress.putInt(key, count); @@ -885,16 +865,12 @@ public class Pony extends Living implements Copyable, Update gravity.fromNBT(compound.getCompound("gravity")); charms.fromNBT(compound.getCompound("charms")); discoveries.fromNBT(compound.getCompound("discoveries")); - levels.set(compound.getInt("levels")); - corruption.set(compound.getInt("corruption")); - mana.fromNBT(compound.getCompound("mana")); acrobatics.fromNBT(compound.getCompound("acrobatics")); magicExhaustion = compound.getFloat("magicExhaustion"); ticksInvulnerable = compound.getInt("ticksInvulnerable"); ticksInSun = compound.getInt("ticksInSun"); hasShades = compound.getBoolean("hasShades"); ticksMetamorphising = compound.getInt("ticksMetamorphising"); - NbtCompound progress = compound.getCompound("advancementProgress"); advancementProgress.clear(); for (String key : progress.getKeys()) { @@ -904,7 +880,6 @@ public class Pony extends Living implements Copyable, Update @Override public void copyFrom(Pony oldPlayer, boolean alive) { - boolean forcedSwap = (!alive && entity instanceof ServerPlayerEntity && entity.getWorld().getGameRules().getBoolean(UGameRules.SWAP_TRIBE_ON_DEATH) @@ -912,17 +887,20 @@ public class Pony extends Living implements Copyable, Update || oldPlayer.getSpecies().isUnset(); Race oldSuppressedRace = oldPlayer.getSuppressedRace(); + Race newRace = oldPlayer.respawnRace != Race.UNSET && !alive ? oldPlayer.respawnRace : oldPlayer.getSpecies(); - if (alive) { - oldPlayer.getSpellSlot().stream(true).forEach(getSpellSlot()::put); + if (forcedSwap || !newRace.canCast()) { + getSpellSlot().clear(); } else { - if (forcedSwap) { - oldSuppressedRace = Race.UNSET; - Channel.SERVER_SELECT_TRIBE.sendToPlayer(new MsgTribeSelect(Race.allPermitted(entity), "gui.unicopia.tribe_selection.respawn"), (ServerPlayerEntity)entity); - } else { - oldPlayer.getSpellSlot().stream(true).filter(SpellPredicate.IS_PLACED).forEach(getSpellSlot()::put); - } + getSpellSlot().copyFrom(oldPlayer.getSpellSlot(), alive); + } + if (forcedSwap) { + oldSuppressedRace = Race.UNSET; + Channel.SERVER_SELECT_TRIBE.sendToPlayer(new MsgTribeSelect(Race.allPermitted(entity), "gui.unicopia.tribe_selection.respawn"), (ServerPlayerEntity)entity); + } + + if (!alive) { // putting it here instead of adding another injection point into ServerPlayerEntity.copyFrom() if (!asWorld().getGameRules().getBoolean(GameRules.KEEP_INVENTORY)) { PlayerInventory inventory = oldPlayer.asEntity().getInventory(); @@ -935,14 +913,13 @@ public class Pony extends Living implements Copyable, Update } } - setSpecies(oldPlayer.respawnRace != Race.UNSET && !alive ? oldPlayer.respawnRace : oldPlayer.getSpecies()); + setSpecies(newRace); setSuppressedRace(oldSuppressedRace); getDiscoveries().copyFrom(oldPlayer.getDiscoveries(), alive); getPhysics().copyFrom(oldPlayer.getPhysics(), alive); if (!forcedSwap) { getArmour().copyFrom(oldPlayer.getArmour(), alive); - getCharms().equipSpell(Hand.MAIN_HAND, oldPlayer.getCharms().getEquippedSpell(Hand.MAIN_HAND)); - getCharms().equipSpell(Hand.OFF_HAND, oldPlayer.getCharms().getEquippedSpell(Hand.OFF_HAND)); + getCharms().copyFrom(oldPlayer.getCharms(), alive); corruption.set(oldPlayer.getCorruption().get()); levels.set(oldPlayer.getLevel().get()); } @@ -955,14 +932,11 @@ public class Pony extends Living implements Copyable, Update } @Override - public void onSpellSet(@Nullable Spell spell) { - if (spell != null) { - if (spell.getAffinity() == Affinity.BAD && entity.getWorld().random.nextInt(20) == 0) { - getCorruption().add(entity.getRandom().nextBetween(1, 10)); - } - getCorruption().add((int)spell.getTraits().getCorruption() * 10); - setDirty(); + public void onSpellAdded(Spell spell) { + if (spell.getAffinity() == Affinity.BAD && entity.getWorld().random.nextInt(20) == 0) { + getCorruption().add(entity.getRandom().nextBetween(1, 10)); } + getCorruption().add(((int)spell.getTypeAndTraits().traits().getCorruption() * 10) + spell.getTypeAndTraits().type().getAffinity().getCorruption()); } public boolean isClientPlayer() { diff --git a/src/main/java/com/minelittlepony/unicopia/item/AlicornAmuletItem.java b/src/main/java/com/minelittlepony/unicopia/item/AlicornAmuletItem.java index ea861cb0..6cb64651 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/AlicornAmuletItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/AlicornAmuletItem.java @@ -23,6 +23,9 @@ import com.minelittlepony.unicopia.server.world.UnicopiaWorldProperties; import com.minelittlepony.unicopia.util.VecHelper; import it.unimi.dsi.fastutil.floats.Float2ObjectFunction; +import it.unimi.dsi.fastutil.objects.Object2FloatMap; +import it.unimi.dsi.fastutil.objects.Object2FloatMaps; +import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.item.v1.FabricItemSettings; @@ -56,13 +59,13 @@ import net.minecraft.world.World.ExplosionSourceType; public class AlicornAmuletItem extends AmuletItem implements ItemTracker.Trackable, ItemImpl.ClingyItem, ItemImpl.GroundTickCallback { private static final UUID EFFECT_UUID = UUID.fromString("c0a870f5-99ef-4716-a23e-f320ee834b26"); - private static final Map EFFECT_SCALES = Map.of( + private static final Object2FloatMap EFFECT_SCALES = Object2FloatMaps.unmodifiable(new Object2FloatOpenHashMap<>(Map.of( EntityAttributes.GENERIC_ATTACK_DAMAGE, 0.2F, EntityAttributes.GENERIC_ATTACK_KNOCKBACK, 0.05F, EntityAttributes.GENERIC_ATTACK_SPEED, 0.2F, EntityAttributes.GENERIC_ARMOR_TOUGHNESS, 0.001F, EntityAttributes.GENERIC_ARMOR, 0.01F - ); + ))); private static final Float2ObjectFunction EFFECT_FACTORY = v -> { return new EntityAttributeModifier(EFFECT_UUID, "Alicorn Amulet Modifier", v, EntityAttributeModifier.Operation.ADDITION); }; @@ -173,8 +176,8 @@ public class AlicornAmuletItem extends AmuletItem implements ItemTracker.Trackab } public static void updateAttributes(Living wearer, float effectScale) { - EFFECT_SCALES.entrySet().forEach(attribute -> { - wearer.updateAttributeModifier(EFFECT_UUID, attribute.getKey(), attribute.getValue() * effectScale, EFFECT_FACTORY, false); + EFFECT_SCALES.object2FloatEntrySet().forEach(entry -> { + wearer.updateAttributeModifier(EFFECT_UUID, entry.getKey(), entry.getFloatValue() * effectScale, EFFECT_FACTORY, false); }); } @@ -244,7 +247,7 @@ public class AlicornAmuletItem extends AmuletItem implements ItemTracker.Trackab // bind to the player after 3 days if (daysAttached >= 3 && !pony.asEntity().isCreative()) { - stack = living.getArmour().getEquippedStack(TrinketsDelegate.NECKLACE); + stack = living.getArmour().getEquippedStack(TrinketsDelegate.NECKLACE).stack(); if (stack.getItem() == this && !EnchantmentHelper.hasBindingCurse(stack)) { pony.playSound(USounds.ITEM_ALICORN_AMULET_HALLUCINATION, 3, 1); stack = stack.copy(); diff --git a/src/main/java/com/minelittlepony/unicopia/item/AmuletItem.java b/src/main/java/com/minelittlepony/unicopia/item/AmuletItem.java index cb3426d5..8e0673f7 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/AmuletItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/AmuletItem.java @@ -86,7 +86,13 @@ public class AmuletItem extends WearableItem implements ChargeableItem { } public final boolean isApplicable(LivingEntity entity) { - return isApplicable(getForEntity(entity)); + return !getForEntity(entity).stack().isEmpty(); + } + + public TrinketsDelegate.EquippedStack getForEntity(LivingEntity entity) { + return TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.NECKLACE, this::isApplicable) + .findFirst() + .orElse(TrinketsDelegate.EquippedStack.EMPTY); } @Override @@ -94,11 +100,10 @@ public class AmuletItem extends WearableItem implements ChargeableItem { return maxEnergy; } - public static ItemStack getForEntity(LivingEntity entity) { + public static TrinketsDelegate.EquippedStack get(LivingEntity entity) { return TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.NECKLACE) - .filter(stack -> stack.getItem() instanceof AmuletItem) .findFirst() - .orElse(ItemStack.EMPTY); + .orElse(TrinketsDelegate.EquippedStack.EMPTY); } public static class ModifiersBuilder { diff --git a/src/main/java/com/minelittlepony/unicopia/item/BellItem.java b/src/main/java/com/minelittlepony/unicopia/item/BellItem.java index cf9af687..2b5f4d99 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/BellItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/BellItem.java @@ -69,7 +69,7 @@ public class BellItem extends Item implements ChargeableItem { @Override public TypedActionResult use(World world, PlayerEntity player, Hand hand) { ItemStack stack = player.getStackInHand(hand); - ItemStack offhandStack = AmuletItem.getForEntity(player); + ItemStack offhandStack = AmuletItem.get(player).stack(); if (!(offhandStack.getItem() instanceof ChargeableItem)) { offhandStack = player.getStackInHand(hand == Hand.MAIN_HAND ? Hand.OFF_HAND : Hand.MAIN_HAND); diff --git a/src/main/java/com/minelittlepony/unicopia/item/EnchantedStaffItem.java b/src/main/java/com/minelittlepony/unicopia/item/EnchantedStaffItem.java index 08798972..fdf89f0c 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/EnchantedStaffItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/EnchantedStaffItem.java @@ -9,9 +9,9 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.CastingMethod; import com.minelittlepony.unicopia.ability.magic.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; @@ -46,7 +46,10 @@ public class EnchantedStaffItem extends StaffItem implements EnchantableItem, Ch public static SpellType getSpellType(Entity entity, boolean remove) { if (entity instanceof CastSpellEntity cast) { - return cast.getSpellSlot().get(c -> !SpellPredicate.IS_PLACED.test(c), true).map(Spell::getType).orElse(SpellType.empty()); + return cast.getSpellSlot().get(SpellType.PLACE_CONTROL_SPELL.negate()) + .map(Spell::getTypeAndTraits) + .map(CustomisedSpellType::type) + .orElse(SpellType.empty()); } if (entity instanceof PlayerEntity player) { if (remove) { diff --git a/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java b/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java index 1828f66d..6fe650ea 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.item; import java.util.List; import java.util.UUID; +import java.util.function.Predicate; import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; @@ -120,28 +121,36 @@ public class FriendshipBraceletItem extends WearableItem implements DyeableItem, public static boolean isComrade(Owned caster, Entity entity) { return entity instanceof LivingEntity l && caster.getMasterId() - .filter(id -> getWornBangles(l).anyMatch(stack -> isSignedBy(stack, id))) + .filter(id -> getWornBangles(l).anyMatch(stack -> isSignedBy(stack.stack(), id))) .isPresent(); } public static boolean isComrade(UUID signator, Entity entity) { - return entity instanceof LivingEntity l && getWornBangles(l).anyMatch(stack -> isSignedBy(stack, signator)); + return entity instanceof LivingEntity l && getWornBangles(l, stack -> isSignedBy(stack, signator)).findAny().isPresent(); } public static Stream getPartyMembers(Caster caster, double radius) { return Pony.stream(caster.findAllEntitiesInRange(radius, entity -> isComrade(caster, entity))); } - public static Stream getWornBangles(LivingEntity entity) { + private static final Predicate IS_BANGLE = stack -> stack.isOf(UItems.FRIENDSHIP_BRACELET); + + public static Stream getWornBangles(LivingEntity entity) { return Stream.concat( - TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.MAINHAND), - TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.OFFHAND) - ).filter(stack -> stack.getItem() == UItems.FRIENDSHIP_BRACELET); + TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.MAIN_GLOVE, IS_BANGLE), + TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.SECONDARY_GLOVE, IS_BANGLE) + ); } - public static Stream getWornBangles(LivingEntity entity, Identifier slot) { - return TrinketsDelegate.getInstance(entity) - .getEquipped(entity, slot) - .filter(stack -> stack.getItem() == UItems.FRIENDSHIP_BRACELET); + public static Stream getWornBangles(LivingEntity entity, @Nullable Predicate predicate) { + predicate = predicate == null ? IS_BANGLE : IS_BANGLE.and(predicate); + return Stream.concat( + TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.MAIN_GLOVE, predicate), + TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.SECONDARY_GLOVE, predicate) + ); + } + + public static Stream getWornBangles(LivingEntity entity, Identifier slot) { + return TrinketsDelegate.getInstance(entity).getEquipped(entity, slot, IS_BANGLE); } } diff --git a/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java b/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java index cc2a70cf..c554972c 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/GemstoneItem.java @@ -8,7 +8,6 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; -import com.minelittlepony.unicopia.client.TextHelper; import com.minelittlepony.unicopia.entity.player.PlayerCharmTracker; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.group.MultiItem; @@ -17,9 +16,7 @@ import net.minecraft.client.item.TooltipContext; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.text.MutableText; import net.minecraft.text.Text; -import net.minecraft.util.Formatting; import net.minecraft.util.Hand; import net.minecraft.util.TypedActionResult; import net.minecraft.world.World; @@ -80,15 +77,7 @@ public class GemstoneItem extends Item implements MultiItem, EnchantableItem { super.appendTooltip(stack, world, lines, tooltipContext); if (EnchantableItem.isEnchanted(stack)) { - SpellType key = EnchantableItem.getSpellKey(stack); - - MutableText line = Text.translatable(key.getTranslationKey() + ".lore").formatted(key.getAffinity().getColor()); - - if (!InteractionManager.getInstance().getClientSpecies().canCast()) { - line = line.formatted(Formatting.OBFUSCATED); - } - - lines.addAll(TextHelper.wrap(line, 180).toList()); + getSpellEffect(stack).appendTooltip(lines); } } diff --git a/src/main/java/com/minelittlepony/unicopia/item/GlassesItem.java b/src/main/java/com/minelittlepony/unicopia/item/GlassesItem.java index 3c49931e..0e88bead 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/GlassesItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/GlassesItem.java @@ -25,13 +25,12 @@ public class GlassesItem extends WearableItem { } public boolean isApplicable(LivingEntity entity) { - return getForEntity(entity).getItem() == this; + return getForEntity(entity).stack().isOf(this); } - public static ItemStack getForEntity(LivingEntity entity) { - return TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.FACE) - .filter(stack -> stack.getItem() instanceof GlassesItem) + public static TrinketsDelegate.EquippedStack getForEntity(LivingEntity entity) { + return TrinketsDelegate.getInstance(entity).getEquipped(entity, TrinketsDelegate.FACE, stack -> stack.getItem() instanceof GlassesItem) .findFirst() - .orElse(ItemStack.EMPTY); + .orElse(TrinketsDelegate.EquippedStack.EMPTY); } } diff --git a/src/main/java/com/minelittlepony/unicopia/item/PegasusAmuletItem.java b/src/main/java/com/minelittlepony/unicopia/item/PegasusAmuletItem.java index b80e5f94..65cdb292 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/PegasusAmuletItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/PegasusAmuletItem.java @@ -25,11 +25,6 @@ public class PegasusAmuletItem extends AmuletItem implements ItemTracker.Trackab } - @Override - public boolean isApplicable(ItemStack stack) { - return super.isApplicable(stack); - } - @Override public int getDefaultCharge() { return getMaxCharge() / 2; diff --git a/src/main/java/com/minelittlepony/unicopia/item/UFoodComponents.java b/src/main/java/com/minelittlepony/unicopia/item/UFoodComponents.java index 165425f5..f278d3ad 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/UFoodComponents.java +++ b/src/main/java/com/minelittlepony/unicopia/item/UFoodComponents.java @@ -67,6 +67,7 @@ public interface UFoodComponents { FoodComponent ACORN = builder(1, 0.01F).snack().alwaysEdible().build(); FoodComponent MANGO = builder(8, 0.8F).alwaysEdible().build(); FoodComponent BANANA = builder(6, 0.9F).build(); + FoodComponent SEEDS = builder(1, 0.2F).build(); FoodComponent CANDY = builder(7, 0.9F).alwaysEdible().build(); FoodComponent SALT_CUBE = builder(0, 2.9F).alwaysEdible().build(); diff --git a/src/main/java/com/minelittlepony/unicopia/item/UItems.java b/src/main/java/com/minelittlepony/unicopia/item/UItems.java index 689e3e3d..ce6b0bcf 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/UItems.java +++ b/src/main/java/com/minelittlepony/unicopia/item/UItems.java @@ -159,6 +159,8 @@ public interface UItems { Item PALM_BOAT = ItemGroupRegistry.register(TerraformBoatItemHelper.registerBoatItem(Unicopia.id("palm_boat"), UWoodTypes.PALM_BOAT_TYPE, false), ItemGroups.FUNCTIONAL); Item PALM_CHEST_BOAT = ItemGroupRegistry.register(TerraformBoatItemHelper.registerBoatItem(Unicopia.id("palm_chest_boat"), UWoodTypes.PALM_BOAT_TYPE, true), ItemGroups.FUNCTIONAL); + Item PALM_SIGN = register("palm_sign", new SignItem(new Item.Settings(), UBlocks.PALM_SIGN, UBlocks.PALM_WALL_SIGN), ItemGroups.FUNCTIONAL); + Item PALM_HANGING_SIGN = register("palm_hanging_sign", new HangingSignItem(UBlocks.PALM_HANGING_SIGN, UBlocks.PALM_WALL_HANGING_SIGN, new Item.Settings()), ItemGroups.FUNCTIONAL); Item SPELLBOOK = register("spellbook", new SpellbookItem(new Item.Settings().maxCount(1).rarity(Rarity.UNCOMMON)), ItemGroups.TOOLS); @@ -218,6 +220,7 @@ public interface UItems { .rarity(Rarity.UNCOMMON), 0), ItemGroups.TOOLS); AmuletItem PEARL_NECKLACE = register("pearl_necklace", new AmuletItem(new FabricItemSettings() .maxCount(1) + .maxDamage(4) .rarity(Rarity.UNCOMMON), 0), ItemGroups.TOOLS); GlassesItem SUNGLASSES = register("sunglasses", new GlassesItem(new FabricItemSettings().maxCount(1)), ItemGroups.COMBAT); diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinAbstractSkeletonEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinAbstractSkeletonEntity.java new file mode 100644 index 00000000..62d7936d --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinAbstractSkeletonEntity.java @@ -0,0 +1,31 @@ +package com.minelittlepony.unicopia.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +import com.minelittlepony.unicopia.entity.ai.TargettingUtil; +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.mob.AbstractSkeletonEntity; +import net.minecraft.entity.mob.HostileEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.projectile.PersistentProjectileEntity; +import net.minecraft.util.math.Vec3d; + +@Mixin(AbstractSkeletonEntity.class) +abstract class MixinAbstractSkeletonEntity extends HostileEntity { + MixinAbstractSkeletonEntity() { super(null, null); } + + @ModifyArg(method = "attack", at = @At(value = "INVOKE", target = "net/minecraft/world/World.spawnEntity(Lnet/minecraft/entity/Entity;)Z")) + private Entity modifyAccuracy(Entity entity) { + if (entity instanceof PersistentProjectileEntity projectile && getTarget() instanceof PlayerEntity player && Pony.of(player).getPhysics().isFlying()) { + Vec3d targetPos = TargettingUtil.getProjectedPos(player) + .add(0, player.getHeight() * 0.33333F, 0) + .subtract(projectile.getPos()); + projectile.setVelocity(targetPos.x, targetPos.y + targetPos.horizontalLength() * 0.2, targetPos.z, 1.6F, (14 - getWorld().getDifficulty().getId() * 4) * 0.25F); + } + return entity; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinChunkBlockLightProvider.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinChunkBlockLightProvider.java index db7ec5cf..e8a40cfb 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinChunkBlockLightProvider.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinChunkBlockLightProvider.java @@ -37,16 +37,6 @@ abstract class MixinChunkBlockLightProvider extends ChunkLightProvider { } } - /* - @Inject(method = "getLightSourceLuminance", at = @At("RETURN"), cancellable = true) - private void onGetLightSourceLuminance(long blockPos, BlockState blockState, CallbackInfoReturnable info) { - int x = ChunkSectionPos.getSectionCoord(BlockPos.unpackLongX(blockPos)); - int z = ChunkSectionPos.getSectionCoord(BlockPos.unpackLongZ(blockPos)); - if (chunkProvider.getChunk(x, z) instanceof WorldChunk chunk) { - info.setReturnValue(Math.max(info.getReturnValue(), LightSources.get(chunk.getWorld()).getLuminance(blockPos))); - } - }*/ - @Inject(method = "method_51529", at = @At("TAIL")) private void onMethod_51529(long blockPos, CallbackInfo info) { long sectionPos = ChunkSectionPos.fromBlockPos(blockPos); diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java index 13644fbb..38934dc3 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinEntity.java @@ -11,6 +11,8 @@ 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.network.track.DataTrackerManager; +import com.minelittlepony.unicopia.network.track.Trackable; import com.minelittlepony.unicopia.EquinePredicates; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.magic.Caster; @@ -19,7 +21,6 @@ import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.duck.EntityDuck; import net.minecraft.entity.Entity; -import net.minecraft.entity.EntityType; import net.minecraft.entity.ItemEntity; import net.minecraft.entity.MovementType; import net.minecraft.entity.Entity.PositionUpdater; @@ -28,13 +29,14 @@ import net.minecraft.fluid.Fluid; import net.minecraft.item.ItemStack; import net.minecraft.registry.tag.TagKey; import net.minecraft.util.math.Vec3d; -import net.minecraft.world.World; @Mixin(Entity.class) -abstract class MixinEntity implements EntityDuck { +abstract class MixinEntity implements EntityDuck, Trackable { @Nullable private transient Caster host; + private DataTrackerManager dataTrackerManager; + @Override @Nullable public Caster getHost() { @@ -46,6 +48,20 @@ abstract class MixinEntity implements EntityDuck { this.host = host; } + @Override + public DataTrackerManager getDataTrackers() { + synchronized (this) { + if (dataTrackerManager == null) { + dataTrackerManager = new DataTrackerManager((Entity)(Object)this); + // ensure lazy registration happens + if (this instanceof Equine.Container eq) { + eq.get(); + } + } + return dataTrackerManager; + } + } + @Override @Accessor("submergedFluidTag") public abstract Set> getSubmergedFluidTags(); @@ -68,14 +84,6 @@ abstract class MixinEntity implements EntityDuck { return self.hasVehicle() && self.getVehicle() instanceof LavaAffine affine && affine.isLavaAffine(); } - - @Inject(method = "", at = @At(value = "INVOKE", target = "net/minecraft/entity/Entity.initDataTracker()V")) - private void onInstanceInit(EntityType type, World world, CallbackInfo info) { - if (this instanceof Equine.Container c) { - c.get().initDataTracker(); - } - } - @Inject(method = "isFireImmune", at = @At("HEAD"), cancellable = true) private void onIsFireImmune(CallbackInfoReturnable info) { if (isLavaAffine() || (this instanceof Equine.Container c) && c.get().getCompositeRace().includes(Race.KIRIN)) { diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinGuardianTargetPredicate.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinGuardianTargetPredicate.java index b7beed91..3b2f31b2 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinGuardianTargetPredicate.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinGuardianTargetPredicate.java @@ -1,21 +1,23 @@ package com.minelittlepony.unicopia.mixin; import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; 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.EquinePredicates; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.minelittlepony.unicopia.entity.effect.SeaponyGraceStatusEffect; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.mob.GuardianEntity; @Mixin(targets = "net.minecraft.entity.mob.GuardianEntity$GuardianTargetPredicate") abstract class MixinGuardianTargetPredicate { - @Inject(method = "test", at = @At("HEAD"), cancellable = true) - private void test(@Nullable LivingEntity livingEntity, CallbackInfoReturnable info) { - if (EquinePredicates.PLAYER_SEAPONY.test(livingEntity)) { - info.setReturnValue(false); - } + @Shadow + private @Final GuardianEntity owner; + + @ModifyReturnValue(method = "test", at = @At("RETURN")) + private boolean unicopia_excludeSeaponysGrace(boolean result, @Nullable LivingEntity target) { + return result && SeaponyGraceStatusEffect.hasIre(target, owner); } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java index 7d41f099..aafd1d35 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java @@ -6,6 +6,7 @@ import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -51,10 +52,12 @@ abstract class MixinLivingEntity extends Entity implements LivingEntityDuck, Equ @Override public Living get() { - if (caster == null) { - caster = create(); + synchronized (this) { + if (caster == null) { + caster = create(); + } + return (Living)caster; } - return (Living)caster; } @Override @@ -107,7 +110,7 @@ abstract class MixinLivingEntity extends Entity implements LivingEntityDuck, Equ @Inject(method = "isPushable()Z", at = @At("HEAD"), cancellable = true) private void onIsPushable(CallbackInfoReturnable info) { Caster.of(this) - .flatMap(c -> c.getSpellSlot().get(SpellPredicate.IS_DISGUISE, false)) + .flatMap(c -> c.getSpellSlot().get(SpellPredicate.IS_DISGUISE)) .map(AbstractDisguiseSpell::getDisguise) .map(EntityAppearance::getAppearance) .filter(Entity::isPushable) @@ -151,6 +154,11 @@ abstract class MixinLivingEntity extends Entity implements LivingEntityDuck, Equ get().onDamage(source, amount).ifPresent(info::setReturnValue); } + @ModifyVariable(method = "handleFallDamage(FFLnet/minecraft/entity/damage/DamageSource;)Z", at = @At("HEAD"), ordinal = 0, argsOnly = true) + private float onHandleFallDamage(float distance, float distanceAgain, float damageMultiplier, DamageSource cause) { + return get().onImpact(distance, damageMultiplier, cause); + } + @Inject(method = "hurtByWater()Z", at = @At("HEAD"), cancellable = true) private void onCanBeHurtByWater(CallbackInfoReturnable info) { TriState hurtByWater = get().canBeHurtByWater(); diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java index dcf21efe..36cc4a75 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLootableContainerBlockEntity.java @@ -10,6 +10,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.minelittlepony.unicopia.entity.mob.MimicEntity; +import net.fabricmc.fabric.api.util.TriState; import net.minecraft.block.entity.LockableContainerBlockEntity; import net.minecraft.block.entity.LootableContainerBlockEntity; import net.minecraft.entity.player.PlayerEntity; @@ -22,7 +23,7 @@ import net.minecraft.util.Identifier; abstract class MixinLootableContainerBlockEntity extends LockableContainerBlockEntity implements MimicEntity.MimicGeneratable { private Identifier mimicLootTable; private boolean allowMimics = true; - private boolean isMimic; + private TriState isMimic = TriState.DEFAULT; @Shadow @Nullable @@ -32,24 +33,25 @@ abstract class MixinLootableContainerBlockEntity extends LockableContainerBlockE @Inject(method = "deserializeLootTable", at = @At("HEAD")) private void deserializeMimic(NbtCompound nbt, CallbackInfoReturnable info) { - isMimic = nbt.getBoolean("mimic"); + isMimic = nbt.contains("mimic") ? TriState.of(nbt.getBoolean("mimic")) : TriState.DEFAULT; } @Inject(method = "serializeLootTable", at = @At("HEAD")) private void serializeMimic(NbtCompound nbt, CallbackInfoReturnable info) { - nbt.putBoolean("mimic", isMimic); + if (isMimic != TriState.DEFAULT) { + nbt.putBoolean("mimic", isMimic.get()); + } } @Override public void setAllowMimics(boolean allowMimics) { this.allowMimics = allowMimics; - this.isMimic &= allowMimics; markDirty(); } @Override public void setMimic(boolean mimic) { - isMimic = mimic; + isMimic = TriState.of(mimic); markDirty(); } @@ -68,10 +70,16 @@ abstract class MixinLootableContainerBlockEntity extends LockableContainerBlockE shift = Shift.AFTER ), cancellable = true) private void onCreateMenu(int syncId, PlayerInventory playerInventory, PlayerEntity player, CallbackInfoReturnable info) { - if (player != null && (isMimic || (allowMimics && MimicEntity.shouldConvert(player.getWorld(), getPos(), player, mimicLootTable)))) { - var mimic = MimicEntity.spawnFromChest(player.getWorld(), getPos()); - if (mimic != null) { - info.setReturnValue(mimic.createScreenHandler(syncId, playerInventory, player)); + if (player != null && allowMimics) { + if (isMimic == TriState.DEFAULT) { + isMimic = TriState.of(MimicEntity.shouldConvert(player.getWorld(), getPos(), player, mimicLootTable)); + } + + if (isMimic.get()) { + var mimic = MimicEntity.spawnFromChest(player.getWorld(), getPos()); + if (mimic != null) { + info.setReturnValue(mimic.createScreenHandler(syncId, playerInventory, player)); + } } mimicLootTable = null; } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java index 7c9bd778..9bf38549 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinMilkBucketItem.java @@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; +import com.minelittlepony.unicopia.ability.magic.SpellSlots; import net.minecraft.entity.LivingEntity; import net.minecraft.item.Item; @@ -20,6 +20,6 @@ abstract class MixinMilkBucketItem extends Item { @Inject(method = "finishUsing", at = @At("HEAD"), cancellable = true) private void finishUsing(ItemStack stack, World world, LivingEntity entity, CallbackInfoReturnable info) { - Caster.of(entity).map(Caster::getSpellSlot).ifPresent(SpellContainer::clear); + Caster.of(entity).map(Caster::getSpellSlot).ifPresent(SpellSlots::clear); } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerEntity.java index b67432fc..d04692bb 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerEntity.java @@ -26,7 +26,6 @@ import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.stat.Stats; import net.minecraft.util.Unit; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -48,24 +47,11 @@ abstract class MixinPlayerEntity extends LivingEntity implements Equine.Containe Pony.registerAttributes(info.getReturnValue()); } - @ModifyVariable(method = "applyDamage(Lnet/minecraft/entity/damage/DamageSource;F)V", at = @At("HEAD"), ordinal = 0) + @ModifyVariable(method = "applyDamage(Lnet/minecraft/entity/damage/DamageSource;F)V", at = @At("HEAD"), ordinal = 0, argsOnly = true) protected float modifyDamageAmount(float amount, DamageSource source) { return get().modifyDamage(source, amount).orElse(amount); } - @Inject(method = "handleFallDamage(FFLnet/minecraft/entity/damage/DamageSource;)Z", at = @At("HEAD"), cancellable = true) - private void onHandleFallDamage(float distance, float damageMultiplier, DamageSource cause, CallbackInfoReturnable info) { - get().onImpact(fallDistance, damageMultiplier, cause).ifPresent(newDistance -> { - PlayerEntity self = (PlayerEntity)(Object)this; - - if (distance >= 2) { - self.increaseStat(Stats.FALL_ONE_CM, Math.round(distance * 100)); - } - - info.setReturnValue(super.handleFallDamage(newDistance, damageMultiplier, cause)); - }); - } - @Inject(method = "eatFood(Lnet/minecraft/world/World;Lnet/minecraft/item/ItemStack;)Lnet/minecraft/item/ItemStack;", at = @At("HEAD")) private void onEatFood(World world, ItemStack stack, CallbackInfoReturnable info) { get().onEat(stack); diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPufferfishEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinPufferfishEntity.java index 1e799247..9231dafa 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPufferfishEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinPufferfishEntity.java @@ -2,12 +2,8 @@ package com.minelittlepony.unicopia.mixin; 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.player.Pony; - +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import com.minelittlepony.unicopia.entity.effect.SeaponyGraceStatusEffect; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.passive.FishEntity; import net.minecraft.entity.passive.PufferfishEntity; @@ -16,10 +12,8 @@ import net.minecraft.entity.passive.PufferfishEntity; abstract class MixinPufferfishEntity extends FishEntity { MixinPufferfishEntity() { super(null, null); } - @Inject(method = "method_6591(Lnet/minecraft/entity/LivingEntity;)Z", at = @At("HEAD"), cancellable = true) - private static void onShouldTarget(LivingEntity entity, CallbackInfoReturnable info) { - Pony.of(entity).filter(pony -> pony.getCompositeRace().includes(Race.SEAPONY)).ifPresent(pony -> { - info.setReturnValue(false); - }); + @ModifyReturnValue(method = "method_6591(Lnet/minecraft/entity/LivingEntity;)Z", at = @At("RETURN")) + private static boolean unicopia_excludeSeaponysGrace(boolean result, LivingEntity entity) { + return result && !SeaponyGraceStatusEffect.hasGrace(entity); } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinClientPlayNetworkHandler.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinClientPlayNetworkHandler.java index 06821d12..607c7bd9 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinClientPlayNetworkHandler.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinClientPlayNetworkHandler.java @@ -1,5 +1,7 @@ package com.minelittlepony.unicopia.mixin.client; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -10,13 +12,19 @@ import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.behaviour.Disguise; import com.minelittlepony.unicopia.entity.behaviour.EntityAppearance; +import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.network.track.Trackable; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.world.ClientWorld; import net.minecraft.network.packet.s2c.play.EntityStatusS2CPacket; +import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket; @Mixin(ClientPlayNetworkHandler.class) abstract class MixinClientPlayNetworkHandler { + @Shadow private @Final MinecraftClient client; @Shadow private ClientWorld world; @Inject(method = "onEntityStatus", at = @At("TAIL")) @@ -24,7 +32,7 @@ abstract class MixinClientPlayNetworkHandler { Living living = Living.living(packet.getEntity(world)); if (living != null) { living.getSpellSlot() - .get(SpellPredicate.IS_DISGUISE, false) + .get(SpellPredicate.IS_DISGUISE) .map(Disguise::getDisguise) .map(EntityAppearance::getAppearance) .ifPresent(appearance -> { @@ -32,4 +40,20 @@ abstract class MixinClientPlayNetworkHandler { }); } } + + @Nullable + private ClientPlayerEntity oldPlayer; + + @Inject(method = "onPlayerRespawn", at = @At("HEAD")) + public void beforeOnPlayerRespawn(PlayerRespawnS2CPacket packet, CallbackInfo info) { + oldPlayer = client.player; + } + + @Inject(method = "onPlayerRespawn", at = @At("RETURN")) + public void afterOnPlayerRespawn(PlayerRespawnS2CPacket packet, CallbackInfo info) { + if (oldPlayer != null && oldPlayer != client.player) { + Trackable.of(oldPlayer).getDataTrackers().copyTo(Trackable.of(client.player).getDataTrackers()); + Pony.of(client.player).copyFrom(Pony.of(oldPlayer), true); + } + } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinEntityTrackerEntry.java b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinEntityTrackerEntry.java new file mode 100644 index 00000000..d25a3e57 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinEntityTrackerEntry.java @@ -0,0 +1,36 @@ +package com.minelittlepony.unicopia.mixin.server; + +import java.util.function.Consumer; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.minelittlepony.unicopia.network.track.Trackable; + +import net.minecraft.entity.Entity; +import net.minecraft.network.listener.ClientPlayPacketListener; +import net.minecraft.network.packet.Packet; +import net.minecraft.server.network.EntityTrackerEntry; +import net.minecraft.server.network.ServerPlayerEntity; + +@Mixin(EntityTrackerEntry.class) +abstract class MixinEntityTrackerEntry { + @Shadow + private @Final Entity entity; + @Shadow + abstract void sendSyncPacket(Packet packet); + + @Inject(method = "tick()V", at = @At("TAIL")) + private void unicopia_onTick(CallbackInfo info) { + Trackable.of(entity).getDataTrackers().tick(this::sendSyncPacket); + } + + @Inject(method = "sendPackets", at = @At("RETURN")) + private void unicopia_onSendPackets(ServerPlayerEntity player, Consumer> sender, CallbackInfo info) { + Trackable.of(entity).getDataTrackers().sendInitial(player, sender); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerManager.java b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinPlayerManager.java similarity index 97% rename from src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerManager.java rename to src/main/java/com/minelittlepony/unicopia/mixin/server/MixinPlayerManager.java index ce8be7b9..0c845c1c 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinPlayerManager.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinPlayerManager.java @@ -1,4 +1,4 @@ -package com.minelittlepony.unicopia.mixin; +package com.minelittlepony.unicopia.mixin.server; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayNetworkHandler.java b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinServerPlayNetworkHandler.java similarity index 68% rename from src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayNetworkHandler.java rename to src/main/java/com/minelittlepony/unicopia/mixin/server/MixinServerPlayNetworkHandler.java index 9cd5d163..5e1d6537 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayNetworkHandler.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinServerPlayNetworkHandler.java @@ -1,4 +1,4 @@ -package com.minelittlepony.unicopia.mixin; +package com.minelittlepony.unicopia.mixin.server; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -16,36 +16,37 @@ import net.minecraft.server.network.ServerPlayerEntity; @Mixin(ServerPlayNetworkHandler.class) abstract class MixinServerPlayNetworkHandler implements ServerPlayPacketListener { + @Shadow public ServerPlayerEntity player; @Shadow private boolean floating; @Shadow private int floatingTicks; - private boolean flyingSurvival; private boolean prevMotionChecks; @Inject(method = "onPlayerMove(Lnet/minecraft/network/packet/c2s/play/PlayerMoveC2SPacket;)V", at = @At("HEAD")) private void beforePlayerMove(PlayerMoveC2SPacket packet, CallbackInfo info) { - ServerPlayerEntity player = ((ServerPlayNetworkHandler)(Object)this).player; NetworkThreadUtils.forceMainThread(packet, this, player.getServerWorld()); - flyingSurvival = Pony.of(player).getPhysics().isFlyingSurvival; - - if (flyingSurvival) { - setPreventMotionChecks(true); + prevMotionChecks = player.isInTeleportationState(); + if (Pony.of(player).getPhysics().isFlyingSurvival) { + ((ServerPlayerEntityDuck)player).setPreventMotionChecks(true); } } @Inject(method = "onPlayerMove(Lnet/minecraft/network/packet/c2s/play/PlayerMoveC2SPacket;)V", at = @At("RETURN")) private void afterPlayerMove(PlayerMoveC2SPacket packet, CallbackInfo info) { - if (flyingSurvival) { - setPreventMotionChecks(prevMotionChecks); + ((ServerPlayerEntityDuck)player).setPreventMotionChecks(prevMotionChecks); + if (Pony.of(player).getPhysics().isFlyingSurvival) { + floating = false; + floatingTicks = 0; + player.fallDistance = 0; } } - private void setPreventMotionChecks(boolean motionChecks) { - ServerPlayerEntity player = ((ServerPlayNetworkHandler)(Object)this).player; - prevMotionChecks = player.isInTeleportationState(); - ((ServerPlayerEntityDuck)player).setPreventMotionChecks(motionChecks); - player.fallDistance = 0; - floating = false; - floatingTicks = 0; + @Inject(method = "tick()V", at = @At("HEAD")) + private void beforePlayerTick(CallbackInfo info) { + if (Pony.of(player).getPhysics().isFlyingSurvival) { + floating = false; + floatingTicks = 0; + player.fallDistance = 0; + } } } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayerEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinServerPlayerEntity.java similarity index 98% rename from src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayerEntity.java rename to src/main/java/com/minelittlepony/unicopia/mixin/server/MixinServerPlayerEntity.java index 17bb934d..2ae21fe5 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerPlayerEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinServerPlayerEntity.java @@ -1,4 +1,4 @@ -package com.minelittlepony.unicopia.mixin; +package com.minelittlepony.unicopia.mixin.server; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerWorld.java b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinServerWorld.java similarity index 77% rename from src/main/java/com/minelittlepony/unicopia/mixin/MixinServerWorld.java rename to src/main/java/com/minelittlepony/unicopia/mixin/server/MixinServerWorld.java index 019d5cbd..b24c24ae 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinServerWorld.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinServerWorld.java @@ -1,6 +1,5 @@ -package com.minelittlepony.unicopia.mixin; +package com.minelittlepony.unicopia.mixin.server; -import java.util.List; import java.util.function.BooleanSupplier; import org.spongepowered.asm.mixin.Mixin; @@ -8,16 +7,13 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyConstant; -import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import com.minelittlepony.unicopia.server.world.BlockDestructionManager; import com.minelittlepony.unicopia.server.world.NocturnalSleepManager; import net.minecraft.block.BlockState; -import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; -import net.minecraft.server.world.SleepManager; import net.minecraft.util.math.BlockPos; import net.minecraft.world.StructureWorldAccess; import net.minecraft.world.World; @@ -57,11 +53,3 @@ abstract class MixinServerWorld extends World implements StructureWorldAccess, N getNocturnalSleepManager().skipTime(); } } - -@Mixin(SleepManager.class) -abstract class MixinSleepManager { - @ModifyVariable(method = "update(Ljava/util/List;)Z", at = @At("HEAD")) - public List modifyPlayers(List players) { - return players.size() <= 0 ? players : ((NocturnalSleepManager.Source)players.get(0).getWorld()).getNocturnalSleepManager().filterPlayers(players); - } -} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinSleepManager.java b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinSleepManager.java new file mode 100644 index 00000000..76c18b65 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/server/MixinSleepManager.java @@ -0,0 +1,20 @@ +package com.minelittlepony.unicopia.mixin.server; + +import java.util.List; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import com.minelittlepony.unicopia.server.world.NocturnalSleepManager; + +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.SleepManager; + +@Mixin(SleepManager.class) +abstract class MixinSleepManager { + @ModifyVariable(method = "update(Ljava/util/List;)Z", at = @At("HEAD")) + public List modifyPlayers(List players) { + return players.size() <= 0 ? players : ((NocturnalSleepManager.Source)players.get(0).getWorld()).getNocturnalSleepManager().filterPlayers(players); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/Channel.java b/src/main/java/com/minelittlepony/unicopia/network/Channel.java index 8e6bc8c2..ee3b3025 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/Channel.java +++ b/src/main/java/com/minelittlepony/unicopia/network/Channel.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.network; import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.network.track.MsgTrackedValues; import com.minelittlepony.unicopia.server.world.UnicopiaWorldProperties; import com.minelittlepony.unicopia.server.world.ZapAppleStageStore; import com.sollace.fabwork.api.packets.*; @@ -31,11 +32,12 @@ public interface Channel { S2CPacketType SERVER_RESOURCES = SimpleNetworking.serverToClient(Unicopia.id("resources"), MsgServerResources::new); - S2CPacketType SERVER_OTHER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClient(Unicopia.id("other_player_capabilities"), MsgOtherPlayerCapabilities::new); - S2CPacketType SERVER_PLAYER_ANIMATION_CHANGE = SimpleNetworking.serverToClient(Unicopia.id("other_player_animation_change"), MsgPlayerAnimationChange::new); + S2CPacketType SERVER_TRACKED_ENTITY_DATA = SimpleNetworking.serverToClient(Unicopia.id("tracked_entity_date"), MsgTrackedValues::new); + S2CPacketType SERVER_PLAYER_ANIMATION_CHANGE = SimpleNetworking.serverToClient(Unicopia.id("player_animation_change"), MsgPlayerAnimationChange::new); S2CPacketType SERVER_SKY_ANGLE = SimpleNetworking.serverToClient(Unicopia.id("sky_angle"), MsgSkyAngle::new); S2CPacketType CONFIGURATION_CHANGE = SimpleNetworking.serverToClient(Unicopia.id("config"), MsgConfigurationChange::new); S2CPacketType SERVER_ZAP_STAGE = SimpleNetworking.serverToClient(Unicopia.id("zap_stage"), MsgZapAppleStage::new); + S2CPacketType SERVER_TRINKET_BROKEN = SimpleNetworking.serverToClient(Unicopia.id("trinket_broken"), MsgTrinketBroken::new); static void bootstrap() { ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgCasterLookRequest.java b/src/main/java/com/minelittlepony/unicopia/network/MsgCasterLookRequest.java index c0dd8e93..96839624 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgCasterLookRequest.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgCasterLookRequest.java @@ -48,11 +48,10 @@ public record MsgCasterLookRequest (UUID spellId) implements Packet { @Override public void handle(ServerPlayerEntity sender) { - Pony.of(sender).getSpellSlot() - .get(SpellPredicate.IS_ORIENTED.withId(spellId), false) - .ifPresent(spell -> { - spell.setOrientation(rotation); - }); + Pony pony = Pony.of(sender); + pony.getSpellSlot() + .get(SpellPredicate.IS_ORIENTED.withId(spellId)) + .ifPresent(spell -> spell.setOrientation(pony, rotation)); } } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgOtherPlayerCapabilities.java b/src/main/java/com/minelittlepony/unicopia/network/MsgOtherPlayerCapabilities.java deleted file mode 100644 index d0492f07..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgOtherPlayerCapabilities.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.minelittlepony.unicopia.network; - -import com.minelittlepony.unicopia.entity.player.Pony; - -import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.network.PacketByteBuf; - -/** - * Sent by the server to update other player's capabilities. - */ -public class MsgOtherPlayerCapabilities extends MsgPlayerCapabilities { - - MsgOtherPlayerCapabilities(PacketByteBuf buffer) { - super(buffer); - } - - public MsgOtherPlayerCapabilities(Pony player) { - super(player); - } - - @Override - protected Pony getRecipient(PlayerEntity sender) { - return Pony.of(MinecraftClient.getInstance().world.getPlayerByUuid(playerId)); - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java index a665b194..79cfab5b 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java @@ -1,20 +1,12 @@ package com.minelittlepony.unicopia.network; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.UUID; - -import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.util.serialization.PacketCodec; import com.sollace.fabwork.api.packets.HandledPacket; -import io.netty.buffer.ByteBufInputStream; -import io.netty.buffer.ByteBufOutputStream; import net.minecraft.network.PacketByteBuf; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtIo; /** * Sent to the client to update various data pertaining to a particular player. @@ -23,46 +15,32 @@ import net.minecraft.nbt.NbtIo; */ public class MsgPlayerCapabilities implements HandledPacket { - protected final UUID playerId; + protected final int playerId; private final NbtCompound compoundTag; MsgPlayerCapabilities(PacketByteBuf buffer) { - playerId = buffer.readUuid(); - try (InputStream in = new ByteBufInputStream(buffer)) { - compoundTag = NbtIo.readCompressed(in); - } catch (IOException e) { - throw new RuntimeException(e); - } + playerId = buffer.readInt(); + compoundTag = PacketCodec.COMPRESSED_NBT.read(buffer); } public MsgPlayerCapabilities(Pony player) { - playerId = player.asEntity().getUuid(); + playerId = player.asEntity().getId(); compoundTag = new NbtCompound(); player.toSyncronisedNbt(compoundTag); } @Override public void toBuffer(PacketByteBuf buffer) { - buffer.writeUuid(playerId); - try (OutputStream out = new ByteBufOutputStream(buffer)) { - NbtIo.writeCompressed(compoundTag, out); - } catch (IOException e) { - } + buffer.writeInt(playerId); + PacketCodec.COMPRESSED_NBT.write(buffer, compoundTag); } @Override public void handle(PlayerEntity sender) { - Pony player = getRecipient(sender); - if (player == null) { - Unicopia.LOGGER.warn("Skipping capabilities for unknown player " + playerId.toString()); - return; + Pony player = Pony.of(sender.getWorld().getEntityById(playerId)).orElse(null); + if (player != null) { + player.fromSynchronizedNbt(compoundTag); } - - player.fromSynchronizedNbt(compoundTag); - } - - protected Pony getRecipient(PlayerEntity sender) { - return Pony.of(sender); } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgTrinketBroken.java b/src/main/java/com/minelittlepony/unicopia/network/MsgTrinketBroken.java new file mode 100644 index 00000000..501be316 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgTrinketBroken.java @@ -0,0 +1,21 @@ +package com.minelittlepony.unicopia.network; + +import com.sollace.fabwork.api.packets.Packet; + +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; + +/** + * Sent to the client when an item equipped in a trinket slot breaks + */ +public record MsgTrinketBroken (ItemStack stack, int entityId) implements Packet { + public MsgTrinketBroken(PacketByteBuf buffer) { + this(buffer.readItemStack(), buffer.readInt()); + } + + @Override + public void toBuffer(PacketByteBuf buffer) { + buffer.writeItemStack(stack); + buffer.writeInt(entityId); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java deleted file mode 100644 index c66cac4c..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.minelittlepony.unicopia.network.datasync; - -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.jetbrains.annotations.Nullable; - -import com.minelittlepony.unicopia.Unicopia; -import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; -import com.minelittlepony.unicopia.ability.magic.SpellPredicate; -import com.minelittlepony.unicopia.ability.magic.spell.Situation; -import com.minelittlepony.unicopia.ability.magic.spell.Spell; -import com.minelittlepony.unicopia.util.NbtSerialisable; - -import net.minecraft.entity.data.TrackedData; -import net.minecraft.nbt.NbtCompound; - -/** - * Synchronisation class for spells. - * Since we can't have our own serializers, we have to intelligently - * determine whether to update it from an nbt tag. - * - * @param The owning entity - */ -public class EffectSync implements SpellContainer, NbtSerialisable { - - private final NetworkedReferenceSet spells; - - private final Caster owner; - - private final TrackedData param; - - @Nullable - private NbtCompound lastValue; - - public EffectSync(Caster owner, TrackedData param) { - spells = new NetworkedReferenceSet<>(Spell::getUuid, () -> new SpellNetworkedReference<>(owner)); - this.owner = owner; - this.param = param; - } - - public void initDataTracker() { - owner.asEntity().getDataTracker().startTracking(param, new NbtCompound()); - } - - public boolean tick(Situation situation) { - return tick(spell -> { - if (spell.isDying()) { - spell.tickDying(owner); - return Operation.ofBoolean(!spell.isDead()); - } - return Operation.ofBoolean(spell.tick(owner, situation)); - }); - } - - public boolean tick(Function tickAction) { - try { - return forEach(spell -> { - try { - return tickAction.apply(spell); - } catch (Throwable t) { - Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner, t); - } - return Operation.REMOVE; - }, owner.isClient()); - } catch (Exception e) { - Unicopia.LOGGER.error("Error whilst ticking spell on entity {}", owner.asEntity(), e); - } - return false; - } - - @Override - public boolean contains(UUID id) { - return spells.containsReference(id) || spells.getReferences().anyMatch(s -> s.equalsOrContains(id)); - } - - @Override - public boolean contains(@Nullable SpellPredicate type) { - return read(type, true, false).findFirst().isPresent(); - } - - @Override - public Optional get(@Nullable SpellPredicate type, boolean update) { - return read(type, update, true).findFirst(); - } - - @Override - public void put(@Nullable Spell effect) { - spells.addReference(effect); - write(); - if (owner instanceof UpdateCallback callback) { - callback.onSpellSet(effect); - } - } - - @Override - public void remove(UUID id) { - Spell spell = spells.getReference(id); - spell.setDead(); - spell.tickDying(owner); - if (spell.isDead()) { - spells.removeReference(id); - } - } - - @Override - public boolean removeWhere(Predicate test, boolean update) { - return reduce(update, (initial, spell) -> { - if (!test.test(spell)) { - return initial; - } - spell.setDead(); - spell.tickDying(owner); - if (spell.isDead()) { - spells.removeReference(spell); - } - return true; - }); - } - - @Override - public boolean forEach(Function test, boolean update) { - return reduce(update, (initial, effect) -> { - Operation op = test.apply(effect); - if (op == Operation.REMOVE) { - spells.removeReference(effect); - } else { - initial |= op != Operation.SKIP; - } - return initial; - }); - } - - @Override - public Stream stream(boolean update) { - return stream(null, update); - } - - @Override - public Stream stream(@Nullable SpellPredicate type, boolean update) { - return read(type, update, true); - } - - @Override - public boolean clear() { - if (spells.clear()) { - write(); - if (owner instanceof UpdateCallback) { - ((UpdateCallback)owner).onSpellSet(null); - } - return true; - } - return false; - } - - @SuppressWarnings("unchecked") - private Stream read(@Nullable SpellPredicate type, boolean synchronize, boolean sendUpdate) { - if (synchronize && spells.fromNbt(owner.asEntity().getDataTracker().get(param)) && sendUpdate) { - write(); - } - - if (type == null) { - return (Stream)spells.getReferences(); - } - return (Stream)spells.getReferences().flatMap(s -> s.findMatches(type)); - } - - private boolean reduce(boolean update, Alteration alteration) { - boolean initial = false; - for (Spell i : read(null, update, false).toList()) { - initial = alteration.apply(initial, i); - } - - write(); - return initial; - } - - private void write() { - if (spells.isDirty()) { - owner.asEntity().getDataTracker().set(param, spells.toNbt()); - } - } - - @Override - public void toNBT(NbtCompound compound) { - compound.put("spells", spells.toNbt()); - } - - @Override - public void fromNBT(NbtCompound compound) { - spells.fromNbt(compound.getCompound("spells")); - owner.asEntity().getDataTracker().set(param, spells.toNbt()); - } - - public interface UpdateCallback { - void onSpellSet(@Nullable Spell spell); - } - - private interface Alteration { - boolean apply(boolean initial, Spell spell); - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReference.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReference.java deleted file mode 100644 index cb1a42d3..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReference.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.minelittlepony.unicopia.network.datasync; - -import java.util.Optional; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.nbt.NbtCompound; - -public interface NetworkedReference { - Optional getReference(); - - void updateReference(@Nullable T newValue); - - boolean fromNbt(NbtCompound comp); - - NbtCompound toNbt(); - - boolean isDirty(); -} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReferenceSet.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReferenceSet.java deleted file mode 100644 index 90582e52..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/NetworkedReferenceSet.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.minelittlepony.unicopia.network.datasync; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.nbt.NbtCompound; -import net.minecraft.nbt.NbtElement; -import net.minecraft.nbt.NbtList; -import net.minecraft.nbt.NbtString; - -public class NetworkedReferenceSet { - - private final List ids = new ArrayList<>(); - - private final Map> values = new HashMap<>(); - - private final Function uuidConverter; - private final Supplier> factory; - - private boolean dirty; - private boolean reading; - - public NetworkedReferenceSet(Function uuidConverter, Supplier> factory) { - this.uuidConverter = uuidConverter; - this.factory = factory; - } - - public synchronized boolean containsReference(UUID id) { - return ids.contains(id); - } - - public synchronized Stream getReferences() { - return ids.stream().map(id -> values.get(id)) - .filter(Objects::nonNull) - .map(a -> a.getReference()) - .filter(Optional::isPresent) - .map(Optional::get); - } - - public synchronized boolean clear() { - dirty |= !ids.isEmpty() || !values.isEmpty(); - ids.clear(); - try { - reading = true; - for (NetworkedReference reference : values.values()) { - reference.updateReference(null); - } - } finally { - reading = false; - values.clear(); - } - return dirty; - } - - public void addReference(@Nullable T newValue) { - if (newValue != null) { - addReference(uuidConverter.apply(newValue)).updateReference(newValue); - } - } - - private synchronized NetworkedReference addReference(UUID newValue) { - return values.computeIfAbsent(newValue, id -> { - dirty = true; - ids.remove(id); - ids.add(0, id); - return factory.get(); - }); - } - - public void removeReference(@Nullable T oldValue) { - if (oldValue != null) { - removeReference(uuidConverter.apply(oldValue)); - } - } - - @Nullable - synchronized T getReference(UUID id) { - NetworkedReference i = values.get(id); - return i == null ? null : i.getReference().orElse(null); - } - - synchronized void removeReference(UUID id) { - dirty |= ids.remove(id); - NetworkedReference i = values.remove(id); - if (i != null) { - dirty = true; - try { - reading = true; - i.updateReference(null); - } finally { - reading = false; - } - } - } - - public boolean fromNbt(NbtCompound comp) { - if (reading) { - return false; - } - reading = true; - try { - List incoming = new ArrayList<>(); - comp.getList("keys", NbtElement.STRING_TYPE).forEach(key -> { - incoming.add(UUID.fromString(key.asString())); - }); - - ids.stream().filter(id -> !incoming.contains(id)).toList().forEach(this::removeReference); - - boolean[] send = new boolean[1]; - incoming.forEach(key -> { - NetworkedReference i = addReference(key); - send[0] |= i.fromNbt(comp.getCompound(key.toString())); - if (i.getReference().isEmpty()) { - removeReference(key); - } - }); - dirty = send[0]; - return send[0]; - } finally { - reading = false; - } - } - - public synchronized NbtCompound toNbt() { - NbtCompound tag = new NbtCompound(); - NbtList ids = new NbtList(); - this.ids.forEach(id -> { - String sid = id.toString(); - NetworkedReference ref = values.get(id); - if (ref != null) { - ids.add(NbtString.of(sid)); - tag.put(sid, values.get(id).toNbt()); - } - }); - tag.put("keys", ids); - dirty = false; - return tag; - } - - public synchronized boolean isDirty() { - return dirty || values.values().stream().anyMatch(NetworkedReference::isDirty); - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java deleted file mode 100644 index fbc8ba3b..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/SpellNetworkedReference.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.minelittlepony.unicopia.network.datasync; - -import java.util.Optional; -import org.jetbrains.annotations.Nullable; - -import com.minelittlepony.unicopia.ability.magic.Caster; -import com.minelittlepony.unicopia.ability.magic.spell.Spell; -import com.minelittlepony.unicopia.ability.magic.spell.SpellReference; - -import net.minecraft.nbt.NbtCompound; - -public class SpellNetworkedReference implements NetworkedReference { - private final SpellReference currentValue = new SpellReference<>(); - private final Caster owner; - private boolean dirty; - - public SpellNetworkedReference(Caster owner) { - this.owner = owner; - } - - @Override - public Optional getReference() { - return Optional.ofNullable(currentValue.get()); - } - - @Override - public void updateReference(@Nullable T newValue) { - dirty |= currentValue.set(newValue, owner); - } - - @Override - public boolean fromNbt(NbtCompound comp) { - dirty = false; - currentValue.fromNBT(comp, owner.isClient()); - return isDirty(); - } - - @Override - public NbtCompound toNbt() { - dirty = false; - return currentValue.toNBT(); - } - - @Override - public boolean isDirty() { - return !owner.isClient() && (dirty || currentValue.hasDirtySpell()); - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/Transmittable.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/Transmittable.java deleted file mode 100644 index 8895bd77..00000000 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/Transmittable.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.minelittlepony.unicopia.network.datasync; - -import net.minecraft.nbt.NbtCompound; - -public interface Transmittable { - void setDirty(); - - void toSyncronisedNbt(NbtCompound compound); - - void fromSynchronizedNbt(NbtCompound compound); -} diff --git a/src/main/java/com/minelittlepony/unicopia/network/handler/ClientNetworkHandlerImpl.java b/src/main/java/com/minelittlepony/unicopia/network/handler/ClientNetworkHandlerImpl.java index e17dc1ff..c014bce5 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/handler/ClientNetworkHandlerImpl.java +++ b/src/main/java/com/minelittlepony/unicopia/network/handler/ClientNetworkHandlerImpl.java @@ -20,8 +20,14 @@ import com.minelittlepony.unicopia.network.*; import com.minelittlepony.unicopia.network.MsgCasterLookRequest.Reply; import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.particle.ItemStackParticleEffect; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.sound.SoundEvents; import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; public class ClientNetworkHandlerImpl { private final MinecraftClient client = MinecraftClient.getInstance(); @@ -36,6 +42,7 @@ public class ClientNetworkHandlerImpl { Channel.SERVER_ZAP_STAGE.receiver().addPersistentListener(this::handleZapStage); Channel.SERVER_PLAYER_ANIMATION_CHANGE.receiver().addPersistentListener(this::handlePlayerAnimation); Channel.SERVER_REQUEST_PLAYER_LOOK.receiver().addPersistentListener(this::handleCasterLookRequest); + Channel.SERVER_TRINKET_BROKEN.receiver().addPersistentListener(this::handleTrinketBroken); Channel.CONFIGURATION_CHANGE.receiver().addPersistentListener(this::handleConfigurationChange); } @@ -96,6 +103,37 @@ public class ClientNetworkHandlerImpl { Channel.CLIENT_CASTER_LOOK.sendToServer(new Reply(packet.spellId(), Rot.of(player))); } + private void handleTrinketBroken(PlayerEntity player, MsgTrinketBroken packet) { + if (player.getWorld().getEntityById(packet.entityId()) instanceof LivingEntity sender) { + ItemStack stack = packet.stack(); + if (!stack.isEmpty()) { + if (!sender.isSilent()) { + sender.getWorld().playSound( + sender.getX(), sender.getY(), sender.getZ(), + SoundEvents.ENTITY_ITEM_BREAK, sender.getSoundCategory(), + 0.8F, + 0.8F + sender.getWorld().random.nextFloat() * 0.4F, + false + ); + } + + int count = 5; + + for (int i = 0; i < count; ++i) { + Vec3d vec3d = new Vec3d((sender.getWorld().random.nextFloat() - 0.5) * 0.1, Math.random() * 0.1 + 0.1, 0.0); + vec3d = vec3d.rotateX(-sender.getPitch() * (float) (Math.PI / 180.0)); + vec3d = vec3d.rotateY(-sender.getYaw() * (float) (Math.PI / 180.0)); + double d = (-sender.getWorld().random.nextFloat()) * 0.6 - 0.3; + Vec3d vec3d2 = new Vec3d((sender.getWorld().random.nextFloat() - 0.5) * 0.3, d, 0.6); + vec3d2 = vec3d2.rotateX(-sender.getPitch() * (float) (Math.PI / 180.0)); + vec3d2 = vec3d2.rotateY(-sender.getYaw() * (float) (Math.PI / 180.0)); + vec3d2 = vec3d2.add(sender.getX(), sender.getEyeY(), sender.getZ()); + sender.getWorld().addParticle(new ItemStackParticleEffect(ParticleTypes.ITEM, stack), vec3d2.x, vec3d2.y, vec3d2.z, vec3d.x, vec3d.y + 0.05, vec3d.z); + } + } + } + } + private void handleConfigurationChange(PlayerEntity sender, MsgConfigurationChange packet) { InteractionManager.getInstance().setSyncedConfig(packet.config()); } diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/DataTracker.java b/src/main/java/com/minelittlepony/unicopia/network/track/DataTracker.java new file mode 100644 index 00000000..1bc68115 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/track/DataTracker.java @@ -0,0 +1,167 @@ +package com.minelittlepony.unicopia.network.track; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.network.PacketByteBuf; + +public class DataTracker { + private final List> codecs = new ObjectArrayList<>(); + private IntSet dirtyIndices = new IntOpenHashSet(); + private List> persistentObjects = new ObjectArrayList<>(); + + private boolean initial = true; + + final int id; + + public DataTracker(int id) { + this.id = id; + } + + public > T startTracking(T value) { + persistentObjects.add(value); + return value; + } + + public Entry startTracking(TrackableDataType type, T initialValue) { + Entry entry = new Entry<>(this, codecs.size()); + codecs.add(new Pair<>(entry.id(), type, initialValue)); + return entry; + } + + @SuppressWarnings("unchecked") + private Pair getPair(Entry entry) { + return (Pair)codecs.get(entry.id()); + } + + private T get(Entry entry) { + return getPair(entry).value; + } + + private void set(Entry entry, T value) { + Pair pair = getPair(entry); + if (!Objects.equals(pair.value, value)) { + synchronized (this) { + pair.value = value; + dirtyIndices.add(entry.id()); + } + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + synchronized void copyTo(DataTracker destination) { + for (int i = 0; i < codecs.size(); i++) { + ((Pair)destination.codecs.get(i)).value = codecs.get(i).value; + if (i < persistentObjects.size() && i < destination.persistentObjects.size()) { + TrackableObject a = persistentObjects.get(i); + TrackableObject b = destination.persistentObjects.get(i); + if (a != null && b != null) { + ((TrackableObject)a).copyTo(b); + } + } + } + } + + synchronized Optional getInitialPairs() { + initial = false; + dirtyIndices = new IntOpenHashSet(); + return Optional.of(new MsgTrackedValues.TrackerEntries(id, true, codecs, writePersistentObjects(true))); + } + + public synchronized Optional getDirtyPairs() { + if (initial) { + return getInitialPairs(); + } + + Map updates = writePersistentObjects(false); + + if (dirtyIndices.isEmpty() && updates.isEmpty()) { + return Optional.empty(); + } + + IntSet toSend = dirtyIndices; + dirtyIndices = new IntOpenHashSet(); + List> pairs = new ArrayList<>(); + for (int i : toSend) { + pairs.add(codecs.get(i)); + } + return Optional.of(new MsgTrackedValues.TrackerEntries(id, false, pairs, updates)); + } + + private Map writePersistentObjects(boolean initial) { + Map updates = new HashMap<>(); + for (int i = 0; i < persistentObjects.size(); i++) { + TrackableObject o = persistentObjects.get(i); + TrackableObject.Status status = initial ? TrackableObject.Status.NEW : o.getStatus(); + int id = i; + o.write(status).ifPresent(data -> updates.put(id, data)); + } + return updates; + } + + public synchronized void load(MsgTrackedValues.TrackerEntries values) { + if (values.wipe()) { + codecs.clear(); + codecs.addAll(values.values()); + } else { + for (var value : values.values()) { + if (value.id >= 0 && value.id < codecs.size()) { + if (codecs.get(value.id).type == value.type) { + codecs.set(value.id, value); + } + } + } + } + + for (var entry : values.objects().entrySet()) { + TrackableObject o = persistentObjects.get(entry.getKey()); + if (o != null) { + o.read(entry.getValue()); + } + } + } + + public record Entry(DataTracker tracker, int id) { + public T get() { + return tracker.get(this); + } + + public void set(T t) { + tracker.set(this, t); + } + } + + static class Pair { + private final TrackableDataType type; + public final int id; + public T value; + + public Pair(int id, TrackableDataType type, T value) { + this.id = id; + this.type = type; + this.value = value; + } + + public Pair(PacketByteBuf buffer) { + this.id = buffer.readInt(); + this.type = TrackableDataType.of(buffer); + this.value = type.read(buffer); + } + + public void write(PacketByteBuf buffer) { + buffer.writeInt(id); + type.write(buffer, value); + } + } + + public interface Updater { + void update(T t); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/DataTrackerManager.java b/src/main/java/com/minelittlepony/unicopia/network/track/DataTrackerManager.java new file mode 100644 index 00000000..5b3cc76b --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/track/DataTrackerManager.java @@ -0,0 +1,120 @@ +package com.minelittlepony.unicopia.network.track; + +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.network.Channel; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.entity.Entity; +import net.minecraft.network.listener.ClientPlayPacketListener; +import net.minecraft.network.packet.Packet; +import net.minecraft.server.network.ServerPlayerEntity; + +public class DataTrackerManager { + private final Entity entity; + final boolean isClient; + private final List trackers = new ObjectArrayList<>(); + private final List> objectTrackers = new ObjectArrayList<>(); + private final List packetEmitters = new ObjectArrayList<>(); + + @Nullable + private DataTracker primaryTracker; + + public DataTrackerManager(Entity entity) { + this.entity = entity; + this.isClient = entity.getWorld().isClient; + } + + public synchronized void addPacketEmitter(PacketEmitter packetEmitter) { + packetEmitters.add(packetEmitter); + } + + public DataTracker getPrimaryTracker() { + if (primaryTracker == null) { + primaryTracker = checkoutTracker(); + } + return primaryTracker; + } + + public synchronized DataTracker checkoutTracker() { + DataTracker tracker = new DataTracker(trackers.size()); + trackers.add(tracker); + packetEmitters.add((sender, initial) -> { + var update = initial ? tracker.getInitialPairs() : tracker.getDirtyPairs(); + if (update.isPresent()) { + sender.accept(Channel.SERVER_TRACKED_ENTITY_DATA.toPacket(new MsgTrackedValues( + entity.getId(), + Optional.empty(), + update + ))); + } + }); + return tracker; + } + + public synchronized > ObjectTracker checkoutTracker(Supplier objFunction) { + ObjectTracker tracker = new ObjectTracker<>(objectTrackers.size(), objFunction); + objectTrackers.add(tracker); + packetEmitters.add((sender, initial) -> { + var update = initial ? tracker.getInitialPairs() : tracker.getDirtyPairs(); + if (update.isPresent()) { + sender.accept(Channel.SERVER_TRACKED_ENTITY_DATA.toPacket(new MsgTrackedValues( + entity.getId(), + update, + Optional.empty() + ))); + } + }); + return tracker; + } + + public void tick(Consumer> sender) { + synchronized (this) { + for (var emitter : packetEmitters) { + emitter.sendPackets(sender, false); + } + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public synchronized void copyTo(DataTrackerManager destination) { + for (int i = 0; i < trackers.size(); i++) { + trackers.get(i).copyTo(i >= destination.trackers.size() ? destination.checkoutTracker() : destination.trackers.get(i)); + } + for (int i = 0; i < objectTrackers.size(); i++) { + ((ObjectTracker)objectTrackers.get(i)).copyTo(destination.objectTrackers.get(i)); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public synchronized void sendInitial(ServerPlayerEntity player, Consumer> sender) { + synchronized (this) { + for (var emitter : packetEmitters) { + emitter.sendPackets((Consumer)sender, true); + } + } + } + + synchronized void load(MsgTrackedValues packet) { + packet.updatedTrackers().ifPresent(update -> { + DataTracker tracker = trackers.get(update.id()); + if (tracker != null) { + tracker.load(update); + } + }); + packet.updatedObjects().ifPresent(update -> { + ObjectTracker tracker = objectTrackers.get(update.id()); + if (tracker != null) { + tracker.load(update); + } + }); + } + + public interface PacketEmitter { + void sendPackets(Consumer> consumer, boolean initial); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/MsgTrackedValues.java b/src/main/java/com/minelittlepony/unicopia/network/track/MsgTrackedValues.java new file mode 100644 index 00000000..d9c405cc --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/track/MsgTrackedValues.java @@ -0,0 +1,81 @@ +package com.minelittlepony.unicopia.network.track; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import com.minelittlepony.unicopia.util.serialization.PacketCodec; +import com.sollace.fabwork.api.packets.HandledPacket; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; + +public record MsgTrackedValues( + int owner, + Optional updatedObjects, + Optional updatedTrackers +) implements HandledPacket { + public MsgTrackedValues(PacketByteBuf buffer) { + this( + buffer.readInt(), + buffer.readOptional(TrackerObjects::new), + buffer.readOptional(TrackerEntries::new) + ); + } + + @Override + public void toBuffer(PacketByteBuf buffer) { + buffer.writeInt(owner); + buffer.writeOptional(updatedObjects, (buf, obj) -> obj.write(buf)); + buffer.writeOptional(updatedTrackers, (buf, tracker) -> tracker.write(buf)); + } + + @Override + public void handle(PlayerEntity sender) { + Entity entity = sender.getWorld().getEntityById(owner); + if (entity instanceof Trackable trackable) { + trackable.getDataTrackers().load(this); + } + } + + public record TrackerObjects(int id, Set removedValues, Map values) { + public TrackerObjects(PacketByteBuf buffer) { + this( + buffer.readInt(), + buffer.readCollection(HashSet::new, PacketByteBuf::readUuid), + buffer.readMap(HashMap::new, PacketByteBuf::readUuid, PacketCodec.RAW_BYTES::read) + ); + + } + + public void write(PacketByteBuf buffer) { + buffer.writeInt(id); + buffer.writeCollection(removedValues, PacketByteBuf::writeUuid); + buffer.writeMap(values, PacketByteBuf::writeUuid, PacketCodec.RAW_BYTES::write); + } + } + + public record TrackerEntries(int id, boolean wipe, List> values, Map objects) { + public TrackerEntries(PacketByteBuf buffer) { + this( + buffer.readInt(), + buffer.readBoolean(), + buffer.readCollection(ArrayList::new, DataTracker.Pair::new), + buffer.readMap(PacketByteBuf::readInt, PacketCodec.RAW_BYTES::read) + ); + } + + public void write(PacketByteBuf buffer) { + buffer.writeInt(id); + buffer.writeBoolean(wipe); + buffer.writeCollection(values, (buf, pair) -> pair.write(buf)); + buffer.writeMap(objects, PacketByteBuf::writeInt, PacketCodec.RAW_BYTES::write); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/ObjectTracker.java b/src/main/java/com/minelittlepony/unicopia/network/track/ObjectTracker.java new file mode 100644 index 00000000..8e4ef2e2 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/track/ObjectTracker.java @@ -0,0 +1,151 @@ +package com.minelittlepony.unicopia.network.track; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import com.minelittlepony.unicopia.network.track.TrackableObject.Status; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.network.PacketByteBuf; + +public class ObjectTracker> { + private final Map trackedObjects = new Object2ObjectOpenHashMap<>(); + private volatile Map quickAccess = Map.of(); + + private final int id; + private final Supplier constructor; + + public ObjectTracker(int id, Supplier constructor) { + this.id = id; + this.constructor = constructor; + } + + public Map entries() { + return quickAccess; + } + + public Set keySet() { + return quickAccess.keySet(); + } + + public Collection values() { + return quickAccess.values(); + } + + @Nullable + public T get(UUID id) { + return quickAccess.get(id); + } + + @Nullable + public T remove(UUID id, boolean immediate) { + T entry = quickAccess.get(id); + if (entry != null) { + entry.discard(immediate); + } + return entry; + } + + public boolean contains(UUID id) { + return quickAccess.containsKey(id); + } + + public boolean isEmpty() { + return quickAccess.isEmpty(); + } + + public boolean clear(boolean immediate) { + if (isEmpty()) { + return false; + } + values().forEach(value -> value.discard(immediate)); + return true; + } + + public synchronized void add(UUID id, T obj) { + trackedObjects.put(id, obj); + quickAccess = Map.copyOf(trackedObjects); + } + + synchronized void copyTo(ObjectTracker destination) { + for (var entry : trackedObjects.entrySet()) { + T copy = destination.constructor.get(); + entry.getValue().copyTo(copy); + destination.trackedObjects.put(entry.getKey(), copy); + } + destination.quickAccess = Map.copyOf(destination.trackedObjects); + } + + synchronized Optional getInitialPairs() { + if (trackedObjects.isEmpty()) { + return Optional.empty(); + } + + Map updates = new HashMap<>(); + quickAccess.entrySet().forEach(object -> { + object.getValue().write(Status.NEW).ifPresent(data -> { + updates.put(object.getKey(), data); + }); + }); + + return Optional.of(new MsgTrackedValues.TrackerObjects(id, Set.of(), updates)); + } + + synchronized Optional getDirtyPairs() { + if (!trackedObjects.isEmpty()) { + Map updates = new HashMap<>(); + Set removedTrackableObjects = new HashSet<>(); + trackedObjects.entrySet().removeIf(object -> { + TrackableObject.Status status = object.getValue().getStatus(); + if (status == TrackableObject.Status.REMOVED) { + removedTrackableObjects.add(object.getKey()); + return true; + } + object.getValue().write(status).ifPresent(data -> { + updates.put(object.getKey(), data); + }); + return false; + }); + quickAccess = Map.copyOf(trackedObjects); + + if (!updates.isEmpty() || !removedTrackableObjects.isEmpty()) { + return Optional.of(new MsgTrackedValues.TrackerObjects(id, removedTrackableObjects, updates)); + } + } + + return Optional.empty(); + } + + synchronized void load(MsgTrackedValues.TrackerObjects objects) { + objects.removedValues().forEach(removedId -> { + T o = trackedObjects.remove(removedId); + if (o != null) { + o.discard(true); + } + }); + objects.values().forEach((id, data) -> { + T o = trackedObjects.get(id); + if (o == null) { + o = constructor.get(); + trackedObjects.put(id, o); + } + o.read(data); + }); + quickAccess = Map.copyOf(trackedObjects); + } + + public void load(Map values) { + synchronized (this) { + trackedObjects.clear(); + trackedObjects.putAll(values); + quickAccess = Map.copyOf(trackedObjects); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/Trackable.java b/src/main/java/com/minelittlepony/unicopia/network/track/Trackable.java new file mode 100644 index 00000000..6c232740 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/track/Trackable.java @@ -0,0 +1,11 @@ +package com.minelittlepony.unicopia.network.track; + +import net.minecraft.entity.Entity; + +public interface Trackable { + DataTrackerManager getDataTrackers(); + + static Trackable of(Entity entity) { + return (Trackable)entity; + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/TrackableDataType.java b/src/main/java/com/minelittlepony/unicopia/network/track/TrackableDataType.java new file mode 100644 index 00000000..9c5c6b74 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/track/TrackableDataType.java @@ -0,0 +1,50 @@ +package com.minelittlepony.unicopia.network.track; + +import com.minelittlepony.unicopia.Race; +import com.minelittlepony.unicopia.Unicopia; +import com.minelittlepony.unicopia.util.serialization.PacketCodec; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; + +public record TrackableDataType(int id, PacketCodec codec) { + private static final Int2ObjectMap> REGISTRY = new Int2ObjectOpenHashMap<>(); + + public static final TrackableDataType INT = of(new Identifier("integer"), PacketCodec.INT); + public static final TrackableDataType FLOAT = of(new Identifier("float"), PacketCodec.FLOAT); + public static final TrackableDataType BOOLEAN = of(new Identifier("boolean"), PacketCodec.BOOLEAN); + public static final TrackableDataType UUID = of(new Identifier("uuid"), PacketCodec.UUID); + public static final TrackableDataType NBT = of(new Identifier("nbt"), PacketCodec.NBT); + public static final TrackableDataType COMPRESSED_NBT = of(new Identifier("compressed_nbt"), PacketCodec.COMPRESSED_NBT); + public static final TrackableDataType> RAW_BYTES = of(new Identifier("raw_bytes"), PacketCodec.RAW_BYTES.asOptional()); + + public static final TrackableDataType> OPTIONAL_POS = of(new Identifier("optional_pos"), PacketCodec.OPTIONAL_POS); + public static final TrackableDataType RACE = TrackableDataType.of(Unicopia.id("race"), PacketCodec.ofRegistry(Race.REGISTRY)); + + @SuppressWarnings("unchecked") + public static TrackableDataType of(PacketByteBuf buffer) { + int id = buffer.readInt(); + return Objects.requireNonNull((TrackableDataType)REGISTRY.get(id), "Unknown trackable data type id: " + id); + } + + @SuppressWarnings("unchecked") + public static TrackableDataType of(Identifier typeName, PacketCodec codec) { + return (TrackableDataType)REGISTRY.computeIfAbsent(typeName.hashCode(), t -> new TrackableDataType<>(t, codec)); + } + + public T read(PacketByteBuf buffer) { + return codec().read(buffer); + } + + public void write(PacketByteBuf buffer, T value) { + buffer.writeInt(id); + codec().write(buffer, value); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/track/TrackableObject.java b/src/main/java/com/minelittlepony/unicopia/network/track/TrackableObject.java new file mode 100644 index 00000000..8065aa81 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/track/TrackableObject.java @@ -0,0 +1,41 @@ +package com.minelittlepony.unicopia.network.track; + +import java.util.Optional; + +import com.minelittlepony.unicopia.util.serialization.PacketCodec; + +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.network.PacketByteBuf; + +public interface TrackableObject> { + Status getStatus(); + + default void read(PacketByteBuf buffer) { + readTrackedNbt(PacketCodec.COMPRESSED_NBT.read(buffer)); + } + + default Optional write(Status status) { + if (status == Status.NEW || status == Status.UPDATED) { + PacketByteBuf buffer = new PacketByteBuf(Unpooled.buffer()); + PacketCodec.COMPRESSED_NBT.write(buffer, writeTrackedNbt()); + return Optional.of(buffer); + } + return Optional.empty(); + } + + void readTrackedNbt(NbtCompound nbt); + + NbtCompound writeTrackedNbt(); + + void discard(boolean immediate); + + void copyTo(T destination); + + public enum Status { + DEFAULT, + NEW, + UPDATED, + REMOVED + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/projectile/MagicBeamEntity.java b/src/main/java/com/minelittlepony/unicopia/projectile/MagicBeamEntity.java index 1c1f9f44..122bef3e 100644 --- a/src/main/java/com/minelittlepony/unicopia/projectile/MagicBeamEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/projectile/MagicBeamEntity.java @@ -4,13 +4,16 @@ import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; +import org.jetbrains.annotations.Nullable; + import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.ability.magic.Affine; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.Levelled; -import com.minelittlepony.unicopia.ability.magic.SpellContainer; -import com.minelittlepony.unicopia.ability.magic.SpellContainer.Operation; +import com.minelittlepony.unicopia.ability.magic.SpellInventory; +import com.minelittlepony.unicopia.ability.magic.SpellInventory.Operation; +import com.minelittlepony.unicopia.ability.magic.SpellSlots; import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.block.state.StatePredicate; @@ -18,8 +21,6 @@ import com.minelittlepony.unicopia.entity.EntityPhysics; import com.minelittlepony.unicopia.entity.MagicImmune; import com.minelittlepony.unicopia.entity.Physics; import com.minelittlepony.unicopia.entity.mob.UEntities; -import com.minelittlepony.unicopia.network.datasync.EffectSync; - import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.entity.data.DataTracker; @@ -31,13 +32,25 @@ import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; public class MagicBeamEntity extends MagicProjectileEntity implements Caster, MagicImmune { - private static final TrackedData GRAVITY = DataTracker.registerData(MagicBeamEntity.class, TrackedDataHandlerRegistry.FLOAT); private static final TrackedData HYDROPHOBIC = DataTracker.registerData(MagicBeamEntity.class, TrackedDataHandlerRegistry.BOOLEAN); - private static final TrackedData EFFECT = DataTracker.registerData(MagicBeamEntity.class, TrackedDataHandlerRegistry.NBT_COMPOUND); + private static final TrackedData LEVEL = DataTracker.registerData(MagicBeamEntity.class, TrackedDataHandlerRegistry.INTEGER); + private static final TrackedData MAX_LEVEL = DataTracker.registerData(MagicBeamEntity.class, TrackedDataHandlerRegistry.INTEGER); + private static final TrackedData CORRUPTION = DataTracker.registerData(MagicBeamEntity.class, TrackedDataHandlerRegistry.INTEGER); + private static final TrackedData MAX_CORRUPTION = DataTracker.registerData(MagicBeamEntity.class, TrackedDataHandlerRegistry.INTEGER); - private final EffectSync effectDelegate = new EffectSync(this, EFFECT); + private final SpellInventory spells = SpellSlots.ofSingle(this); + private final EntityPhysics physics = new EntityPhysics<>(this); - private final EntityPhysics physics = new EntityPhysics<>(this, GRAVITY); + private final LevelStore level = Levelled.of( + () -> dataTracker.get(LEVEL), + l -> dataTracker.set(LEVEL, l), + () -> dataTracker.get(MAX_LEVEL) + ); + private final LevelStore corruption = Levelled.of( + () -> dataTracker.get(CORRUPTION), + l -> dataTracker.set(CORRUPTION, l), + () -> dataTracker.get(MAX_CORRUPTION) + ); public MagicBeamEntity(EntityType type, World world) { super(type, world); @@ -55,19 +68,21 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster { + dataTracker.set(LEVEL, caster.getLevel().get()); + dataTracker.set(MAX_LEVEL, caster.getLevel().getMax()); + dataTracker.set(CORRUPTION, caster.getCorruption().get()); + dataTracker.set(MAX_CORRUPTION, caster.getCorruption().getMax()); + }); + } + @Override public LevelStore getLevel() { - return getMasterReference().getTarget().map(target -> target.level()).orElse(Levelled.EMPTY); + return level; } @Override public LevelStore getCorruption() { - return getMasterReference().getTarget().map(target -> target.corruption()).orElse(Levelled.EMPTY); + return corruption; } @Override @@ -115,12 +141,12 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster void forEachDelegates(Consumer consumer, Function predicate) { - effectDelegate.tick(spell -> { + spells.tick(spell -> { Optional.ofNullable(predicate.apply(spell)).ifPresent(consumer); return Operation.SKIP; }); @@ -154,18 +180,22 @@ public class MagicBeamEntity extends MagicProjectileEntity implements Caster { - compound.put("effect", Spell.writeNbt(effect)); - }); + spells.getSlots().toNBT(compound); } } diff --git a/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java b/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java index 3b8064ae..be5afd69 100644 --- a/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/projectile/MagicProjectileEntity.java @@ -14,6 +14,7 @@ import com.minelittlepony.unicopia.entity.mob.UEntities; import com.minelittlepony.unicopia.item.UItems; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; +import net.minecraft.entity.FallingBlockEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.data.DataTracker; import net.minecraft.entity.data.TrackedData; @@ -78,7 +79,7 @@ public class MagicProjectileEntity extends ThrownItemEntity implements WeaklyOwn } @Override - public void setMaster(LivingEntity owner) { + public final void setMaster(LivingEntity owner) { setOwner(owner); } @@ -86,13 +87,13 @@ public class MagicProjectileEntity extends ThrownItemEntity implements WeaklyOwn public void setOwner(@Nullable Entity entity) { super.setOwner(entity); if (entity instanceof LivingEntity l) { - getMasterReference().set(l); + WeaklyOwned.Mutable.super.setMaster(l); } } @Override @Nullable - public Entity getOwner() { + public final Entity getOwner() { return getMaster(); } @@ -207,7 +208,7 @@ public class MagicProjectileEntity extends ThrownItemEntity implements WeaklyOwn protected void onEntityHit(EntityHitResult hit) { Entity entity = hit.getEntity(); - if (EquinePredicates.IS_MAGIC_IMMUNE.test(entity)) { + if (!(entity instanceof FallingBlockEntity) && EquinePredicates.IS_MAGIC_IMMUNE.test(entity)) { return; } diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/Ether.java b/src/main/java/com/minelittlepony/unicopia/server/world/Ether.java index d60b7607..840e66fa 100644 --- a/src/main/java/com/minelittlepony/unicopia/server/world/Ether.java +++ b/src/main/java/com/minelittlepony/unicopia/server/world/Ether.java @@ -2,6 +2,7 @@ package com.minelittlepony.unicopia.server.world; import java.lang.ref.WeakReference; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiPredicate; import java.util.function.Predicate; @@ -15,6 +16,7 @@ import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.util.NbtSerialisable; import net.minecraft.nbt.*; import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.PersistentState; import net.minecraft.world.World; @@ -35,7 +37,7 @@ public class Ether extends PersistentState { this.world = world; this.endpoints = NbtSerialisable.readMap(compound.getCompound("endpoints"), Identifier::tryParse, typeNbt -> { return NbtSerialisable.readMap((NbtCompound)typeNbt, UUID::fromString, entityNbt -> { - return NbtSerialisable.readMap((NbtCompound)entityNbt, UUID::fromString, Entry::new); + return NbtSerialisable.readMap((NbtCompound)entityNbt, UUID::fromString, nbt -> new Entry<>(nbt)); }); }); } @@ -62,7 +64,7 @@ public class Ether extends PersistentState { public Entry getOrCreate(T spell, Caster caster) { synchronized (locker) { Entry entry = (Entry)endpoints - .computeIfAbsent(spell.getType().getId(), typeId -> new HashMap<>()) + .computeIfAbsent(spell.getTypeAndTraits().type().getId(), typeId -> new HashMap<>()) .computeIfAbsent(caster.asEntity().getUuid(), entityId -> new HashMap<>()) .computeIfAbsent(spell.getUuid(), spellid -> { markDirty(); @@ -104,7 +106,7 @@ public class Ether extends PersistentState { @SuppressWarnings("unchecked") public Entry get(T spell, Caster caster) { - return get((SpellType)spell.getType(), caster.asEntity().getUuid(), spell.getUuid()); + return get((SpellType)spell.getTypeAndTraits().type(), caster.asEntity().getUuid(), spell.getUuid()); } public Entry get(SpellType spell, EntityReference.EntityValues entityId, @Nullable UUID spellId) { @@ -113,7 +115,7 @@ public class Ether extends PersistentState { @SuppressWarnings("unchecked") @Nullable - private Entry get(SpellType spell, UUID entityId, @Nullable UUID spellId) { + public Entry get(SpellType spell, UUID entityId, @Nullable UUID spellId) { if (spellId == null) { return null; } @@ -166,11 +168,13 @@ public class Ether extends PersistentState { private WeakReference spell; private boolean removed; - private boolean taken; - public float pitch; - public float yaw; - public float radius; + private float pitch; + private final AtomicBoolean changed = new AtomicBoolean(true); + private float yaw; + private float radius; + + private final Set claimants = new HashSet<>(); private Entry(NbtElement nbt) { this.entity = new EntityReference<>(); @@ -184,7 +188,47 @@ public class Ether extends PersistentState { spellId = spell.getUuid(); } - boolean isAlive() { + public boolean hasChanged() { + return changed.getAndSet(false); + } + + public float getPitch() { + return pitch; + } + + public void setPitch(float pitch) { + if (!MathHelper.approximatelyEquals(this.pitch, pitch)) { + this.pitch = pitch; + changed.set(true); + } + markDirty(); + } + + public float getYaw() { + return yaw; + } + + public void setYaw(float yaw) { + if (!MathHelper.approximatelyEquals(this.yaw, yaw)) { + this.yaw = yaw; + changed.set(true); + } + markDirty(); + } + + public float getRadius() { + return radius; + } + + public void setRadius(float radius) { + if (!MathHelper.approximatelyEquals(this.radius, radius)) { + this.radius = radius; + changed.set(true); + } + markDirty(); + } + + public boolean isAlive() { return !isDead(); } @@ -203,6 +247,7 @@ public class Ether extends PersistentState { public void markDead() { Unicopia.LOGGER.debug("Marking " + entity.getTarget().orElse(null) + " as dead"); removed = true; + claimants.clear(); markDirty(); } @@ -210,25 +255,22 @@ public class Ether extends PersistentState { return entity.getTarget().filter(target -> uuid.equals(target.uuid())).isPresent(); } - public boolean isAvailable() { - return !isDead() && !taken && entity.isSet(); - } - - public void setTaken(boolean taken) { - this.taken = taken; + public void claim(UUID claimant) { + claimants.add(claimant); markDirty(); } - public void release() { - setTaken(false); + public void release(UUID claimant) { + claimants.remove(claimant); + markDirty(); } - public boolean claim() { - if (isAvailable()) { - setTaken(true); - return true; - } - return false; + public boolean isClaimedBy(UUID claimant) { + return claimants.contains(claimant); + } + + public boolean hasClaimant() { + return !claimants.isEmpty(); } @Nullable @@ -242,7 +284,7 @@ public class Ether extends PersistentState { spell = entity .getOrEmpty(world) .flatMap(Caster::of) - .flatMap(caster -> caster.getSpellSlot().get(s -> s.getUuid().equals(spellId), true)) + .flatMap(caster -> caster.getSpellSlot().get(s -> s.getUuid().equals(spellId))) .orElse(null); if (spell != null) { @@ -272,24 +314,34 @@ public class Ether extends PersistentState { public void toNBT(NbtCompound compound) { entity.toNBT(compound); compound.putBoolean("removed", removed); - compound.putBoolean("taken", taken); compound.putFloat("pitch", pitch); compound.putFloat("yaw", yaw); compound.putFloat("radius", radius); if (spellId != null) { compound.putUuid("spellId", spellId); } + NbtList list = new NbtList(); + claimants.forEach(claimant -> { + list.add(NbtHelper.fromUuid(claimant)); + }); + compound.put("claimants", list); } @Override public void fromNBT(NbtCompound compound) { entity.fromNBT(compound); removed = compound.getBoolean("removed"); - taken = compound.getBoolean("taken"); pitch = compound.getFloat("pitch"); yaw = compound.getFloat("yaw"); radius = compound.getFloat("radius"); spellId = compound.containsUuid("spellid") ? compound.getUuid("spellId") : null; + + claimants.clear(); + if (compound.contains("claimants", NbtElement.LIST_TYPE)) { + compound.getList("claimants", NbtElement.INT_ARRAY_TYPE).forEach(el -> { + claimants.add(NbtHelper.toUuid(el)); + }); + } } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java b/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java index d6adf192..ac2d4e84 100644 --- a/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java +++ b/src/main/java/com/minelittlepony/unicopia/server/world/WeatherConditions.java @@ -212,7 +212,15 @@ public class WeatherConditions extends PersistentState implements Tickable { } if (state.getFluidState().isIn(FluidTags.WATER)) { - return MeteorlogicalUtil.getSunIntensity(world); + float sunIntensity = MeteorlogicalUtil.getSunIntensity(world); + int depth = 0; + BlockPos.Mutable mutable = pos.mutableCopy(); + while (depth < 15 && world.getFluidState(mutable).isIn(FluidTags.WATER)) { + mutable.move(Direction.DOWN); + depth++; + } + + return sunIntensity * (depth / 15F); } return 0; diff --git a/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java b/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java index 54093f51..32f9ea21 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java +++ b/src/main/java/com/minelittlepony/unicopia/util/NbtSerialisable.java @@ -46,7 +46,6 @@ public interface NbtSerialisable { } static Vec3d readVector(NbtList list) { - return new Vec3d(list.getDouble(0), list.getDouble(1), list.getDouble(2)); } @@ -70,14 +69,31 @@ public interface NbtSerialisable { } static Map readMap(NbtCompound nbt, Function keyFunction, Function valueFunction) { - return nbt.getKeys().stream().collect(Collectors.toMap(keyFunction, k -> valueFunction.apply(nbt.get(k)))); + return readMap(nbt, keyFunction, (k, v) -> valueFunction.apply(v)); + } + + static Map readMap(NbtCompound nbt, Function keyFunction, BiFunction valueFunction) { + return nbt.getKeys().stream().map(k -> { + K key = keyFunction.apply(k); + if (key == null) { + return null; + } + V value = valueFunction.apply(key, nbt.get(k)); + if (value == null) { + return null; + } + return Map.entry(key, value); + }) + .filter(Objects::nonNull) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } static NbtCompound writeMap(Map map, Function keyFunction, Function valueFunction) { - NbtCompound nbt = new NbtCompound(); - map.forEach((k, v) -> { - nbt.put(keyFunction.apply(k), valueFunction.apply(v)); - }); + return writeMap(new NbtCompound(), map, keyFunction, valueFunction); + } + + static NbtCompound writeMap(NbtCompound nbt, Map map, Function keyFunction, Function valueFunction) { + map.forEach((k, v) -> nbt.put(keyFunction.apply(k), valueFunction.apply(v))); return nbt; } diff --git a/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java b/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java index 2cb644e4..d7c00444 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java +++ b/src/main/java/com/minelittlepony/unicopia/util/PosHelper.java @@ -28,6 +28,20 @@ public interface PosHelper { return a.add(b.getX(), b.getY(), b.getZ()); } + static BlockPos findNearestSurface(World world, BlockPos pos) { + BlockPos.Mutable mutable = pos.mutableCopy(); + + while (mutable.getY() > world.getBottomY() && world.isAir(mutable)) { + mutable.move(Direction.DOWN); + } + while (world.isInBuildLimit(mutable) && !world.isAir(mutable)) { + mutable.move(Direction.UP); + } + mutable.move(Direction.DOWN); + + return mutable; + } + static BlockPos findSolidGroundAt(World world, BlockPos pos, int signum) { BlockPos.Mutable mutable = pos.mutableCopy(); while (world.isInBuildLimit(mutable) && (world.isAir(mutable) || !world.getBlockState(mutable).canPlaceAt(world, mutable))) { @@ -37,6 +51,10 @@ public interface PosHelper { return mutable.toImmutable(); } + static boolean isOverVoid(World world, BlockPos pos, int signum) { + return signum > 0 && findSolidGroundAt(world, pos, signum).getY() < world.getBottomY(); + } + static void fastAll(BlockPos origin, Consumer consumer, Direction... directions) { final BlockPos immutable = origin instanceof BlockPos.Mutable m ? m.toImmutable() : origin; final BlockPos.Mutable mutable = origin instanceof BlockPos.Mutable m ? m : origin.mutableCopy(); diff --git a/src/main/java/com/minelittlepony/unicopia/util/serialization/PacketCodec.java b/src/main/java/com/minelittlepony/unicopia/util/serialization/PacketCodec.java index 2e2c3bd9..03336ec2 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/serialization/PacketCodec.java +++ b/src/main/java/com/minelittlepony/unicopia/util/serialization/PacketCodec.java @@ -1,25 +1,64 @@ package com.minelittlepony.unicopia.util.serialization; +import java.io.IOException; +import java.io.InputStream; import java.util.Optional; +import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtIo; import net.minecraft.network.PacketByteBuf; import net.minecraft.registry.Registry; import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; public record PacketCodec(PacketByteBuf.PacketReader reader, PacketByteBuf.PacketWriter writer) { + public static final PacketCodec BOOLEAN = new PacketCodec<>(PacketByteBuf::readBoolean, PacketByteBuf::writeBoolean); public static final PacketCodec FLOAT = new PacketCodec<>(PacketByteBuf::readFloat, PacketByteBuf::writeFloat); public static final PacketCodec INT = new PacketCodec<>(PacketByteBuf::readInt, PacketByteBuf::writeInt); public static final PacketCodec BYTE = new PacketCodec<>(PacketByteBuf::readByte, (b, v) -> b.writeByte(v)); public static final PacketCodec LONG = new PacketCodec<>(PacketByteBuf::readLong, PacketByteBuf::writeLong); public static final PacketCodec STRING = new PacketCodec<>(PacketByteBuf::readString, PacketByteBuf::writeString); + public static final PacketCodec UUID = new PacketCodec<>(PacketByteBuf::readUuid, PacketByteBuf::writeUuid); + public static final PacketCodec> OPTIONAL_UUID = UUID.asOptional(); public static final PacketCodec IDENTIFIER = STRING.xMap(Identifier::new, Identifier::toString); + public static final PacketCodec NBT = new PacketCodec<>(PacketByteBuf::readNbt, PacketByteBuf::writeNbt); + + public static final PacketCodec RAW_BYTES = new PacketCodec<>( + buffer -> new PacketByteBuf(buffer.readBytes(buffer.readInt())), + (buffer, bytes) -> { + buffer.writeInt(bytes.writerIndex()); + buffer.writeBytes(bytes); + }); + public static final PacketCodec COMPRESSED_NBT = RAW_BYTES.xMap(buffer -> { + try (InputStream in = new ByteBufInputStream(buffer)) { + return NbtIo.readCompressed(in); + } catch (IOException e) { + throw new RuntimeException(e); + } + }, nbt -> { + var buffer = new PacketByteBuf(Unpooled.buffer()); + try (ByteBufOutputStream out = new ByteBufOutputStream(buffer)) { + NbtIo.writeCompressed(nbt, out); + return buffer; + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + public static final PacketCodec POS = new PacketCodec<>(PacketByteBuf::readBlockPos, PacketByteBuf::writeBlockPos); + public static final PacketCodec> OPTIONAL_POS = POS.asOptional(); + public static final PacketCodec ofRegistry(Registry registry) { return INT.xMap(registry::get, registry::getRawId); } diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 18adf7d8..3d8903f2 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -65,7 +65,6 @@ "item.unicopia.butterfly_spawn_egg": "Butterfly Spawn Egg", "item.unicopia.butterfly": "Butterfly", "item.unicopia.loot_bug_spawn_egg": "Loot Bug Spawn Egg", - "item.unicopia.loot_bug": "Loot bug", "item.unicopia.green_apple": "Granny Smith Apple", "item.unicopia.sweet_apple": "Sweet Apple Acres Apple", @@ -376,9 +375,11 @@ "block.unicopia.oats_crown": "Oats", "entity.unicopia.butterfly": "Butterfly", + "entity.unicopia.loot_bug": "Loot bug", "entity.unicopia.twittermite": "Twittermite", "entity.unicopia.specter": "Specter", "entity.unicopia.mimic": "Mimic", + "entity.unicopia.magic_beam": "Magic Beam", "entity.unicopia.cast_spell": "Cast Spell", "entity.unicopia.cast_spell.by": "a spell cast by %s", "entity.unicopia.spellbook": "Spellbook", @@ -389,6 +390,7 @@ "entity.unicopia.crystal_shards": "Crystal Shards", "entity.unicopia.ignominious_vine": "Ignominious Vine", "entity.unicopia.ignominious_bulb": "Ignominious Bulb", + "entity.unicopia.thrown_item": "Thrown Item", "player.reachDistance": "Reach Distance", "player.miningSpeed": "Mining Speed", @@ -404,6 +406,10 @@ "effect.unicopia.corrupt_influence": "Corrupt Influence", "effect.unicopia.paralysis": "Paralysis", "effect.unicopia.butter_fingers": "Butterfingers", + "effect.unicopia.fortification": "Fortification", + "effect.unicopia.broken_wings": "Broken Wings", + "effect.unicopia.seaponys_ire": "Seaponys Ire", + "effect.unicopia.seaponys_grace": "Seaponys Grace", "effect.unicopia.change_race_earth": "Earth Pony Metamorphosis", "effect.unicopia.change_race_unicorn": "Unicorn Metamorphosis", @@ -579,6 +585,44 @@ "spell.unicopia.catapult.lore": "Grabs a nearby block or entity and throws it into the air", "spell.unicopia.dispel_evil": "Dispel Evil", "spell.unicopia.dispel_evil.lore": "Casts away any nearby unearthly forces", + + "spell_attribute.unicopia.added_trait.plus": " + %s x%s", + "spell_attribute.unicopia.added_trait.take": " - %s x%s", + "spell_attribute.unicopia.added_trait.unknown": "Undiscovered Trait", + "spell_attribute.unicopia.cast_on.location": "Applies to location", + "spell_attribute.unicopia.cast_on.self": "Applies to self", + "spell_attribute.unicopia.focused_entity": "Applies to focused entity", + "spell_attribute.unicopia.affects.both": "Affects blocks and entities", + "spell_attribute.unicopia.affects.entities": "Affects only entities", + "spell_attribute.unicopia.affects.blocks": "Affects only blocks", + "spell_attribute.unicopia.follows_target": "Follows target", + "spell_attribute.unicopia.causes_levitation": "Causes Levitation", + + "spell_attribute.unicopia.permit_items": " Permits Items", + "spell_attribute.unicopia.permit_passive": "Permits Passive Mobs", + "spell_attribute.unicopia.permit_hostile": "Permits Hostile Mobs", + "spell_attribute.unicopia.permit_player": " Permits Other Players", + + "spell_attribute.unicopia.range": "Effect Range", + "spell_attribute.unicopia.duration": "Effect Duration", + "spell_attribute.unicopia.strength": "Effect Strength", + "spell_attribute.unicopia.soapiness": "Soapiness", + "spell_attribute.unicopia.velocity": "Velocity", + "spell_attribute.unicopia.vertical_velocity": "Initial Launch Speed", + "spell_attribute.unicopia.hang_time": "Hang Time", + "spell_attribute.unicopia.pushing_power": "Pushing Power", + "spell_attribute.unicopia.damage_to_target": "Damage to Target", + "spell_attribute.unicopia.simultanious_targets": "Simultanious Targets", + "spell_attribute.unicopia.cost_per_individual": "Mana cost per individual", + "spell_attribute.unicopia.explosion_strength": "Blast Strength", + "spell_attribute.unicopia.projectile_count": "Projectile Count", + "spell_attribute.unicopia.follow_range": "Following Range", + "spell_attribute.unicopia.stick_to_target": "Attaches to Target", + "spell_attribute.unicopia.orb_count": "Orb Count", + "spell_attribute.unicopia.wave_size": "Wave Size", + "spell_attribute.unicopia.target_preference": "Target Preference", + "spell_attribute.unicopia.caster_preference": "Caster Preference", + "spell_attribute.unicopia.negates_fall_damage": "Negates Fall Damage", "trait.unicopia.strength.name": "Strength", "trait.unicopia.strength.description": "Imparts physical strength or enhances endurance.\nSpells with more of the strength trait hit harder and last longer.", @@ -722,6 +766,12 @@ "ability.unicopia.dash": "Flying Dash", "ability.unicopia.change_form": "Change Form", "ability.unicopia.sonar_pulse": "Sonar Pulse", + + "affinity.unicopia.good": "Good", + "affinity.unicopia.bad": "Bad", + "affinity.unicopia.neutral": "Neutral", + "affinity.unicopia.when_cast": "When Cast:", + "affinity.unicopia.corruption": "Corruption", "gui.unicopia.trait.label": "Element of %s", "gui.unicopia.trait.group": "\n %s", diff --git a/src/main/resources/assets/unicopia/models/block/door_left.json b/src/main/resources/assets/unicopia/models/block/door_left.json index 6c5b0b27..e0a63f70 100644 --- a/src/main/resources/assets/unicopia/models/block/door_left.json +++ b/src/main/resources/assets/unicopia/models/block/door_left.json @@ -9,10 +9,10 @@ "faces": { "up": { "uv": [ 13, 0, 16, 16 ], "texture": "#bottom", "cullface": "up" }, "down": { "uv": [ 13, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, - "north": { "uv": [ 3, 0, 0, 16 ], "texture": "#top", "cullface": "north" }, - "south": { "uv": [ 0, 0, 3, 16 ], "texture": "#top", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#top", "cullface": "west" }, - "east": { "uv": [ 16, 0, 0, 16 ], "texture": "#top", "cullface": "east" } + "north": { "uv": [ 3, 0, 0, 16 ], "texture": "#top" }, + "south": { "uv": [ 0, 0, 3, 16 ], "texture": "#top" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#top" }, + "east": { "uv": [ 16, 0, 0, 16 ], "texture": "#top" } } } ] diff --git a/src/main/resources/assets/unicopia/models/block/door_right.json b/src/main/resources/assets/unicopia/models/block/door_right.json index bf096276..632e5e99 100644 --- a/src/main/resources/assets/unicopia/models/block/door_right.json +++ b/src/main/resources/assets/unicopia/models/block/door_right.json @@ -9,10 +9,10 @@ "faces": { "up": { "uv": [ 13, 0, 16, 16 ], "texture": "#bottom", "cullface": "up" }, "down": { "uv": [ 13, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" }, - "north": { "uv": [ 3, 0, 0, 16 ], "texture": "#top", "cullface": "north" }, - "south": { "uv": [ 0, 0, 3, 16 ], "texture": "#top", "cullface": "south" }, - "west": { "uv": [ 16, 0, 0, 16 ], "texture": "#top", "cullface": "west" }, - "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#top", "cullface": "east" } + "north": { "uv": [ 3, 0, 0, 16 ], "texture": "#top" }, + "south": { "uv": [ 0, 0, 3, 16 ], "texture": "#top" }, + "west": { "uv": [ 16, 0, 0, 16 ], "texture": "#top" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#top" } } } ] diff --git a/src/main/resources/assets/unicopia/textures/mob_effect/broken_wings.png b/src/main/resources/assets/unicopia/textures/mob_effect/broken_wings.png new file mode 100644 index 00000000..f83f4b0f Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/mob_effect/broken_wings.png differ diff --git a/src/main/resources/assets/unicopia/textures/mob_effect/fortification.png b/src/main/resources/assets/unicopia/textures/mob_effect/fortification.png new file mode 100644 index 00000000..106e3a2f Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/mob_effect/fortification.png differ diff --git a/src/main/resources/assets/unicopia/textures/mob_effect/seaponys_grace.png b/src/main/resources/assets/unicopia/textures/mob_effect/seaponys_grace.png new file mode 100644 index 00000000..d8ec0b41 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/mob_effect/seaponys_grace.png differ diff --git a/src/main/resources/assets/unicopia/textures/mob_effect/seaponys_ire.png b/src/main/resources/assets/unicopia/textures/mob_effect/seaponys_ire.png new file mode 100644 index 00000000..9c560cb3 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/mob_effect/seaponys_ire.png differ diff --git a/src/main/resources/data/unicopia/recipes/cutting/hay_fries.json b/src/main/resources/data/unicopia/recipes/cutting/hay_fries.json index c0694f55..abb99342 100644 --- a/src/main/resources/data/unicopia/recipes/cutting/hay_fries.json +++ b/src/main/resources/data/unicopia/recipes/cutting/hay_fries.json @@ -7,7 +7,7 @@ ], "tool": { "type": "farmersdelight:tool", - "tag": "c:tools/axes" + "tag": "minecraft:axes" }, "result": [ { diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index ba92c81c..17bd2121 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -7,6 +7,7 @@ "compatibilityLevel": "JAVA_17", "mixins": [ "MixinAbstractDecorationEntity", + "MixinAbstractSkeletonEntity", "MixinBlazeEntity", "MixinBlock", "MixinBlockEntity", @@ -36,15 +37,10 @@ "MixinPersistentProjectileEntity", "MixinPlayerEntity", "MixinPlayerInventory", - "MixinPlayerManager", "MixinPointOfInterestType", "MixinPowderSnowBlock", "MixinProjectileEntity", "MixinPufferfishEntity", - "MixinServerPlayerEntity", - "MixinServerPlayNetworkHandler", - "MixinServerWorld", - "MixinSleepManager", "MixinSheepEntity", "MixinShulkerEntity", "MixinStateManager", @@ -55,6 +51,12 @@ "MixinWardenEntity", "MixinWorld", "PointOfInterestTypesAccessor", + "server.MixinEntityTrackerEntry", + "server.MixinPlayerManager", + "server.MixinServerPlayerEntity", + "server.MixinServerPlayNetworkHandler", + "server.MixinServerWorld", + "server.MixinSleepManager", "gravity.MixinBrain", "gravity.MixinEntity", "gravity.MixinLivingEntity",