From 69985e50f9ded41b27f9b073c340dd40191777e1 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 20 Jan 2024 15:42:26 +0000 Subject: [PATCH 01/16] Fixed placed spells breaking when reloading a world --- .../ability/magic/spell/PlaceableSpell.java | 4 ++++ .../advancements/unicopia/earth/dead_ringer.json | 12 ++++++------ .../unicopia/earth/sticks_and_stones.json | 14 +++++++------- 3 files changed, 17 insertions(+), 13 deletions(-) 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 index e3736e59..082460cf 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java @@ -219,6 +219,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS 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()); } @@ -232,6 +235,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS 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) { diff --git a/src/main/resources/data/unicopia/advancements/unicopia/earth/dead_ringer.json b/src/main/resources/data/unicopia/advancements/unicopia/earth/dead_ringer.json index 881e8c8d..b9859933 100644 --- a/src/main/resources/data/unicopia/advancements/unicopia/earth/dead_ringer.json +++ b/src/main/resources/data/unicopia/advancements/unicopia/earth/dead_ringer.json @@ -2,13 +2,13 @@ "parent": "unicopia:unicopia/earth/born_on_a_rock_farm", "display": { "icon": { - "item": "unicopia:rock" + "item": "unicopia:iron_horseshoe" }, "title": { - "translate": "advancements.unicopia.sticks_and_stones.title" + "translate": "advancements.unicopia.dead_ringer.title" }, "description": { - "translate": "advancements.unicopia.sticks_and_stones.description" + "translate": "advancements.unicopia.dead_ringer.description" }, "frame": "task", "show_toast": true, @@ -16,13 +16,13 @@ "hidden": true }, "criteria": { - "killed_entity_with_rock": { + "killed_entity_with_horseshoe": { "trigger": "minecraft:player_killed_entity", "conditions": { "killing_blow": { "tags": [ { - "id": "unicopia:from_rocks", + "id": "unicopia:from_horseshoes", "expected": true } ] @@ -31,6 +31,6 @@ } }, "requirements": [ - [ "killed_entity_with_rock" ] + [ "killed_entity_with_horseshoe" ] ] } diff --git a/src/main/resources/data/unicopia/advancements/unicopia/earth/sticks_and_stones.json b/src/main/resources/data/unicopia/advancements/unicopia/earth/sticks_and_stones.json index 591e4616..083c3c04 100644 --- a/src/main/resources/data/unicopia/advancements/unicopia/earth/sticks_and_stones.json +++ b/src/main/resources/data/unicopia/advancements/unicopia/earth/sticks_and_stones.json @@ -2,13 +2,13 @@ "parent": "unicopia:unicopia/earth/born_on_a_rock_farm", "display": { "icon": { - "item": "unicopia:horseshoe" + "item": "unicopia:rock" }, "title": { - "translate": "advancements.unicopia.dead_ringer.title" + "translate": "advancements.unicopia.sticks_and_stones.title" }, "description": { - "translate": "advancements.unicopia.dead_ringer.description" + "translate": "advancements.unicopia.sticks_and_stones.description" }, "frame": "task", "show_toast": true, @@ -16,13 +16,13 @@ "hidden": true }, "criteria": { - "killed_entity_with_horseshoe": { + "killed_entity_with_rock": { "trigger": "minecraft:player_killed_entity", "conditions": { "killing_blow": { "tags": [ { - "id": "unicopia:from_horseshoes", + "id": "unicopia:from_rocks", "expected": true } ] @@ -31,6 +31,6 @@ } }, "requirements": [ - [ "killed_entity_with_horseshoe" ] + [ "killed_entity_with_rock" ] ] -} +} \ No newline at end of file From 7b1f5ce0db11a896b5ff4ba837aa792d952af397 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 20 Jan 2024 16:44:33 +0000 Subject: [PATCH 02/16] Fix error loading the dead ringer advancement --- .../data/unicopia/advancements/unicopia/earth/dead_ringer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/data/unicopia/advancements/unicopia/earth/dead_ringer.json b/src/main/resources/data/unicopia/advancements/unicopia/earth/dead_ringer.json index b9859933..dc70d6e7 100644 --- a/src/main/resources/data/unicopia/advancements/unicopia/earth/dead_ringer.json +++ b/src/main/resources/data/unicopia/advancements/unicopia/earth/dead_ringer.json @@ -2,7 +2,7 @@ "parent": "unicopia:unicopia/earth/born_on_a_rock_farm", "display": { "icon": { - "item": "unicopia:iron_horseshoe" + "item": "unicopia:iron_horse_shoe" }, "title": { "translate": "advancements.unicopia.dead_ringer.title" From e017fb1b899d27587f74323f18d4599e7e9b3804 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 20 Jan 2024 16:45:22 +0000 Subject: [PATCH 03/16] Fix lag when both a dark vortex and hydrophobic (as well as other combinations) of spells are active at once --- .../ability/magic/spell/effect/AttractiveSpell.java | 3 ++- .../ability/magic/spell/effect/DarkVortexSpell.java | 13 ++++++------- .../unicopia/network/datasync/EffectSync.java | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) 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 669411a1..d91970b2 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 @@ -44,9 +44,10 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp if (timer.getTicksRemaining() <= 0) { return false; } + + setDirty(); } - setDirty(); target.getOrEmpty(caster.asWorld()) .filter(entity -> entity.distanceTo(caster.asEntity()) > getDrawDropOffRange(caster)) .ifPresent(entity -> { 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 9402d692..4e50d13e 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 @@ -80,10 +80,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega return true; } - age++; - setDirty(); - - if (age % 20 == 0) { + if (++age % 20 == 0) { source.asWorld().playSound(null, source.getOrigin(), USounds.AMBIENT_DARK_VORTEX_ADDITIONS, SoundCategory.AMBIENT, 1, 1); } @@ -162,7 +159,6 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega applyRadialEffect(source, e, e.getPos().distanceTo(origin), radius); }); } - setDirty(); }); } } @@ -221,8 +217,11 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega double massOfTarget = AttractionUtils.getMass(target); - accumulatedMass += massOfTarget; - setDirty(); + if (massOfTarget != 0) { + accumulatedMass += massOfTarget; + setDirty(); + } + target.damage(source.damageOf(UDamageTypes.GAVITY_WELL_RECOIL, source), Integer.MAX_VALUE); if (!(target instanceof PlayerEntity)) { target.discard(); diff --git a/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java b/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java index f54aebaa..2a4568e9 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java +++ b/src/main/java/com/minelittlepony/unicopia/network/datasync/EffectSync.java @@ -136,7 +136,7 @@ public class EffectSync implements SpellContainer, NbtSerialisable { @SuppressWarnings("unchecked") private Stream read(@Nullable SpellPredicate type, boolean synchronize, boolean sendUpdate) { if (synchronize && spells.fromNbt(owner.asEntity().getDataTracker().get(param)) && sendUpdate) { - owner.asEntity().getDataTracker().set(param, spells.toNbt()); + write(); } if (type == null) { From 81070be4b3ea012adae135ac523929d63ef4fed2 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 20 Jan 2024 16:45:33 +0000 Subject: [PATCH 04/16] Fixed spells not affecting experience orbs --- src/main/java/com/minelittlepony/unicopia/EquinePredicates.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java b/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java index be559b38..9f055212 100644 --- a/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java +++ b/src/main/java/com/minelittlepony/unicopia/EquinePredicates.java @@ -33,7 +33,7 @@ public interface EquinePredicates { Predicate IS_CASTER = e -> !e.isRemoved() && (e instanceof Caster || IS_PLAYER.test(e)); Predicate IS_PLACED_SPELL = e -> e instanceof Caster && !e.isRemoved(); - Predicate IS_MAGIC_IMMUNE = e -> (e instanceof MagicImmune || !(e instanceof LivingEntity)) && !(e instanceof ItemEntity); + Predicate IS_MAGIC_IMMUNE = e -> (e instanceof MagicImmune || !(e instanceof LivingEntity)) && !(e instanceof ItemEntity) && !(e instanceof ExperienceOrbEntity); Predicate EXCEPT_MAGIC_IMMUNE = IS_MAGIC_IMMUNE.negate(); Predicate VALID_LIVING_AND_NOT_MAGIC_IMMUNE = EntityPredicates.VALID_LIVING_ENTITY.and(EXCEPT_MAGIC_IMMUNE); From b3a7d338d236059c60790074491c600aca79f3c3 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 20 Jan 2024 19:48:43 +0000 Subject: [PATCH 05/16] Reimplement placed spell rendering to no longer require a particle --- .../ability/magic/spell/PlaceableSpell.java | 47 ++++------ .../magic/spell/RainboomAbilitySpell.java | 2 +- .../magic/spell/effect/DisplacementSpell.java | 17 +--- .../magic/spell/effect/PortalSpell.java | 9 -- .../particle/OrientedBillboardParticle.java | 1 - .../client/particle/RunesParticle.java | 1 + .../unicopia/client/render/RenderUtil.java | 24 +++-- .../entity/CastSpellEntityRenderer.java | 4 +- .../render/spell/PlacedSpellRenderer.java | 88 +++++++++++++++++++ .../spell/SpellEffectsRenderDispatcher.java | 6 ++ .../unicopia/particle/ParticleHandle.java | 1 + .../unicopia/particle/UParticles.java | 1 + 12 files changed, 125 insertions(+), 76 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java 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 index 082460cf..98095393 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java @@ -13,10 +13,6 @@ 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.particle.OrientedBillboardParticleEffect; -import com.minelittlepony.unicopia.particle.ParticleHandle; -import com.minelittlepony.unicopia.particle.UParticles; -import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment; import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.util.NbtSerialisable; @@ -24,6 +20,7 @@ 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; @@ -40,11 +37,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS @Nullable private RegistryKey dimension; - /** - * The visual effect - */ - private final ParticleHandle particlEffect = new ParticleHandle(); - /** * ID of the placed counterpart of this spell. */ @@ -64,6 +56,9 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS public float pitch; public float yaw; + private int prevAge; + private int age; + private Optional position = Optional.empty(); public PlaceableSpell(CustomisedSpellType type) { @@ -75,15 +70,13 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS return this; } - @Override - public Collection getDelegates() { - return List.of(spell); + public float getAge(float tickDelta) { + return MathHelper.lerp(tickDelta, prevAge, age); } @Override - public void setDead() { - super.setDead(); - particlEffect.destroy(); + public Collection getDelegates() { + return List.of(spell); } @Override @@ -113,16 +106,13 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS setDead(); return false; } + } else { + prevAge = age; + if (age < 25) { + age++; + } } - if (spell instanceof PlacementDelegate delegate) { - delegate.updatePlacement(source, this); - } - - getParticleEffectAttachment(source).ifPresent(p -> { - p.setAttribute(Attachment.ATTR_COLOR, spell.getType().getColor()); - }); - return super.tick(source, Situation.GROUND); } @@ -200,12 +190,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS return castEntity.getTarget().map(EntityValues::pos); } - public Optional getParticleEffectAttachment(Caster source) { - return particlEffect.update(getUuid(), source, spawner -> { - spawner.addParticle(new OrientedBillboardParticleEffect(UParticles.MAGIC_RUNES, pitch + 90, yaw), Vec3d.ZERO, Vec3d.ZERO); - }); - } - protected Optional getWorld(Caster source) { return Optional.ofNullable(dimension) .map(dim -> source.asWorld().getServer().getWorld(dim)); @@ -214,6 +198,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS @Override public void toNBT(NbtCompound compound) { super.toNBT(compound); + compound.putInt("age", age); compound.putFloat("pitch", pitch); compound.putFloat("yaw", yaw); position.ifPresent(pos -> { @@ -232,6 +217,7 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS @Override public void fromNBT(NbtCompound compound) { super.fromNBT(compound); + 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(); @@ -264,9 +250,6 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS } public interface PlacementDelegate { - void onPlaced(Caster source, PlaceableSpell parent, CastSpellEntity entity); - - void updatePlacement(Caster source, PlaceableSpell parent); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java index 4bf0d726..d7b7b562 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/RainboomAbilitySpell.java @@ -54,7 +54,7 @@ public class RainboomAbilitySpell extends AbstractSpell { }); if (source.isClient()) { - // source.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, source.getPhysics().getMotionAngle()), source.getOriginVector(), Vec3d.ZERO); + //source.addParticle(new OrientedBillboardParticleEffect(UParticles.RAINBOOM_RING, source.getPhysics().getMotionAngle()), source.getOriginVector(), Vec3d.ZERO); } source.findAllEntitiesInRange(RADIUS).forEach(e -> { 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 14d3abf8..a58b77de 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 @@ -6,8 +6,6 @@ import com.minelittlepony.unicopia.ability.magic.spell.*; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; -import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; -import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.ProjectileDelegate; @@ -16,7 +14,7 @@ import net.minecraft.nbt.NbtCompound; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.Vec3d; -public class DisplacementSpell extends AbstractSpell implements HomingSpell, PlaceableSpell.PlacementDelegate, ProjectileDelegate.EntityHitListener { +public class DisplacementSpell extends AbstractSpell implements HomingSpell, ProjectileDelegate.EntityHitListener { private final EntityReference target = new EntityReference<>(); @@ -67,19 +65,6 @@ public class DisplacementSpell extends AbstractSpell implements HomingSpell, Pla originator.subtractEnergyCost(destinationPos.distanceTo(sourcePos) / 20F); } - @Override - public void onPlaced(Caster source, PlaceableSpell parent, CastSpellEntity entity) { - - } - - @Override - public void updatePlacement(Caster caster, PlaceableSpell parent) { - parent.getParticleEffectAttachment(caster).ifPresent(attachment -> { - float r = 3 - (1 - ((ticks + 10) / 20F)) * 3; - attachment.setAttribute(Attachment.ATTR_RADIUS, r); - }); - } - private void teleport(Caster source, Entity entity, Vec3d pos, Vec3d vel) { entity.teleport(pos.x, pos.y, pos.z); entity.setVelocity(vel); 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 8b25fbe9..e55f388c 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 @@ -14,7 +14,6 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import com.minelittlepony.unicopia.particle.*; -import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment; import com.minelittlepony.unicopia.server.world.Ether; import com.minelittlepony.unicopia.util.shape.*; @@ -177,14 +176,6 @@ public class PortalSpell extends AbstractSpell implements PlaceableSpell.Placeme entity.setPos(targetPos.x, caster.getY() + 1.5, targetPos.z); } - @Override - public void updatePlacement(Caster source, PlaceableSpell parent) { - parent.getParticleEffectAttachment(source).ifPresent(attachment -> { - attachment.setAttribute(Attachment.ATTR_RADIUS, 2); - attachment.setAttribute(Attachment.ATTR_OPACITY, 0.92F); - }); - } - @Override protected void onDestroyed(Caster caster) { particleEffect.destroy(); diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/OrientedBillboardParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/OrientedBillboardParticle.java index f3f04b23..4c25b465 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/OrientedBillboardParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/OrientedBillboardParticle.java @@ -22,7 +22,6 @@ public abstract class OrientedBillboardParticle extends AbstractBillboardParticl fixed = effect.fixed(); if (fixed) { - // Was hamiltonianProduct (CHECK THIS!!) rotation.mul(RotationAxis.POSITIVE_Y.rotationDegrees(effect.pitch())); rotation.mul(RotationAxis.POSITIVE_X.rotationDegrees(180 - effect.yaw())); } diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/RunesParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/RunesParticle.java index 58a8d453..87ee30c2 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/RunesParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/RunesParticle.java @@ -20,6 +20,7 @@ import net.minecraft.entity.Entity; import net.minecraft.util.Identifier; import net.minecraft.util.math.*; +@Deprecated public class RunesParticle extends OrientedBillboardParticle implements Attachment { private static final Identifier[] TEXTURES = new Identifier[] { diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/RenderUtil.java b/src/main/java/com/minelittlepony/unicopia/client/render/RenderUtil.java index a126ee28..fc981555 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/RenderUtil.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/RenderUtil.java @@ -1,6 +1,5 @@ package com.minelittlepony.unicopia.client.render; -import org.joml.Matrix4f; import org.joml.Vector3f; import org.joml.Vector4f; @@ -11,7 +10,7 @@ import net.minecraft.client.render.VertexFormats; import net.minecraft.client.util.math.MatrixStack; public class RenderUtil { - private static final Vector4f TEMP_VECTOR = new Vector4f(); + public static final Vector4f TEMP_VECTOR = new Vector4f(); public static final Vertex[] UNIT_FACE = new Vertex[] { new Vertex(new Vector3f(0, 0, 0), 1, 1), new Vertex(new Vector3f(0, 1, 0), 1, 0), @@ -21,21 +20,18 @@ public class RenderUtil { public static void renderFace(MatrixStack matrices, Tessellator te, BufferBuilder buffer, float r, float g, float b, float a, int light) { buffer.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE_COLOR_LIGHT); - - Vertex[] UNIT_FACE = new Vertex[] { - new Vertex(new Vector3f(0, 0, 0), 1, 1), - new Vertex(new Vector3f(0, 1, 0), 1, 0), - new Vertex(new Vector3f(1, 1, 0), 0, 0), - new Vertex(new Vector3f(1, 0, 0), 0, 1) - }; - - Matrix4f transformation = matrices.peek().getPositionMatrix(); for (Vertex vertex : UNIT_FACE) { - transformation.transform(TEMP_VECTOR.set(vertex.position(), 1)); - buffer.vertex(TEMP_VECTOR.x, TEMP_VECTOR.y, TEMP_VECTOR.z).texture(vertex.u(), vertex.v()).color(r, g, b, a).light(light).next(); + Vector4f position = vertex.position(matrices); + buffer.vertex(position.x, position.y, position.z).texture(vertex.u(), vertex.v()).color(r, g, b, a).light(light).next(); } te.draw(); } - record Vertex(Vector3f position, float u, float v) {} + public record Vertex(Vector3f position, float u, float v) { + + public Vector4f position(MatrixStack matrices) { + matrices.peek().getPositionMatrix().transform(TEMP_VECTOR.set(position, 1)); + return TEMP_VECTOR; + } + } } 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 7b4fae4e..ece6638b 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 @@ -11,8 +11,6 @@ import net.minecraft.screen.PlayerScreenHandler; import net.minecraft.util.Identifier; public class CastSpellEntityRenderer extends EntityRenderer { - private final SpellEffectsRenderDispatcher spellRenderDispatcher = new SpellEffectsRenderDispatcher(); - public CastSpellEntityRenderer(EntityRendererFactory.Context ctx) { super(ctx); } @@ -24,7 +22,7 @@ public class CastSpellEntityRenderer extends EntityRenderer { @Override public void render(CastSpellEntity entity, float yaw, float tickDelta, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light) { - spellRenderDispatcher.render(matrices, vertexConsumers, light, entity, 0, 0, tickDelta, getAnimationProgress(entity, tickDelta), yaw, 0); + SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertexConsumers, light, entity, 0, 0, tickDelta, getAnimationProgress(entity, tickDelta), yaw, 0); } protected float getAnimationProgress(CastSpellEntity entity, float tickDelta) { 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 new file mode 100644 index 00000000..4e293248 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java @@ -0,0 +1,88 @@ +package com.minelittlepony.unicopia.client.render.spell; + +import org.joml.Vector3f; +import org.joml.Vector4f; + +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.RenderUtil; + +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 implements 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") + }; + private static final RenderUtil.Vertex[] CORNERS = new RenderUtil.Vertex[]{ + new RenderUtil.Vertex(new Vector3f(-1, -1, 0), 0, 0), + new RenderUtil.Vertex(new Vector3f(-1, 1, 0), 1, 0), + new RenderUtil.Vertex(new Vector3f( 1, 1, 0), 1, 1), + new RenderUtil.Vertex(new Vector3f( 1, -1, 0), 0, 1) + }; + + @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) { + + for (Spell delegate : spell.getDelegates()) { + + matrices.push(); + matrices.translate(0, 0.001, 0); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(spell.pitch)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90 - spell.yaw)); + float scale = (spell.getAge(tickDelta) / 25F) * 3; + matrices.scale(scale, scale, scale); + + float alpha = 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); + + for (int i = 0; i < TEXTURES.length; i++) { + 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)); + renderQuad(buffer, matrices, red, green, blue, alpha / ((float)(dim * 3) + 1), light); + matrices.pop(); + } + } + + matrices.pop(); + + var renderer = SpellEffectsRenderDispatcher.INSTANCE.getRenderer(delegate); + if (renderer != null) { + renderer.render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); + } + } + } + + protected final void renderQuad(VertexConsumer buffer, MatrixStack matrices, float red, float green, float blue, float alpha, int light) { + for (var corner : CORNERS) { + Vector4f pos = corner.position(matrices); + buffer.vertex(pos.x, pos.y, pos.z, red, green, blue, alpha, corner.u(), corner.v(), 0, light, 1, 1, 1); + } + } +} 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 7f5d26a8..f44aad5f 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 @@ -43,10 +43,16 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader REGISTRY.put(type, rendererFactory); } + static { + register(SpellType.PLACED_SPELL, PlacedSpellRenderer::new); + } + @Nullable private Map, SpellRenderer> renderers = Map.of(); private final MinecraftClient client = MinecraftClient.getInstance(); + private SpellEffectsRenderDispatcher() {} + @Override public Identifier getFabricId() { return ID; diff --git a/src/main/java/com/minelittlepony/unicopia/particle/ParticleHandle.java b/src/main/java/com/minelittlepony/unicopia/particle/ParticleHandle.java index 1fc12c06..b099f347 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/ParticleHandle.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/ParticleHandle.java @@ -20,6 +20,7 @@ import net.minecraft.world.World; /** * A connection class for updating and persisting an attached particle effect. */ +@Deprecated public class ParticleHandle { private final Map loadedEffects = new WeakHashMap<>(); diff --git a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java index 8d5208f0..186be3e0 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java @@ -16,6 +16,7 @@ public interface UParticles { ParticleType RAINBOOM_RING = register("rainboom_ring", FabricParticleTypes.complex(OrientedBillboardParticleEffect.FACTORY)); DefaultParticleType RAINBOOM_TRAIL = register("rainboom_trail", FabricParticleTypes.simple()); + @Deprecated ParticleType MAGIC_RUNES = register("magic_runes", FabricParticleTypes.complex(OrientedBillboardParticleEffect.FACTORY)); DefaultParticleType RAIN_DROPS = register("rain_drops", FabricParticleTypes.simple()); From 7fb9543dbe21fd3f57d1216916f5a50d6be0cf6b Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 20 Jan 2024 22:31:32 +0000 Subject: [PATCH 06/16] Move shield effect over to a spell renderer --- .../magic/spell/effect/DarkVortexSpell.java | 9 ++++ .../magic/spell/effect/ShieldSpell.java | 53 +++++++++++-------- .../client/particle/MagicParticle.java | 6 +-- .../unicopia/client/render/RenderLayers.java | 10 +++- .../client/render/model/BakedModel.java | 25 +++++---- .../render/spell/ShieldSpellRenderer.java | 41 ++++++++++++++ .../spell/SpellEffectsRenderDispatcher.java | 1 + 7 files changed, 107 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java 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 4e50d13e..740a2a21 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 @@ -13,6 +13,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; +import com.minelittlepony.unicopia.particle.ParticleHandle; import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.particle.SphereParticleEffect; import com.minelittlepony.unicopia.particle.UParticles; @@ -51,6 +52,9 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega private int age = 0; private float accumulatedMass = 0; + @Deprecated + protected final ParticleHandle particlEffect = new ParticleHandle(); + protected DarkVortexSpell(CustomisedSpellType type) { super(type); } @@ -109,6 +113,11 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega return EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.test(entity) && getAttractiveForce(source, entity) > 0; } + @Override + protected void onDestroyed(Caster caster) { + particlEffect.destroy(); + } + @Override public void generateParticles(Caster source) { super.generateParticles(source); 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 71f5cede..023da1c7 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 @@ -1,5 +1,7 @@ package com.minelittlepony.unicopia.ability.magic.spell.effect; +import java.util.Optional; + import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.USounds; import com.minelittlepony.unicopia.Unicopia; @@ -10,11 +12,9 @@ import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.MagicParticleEffect; -import com.minelittlepony.unicopia.particle.ParticleHandle; -import com.minelittlepony.unicopia.particle.SphereParticleEffect; -import com.minelittlepony.unicopia.particle.UParticles; -import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment; +import com.minelittlepony.unicopia.particle.ParticleUtils; import com.minelittlepony.unicopia.projectile.ProjectileUtil; import com.minelittlepony.unicopia.util.shape.Sphere; @@ -30,6 +30,7 @@ import net.minecraft.entity.passive.PassiveEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.vehicle.AbstractMinecartEntity; import net.minecraft.entity.vehicle.BoatEntity; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; public class ShieldSpell extends AbstractSpell { @@ -40,10 +41,14 @@ public class ShieldSpell extends AbstractSpell { .with(Trait.AIR, 9) .build(); - protected final ParticleHandle particlEffect = new ParticleHandle(); - private final TargetSelecter targetSelecter = new TargetSelecter(this); + private float prevRadius; + private float radius; + + private float rangeMultiplier; + private float targetRangeMultiplier; + protected ShieldSpell(CustomisedSpellType type) { super(type); } @@ -53,33 +58,27 @@ public class ShieldSpell extends AbstractSpell { return method == CastingMethod.STAFF || getTraits().get(Trait.GENEROSITY) > 0 ? toPlaceable() : this; } - @Override - protected void onDestroyed(Caster caster) { - particlEffect.destroy(); - } - @Override public Affinity getAffinity() { return getTraits().get(Trait.DARKNESS) > 0 ? Affinity.BAD : Affinity.GOOD; } protected void generateParticles(Caster source) { - float radius = (float)getDrawDropOffRange(source); Vec3d origin = getOrigin(source); source.spawnParticles(origin, new Sphere(true, radius), (int)(radius * 6), pos -> { source.addParticle(new MagicParticleEffect(getType().getColor()), pos, Vec3d.ZERO); - }); - particlEffect.update(getUuid(), source, spawner -> { - spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, getType().getColor(), 0.3F, radius), origin, Vec3d.ZERO); - }).ifPresent(p -> { - p.setAttribute(Attachment.ATTR_RADIUS, radius); + if (source.asWorld().random.nextInt(10) == 0 && source.asWorld().random.nextFloat() < source.getCorruption().getScaled(1)) { + ParticleUtils.spawnParticle(source.asWorld(), new LightningBoltParticleEffect(true, 3, 2, 0.1F, Optional.empty()), pos, Vec3d.ZERO); + } }); } @Override public boolean tick(Caster source, Situation situation) { + prevRadius = radius; + radius = (float)getDrawDropOffRange(source); if (source.isClient()) { generateParticles(source); @@ -108,20 +107,32 @@ public class ShieldSpell extends AbstractSpell { cost *= costMultiplier / ((1 + source.getLevel().get()) * 3F); cost /= knowledge; - cost += getDrawDropOffRange(source) / 10F; + cost += radius / 10F; if (!source.subtractEnergyCost(cost)) { setDead(); } } + public float getRadius(float tickDelta) { + return MathHelper.lerp(tickDelta, prevRadius, radius); + } + /** * Calculates the maximum radius of the shield. aka The area of effect. */ public double getDrawDropOffRange(Caster source) { - float multiplier = source instanceof Pony pony && pony.asEntity().isSneaking() ? 1 : 2; + targetRangeMultiplier = source instanceof Pony pony && pony.asEntity().isSneaking() ? 1 : 2; + if (rangeMultiplier < targetRangeMultiplier - 0.1F) { + rangeMultiplier += 0.1F; + } else if (rangeMultiplier > targetRangeMultiplier + 0.1) { + rangeMultiplier -= 0.1F; + } else { + rangeMultiplier = targetRangeMultiplier; + } + float min = (source instanceof Pony ? 4 : 6) + getTraits().get(Trait.POWER); - double range = (min + (source.getLevel().getScaled(source instanceof Pony ? 4 : 40) * (source instanceof Pony ? 2 : 10))) / multiplier; + double range = (min + (source.getLevel().getScaled(source instanceof Pony ? 4 : 40) * (source instanceof Pony ? 2 : 10))) / rangeMultiplier; return range; } @@ -150,7 +161,7 @@ public class ShieldSpell extends AbstractSpell { } protected long applyEntities(Caster source) { - double radius = getDrawDropOffRange(source); + double radius = this.radius; Vec3d origin = getOrigin(source); diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java index 700963a6..fd40922f 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java @@ -22,9 +22,9 @@ public class MagicParticle extends SpriteBillboardParticle { velocityX = vX; velocityY = vY; velocityZ = vZ; - startX = x + random.nextGaussian()/3; - startY = y + random.nextGaussian()/3; - startZ = z + random.nextGaussian()/3; + startX = x;// + random.nextGaussian()/3; + startY = y;// + random.nextGaussian()/3; + startZ = z;// + random.nextGaussian()/3; scale = random.nextFloat() * 0.12F; maxAge = (int)(Math.random() * 10) + 20; diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/RenderLayers.java b/src/main/java/com/minelittlepony/unicopia/client/render/RenderLayers.java index 80eeefd4..5f58b7b1 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/RenderLayers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/RenderLayers.java @@ -72,7 +72,15 @@ public final class RenderLayers extends RenderLayer { } public static RenderLayer getMagicNoColor() { - return MAGIC_NO_COLOR; + //return MAGIC_NO_COLOR; + return of("magic_no_color", VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL, + VertexFormat.DrawMode.QUADS, 256, true, true, MultiPhaseParameters.builder() + .program(COLOR_PROGRAM) + .transparency(TRANSLUCENT_TRANSPARENCY) + .target(TRANSLUCENT_TARGET) + .cull(DISABLE_CULLING) + .writeMaskState(COLOR_MASK) + .build(false)); } public static RenderLayer getMagicColored() { diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/model/BakedModel.java b/src/main/java/com/minelittlepony/unicopia/client/render/model/BakedModel.java index f868ff04..694571c3 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/model/BakedModel.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/model/BakedModel.java @@ -3,38 +3,37 @@ package com.minelittlepony.unicopia.client.render.model; import java.util.ArrayList; import java.util.List; -import org.joml.Matrix4f; +import org.joml.Vector3f; import org.joml.Vector4f; +import com.minelittlepony.unicopia.client.render.RenderUtil; + import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.util.math.MatrixStack; public class BakedModel { - private static final Vector4f drawVert = new Vector4f(); - - protected final List vertices = new ArrayList<>(); + protected final List vertices = new ArrayList<>(); protected void addVertex(Vector4f vertex) { addVertex(vertex.x, vertex.y, vertex.z, 0, 0); } protected void addVertex(float x, float y, float z, float u, float v) { - vertices.add(new Vertex(x, y, z, u, v)); + vertices.add(new RenderUtil.Vertex(new Vector3f(x, y, z), u, v)); } - public final void render(MatrixStack matrices, VertexConsumer vertexWriter, int light, int overlay, float scale, float r, float g, float b, float a) { + public final void render(MatrixStack matrices, VertexConsumer buffer, int light, int overlay, float scale, float r, float g, float b, float a) { scale = Math.abs(scale); if (scale < 0.001F) { return; } - Matrix4f model = matrices.peek().getPositionMatrix(); - for (Vertex vertex : vertices) { - drawVert.set(vertex.x() * scale, vertex.y() * scale, vertex.z() * scale, 1); - drawVert.mul(model); - vertexWriter.vertex(drawVert.x, drawVert.y, drawVert.z, r, g, b, a, vertex.u(), vertex.v(), overlay, light, 0, 0, 0); + matrices.push(); + matrices.scale(scale, scale, scale); + for (RenderUtil.Vertex vertex : vertices) { + Vector4f pos = vertex.position(matrices); + buffer.vertex(pos.x, pos.y, pos.z, r, g, b, a, vertex.u(), vertex.v(), overlay, light, 0, 0, 0); } + matrices.pop(); } - - record Vertex(float x, float y, float z, float u, float v) {} } 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 new file mode 100644 index 00000000..45731fd0 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/ShieldSpellRenderer.java @@ -0,0 +1,41 @@ +package com.minelittlepony.unicopia.client.render.spell; + +import com.minelittlepony.common.util.Color; +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.ShieldSpell; +import com.minelittlepony.unicopia.client.render.RenderLayers; +import com.minelittlepony.unicopia.client.render.model.SphereModel; +import com.minelittlepony.unicopia.util.ColorHelper; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.VertexConsumerProvider.Immediate; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.MathHelper; + +public class ShieldSpellRenderer implements SpellRenderer { + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertices, ShieldSpell spell, Caster caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + matrices.push(); + double height = caster.asEntity().getEyeY() - caster.getOriginVector().y; + matrices.translate(0, height, 0); + + Immediate immediate = MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers(); + immediate.draw(); + + int color = ColorHelper.lerp(caster.getCorruption().getScaled(1) * (tickDelta / (1 + caster.asWorld().random.nextFloat())), spell.getType().getColor(), 0xFF000); + float[] colors = ColorHelper.changeSaturation(Color.r(color), Color.g(color), Color.b(color), 4); + float radius = 0.35F + spell.getRadius(tickDelta) + MathHelper.sin(animationProgress / 30F) * 0.01F; + + VertexConsumer buffer = vertices.getBuffer(RenderLayers.getMagicNoColor()); + + float thickness = 0.02F * MathHelper.sin(animationProgress / 30F); + float alpha = 1 - Math.abs(MathHelper.sin(animationProgress / 20F)) * 0.2F; + SphereModel.SPHERE.render(matrices, buffer, light, 1, radius + thickness, colors[0], colors[1], colors[2], alpha * 0.08F); + SphereModel.SPHERE.render(matrices, buffer, light, 1, radius - thickness, colors[0], colors[1], colors[2], alpha * 0.05F); + SphereModel.SPHERE.render(matrices, buffer, light, 1, radius + thickness * 2, colors[0], colors[1], colors[2], alpha * 0.05F); + + matrices.pop(); + } +} 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 f44aad5f..692a8e89 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 @@ -45,6 +45,7 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader static { register(SpellType.PLACED_SPELL, PlacedSpellRenderer::new); + register(SpellType.SHIELD, ShieldSpellRenderer::new); } @Nullable From 3827deb2350170ba49242f98e877202d133e296b Mon Sep 17 00:00:00 2001 From: Sollace Date: Sun, 21 Jan 2024 15:17:21 +0000 Subject: [PATCH 07/16] Set up a proper render layer for shields --- .../magic/spell/effect/DarkVortexSpell.java | 14 +++-------- .../unicopia/client/render/RenderLayers.java | 23 +++++++++++-------- .../render/spell/ShieldSpellRenderer.java | 2 +- 3 files changed, 18 insertions(+), 21 deletions(-) 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 740a2a21..0cea87e2 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 @@ -11,12 +11,9 @@ import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; -import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; import com.minelittlepony.unicopia.particle.ParticleHandle; import com.minelittlepony.unicopia.particle.ParticleUtils; -import com.minelittlepony.unicopia.particle.SphereParticleEffect; -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; @@ -113,16 +110,11 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega return EntityPredicates.EXCEPT_CREATIVE_OR_SPECTATOR.test(entity) && getAttractiveForce(source, entity) > 0; } - @Override - protected void onDestroyed(Caster caster) { - particlEffect.destroy(); - } - @Override public void generateParticles(Caster source) { super.generateParticles(source); - float radius = (float)getEventHorizonRadius(); + /*float radius = (float)getEventHorizonRadius(); particlEffect.update(getUuid(), source, spawner -> { spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, 0x000000, 0.99F, radius, SPHERE_OFFSET), source.getOriginVector(), Vec3d.ZERO); @@ -134,7 +126,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega spawner.addParticle(new SphereParticleEffect(UParticles.DISK, 0xAAAAAA, 0.4F, radius + 1, SPHERE_OFFSET), getOrigin(source), Vec3d.ZERO); }).ifPresent(p -> { p.setAttribute(Attachment.ATTR_RADIUS, radius * 0F); - }); + });*/ source.spawnParticles(ParticleTypes.SMOKE, 3); } @@ -185,7 +177,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega // 2. max force (at dist 0) is taken from accumulated mass // 3. force reaches 0 at distance of drawDropOffRange - private double getEventHorizonRadius() { + public double getEventHorizonRadius() { return Math.sqrt(Math.max(0.001, getMass() - 12)); } diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/RenderLayers.java b/src/main/java/com/minelittlepony/unicopia/client/render/RenderLayers.java index 5f58b7b1..45a3ae35 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/RenderLayers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/RenderLayers.java @@ -39,6 +39,15 @@ public final class RenderLayers extends RenderLayer { .target(TRANSLUCENT_TARGET) .build(false)); + private static final RenderLayer MAGIC_SHIELD = of("magic_shield", VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL, + VertexFormat.DrawMode.QUADS, 256, true, true, MultiPhaseParameters.builder() + .program(COLOR_PROGRAM) + .transparency(TRANSLUCENT_TRANSPARENCY) + .target(TRANSLUCENT_TARGET) + .cull(DISABLE_CULLING) + .writeMaskState(COLOR_MASK) + .build(false)); + private static final Function MAGIC_COLORIN_FUNC = Util.memoize(color -> { return of("magic_colored_" + color, VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL, @@ -72,15 +81,11 @@ public final class RenderLayers extends RenderLayer { } public static RenderLayer getMagicNoColor() { - //return MAGIC_NO_COLOR; - return of("magic_no_color", VertexFormats.POSITION_COLOR_TEXTURE_OVERLAY_LIGHT_NORMAL, - VertexFormat.DrawMode.QUADS, 256, true, true, MultiPhaseParameters.builder() - .program(COLOR_PROGRAM) - .transparency(TRANSLUCENT_TRANSPARENCY) - .target(TRANSLUCENT_TARGET) - .cull(DISABLE_CULLING) - .writeMaskState(COLOR_MASK) - .build(false)); + return MAGIC_NO_COLOR; + } + + public static RenderLayer getMagicShield() { + return MAGIC_SHIELD; } public static RenderLayer getMagicColored() { 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 45731fd0..347a66ba 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 @@ -28,7 +28,7 @@ public class ShieldSpellRenderer implements SpellRenderer { float[] colors = ColorHelper.changeSaturation(Color.r(color), Color.g(color), Color.b(color), 4); float radius = 0.35F + spell.getRadius(tickDelta) + MathHelper.sin(animationProgress / 30F) * 0.01F; - VertexConsumer buffer = vertices.getBuffer(RenderLayers.getMagicNoColor()); + VertexConsumer buffer = vertices.getBuffer(RenderLayers.getMagicShield()); float thickness = 0.02F * MathHelper.sin(animationProgress / 30F); float alpha = 1 - Math.abs(MathHelper.sin(animationProgress / 20F)) * 0.2F; From e09ade57b15935a1ef9d838537e2e1c8cafac0b2 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sun, 21 Jan 2024 15:19:49 +0000 Subject: [PATCH 08/16] Add a method to render a spell using the dispatcher --- .../client/render/spell/PlacedSpellRenderer.java | 5 +---- .../render/spell/SpellEffectsRenderDispatcher.java | 12 ++++++++---- 2 files changed, 9 insertions(+), 8 deletions(-) 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 index 4e293248..78789761 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java @@ -72,10 +72,7 @@ public class PlacedSpellRenderer implements SpellRenderer { matrices.pop(); - var renderer = SpellEffectsRenderDispatcher.INSTANCE.getRenderer(delegate); - if (renderer != null) { - renderer.render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); - } + SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertices, delegate, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); } } 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 692a8e89..e911a81a 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 @@ -64,12 +64,16 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader return (SpellRenderer)renderers.get(spell.getType()); } + 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) { + var renderer = getRenderer(spell); + if (renderer != null) { + renderer.render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); + } + } + public void render(MatrixStack matrices, VertexConsumerProvider vertices, int light, Caster caster, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { caster.getSpellSlot().forEach(spell -> { - var renderer = getRenderer(spell); - if (renderer != null) { - renderer.render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); - } + render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); return Operation.SKIP; }, false); From 9510b83c72f7e2ff21ad025ad0547739f7d3f6af Mon Sep 17 00:00:00 2001 From: Sollace Date: Sun, 21 Jan 2024 18:06:53 +0000 Subject: [PATCH 09/16] Implement a renderer for the back hole --- .../ability/magic/spell/PlaceableSpell.java | 10 +- .../magic/spell/effect/AttractiveSpell.java | 6 +- .../magic/spell/effect/DarkVortexSpell.java | 38 +++---- .../render/spell/DarkVortexSpellRenderer.java | 99 +++++++++++++++++++ .../render/spell/PlacedSpellRenderer.java | 5 + .../render/spell/ShieldSpellRenderer.java | 5 - .../spell/SpellEffectsRenderDispatcher.java | 2 + .../unicopia/entity/mob/CastSpellEntity.java | 1 + .../unicopia/entity/player/PlayerCamera.java | 2 + 9 files changed, 133 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java 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 index 98095393..bba345b4 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/PlaceableSpell.java @@ -106,11 +106,11 @@ public class PlaceableSpell extends AbstractDelegatingSpell implements OrientedS setDead(); return false; } - } else { - prevAge = age; - if (age < 25) { - age++; - } + } + + prevAge = age; + if (age < 25) { + age++; } return super.tick(source, Situation.GROUND); 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 d91970b2..a28dc8ee 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 @@ -7,7 +7,6 @@ import com.minelittlepony.unicopia.entity.EntityReference; import com.minelittlepony.unicopia.entity.Living; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; import com.minelittlepony.unicopia.particle.FollowingParticleEffect; -import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.particle.UParticles; import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; import com.minelittlepony.unicopia.projectile.ProjectileDelegate; @@ -16,6 +15,7 @@ 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; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; @@ -60,12 +60,12 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp @Override public void generateParticles(Caster source) { - double range = getDrawDropOffRange(source) + 10; + double range = getDrawDropOffRange(source); source.spawnParticles(getOrigin(source), new Sphere(false, range), 7, p -> { source.addParticle( new FollowingParticleEffect(UParticles.HEALTH_DRAIN, source.asEntity(), 0.4F) - .withChild(new MagicParticleEffect(getType().getColor())), + .withChild(ParticleTypes.AMBIENT_ENTITY_EFFECT), p, Vec3d.ZERO ); 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 0cea87e2..edf3b732 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 @@ -11,9 +11,10 @@ import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.damage.UDamageTypes; +import com.minelittlepony.unicopia.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; -import com.minelittlepony.unicopia.particle.ParticleHandle; import com.minelittlepony.unicopia.particle.ParticleUtils; +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; @@ -49,9 +50,6 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega private int age = 0; private float accumulatedMass = 0; - @Deprecated - protected final ParticleHandle particlEffect = new ParticleHandle(); - protected DarkVortexSpell(CustomisedSpellType type) { super(type); } @@ -114,21 +112,17 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega public void generateParticles(Caster source) { super.generateParticles(source); - /*float radius = (float)getEventHorizonRadius(); - - particlEffect.update(getUuid(), source, spawner -> { - spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, 0x000000, 0.99F, radius, SPHERE_OFFSET), source.getOriginVector(), Vec3d.ZERO); - }).ifPresent(p -> { - p.setAttribute(Attachment.ATTR_RADIUS, radius); - p.setAttribute(Attachment.ATTR_OPACITY, 2F); - }); - particlEffect.update(getUuid(), "_ring", source, spawner -> { - spawner.addParticle(new SphereParticleEffect(UParticles.DISK, 0xAAAAAA, 0.4F, radius + 1, SPHERE_OFFSET), getOrigin(source), Vec3d.ZERO); - }).ifPresent(p -> { - p.setAttribute(Attachment.ATTR_RADIUS, radius * 0F); - });*/ - - source.spawnParticles(ParticleTypes.SMOKE, 3); + if (getEventHorizonRadius() > 3) { + double range = getDrawDropOffRange(source); + source.spawnParticles(getOrigin(source), new Sphere(false, range), 17, p -> { + source.addParticle( + new FollowingParticleEffect(UParticles.HEALTH_DRAIN, source.asEntity(), 0.4F) + .withChild(ParticleTypes.CAMPFIRE_SIGNAL_SMOKE), + p, + Vec3d.ZERO + ); + }); + } } @Override @@ -178,7 +172,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega // 3. force reaches 0 at distance of drawDropOffRange public double getEventHorizonRadius() { - return Math.sqrt(Math.max(0.001, getMass() - 12)); + return Math.sqrt(Math.max(0.001, getMass() / 3F)); } private double getAttractiveForce(Caster source, Entity target) { @@ -186,8 +180,8 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega } private double getMass() { - float pulse = (float)Math.sin(age * 8) / 1F; - return 10 + Math.min(15, Math.min(0.5F + pulse, (float)Math.exp(age) / 8F - 90) + accumulatedMass / 10F) + pulse; + float pulse = (float)Math.sin(age * 8) / 160F; + return Math.min(15, Math.min(0.5F, (float)Math.exp(age) / 8F - 90) + accumulatedMass / 10F) + pulse; } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java new file mode 100644 index 00000000..98b32575 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java @@ -0,0 +1,99 @@ +package com.minelittlepony.unicopia.client.render.spell; + +import org.joml.Vector3f; +import org.joml.Vector4f; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.DarkVortexSpell; +import com.minelittlepony.unicopia.client.render.RenderLayers; +import com.minelittlepony.unicopia.client.render.RenderUtil; +import com.minelittlepony.unicopia.client.render.model.SphereModel; +import net.minecraft.client.MinecraftClient; +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.entity.Entity; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RotationAxis; + +public class DarkVortexSpellRenderer implements SpellRenderer { + + private static float cameraDistortion; + + public static float getCameraDistortion() { + cameraDistortion *= 0.9F; + cameraDistortion = MathHelper.clamp(cameraDistortion, 0, 80); + System.out.println(cameraDistortion); + return cameraDistortion; + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertices, DarkVortexSpell spell, Caster caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + matrices.push(); + double height = caster.asEntity().getEyeY() - caster.getOriginVector().y; + matrices.translate(0, height + 2, 0); + + Entity cameraEntity = MinecraftClient.getInstance().getCameraEntity(); + + float radius = (float)spell.getEventHorizonRadius(); + float absDistance = (float)cameraEntity.getEyePos().distanceTo(caster.getOriginVector().add(0, 2, 0)); + + SphereModel.SPHERE.render(matrices, vertices.getBuffer(RenderLayers.getSolid()), light, 1, Math.min(radius, absDistance / 2F), 0, 0, 0, 1); + + matrices.push(); + + + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(90)); + matrices.multiply(RotationAxis.NEGATIVE_X.rotationDegrees(90 + cameraEntity.getYaw(tickDelta))); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-cameraEntity.getPitch(tickDelta))); + matrices.scale(0.7F, 1, 1); + + + float distance = 1F / MathHelper.clamp((absDistance / (radius * 4)), 0.0000001F, 1); + distance *= distance; + if (absDistance < radius * 4) { + cameraDistortion += distance; + } + + matrices.scale(distance, distance, distance); + + matrices.push(); + for (int i = 0; i < 10; i++) { + matrices.scale(0.96F, 1, 1); + float brightness = i / 10F; + SphereModel.DISK.render(matrices, vertices.getBuffer(RenderLayers.getMagicNoColor()), light, 1, radius * (1 + (0.25F * i)), brightness, brightness, brightness, 0.2F); + } + matrices.pop(); + + SphereModel.DISK.render(matrices, vertices.getBuffer(RenderLayers.getEndPortal()), light, 1, radius * 0.5F, 1, 0.5F, 0, 1); + + matrices.pop(); + + if (radius > 1 && absDistance > radius) { + radius *= 1.1F; + + matrices.scale(radius, radius, radius); + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(animationProgress)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(45)); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(animationProgress * 168)); + + + RenderUtil.Vertex[] CORNERS = new RenderUtil.Vertex[]{ + new RenderUtil.Vertex(new Vector3f(-1, -1, 0), 0, 0), + new RenderUtil.Vertex(new Vector3f(-1, 1, 0), 1, 0), + new RenderUtil.Vertex(new Vector3f( 1, 1, 0), 1, 1), + new RenderUtil.Vertex(new Vector3f( 1, -1, 0), 0, 1) + }; + + VertexConsumer buffer = vertices.getBuffer(RenderLayer.getEntityTranslucent(new Identifier("textures/misc/forcefield.png"))); + + for (var corner : CORNERS) { + Vector4f pos = corner.position(matrices); + buffer.vertex(pos.x, pos.y, pos.z, 1, 1, 1, 1, corner.u(), corner.v(), 0, light, 1, 1, 1); + } + } + matrices.pop(); + } +} 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 index 78789761..142b11ac 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java @@ -9,6 +9,7 @@ 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.RenderUtil; +import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.VertexConsumer; @@ -36,6 +37,10 @@ public class PlacedSpellRenderer implements SpellRenderer { @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)) { + return; + } + for (Spell delegate : spell.getDelegates()) { matrices.push(); 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 347a66ba..55844dd9 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 @@ -7,10 +7,8 @@ import com.minelittlepony.unicopia.client.render.RenderLayers; import com.minelittlepony.unicopia.client.render.model.SphereModel; import com.minelittlepony.unicopia.util.ColorHelper; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumerProvider; -import net.minecraft.client.render.VertexConsumerProvider.Immediate; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.math.MathHelper; @@ -21,9 +19,6 @@ public class ShieldSpellRenderer implements SpellRenderer { double height = caster.asEntity().getEyeY() - caster.getOriginVector().y; matrices.translate(0, height, 0); - Immediate immediate = MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers(); - immediate.draw(); - int color = ColorHelper.lerp(caster.getCorruption().getScaled(1) * (tickDelta / (1 + caster.asWorld().random.nextFloat())), spell.getType().getColor(), 0xFF000); float[] colors = ColorHelper.changeSaturation(Color.r(color), Color.g(color), Color.b(color), 4); float radius = 0.35F + spell.getRadius(tickDelta) + MathHelper.sin(animationProgress / 30F) * 0.01F; 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 e911a81a..9de95849 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 @@ -46,6 +46,7 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader static { register(SpellType.PLACED_SPELL, PlacedSpellRenderer::new); register(SpellType.SHIELD, ShieldSpellRenderer::new); + register(SpellType.DARK_VORTEX, DarkVortexSpellRenderer::new); } @Nullable @@ -67,6 +68,7 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader 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) { var renderer = getRenderer(spell); if (renderer != null) { + MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers().draw(); renderer.render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); } } 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 df9c7e37..16a4100b 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/mob/CastSpellEntity.java @@ -37,6 +37,7 @@ public class CastSpellEntity extends LightEmittingEntity implements Caster type, World world) { super(type, world); + ignoreCameraFrustum = true; } @Override 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 11b71c2e..2a2a8449 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCamera.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCamera.java @@ -5,6 +5,7 @@ import java.util.Optional; import com.minelittlepony.common.util.animation.MotionCompositor; import com.minelittlepony.unicopia.ability.magic.SpellPredicate; import com.minelittlepony.unicopia.ability.magic.spell.AbstractDisguiseSpell; +import com.minelittlepony.unicopia.client.render.spell.DarkVortexSpellRenderer; import net.minecraft.util.math.Vec3d; @@ -62,6 +63,7 @@ public class PlayerCamera extends MotionCompositor { public double calculateFieldOfView(double fov) { fov += player.getMagicalReserves().getExertion().get() / 5F; fov += getEnergyAddition(); + fov += DarkVortexSpellRenderer.getCameraDistortion(); return fov; } From 134db120cdc584cbc0640f9e1cdb433ee1552312 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 22 Jan 2024 13:17:45 +0000 Subject: [PATCH 10/16] Fix timed spells' timers not displaying correctly --- .../minelittlepony/unicopia/ability/magic/spell/TimedSpell.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9e19b8ae..d81e4f39 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 @@ -29,7 +29,7 @@ public interface TimedSpell extends Spell { } public float getPercentTimeRemaining(float tickDelta) { - return MathHelper.lerp(tickDelta, prevDuration, duration) / maxDuration; + return MathHelper.lerp(tickDelta, prevDuration, duration) / (float)maxDuration; } public int getTicksRemaining() { From 8cb3ba298d085c75120b9ef272ad1f2fbdb2232a Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 22 Jan 2024 13:19:01 +0000 Subject: [PATCH 11/16] Fixed dark vortex particles not going to its actual center --- .../magic/spell/effect/AttractiveSpell.java | 5 ++-- .../magic/spell/effect/DarkVortexSpell.java | 5 ++-- .../particle/CloudsEscapingParticle.java | 2 +- .../client/particle/MagicParticle.java | 6 ++--- .../client/render/model/PlaneModel.java | 12 ++++++++++ .../render/spell/DarkVortexSpellRenderer.java | 23 ++++--------------- .../render/spell/PlacedSpellRenderer.java | 22 ++---------------- .../unicopia/util/shape/Sphere.java | 15 ++++++++++++ 8 files changed, 44 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/model/PlaneModel.java 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 a28dc8ee..9af5cddb 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 @@ -61,10 +61,11 @@ public class AttractiveSpell extends ShieldSpell implements HomingSpell, TimedSp @Override public void generateParticles(Caster source) { double range = getDrawDropOffRange(source); + Vec3d origin = getOrigin(source); - source.spawnParticles(getOrigin(source), new Sphere(false, range), 7, p -> { + source.spawnParticles(origin, new Sphere(false, range), 7, p -> { source.addParticle( - new FollowingParticleEffect(UParticles.HEALTH_DRAIN, source.asEntity(), 0.4F) + new FollowingParticleEffect(UParticles.HEALTH_DRAIN, origin, 0.4F) .withChild(ParticleTypes.AMBIENT_ENTITY_EFFECT), p, Vec3d.ZERO 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 edf3b732..c8fb3a6d 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 @@ -114,9 +114,10 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega if (getEventHorizonRadius() > 3) { double range = getDrawDropOffRange(source); - source.spawnParticles(getOrigin(source), new Sphere(false, range), 17, p -> { + Vec3d origin = getOrigin(source); + source.spawnParticles(origin, new Sphere(false, range), 17, p -> { source.addParticle( - new FollowingParticleEffect(UParticles.HEALTH_DRAIN, source.asEntity(), 0.4F) + new FollowingParticleEffect(UParticles.HEALTH_DRAIN, origin, 0.4F) .withChild(ParticleTypes.CAMPFIRE_SIGNAL_SMOKE), p, Vec3d.ZERO diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/CloudsEscapingParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/CloudsEscapingParticle.java index 50792cfe..32ff15f7 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/CloudsEscapingParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/CloudsEscapingParticle.java @@ -30,7 +30,7 @@ public class CloudsEscapingParticle extends GroundPoundParticle { ); double columnHeight = 1 + age / 30; - new Sphere(true, columnHeight, 1, 1, 1) + new Sphere(true, columnHeight) .translate(center) .randomPoints(random) .forEach(point -> { diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java index fd40922f..700963a6 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/MagicParticle.java @@ -22,9 +22,9 @@ public class MagicParticle extends SpriteBillboardParticle { velocityX = vX; velocityY = vY; velocityZ = vZ; - startX = x;// + random.nextGaussian()/3; - startY = y;// + random.nextGaussian()/3; - startZ = z;// + random.nextGaussian()/3; + startX = x + random.nextGaussian()/3; + startY = y + random.nextGaussian()/3; + startZ = z + random.nextGaussian()/3; scale = random.nextFloat() * 0.12F; maxAge = (int)(Math.random() * 10) + 20; diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/model/PlaneModel.java b/src/main/java/com/minelittlepony/unicopia/client/render/model/PlaneModel.java new file mode 100644 index 00000000..bfbcfc24 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/model/PlaneModel.java @@ -0,0 +1,12 @@ +package com.minelittlepony.unicopia.client.render.model; + +public class PlaneModel extends BakedModel { + public static final PlaneModel INSTANCE = new PlaneModel(); + + private PlaneModel() { + addVertex(-1, -1, 0, 0, 0); + addVertex(-1, 1, 0, 1, 0); + addVertex( 1, 1, 0, 1, 1); + addVertex( 1, -1, 0, 0, 1); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java index 98b32575..0389cfca 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java @@ -1,12 +1,9 @@ package com.minelittlepony.unicopia.client.render.spell; -import org.joml.Vector3f; -import org.joml.Vector4f; - import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.effect.DarkVortexSpell; import com.minelittlepony.unicopia.client.render.RenderLayers; -import com.minelittlepony.unicopia.client.render.RenderUtil; +import com.minelittlepony.unicopia.client.render.model.PlaneModel; import com.minelittlepony.unicopia.client.render.model.SphereModel; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.RenderLayer; @@ -20,12 +17,13 @@ import net.minecraft.util.math.RotationAxis; public class DarkVortexSpellRenderer implements SpellRenderer { + private static final Identifier ECRETION_RING_TEXTURE = new Identifier("textures/misc/forcefield.png"); + private static float cameraDistortion; public static float getCameraDistortion() { cameraDistortion *= 0.9F; cameraDistortion = MathHelper.clamp(cameraDistortion, 0, 80); - System.out.println(cameraDistortion); return cameraDistortion; } @@ -79,20 +77,9 @@ public class DarkVortexSpellRenderer implements SpellRenderer { matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(45)); matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(animationProgress * 168)); + VertexConsumer buffer = vertices.getBuffer(RenderLayer.getEntityTranslucent(ECRETION_RING_TEXTURE)); - RenderUtil.Vertex[] CORNERS = new RenderUtil.Vertex[]{ - new RenderUtil.Vertex(new Vector3f(-1, -1, 0), 0, 0), - new RenderUtil.Vertex(new Vector3f(-1, 1, 0), 1, 0), - new RenderUtil.Vertex(new Vector3f( 1, 1, 0), 1, 1), - new RenderUtil.Vertex(new Vector3f( 1, -1, 0), 0, 1) - }; - - VertexConsumer buffer = vertices.getBuffer(RenderLayer.getEntityTranslucent(new Identifier("textures/misc/forcefield.png"))); - - for (var corner : CORNERS) { - Vector4f pos = corner.position(matrices); - buffer.vertex(pos.x, pos.y, pos.z, 1, 1, 1, 1, corner.u(), corner.v(), 0, light, 1, 1, 1); - } + PlaneModel.INSTANCE.render(matrices, buffer, light, 0, 1, 1, 1, 1, 1); } matrices.pop(); } 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 index 142b11ac..4630912b 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/PlacedSpellRenderer.java @@ -1,14 +1,11 @@ package com.minelittlepony.unicopia.client.render.spell; -import org.joml.Vector3f; -import org.joml.Vector4f; - 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.RenderUtil; +import com.minelittlepony.unicopia.client.render.model.PlaneModel; import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; import net.minecraft.client.render.RenderLayer; @@ -27,12 +24,6 @@ public class PlacedSpellRenderer implements SpellRenderer { Unicopia.id("textures/particles/runes_4.png"), Unicopia.id("textures/particles/runes_5.png") }; - private static final RenderUtil.Vertex[] CORNERS = new RenderUtil.Vertex[]{ - new RenderUtil.Vertex(new Vector3f(-1, -1, 0), 0, 0), - new RenderUtil.Vertex(new Vector3f(-1, 1, 0), 1, 0), - new RenderUtil.Vertex(new Vector3f( 1, 1, 0), 1, 1), - new RenderUtil.Vertex(new Vector3f( 1, -1, 0), 0, 1) - }; @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) { @@ -50,8 +41,6 @@ public class PlacedSpellRenderer implements SpellRenderer { float scale = (spell.getAge(tickDelta) / 25F) * 3; matrices.scale(scale, scale, scale); - float alpha = scale; - float angle = (animationProgress / 9F) % 360; int color = delegate.getType().getColor(); @@ -70,7 +59,7 @@ public class PlacedSpellRenderer implements SpellRenderer { 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)); - renderQuad(buffer, matrices, red, green, blue, alpha / ((float)(dim * 3) + 1), light); + PlaneModel.INSTANCE.render(matrices, buffer, light, 0, 1, red, green, blue, scale / ((float)(dim * 3) + 1)); matrices.pop(); } } @@ -80,11 +69,4 @@ public class PlacedSpellRenderer implements SpellRenderer { SpellEffectsRenderDispatcher.INSTANCE.render(matrices, vertices, delegate, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); } } - - protected final void renderQuad(VertexConsumer buffer, MatrixStack matrices, float red, float green, float blue, float alpha, int light) { - for (var corner : CORNERS) { - Vector4f pos = corner.position(matrices); - buffer.vertex(pos.x, pos.y, pos.z, red, green, blue, alpha, corner.u(), corner.v(), 0, light, 1, 1, 1); - } - } } diff --git a/src/main/java/com/minelittlepony/unicopia/util/shape/Sphere.java b/src/main/java/com/minelittlepony/unicopia/util/shape/Sphere.java index 7ebf61af..c151b47c 100644 --- a/src/main/java/com/minelittlepony/unicopia/util/shape/Sphere.java +++ b/src/main/java/com/minelittlepony/unicopia/util/shape/Sphere.java @@ -1,5 +1,7 @@ package com.minelittlepony.unicopia.util.shape; +import org.spongepowered.include.com.google.common.base.Objects; + import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.random.Random; @@ -102,6 +104,19 @@ public class Sphere implements Shape { return stretch.multiply(rad); } + @Override + public boolean equals(Object other) { + return other instanceof Sphere o + && Objects.equal(stretch, o.stretch) + && hollow == o.hollow + && Double.compare(rad, o.rad) == 0; + } + + @Override + public int hashCode() { + return Objects.hashCode(stretch, hollow, rad); + } + public static double computeEllipsoidArea(double rad, Vec3d stretch) { double p = 1.6075; double result = Math.pow(rad * stretch.x, p) * Math.pow(rad * stretch.y, p); From e1d9faa223dd86c1bec6829351ce119adc0f5091 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 22 Jan 2024 13:19:29 +0000 Subject: [PATCH 12/16] Switch to using a spell renderer for bubble spells --- .../magic/spell/effect/BubbleSpell.java | 32 +++++++-------- .../unicopia/client/URenderers.java | 2 + .../particle/FloatingBubbleParticle.java | 31 ++++++++++++++ .../render/spell/BubbleSpellRenderer.java | 41 +++++++++++++++++++ .../unicopia/particle/UParticles.java | 1 + .../assets/unicopia/particles/bubble.json | 5 +++ 6 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/particle/FloatingBubbleParticle.java create mode 100644 src/main/java/com/minelittlepony/unicopia/client/render/spell/BubbleSpellRenderer.java create mode 100644 src/main/resources/assets/unicopia/particles/bubble.json 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 4a9dc915..db9f7d5e 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 @@ -11,12 +11,10 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; import com.minelittlepony.unicopia.entity.*; import com.minelittlepony.unicopia.entity.mob.UEntityAttributes; import com.minelittlepony.unicopia.entity.player.Pony; -import com.minelittlepony.unicopia.particle.ParticleHandle; -import com.minelittlepony.unicopia.particle.SphereParticleEffect; import com.minelittlepony.unicopia.particle.UParticles; -import com.minelittlepony.unicopia.particle.ParticleHandle.Attachment; 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; @@ -48,11 +46,11 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, .with(Trait.POWER, 1) .build(); - protected final ParticleHandle particlEffect = new ParticleHandle(); - private final Timer timer; private int struggles; + + private float prevRadius; private float radius; protected BubbleSpell(CustomisedSpellType type) { @@ -66,6 +64,10 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, return timer; } + public float getRadius(float tickDelta) { + return MathHelper.lerp(tickDelta, prevRadius, radius); + } + @Override public boolean apply(Caster source) { @@ -95,14 +97,19 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, public boolean tick(Caster source, Situation situation) { if (situation == Situation.PROJECTILE) { - - source.spawnParticles(ParticleTypes.BUBBLE, 2); + source.spawnParticles(UParticles.BUBBLE, 2); return true; } timer.tick(); - if (timer.getTicksRemaining() <= 0) { + 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.addParticle(done ? ParticleTypes.BUBBLE_POP : UParticles.BUBBLE, pos, Vec3d.ZERO); + }); + + if (done) { return false; } @@ -116,7 +123,7 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, source.asEntity().fallDistance = 0; - Vec3d origin = source.getOriginVector(); + prevRadius = radius; if (source instanceof Pony pony && pony.sneakingChanged() && pony.asEntity().isSneaking()) { setDirty(); @@ -128,18 +135,11 @@ public class BubbleSpell extends AbstractSpell implements TimedSpell, } } - particlEffect.update(getUuid(), source, spawner -> { - spawner.addParticle(new SphereParticleEffect(UParticles.SPHERE, 0xFFFFFF, 0.3F, 0, new Vec3d(0, radius / 2F, 0)), origin, Vec3d.ZERO); - }).ifPresent(p -> { - p.setAttribute(Attachment.ATTR_RADIUS, radius); - }); - return !isDead(); } @Override protected void onDestroyed(Caster source) { - particlEffect.destroy(); if (source.asEntity() instanceof LivingEntity l) { MODIFIERS.forEach((attribute, modifier) -> { if (l.getAttributes().hasAttribute(attribute)) { diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index 98046073..1d91a1d5 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -10,6 +10,7 @@ import com.minelittlepony.unicopia.block.cloud.CloudChestBlock; import com.minelittlepony.unicopia.client.particle.ChangelingMagicParticle; import com.minelittlepony.unicopia.client.particle.CloudsEscapingParticle; import com.minelittlepony.unicopia.client.particle.DiskParticle; +import com.minelittlepony.unicopia.client.particle.FloatingBubbleParticle; import com.minelittlepony.unicopia.client.particle.GroundPoundParticle; import com.minelittlepony.unicopia.client.particle.HealthDrainParticle; import com.minelittlepony.unicopia.client.particle.LightningBoltParticle; @@ -65,6 +66,7 @@ public interface URenderers { static void bootstrap() { ParticleFactoryRegistry.getInstance().register(UParticles.UNICORN_MAGIC, createFactory(MagicParticle::new)); ParticleFactoryRegistry.getInstance().register(UParticles.CHANGELING_MAGIC, createFactory(ChangelingMagicParticle::new)); + ParticleFactoryRegistry.getInstance().register(UParticles.BUBBLE, createFactory(FloatingBubbleParticle::new)); ParticleFactoryRegistry.getInstance().register(UParticles.RAIN_DROPS, createFactory(RaindropsParticle::new)); ParticleFactoryRegistry.getInstance().register(UParticles.HEALTH_DRAIN, createFactory(HealthDrainParticle::create)); ParticleFactoryRegistry.getInstance().register(UParticles.RAINBOOM_RING, RainboomParticle::new); diff --git a/src/main/java/com/minelittlepony/unicopia/client/particle/FloatingBubbleParticle.java b/src/main/java/com/minelittlepony/unicopia/client/particle/FloatingBubbleParticle.java new file mode 100644 index 00000000..68d51be7 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/particle/FloatingBubbleParticle.java @@ -0,0 +1,31 @@ +package com.minelittlepony.unicopia.client.particle; + +import net.minecraft.client.particle.ParticleTextureSheet; +import net.minecraft.client.particle.SpriteBillboardParticle; +import net.minecraft.client.particle.SpriteProvider; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.particle.ParticleEffect; +import net.minecraft.particle.ParticleTypes; + +public class FloatingBubbleParticle extends SpriteBillboardParticle { + public FloatingBubbleParticle(ParticleEffect effect, SpriteProvider provider, ClientWorld clientWorld, double x, double y, double z, double dX, double dY, double dZ) { + super(clientWorld, x, y, z, dX, dY, dZ); + setSprite(provider); + scale((float)clientWorld.random.nextTriangular(1F, 0.5F)); + this.velocityX *= -0.1F; + this.velocityY *= -0.1F; + this.velocityZ *= -0.1F; + this.maxAge *= 3; + } + + @Override + public ParticleTextureSheet getType() { + return ParticleTextureSheet.PARTICLE_SHEET_OPAQUE; + } + + @Override + public void markDead() { + super.markDead(); + world.addParticle(ParticleTypes.BUBBLE_POP, x, y, z, 0, 0, 0); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/BubbleSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/BubbleSpellRenderer.java new file mode 100644 index 00000000..d15863b7 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/BubbleSpellRenderer.java @@ -0,0 +1,41 @@ +package com.minelittlepony.unicopia.client.render.spell; + +import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.ability.magic.spell.effect.BubbleSpell; +import com.minelittlepony.unicopia.client.gui.DrawableUtil; +import com.minelittlepony.unicopia.client.render.RenderLayers; +import com.minelittlepony.unicopia.client.render.model.SphereModel; + +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 BubbleSpellRenderer implements SpellRenderer { + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertices, BubbleSpell spell, Caster caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + matrices.push(); + double height = caster.asEntity().getEyeY() - caster.getOriginVector().y; + matrices.translate(0, height * 0.5F, 0); + + float radius = spell.getRadius(tickDelta) * 0.7F; + + VertexConsumer buffer = vertices.getBuffer(RenderLayers.getMagicNoColor()); + + Entity cameraEntity = MinecraftClient.getInstance().getCameraEntity(); + + matrices.push(); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-45)); + matrices.multiply(RotationAxis.NEGATIVE_X.rotationDegrees(45 + cameraEntity.getYaw(tickDelta))); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-cameraEntity.getPitch(tickDelta))); + + new SphereModel(40, 40, DrawableUtil.PI * 0.25F).render(matrices, buffer, light, 0, radius - 0.1F, 0.9F, 0.9F, 1, 0.3F); + matrices.pop(); + + SphereModel.SPHERE.render(matrices, buffer, light, 0, radius, 0.9F, 0.9F, 1, 0.25F); + + matrices.pop(); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java index 186be3e0..f1e88314 100644 --- a/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java +++ b/src/main/java/com/minelittlepony/unicopia/particle/UParticles.java @@ -12,6 +12,7 @@ public interface UParticles { ParticleType UNICORN_MAGIC = register("unicorn_magic", FabricParticleTypes.complex(MagicParticleEffect.FACTORY)); DefaultParticleType CHANGELING_MAGIC = register("changeling_magic", FabricParticleTypes.simple()); + DefaultParticleType BUBBLE = register("bubble", FabricParticleTypes.simple()); ParticleType RAINBOOM_RING = register("rainboom_ring", FabricParticleTypes.complex(OrientedBillboardParticleEffect.FACTORY)); DefaultParticleType RAINBOOM_TRAIL = register("rainboom_trail", FabricParticleTypes.simple()); diff --git a/src/main/resources/assets/unicopia/particles/bubble.json b/src/main/resources/assets/unicopia/particles/bubble.json new file mode 100644 index 00000000..21bb4eeb --- /dev/null +++ b/src/main/resources/assets/unicopia/particles/bubble.json @@ -0,0 +1,5 @@ +{ + "textures": [ + "minecraft:bubble" + ] +} From dd9bec2a0ccde9a06b3f6092a2430e170d060dd5 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 22 Jan 2024 13:19:58 +0000 Subject: [PATCH 13/16] Added a visible icon for active spells in the world (shows timer countdown when applicable) --- .../spell/SpellEffectsRenderDispatcher.java | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) 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 9de95849..341abe8a 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 @@ -8,12 +8,18 @@ import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; +import com.minelittlepony.unicopia.EquinePredicates; 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.spell.Spell; +import com.minelittlepony.unicopia.ability.magic.spell.TimedSpell; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.client.gui.DrawableUtil; import com.minelittlepony.unicopia.entity.Living; +import com.minelittlepony.unicopia.entity.mob.CastSpellEntity; +import com.minelittlepony.unicopia.projectile.MagicProjectileEntity; +import com.minelittlepony.unicopia.util.ColorHelper; import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.minecraft.client.MinecraftClient; @@ -22,6 +28,7 @@ import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.WorldRenderer; +import net.minecraft.client.render.model.json.ModelTransformationMode; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.registry.Registries; import net.minecraft.resource.ResourceManager; @@ -31,6 +38,7 @@ import net.minecraft.util.Colors; import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import net.minecraft.util.math.Box; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.RotationAxis; import net.minecraft.util.math.Vec3d; @@ -47,6 +55,7 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader register(SpellType.PLACED_SPELL, PlacedSpellRenderer::new); register(SpellType.SHIELD, ShieldSpellRenderer::new); register(SpellType.DARK_VORTEX, DarkVortexSpellRenderer::new); + register(SpellType.BUBBLE, BubbleSpellRenderer::new); } @Nullable @@ -67,9 +76,15 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader 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) { var renderer = getRenderer(spell); + if (renderer != null) { - MinecraftClient.getInstance().getBufferBuilders().getEntityVertexConsumers().draw(); + client.getBufferBuilders().getEntityVertexConsumers().draw(); + renderer.render(matrices, vertices, spell, caster, light, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); + + if (EquinePredicates.IS_CASTER.test(client.player)) { + renderGemstone(matrices, vertices, spell, caster, light, tickDelta, animationProgress); + } } } @@ -90,6 +105,38 @@ public class SpellEffectsRenderDispatcher implements SynchronousResourceReloader renderers = REGISTRY.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().create())); } + private void renderGemstone(MatrixStack matrices, VertexConsumerProvider vertices, Spell spell, Caster caster, int light, float tickDelta, float animationProgress) { + matrices.push(); + + if (!(caster.asEntity() instanceof MagicProjectileEntity)) { + + float y = -caster.asEntity().getHeight(); + if (caster.asEntity() instanceof CastSpellEntity) { + y = 1F; + } + + matrices.translate(0, y + MathHelper.sin(animationProgress / 3F) * 0.2F, 0); + 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); + matrices.pop(); + + if (spell instanceof TimedSpell timed && spell.getType() != SpellType.DARK_VORTEX) { + matrices.multiply(client.getEntityRenderDispatcher().getRotation().invert()); + float radius = 0.6F; + float timeRemaining = timed.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 + ); + } + } + + matrices.pop(); + } + private void renderSpellDebugInfo(MatrixStack matrices, VertexConsumerProvider vertices, Caster caster, int light) { matrices.push(); matrices.multiply(client.getEntityRenderDispatcher().getRotation()); From 39672e5028f2aa5ed3754f2d1e0190b7ca7c5b87 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 22 Jan 2024 13:48:03 +0000 Subject: [PATCH 14/16] Fixed crash when equipping the bangle of comradery --- .../minelittlepony/unicopia/item/FriendshipBraceletItem.java | 2 +- .../java/com/minelittlepony/unicopia/item/WearableItem.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java b/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java index 326ddd43..da2018d0 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/FriendshipBraceletItem.java @@ -78,7 +78,7 @@ public class FriendshipBraceletItem extends WearableItem implements DyeableItem, @Override public EquipmentSlot getSlotType(ItemStack stack) { - return isSigned(stack) ? EquipmentSlot.CHEST : super.getSlotType(); + return isSigned(stack) ? EquipmentSlot.CHEST : super.getSlotType(stack); } private boolean checkSignature(ItemStack stack, PlayerEntity player) { diff --git a/src/main/java/com/minelittlepony/unicopia/item/WearableItem.java b/src/main/java/com/minelittlepony/unicopia/item/WearableItem.java index b745b9bb..1b7067da 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/WearableItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/WearableItem.java @@ -49,6 +49,7 @@ public abstract class WearableItem extends Item implements Equipment { return ArmorMaterials.LEATHER.getEquipSound(); } + @Deprecated @Override public final EquipmentSlot getSlotType() { return getSlotType(getDefaultStack()); From 34aa3b8ced535885cfc245013aadf02d9eb852f8 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 22 Jan 2024 13:48:24 +0000 Subject: [PATCH 15/16] Fixed spells not rendering on non-pony entities --- .../render/AccessoryFeatureRenderer.java | 15 +++++--- .../client/MixinArmorFeatureRenderer.java | 18 +++------- .../client/MixinLivingEntityRenderer.java | 34 ++++++++++++++++++- .../client/MixinPlayerEntityRenderer.java | 23 ++----------- 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java index 5ef86cf0..857e4c96 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java @@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.client.FirstPersonRendererOverrides.ArmRenderer; +import com.minelittlepony.unicopia.client.minelittlepony.MineLPDelegate; import com.minelittlepony.unicopia.client.render.spell.SpellEffectsRenderDispatcher; import net.minecraft.client.MinecraftClient; @@ -13,14 +14,14 @@ import net.minecraft.client.model.ModelPart; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.feature.FeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRendererContext; -import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.model.EntityModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.LivingEntity; import net.minecraft.util.Arm; public class AccessoryFeatureRenderer< T extends LivingEntity, - M extends BipedEntityModel> extends FeatureRenderer { + M extends EntityModel> extends FeatureRenderer { private static final List> REGISTRY = new ArrayList<>(); @@ -40,6 +41,10 @@ public class AccessoryFeatureRenderer< @Override public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + if (MineLPDelegate.getInstance().getRace(entity).isEquine()) { + return; + } + features.forEach(feature -> feature.render(matrices, vertexConsumers, light, entity, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch)); Caster.of(entity).ifPresent(caster -> { @@ -60,7 +65,7 @@ public class AccessoryFeatureRenderer< } public interface FeatureFactory { - Feature create(FeatureRendererContext> context); + Feature create(FeatureRendererContext> context); } public interface Feature { @@ -75,11 +80,11 @@ public class AccessoryFeatureRenderer< public interface FeatureRoot< T extends LivingEntity, - M extends BipedEntityModel> { + M extends EntityModel> { AccessoryFeatureRenderer getAccessories(); @SuppressWarnings("unchecked") @Nullable - static > FeatureRoot of(T entity) { + static > FeatureRoot of(T entity) { var renderer = MinecraftClient.getInstance().getEntityRenderDispatcher().getRenderer(entity); if (renderer instanceof FeatureRoot) { return (FeatureRoot)renderer; diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinArmorFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinArmorFeatureRenderer.java index be01f778..2e6cc68b 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinArmorFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinArmorFeatureRenderer.java @@ -1,19 +1,9 @@ package com.minelittlepony.unicopia.mixin.client; 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.CallbackInfo; - -import com.minelittlepony.unicopia.client.render.AccessoryFeatureRenderer; - -import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRenderer; -import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; -import net.minecraft.client.render.model.BakedModelManager; -import net.minecraft.client.util.math.MatrixStack; import net.minecraft.entity.LivingEntity; @Mixin(ArmorFeatureRenderer.class) @@ -21,13 +11,13 @@ abstract class MixinArmorFeatureRenderer< T extends LivingEntity, M extends BipedEntityModel, A extends BipedEntityModel> - extends FeatureRenderer implements AccessoryFeatureRenderer.FeatureRoot { + extends FeatureRenderer/* implements AccessoryFeatureRenderer.FeatureRoot*/ { - private AccessoryFeatureRenderer accessories; + //private AccessoryFeatureRenderer accessories; MixinArmorFeatureRenderer() { super(null); } - @Override + /*@Override public AccessoryFeatureRenderer getAccessories() { return accessories; } @@ -40,5 +30,5 @@ abstract class MixinArmorFeatureRenderer< @Inject(method = "render", at = @At("RETURN")) private void onRender(MatrixStack stack, VertexConsumerProvider renderContext, int lightUv, T entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch, CallbackInfo info) { getAccessories().render(stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch); - } + }*/ } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinLivingEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinLivingEntityRenderer.java index 3e153574..6f915b70 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinLivingEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinLivingEntityRenderer.java @@ -1,17 +1,25 @@ package com.minelittlepony.unicopia.mixin.client; +import java.util.List; + +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.At.Shift; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import com.minelittlepony.unicopia.client.render.AccessoryFeatureRenderer; import com.minelittlepony.unicopia.client.render.AnimalPoser; import com.minelittlepony.unicopia.client.render.PlayerPoser; +import com.minelittlepony.unicopia.client.render.AccessoryFeatureRenderer.FeatureRoot; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.EntityRenderer; import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.feature.FeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.render.entity.model.EntityModel; @@ -21,8 +29,31 @@ import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.player.PlayerEntity; @Mixin(LivingEntityRenderer.class) -abstract class MixinLivingEntityRenderer> extends EntityRenderer implements FeatureRendererContext { +abstract class MixinLivingEntityRenderer> extends EntityRenderer + implements FeatureRendererContext, FeatureRoot { + @Shadow + private @Final List> features; + MixinLivingEntityRenderer() { super(null); } + @Nullable + private AccessoryFeatureRenderer accessories; + + @Override + @SuppressWarnings("unchecked") + public AccessoryFeatureRenderer getAccessories() { + if (accessories == null) { + accessories = features.stream() + .filter(a -> a instanceof FeatureRoot) + .map(a -> ((FeatureRoot)a).getAccessories()) + .findFirst() + .orElseGet(() -> { + var feature = new AccessoryFeatureRenderer<>(this); + features.add(feature); + return feature; + }); + } + return accessories; + } @Inject(method = "render", at = @At( @@ -36,6 +67,7 @@ abstract class MixinLivingEntityRenderer)getModel(), PlayerPoser.Context.THIRD_PERSON); } diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinPlayerEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinPlayerEntityRenderer.java index 35503ffd..cd1eac71 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinPlayerEntityRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinPlayerEntityRenderer.java @@ -1,13 +1,11 @@ package com.minelittlepony.unicopia.mixin.client; -import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.At.Shift; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import com.minelittlepony.unicopia.client.render.AccessoryFeatureRenderer; import com.minelittlepony.unicopia.client.render.PlayerPoser; import com.minelittlepony.unicopia.client.render.AccessoryFeatureRenderer.FeatureRoot; @@ -21,31 +19,14 @@ import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.Arm; @Mixin(PlayerEntityRenderer.class) -abstract class MixinPlayerEntityRenderer - extends LivingEntityRenderer> - implements FeatureRoot> { - @Nullable - private AccessoryFeatureRenderer> accessories; - +abstract class MixinPlayerEntityRenderer extends LivingEntityRenderer> { MixinPlayerEntityRenderer() { super(null, null, 0); } - @Override @SuppressWarnings("unchecked") - public AccessoryFeatureRenderer> getAccessories() { - if (accessories == null) { - accessories = features.stream() - .filter(a -> a instanceof FeatureRoot) - .map(a -> ((FeatureRoot>)a).getAccessories()) - .findFirst() - .orElseGet(() -> new AccessoryFeatureRenderer<>(this)); - } - return accessories; - } - @Inject(method = "renderArm", at = @At("RETURN")) private void onRenderArm(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, AbstractClientPlayerEntity player, ModelPart arm, ModelPart sleeve, CallbackInfo info) { Arm a = this.getModel().leftArm == arm ? Arm.LEFT : Arm.RIGHT; - getAccessories().renderArm(matrices, vertexConsumers, light, player, arm, a); + ((FeatureRoot>)this).getAccessories().renderArm(matrices, vertexConsumers, light, player, arm, a); } @Inject(method = "renderArm", From 225c67d5582d51c109f7cbfce63e036891ac6270 Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 22 Jan 2024 15:47:58 +0000 Subject: [PATCH 16/16] Tweak dark vortex rendering and fix its behaviour --- .../magic/spell/effect/DarkVortexSpell.java | 37 +++++++------ .../magic/spell/effect/TargetSelecter.java | 8 ++- .../unicopia/client/gui/UHud.java | 15 ++++++ .../render/spell/DarkVortexSpellRenderer.java | 50 +++++++++++------- .../unicopia/entity/player/PlayerCamera.java | 2 +- .../spells/dark_vortex/accretion_disk.png | Bin 0 -> 55384 bytes 6 files changed, 76 insertions(+), 36 deletions(-) create mode 100644 src/main/resources/assets/unicopia/textures/spells/dark_vortex/accretion_disk.png 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 c8fb3a6d..7c05d439 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 @@ -10,6 +10,7 @@ import com.minelittlepony.unicopia.ability.magic.spell.Situation; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits; 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.particle.FollowingParticleEffect; import com.minelittlepony.unicopia.particle.LightningBoltParticleEffect; @@ -47,7 +48,6 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega private static final Vec3d SPHERE_OFFSET = new Vec3d(0, 2, 0); - private int age = 0; private float accumulatedMass = 0; protected DarkVortexSpell(CustomisedSpellType type) { @@ -79,7 +79,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega return true; } - if (++age % 20 == 0) { + if (source.asEntity().age % 20 == 0) { source.asWorld().playSound(null, source.getOrigin(), USounds.AMBIENT_DARK_VORTEX_ADDITIONS, SoundCategory.AMBIENT, 1, 1); } @@ -112,16 +112,18 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega public void generateParticles(Caster source) { super.generateParticles(source); - if (getEventHorizonRadius() > 3) { + if (getEventHorizonRadius() > 0.3) { double range = getDrawDropOffRange(source); Vec3d origin = getOrigin(source); - source.spawnParticles(origin, new Sphere(false, range), 17, p -> { - source.addParticle( - new FollowingParticleEffect(UParticles.HEALTH_DRAIN, origin, 0.4F) - .withChild(ParticleTypes.CAMPFIRE_SIGNAL_SMOKE), - p, - Vec3d.ZERO - ); + source.spawnParticles(origin, new Sphere(false, range), 1, p -> { + if (!source.asWorld().isAir(BlockPos.ofFloored(p))) { + source.addParticle( + new FollowingParticleEffect(UParticles.HEALTH_DRAIN, origin, 0.4F) + .withChild(ParticleTypes.CAMPFIRE_SIGNAL_SMOKE), + p, + Vec3d.ZERO + ); + } }); } } @@ -181,8 +183,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega } private double getMass() { - float pulse = (float)Math.sin(age * 8) / 160F; - return Math.min(15, Math.min(0.5F, (float)Math.exp(age) / 8F - 90) + accumulatedMass / 10F) + pulse; + return Math.min(15, 0.1F + accumulatedMass / 10F); } @Override @@ -194,6 +195,11 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega if (distance <= getEventHorizonRadius() + 0.5) { target.setVelocity(target.getVelocity().multiply(distance / (2 * radius))); + if (distance < 1) { + target.setVelocity(target.getVelocity().multiply(distance)); + + } + Living.updateVelocity(target); @Nullable Entity master = source.getMaster(); @@ -213,7 +219,7 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega double massOfTarget = AttractionUtils.getMass(target); - if (massOfTarget != 0) { + if (!source.isClient() && massOfTarget != 0) { accumulatedMass += massOfTarget; setDirty(); } @@ -223,6 +229,9 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega target.discard(); source.asWorld().playSound(null, source.getOrigin(), USounds.ENCHANTMENT_CONSUMPTION_CONSUME, SoundCategory.AMBIENT, 2, 0.02F); } + if (target.isAlive()) { + target.damage(source.asEntity().getDamageSources().outOfWorld(), Integer.MAX_VALUE); + } source.subtractEnergyCost(-massOfTarget * 10); source.asWorld().playSound(null, source.getOrigin(), USounds.AMBIENT_DARK_VORTEX_MOOD, SoundCategory.AMBIENT, 2, 0.02F); @@ -238,14 +247,12 @@ public class DarkVortexSpell extends AttractiveSpell implements ProjectileDelega @Override public void toNBT(NbtCompound compound) { super.toNBT(compound); - compound.putInt("age", age); compound.putFloat("accumulatedMass", accumulatedMass); } @Override public void fromNBT(NbtCompound compound) { super.fromNBT(compound); - age = compound.getInt("age"); accumulatedMass = compound.getFloat("accumulatedMass"); } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java index b2482157..d1730e78 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/magic/spell/effect/TargetSelecter.java @@ -10,7 +10,6 @@ import java.util.stream.Stream; import com.minelittlepony.unicopia.EquinePredicates; 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.Spell; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.FriendshipBraceletItem; @@ -29,7 +28,7 @@ public class TargetSelecter { public Stream getEntities(Caster source, double radius, BiPredicate, Entity> filter) { targets.values().removeIf(Target::tick); return source.findAllEntitiesInRange(radius) - .filter(entity -> entity.isAlive() && !entity.isRemoved() && notOwnerOrFriend(spell, source, entity) && !SpellPredicate.IS_SHIELD_LIKE.isOn(entity)) + .filter(entity -> entity.isAlive() && !entity.isRemoved() && notOwnerOrFriend(spell, source, entity)) .filter(EquinePredicates.EXCEPT_MAGIC_IMMUNE) .filter(e -> filter.test(source, e)) .map(i -> { @@ -57,6 +56,11 @@ public class TargetSelecter { public static boolean isOwnerOrFriend(Affine affine, Caster source, Entity target) { Entity owner = source.getMaster(); + var equine = Pony.of(target); + if (equine.isPresent() && !affine.isFriendlyTogether(equine.get())) { + return false; + } + if (affine.isEnemy(source)) { return FriendshipBraceletItem.isComrade(source, target); } 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 8cde15c2..c731c559 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java @@ -7,6 +7,8 @@ import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.*; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType; +import com.minelittlepony.unicopia.client.render.RenderLayers; +import com.minelittlepony.unicopia.client.render.spell.DarkVortexSpellRenderer; import com.minelittlepony.unicopia.client.sound.*; import com.minelittlepony.unicopia.entity.ItemTracker; import com.minelittlepony.unicopia.entity.effect.EffectUtils; @@ -193,6 +195,19 @@ public class UHud { protected void renderViewEffects(Pony pony, DrawContext context, int scaledWidth, int scaledHeight, float tickDelta) { + float vortexDistortion = DarkVortexSpellRenderer.getCameraDistortion(); + + if (vortexDistortion > 20) { + 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); + context.getMatrices().pop(); + return; + } else if (vortexDistortion > 0) { + context.fill(0, 0, scaledWidth, scaledHeight, (int)((vortexDistortion / 20F) * 255) << 24); + } + boolean hasEffect = client.player.hasStatusEffect(UEffects.SUN_BLINDNESS); ItemStack glasses = GlassesItem.getForEntity(client.player); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java index 0389cfca..548854ae 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/spell/DarkVortexSpellRenderer.java @@ -1,5 +1,6 @@ package com.minelittlepony.unicopia.client.render.spell; +import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.Caster; import com.minelittlepony.unicopia.ability.magic.spell.effect.DarkVortexSpell; import com.minelittlepony.unicopia.client.render.RenderLayers; @@ -17,7 +18,7 @@ import net.minecraft.util.math.RotationAxis; public class DarkVortexSpellRenderer implements SpellRenderer { - private static final Identifier ECRETION_RING_TEXTURE = new Identifier("textures/misc/forcefield.png"); + private static final Identifier ACCRETION_DISK_TEXTURE = Unicopia.id("textures/spells/dark_vortex/accretion_disk.png"); private static float cameraDistortion; @@ -29,16 +30,17 @@ public class DarkVortexSpellRenderer implements SpellRenderer { @Override public void render(MatrixStack matrices, VertexConsumerProvider vertices, DarkVortexSpell spell, Caster caster, int light, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { - matrices.push(); - double height = caster.asEntity().getEyeY() - caster.getOriginVector().y; - matrices.translate(0, height + 2, 0); + Entity cameraEntity = MinecraftClient.getInstance().getCameraEntity(); float radius = (float)spell.getEventHorizonRadius(); float absDistance = (float)cameraEntity.getEyePos().distanceTo(caster.getOriginVector().add(0, 2, 0)); - SphereModel.SPHERE.render(matrices, vertices.getBuffer(RenderLayers.getSolid()), light, 1, Math.min(radius, absDistance / 2F), 0, 0, 0, 1); + matrices.push(); + matrices.translate(0, 2 + radius, 0); + + SphereModel.SPHERE.render(matrices, vertices.getBuffer(RenderLayers.getSolid()), light, 1, Math.min(radius * 0.6F, absDistance * 0.1F), 0, 0, 0, 1); matrices.push(); @@ -46,6 +48,7 @@ public class DarkVortexSpellRenderer implements SpellRenderer { matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(90)); matrices.multiply(RotationAxis.NEGATIVE_X.rotationDegrees(90 + cameraEntity.getYaw(tickDelta))); matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(-cameraEntity.getPitch(tickDelta))); + matrices.scale(0.7F, 1, 1); @@ -57,30 +60,41 @@ public class DarkVortexSpellRenderer implements SpellRenderer { matrices.scale(distance, distance, distance); - matrices.push(); - for (int i = 0; i < 10; i++) { - matrices.scale(0.96F, 1, 1); - float brightness = i / 10F; - SphereModel.DISK.render(matrices, vertices.getBuffer(RenderLayers.getMagicNoColor()), light, 1, radius * (1 + (0.25F * i)), brightness, brightness, brightness, 0.2F); + if (absDistance > radius) { + matrices.push(); + matrices.translate(0, -0.1F, 0); + for (int i = 0; i < 10; i++) { + matrices.scale(1, 1, 0.796F); + float brightness = i / 10F; + SphereModel.DISK.render(matrices, vertices.getBuffer(RenderLayers.getMagicNoColor()), light, 1, radius * (1 + (0.25F * i)) * 0.7F, brightness, brightness, brightness, 0.2F); + } + matrices.pop(); } - matrices.pop(); SphereModel.DISK.render(matrices, vertices.getBuffer(RenderLayers.getEndPortal()), light, 1, radius * 0.5F, 1, 0.5F, 0, 1); - matrices.pop(); - - if (radius > 1 && absDistance > radius) { - radius *= 1.1F; + if (radius > 0.3F && absDistance > radius) { + radius *= 3 + radius; matrices.scale(radius, radius, radius); - matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(animationProgress)); - matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(45)); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(90)); matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(animationProgress * 168)); - VertexConsumer buffer = vertices.getBuffer(RenderLayer.getEntityTranslucent(ECRETION_RING_TEXTURE)); + VertexConsumer buffer = vertices.getBuffer(RenderLayer.getEntityTranslucent(ACCRETION_DISK_TEXTURE)); PlaneModel.INSTANCE.render(matrices, buffer, light, 0, 1, 1, 1, 1, 1); + + matrices.push(); + matrices.scale(0.5F, 0.5F, 0.5F); + matrices.multiply(RotationAxis.POSITIVE_Z.rotationDegrees(33)); + + PlaneModel.INSTANCE.render(matrices, buffer, light, 0, 1, 1, 1, 1, 1); + matrices.pop(); + + matrices.multiply(RotationAxis.POSITIVE_Y.rotationDegrees(45)); + PlaneModel.INSTANCE.render(matrices, buffer, light, 0, 1, 1, 1, 1, 1); } matrices.pop(); + matrices.pop(); } } 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 2a2a8449..26c6d4c7 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCamera.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerCamera.java @@ -63,7 +63,7 @@ public class PlayerCamera extends MotionCompositor { public double calculateFieldOfView(double fov) { fov += player.getMagicalReserves().getExertion().get() / 5F; fov += getEnergyAddition(); - fov += DarkVortexSpellRenderer.getCameraDistortion(); + fov += DarkVortexSpellRenderer.getCameraDistortion() * 2.5F; return fov; } diff --git a/src/main/resources/assets/unicopia/textures/spells/dark_vortex/accretion_disk.png b/src/main/resources/assets/unicopia/textures/spells/dark_vortex/accretion_disk.png new file mode 100644 index 0000000000000000000000000000000000000000..d1ffe0c5253680da875ca764aefae2f0e76c788e GIT binary patch literal 55384 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 UR1Y95jsoCy*~AJ}e$gZTfAvZM4gdfE literal 0 HcmV?d00001