mirror of
https://github.com/Sollace/Unicopia.git
synced 2024-11-23 21:38:00 +01:00
Added mana and mana bars
This commit is contained in:
parent
dd2e2a1f81
commit
8025cf570b
16 changed files with 264 additions and 197 deletions
|
@ -25,13 +25,20 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Hit tryActivate(Pony player) {
|
public Hit tryActivate(Pony player) {
|
||||||
return Hit.INSTANCE;
|
if (player.getMagicalReserves().getMana().getPercentFill() >= 0.9F) {
|
||||||
|
return Hit.INSTANCE;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(Pony iplayer, Hit data) {
|
public void apply(Pony iplayer, Hit data) {
|
||||||
PlayerEntity player = iplayer.getOwner();
|
PlayerEntity player = iplayer.getOwner();
|
||||||
|
|
||||||
|
if (iplayer.getMagicalReserves().getMana().getPercentFill() < 0.9F) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
RayTraceHelper.Trace trace = RayTraceHelper.doTrace(player, 10, 1, EntityPredicates.EXCEPT_SPECTATOR.and(e -> !(e instanceof LightningEntity)));
|
RayTraceHelper.Trace trace = RayTraceHelper.doTrace(player, 10, 1, EntityPredicates.EXCEPT_SPECTATOR.and(e -> !(e instanceof LightningEntity)));
|
||||||
|
|
||||||
Entity looked = trace.getEntity().map(e -> {
|
Entity looked = trace.getEntity().map(e -> {
|
||||||
|
@ -56,19 +63,21 @@ public class ChangelingDisguiseAbility extends ChangelingFeedAbility {
|
||||||
return disc;
|
return disc;
|
||||||
}).setDisguise(looked);
|
}).setDisguise(looked);
|
||||||
|
|
||||||
|
iplayer.getMagicalReserves().getMana().multiply(0.1F);
|
||||||
|
|
||||||
player.calculateDimensions();
|
player.calculateDimensions();
|
||||||
iplayer.setDirty();
|
iplayer.setDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preApply(Pony player, AbilitySlot slot) {
|
public void preApply(Pony player, AbilitySlot slot) {
|
||||||
player.getMagicalReserves().addEnergy(20);
|
player.getMagicalReserves().getEnergy().add(20);
|
||||||
player.spawnParticles(UParticles.CHANGELING_MAGIC, 5);
|
player.spawnParticles(UParticles.CHANGELING_MAGIC, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postApply(Pony player, AbilitySlot slot) {
|
public void postApply(Pony player, AbilitySlot slot) {
|
||||||
player.getMagicalReserves().setEnergy(0);
|
player.getMagicalReserves().getEnergy().set(0);
|
||||||
player.spawnParticles(UParticles.CHANGELING_MAGIC, 5);
|
player.spawnParticles(UParticles.CHANGELING_MAGIC, 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ public class ChangelingFeedAbility implements Ability<Hit> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preApply(Pony player, AbilitySlot slot) {
|
public void preApply(Pony player, AbilitySlot slot) {
|
||||||
player.getMagicalReserves().addExertion(6);
|
player.getMagicalReserves().getExertion().add(6);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -73,7 +73,7 @@ public class EarthPonyGrowAbility implements Ability<Pos> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preApply(Pony player, AbilitySlot slot) {
|
public void preApply(Pony player, AbilitySlot slot) {
|
||||||
player.getMagicalReserves().addExertion(30);
|
player.getMagicalReserves().getExertion().add(30);
|
||||||
|
|
||||||
if (player.getWorld().isClient()) {
|
if (player.getWorld().isClient()) {
|
||||||
player.spawnParticles(MagicParticleEffect.UNICORN, 1);
|
player.spawnParticles(MagicParticleEffect.UNICORN, 1);
|
||||||
|
|
|
@ -184,7 +184,7 @@ public class EarthPonyStompAbility implements Ability<Multi> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preApply(Pony player, AbilitySlot slot) {
|
public void preApply(Pony player, AbilitySlot slot) {
|
||||||
player.getMagicalReserves().addExertion(40);
|
player.getMagicalReserves().getExertion().add(40);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class UnicornCastingAbility implements Ability<Hit> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preApply(Pony player, AbilitySlot slot) {
|
public void preApply(Pony player, AbilitySlot slot) {
|
||||||
player.getMagicalReserves().addEnergy(3);
|
player.getMagicalReserves().getEnergy().add(3);
|
||||||
player.spawnParticles(MagicParticleEffect.UNICORN, 5);
|
player.spawnParticles(MagicParticleEffect.UNICORN, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,8 @@ public class UnicornTeleportAbility implements Ability<Pos> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pos tryActivate(Pony player) {
|
public Pos tryActivate(Pony player) {
|
||||||
HitResult ray = RayTraceHelper.doTrace(player.getOwner(), 100, 1, EntityPredicates.EXCEPT_SPECTATOR).getResult();
|
int maxDistance = player.getOwner().isCreative() ? 1000 : 100;
|
||||||
|
HitResult ray = RayTraceHelper.doTrace(player.getOwner(), maxDistance, 1, EntityPredicates.EXCEPT_SPECTATOR).getResult();
|
||||||
|
|
||||||
World w = player.getWorld();
|
World w = player.getWorld();
|
||||||
|
|
||||||
|
@ -152,7 +153,7 @@ public class UnicornTeleportAbility implements Ability<Pos> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preApply(Pony player, AbilitySlot slot) {
|
public void preApply(Pony player, AbilitySlot slot) {
|
||||||
player.getMagicalReserves().addExertion(30);
|
player.getMagicalReserves().getExertion().add(30);
|
||||||
player.spawnParticles(MagicParticleEffect.UNICORN, 5);
|
player.spawnParticles(MagicParticleEffect.UNICORN, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,17 +81,19 @@ class Slot {
|
||||||
}
|
}
|
||||||
|
|
||||||
// contents
|
// contents
|
||||||
//int middle = (slotPadding + size - iconSize)/2;
|
|
||||||
|
|
||||||
uHud.renderAbilityIcon(matrices, stat, slotPadding / 2, slotPadding / 2, iconSize, iconSize, iconSize, iconSize);
|
uHud.renderAbilityIcon(matrices, stat, slotPadding / 2, slotPadding / 2, iconSize, iconSize, iconSize, iconSize);
|
||||||
|
|
||||||
// foreground
|
|
||||||
UHud.drawTexture(matrices, 0, 0, backgroundU, backgroundV, size, size, 128, 128);
|
|
||||||
|
|
||||||
matrices.pop();
|
matrices.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderForeground(MatrixStack matrices, AbilityDispatcher abilities, float tickDelta) {
|
void renderForeground(MatrixStack matrices, AbilityDispatcher abilities, float tickDelta) {
|
||||||
|
matrices.push();
|
||||||
|
matrices.translate(x, y, 0);
|
||||||
|
UHud.drawTexture(matrices, 0, 0, backgroundU, backgroundV, size, size, 128, 128);
|
||||||
|
matrices.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void renderLabel(MatrixStack matrices, AbilityDispatcher abilities, float tickDelta) {
|
||||||
Text label = KeyBindingsHandler.INSTANCE.getBinding(aSlot).getBoundKeyLocalizedText();
|
Text label = KeyBindingsHandler.INSTANCE.getBinding(aSlot).getBoundKeyLocalizedText();
|
||||||
|
|
||||||
matrices.push();
|
matrices.push();
|
||||||
|
|
|
@ -6,6 +6,7 @@ import java.util.List;
|
||||||
import com.minelittlepony.unicopia.ability.Abilities;
|
import com.minelittlepony.unicopia.ability.Abilities;
|
||||||
import com.minelittlepony.unicopia.ability.AbilityDispatcher;
|
import com.minelittlepony.unicopia.ability.AbilityDispatcher;
|
||||||
import com.minelittlepony.unicopia.ability.AbilitySlot;
|
import com.minelittlepony.unicopia.ability.AbilitySlot;
|
||||||
|
import com.minelittlepony.unicopia.entity.player.MagicReserves.Bar;
|
||||||
import com.minelittlepony.unicopia.entity.player.Pony;
|
import com.minelittlepony.unicopia.entity.player.Pony;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
|
||||||
|
@ -13,9 +14,15 @@ import net.minecraft.client.MinecraftClient;
|
||||||
import net.minecraft.client.font.TextRenderer;
|
import net.minecraft.client.font.TextRenderer;
|
||||||
import net.minecraft.client.gui.DrawableHelper;
|
import net.minecraft.client.gui.DrawableHelper;
|
||||||
import net.minecraft.client.gui.hud.InGameHud;
|
import net.minecraft.client.gui.hud.InGameHud;
|
||||||
|
import net.minecraft.client.render.BufferBuilder;
|
||||||
|
import net.minecraft.client.render.BufferRenderer;
|
||||||
|
import net.minecraft.client.render.Tessellator;
|
||||||
|
import net.minecraft.client.render.VertexFormats;
|
||||||
import net.minecraft.client.util.math.MatrixStack;
|
import net.minecraft.client.util.math.MatrixStack;
|
||||||
import net.minecraft.util.Identifier;
|
import net.minecraft.util.Identifier;
|
||||||
import net.minecraft.util.Util;
|
import net.minecraft.util.Util;
|
||||||
|
import net.minecraft.util.math.MathHelper;
|
||||||
|
import net.minecraft.util.math.Matrix4f;
|
||||||
|
|
||||||
public class UHud extends DrawableHelper {
|
public class UHud extends DrawableHelper {
|
||||||
|
|
||||||
|
@ -57,8 +64,13 @@ public class UHud extends DrawableHelper {
|
||||||
boolean swap = client.options.keySneak.isPressed();
|
boolean swap = client.options.keySneak.isPressed();
|
||||||
|
|
||||||
slots.forEach(slot -> slot.renderBackground(matrices, abilities, swap, tickDelta));
|
slots.forEach(slot -> slot.renderBackground(matrices, abilities, swap, tickDelta));
|
||||||
|
|
||||||
|
renderManaRings(matrices);
|
||||||
|
|
||||||
slots.forEach(slot -> slot.renderForeground(matrices, abilities, tickDelta));
|
slots.forEach(slot -> slot.renderForeground(matrices, abilities, tickDelta));
|
||||||
|
|
||||||
|
slots.forEach(slot -> slot.renderLabel(matrices, abilities, tickDelta));
|
||||||
|
|
||||||
RenderSystem.disableBlend();
|
RenderSystem.disableBlend();
|
||||||
RenderSystem.disableAlphaTest();
|
RenderSystem.disableAlphaTest();
|
||||||
|
|
||||||
|
@ -75,4 +87,74 @@ public class UHud extends DrawableHelper {
|
||||||
client.getTextureManager().bindTexture(HUD_TEXTURE);
|
client.getTextureManager().bindTexture(HUD_TEXTURE);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void renderManaRings(MatrixStack matrices) {
|
||||||
|
|
||||||
|
matrices.push();
|
||||||
|
matrices.translate(24.5, 25.5, 0);
|
||||||
|
|
||||||
|
Bar mana = Pony.of(client.player).getMagicalReserves().getMana();
|
||||||
|
Bar exer = Pony.of(client.player).getMagicalReserves().getEnergy();
|
||||||
|
|
||||||
|
renderRing(matrices, 17, 13, MathHelper.lerp(client.getTickDelta(), mana.getPrev(), mana.get()) / mana.getMax(), 0xFF88FF99);
|
||||||
|
renderRing(matrices, 17, 13, MathHelper.lerp(client.getTickDelta(), exer.getPrev(), exer.get()) / exer.getMax(), 0xFF002299);
|
||||||
|
|
||||||
|
matrices.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void renderRing(MatrixStack matrices, double outerRadius, double innerRadius, double maxAngle, int color) {
|
||||||
|
|
||||||
|
float f = (color >> 24 & 255) / 255.0F;
|
||||||
|
float g = (color >> 16 & 255) / 255.0F;
|
||||||
|
float h = (color >> 8 & 255) / 255.0F;
|
||||||
|
float k = (color & 255) / 255.0F;
|
||||||
|
|
||||||
|
final double num_rings = 300;
|
||||||
|
double twoPi = Math.PI * 2;
|
||||||
|
final double increment = twoPi / num_rings;
|
||||||
|
|
||||||
|
maxAngle *= twoPi;
|
||||||
|
maxAngle = MathHelper.clamp(maxAngle, 0, twoPi - increment);
|
||||||
|
|
||||||
|
if (maxAngle < increment) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferBuilder bufferBuilder = Tessellator.getInstance().getBuffer();
|
||||||
|
RenderSystem.enableBlend();
|
||||||
|
RenderSystem.disableTexture();
|
||||||
|
RenderSystem.defaultBlendFunc();
|
||||||
|
|
||||||
|
bufferBuilder.begin(7, VertexFormats.POSITION_COLOR);
|
||||||
|
|
||||||
|
Matrix4f model = matrices.peek().getModel();
|
||||||
|
|
||||||
|
for (double angle = 0; angle >= -maxAngle; angle -= increment) {
|
||||||
|
// center
|
||||||
|
bufferBuilder.vertex(model,
|
||||||
|
(float)(innerRadius * Math.sin(angle)),
|
||||||
|
(float)(innerRadius * Math.cos(angle)), 0).color(f, g, h, k).next();
|
||||||
|
|
||||||
|
// point one
|
||||||
|
bufferBuilder.vertex(model,
|
||||||
|
(float)(outerRadius * Math.sin(angle)),
|
||||||
|
(float)(outerRadius * Math.cos(angle)), 0).color(f, g, h, k).next();
|
||||||
|
|
||||||
|
// point two
|
||||||
|
bufferBuilder.vertex(model,
|
||||||
|
(float)(outerRadius * Math.sin(angle + increment)),
|
||||||
|
(float)(outerRadius * Math.cos(angle + increment)), 0).color(f, g, h, k).next();
|
||||||
|
|
||||||
|
// back to center
|
||||||
|
bufferBuilder.vertex(model,
|
||||||
|
(float)(innerRadius * Math.sin(angle + increment)),
|
||||||
|
(float)(innerRadius * Math.cos(angle + increment)), 0).color(f, g, h, k).next();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferBuilder.end();
|
||||||
|
BufferRenderer.draw(bufferBuilder);
|
||||||
|
|
||||||
|
RenderSystem.enableTexture();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ public class SphereModel {
|
||||||
drawVertex(model, vertexWriter, radius, zenith, azimuth + azimuthIncrement, light, overlay, r, g, b, a);
|
drawVertex(model, vertexWriter, radius, zenith, azimuth + azimuthIncrement, light, overlay, r, g, b, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void drawVertex(Matrix4f model, VertexConsumer vertexWriter,
|
public static void drawVertex(Matrix4f model, VertexConsumer vertexWriter,
|
||||||
double radius, double zenith, double azimuth,
|
double radius, double zenith, double azimuth,
|
||||||
int light, int overlay, float r, float g, float b, float a) {
|
int light, int overlay, float r, float g, float b, float a) {
|
||||||
Vector4f position = convertToCartesianCoord(radius, zenith, azimuth);
|
Vector4f position = convertToCartesianCoord(radius, zenith, azimuth);
|
||||||
|
@ -87,7 +87,7 @@ public class SphereModel {
|
||||||
vertexWriter.vertex(position.getX(), position.getY(), position.getZ(), r, g, b, a, 0, 0, overlay, light, 0, 0, 0);
|
vertexWriter.vertex(position.getX(), position.getY(), position.getZ(), r, g, b, a, 0, 0, overlay, light, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Vector4f convertToCartesianCoord(double r, double theta, double phi) {
|
public static Vector4f convertToCartesianCoord(double r, double theta, double phi) {
|
||||||
|
|
||||||
double x = r * Math.sin(theta) * Math.cos(phi);
|
double x = r * Math.sin(theta) * Math.cos(phi);
|
||||||
double y = r * Math.sin(theta) * Math.sin(phi);
|
double y = r * Math.sin(theta) * Math.sin(phi);
|
||||||
|
|
|
@ -91,8 +91,8 @@ public class RaceChangeStatusEffect extends StatusEffect {
|
||||||
if (entity instanceof PlayerEntity) {
|
if (entity instanceof PlayerEntity) {
|
||||||
Pony pony = (Pony)eq;
|
Pony pony = (Pony)eq;
|
||||||
MagicReserves magic = pony.getMagicalReserves();
|
MagicReserves magic = pony.getMagicalReserves();
|
||||||
magic.addExertion(50);
|
magic.getExertion().add(50);
|
||||||
magic.addEnergy(3);
|
magic.getEnergy().add(3);
|
||||||
|
|
||||||
if (state.shouldShowParticles()) {
|
if (state.shouldShowParticles()) {
|
||||||
pony.spawnParticles(ParticleTypes.TOTEM_OF_UNDYING, 5);
|
pony.spawnParticles(ParticleTypes.TOTEM_OF_UNDYING, 5);
|
||||||
|
|
|
@ -1,40 +1,69 @@
|
||||||
package com.minelittlepony.unicopia.entity.player;
|
package com.minelittlepony.unicopia.entity.player;
|
||||||
|
|
||||||
public interface MagicReserves {
|
public interface MagicReserves {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the amount of exertion this player has put toward any given activity.
|
* Gets the amount of exertion this player has put toward any given activity.
|
||||||
* This is simillar to tiredness.
|
* This is simillar to tiredness.
|
||||||
*/
|
*/
|
||||||
float getExertion();
|
Bar getExertion();
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the player's exertion level.
|
|
||||||
*/
|
|
||||||
void setExertion(float exertion);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds player tiredness.
|
|
||||||
*/
|
|
||||||
default void addExertion(int exertion) {
|
|
||||||
setExertion(getExertion() + exertion/100F);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the amount of excess energy the player has.
|
* Gets the amount of excess energy the player has.
|
||||||
* This is increased by eating sugar.
|
* This is increased by eating sugar.
|
||||||
*/
|
*/
|
||||||
float getEnergy();
|
Bar getEnergy();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the player's energy level.
|
* Gets the amount of magical energy the player has.
|
||||||
|
* This is increases slowly with time by performing certain actions.
|
||||||
*/
|
*/
|
||||||
void setEnergy(float energy);
|
Bar getMana();
|
||||||
|
|
||||||
/**
|
public interface Bar {
|
||||||
* Adds energy to the player's existing energy level.
|
|
||||||
*/
|
/**
|
||||||
default void addEnergy(int energy) {
|
* Gets the current value of this bar
|
||||||
setEnergy(getEnergy() + energy / 100F);
|
*/
|
||||||
|
float get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the previous value from the last tick.
|
||||||
|
* Only updated when calling getPrev again.
|
||||||
|
*/
|
||||||
|
float getPrev();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the absolute value
|
||||||
|
*/
|
||||||
|
void set(float value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the percentage fill of this bar
|
||||||
|
*/
|
||||||
|
default float getPercentFill() {
|
||||||
|
return get() / getMax();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a percentage increment to this bar's current value
|
||||||
|
*/
|
||||||
|
default void add(int step) {
|
||||||
|
set(get() + (step / getMax()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multiplies the current value.
|
||||||
|
*/
|
||||||
|
default void multiply(float scalar) {
|
||||||
|
set(get() * scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum value this bar is allowed to contain
|
||||||
|
*/
|
||||||
|
default float getMax() {
|
||||||
|
return 100F;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,62 @@
|
||||||
package com.minelittlepony.unicopia.entity.player;
|
package com.minelittlepony.unicopia.entity.player;
|
||||||
|
|
||||||
|
import net.minecraft.entity.data.TrackedData;
|
||||||
|
import net.minecraft.util.math.MathHelper;
|
||||||
|
|
||||||
public class ManaContainer implements MagicReserves {
|
public class ManaContainer implements MagicReserves {
|
||||||
private final Pony pony;
|
private final Pony pony;
|
||||||
|
|
||||||
|
private final Bar energy;
|
||||||
|
private final Bar exertion;
|
||||||
|
private final Bar mana;
|
||||||
|
|
||||||
public ManaContainer(Pony pony) {
|
public ManaContainer(Pony pony) {
|
||||||
this.pony = pony;
|
this.pony = pony;
|
||||||
pony.getOwner().getDataTracker().startTracking(Pony.ENERGY, 0F);
|
this.energy = new BarInst(Pony.ENERGY);
|
||||||
pony.getOwner().getDataTracker().startTracking(Pony.EXERTION, 0F);
|
this.exertion = new BarInst(Pony.EXERTION);
|
||||||
|
this.mana = new BarInst(Pony.MANA);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getExertion() {
|
public Bar getExertion() {
|
||||||
return pony.getOwner().getDataTracker().get(Pony.EXERTION);
|
return exertion;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setExertion(float exertion) {
|
public Bar getEnergy() {
|
||||||
pony.getOwner().getDataTracker().set(Pony.EXERTION, Math.max(0, exertion));
|
return energy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getEnergy() {
|
public Bar getMana() {
|
||||||
return pony.getOwner().getDataTracker().get(Pony.ENERGY);
|
return mana;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
class BarInst implements Bar {
|
||||||
public void setEnergy(float energy) {
|
|
||||||
pony.getOwner().getDataTracker().set(Pony.ENERGY, Math.max(0, energy));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private final TrackedData<Float> marker;
|
||||||
|
private float prev;
|
||||||
|
|
||||||
|
BarInst(TrackedData<Float> marker) {
|
||||||
|
this.marker = marker;
|
||||||
|
pony.getOwner().getDataTracker().startTracking(marker, 0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float get() {
|
||||||
|
return pony.getOwner().getDataTracker().get(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getPrev() {
|
||||||
|
float value = prev;
|
||||||
|
prev = get();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(float value) {
|
||||||
|
pony.getOwner().getDataTracker().set(marker, MathHelper.clamp(value, 0, getMax()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,5 @@ public interface Motion {
|
||||||
*/
|
*/
|
||||||
boolean isFlying();
|
boolean isFlying();
|
||||||
|
|
||||||
float getFlightExperience();
|
|
||||||
|
|
||||||
float getFlightDuration();
|
|
||||||
|
|
||||||
boolean isExperienceCritical();
|
|
||||||
|
|
||||||
PlayerDimensions getDimensions();
|
PlayerDimensions getDimensions();
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,14 +45,14 @@ public class PlayerCamera extends MotionCompositor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public double calculateFieldOfView(double fov) {
|
public double calculateFieldOfView(double fov) {
|
||||||
fov += player.getMagicalReserves().getExertion() / 5F;
|
fov += player.getMagicalReserves().getExertion().get() / 5F;
|
||||||
fov += getEnergyAddition();
|
fov += getEnergyAddition();
|
||||||
|
|
||||||
return fov;
|
return fov;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected float getEnergyAddition() {
|
protected float getEnergyAddition() {
|
||||||
int maxE = (int)Math.floor(player.getMagicalReserves().getEnergy() * 100);
|
int maxE = (int)Math.floor(player.getMagicalReserves().getEnergy().get() * 100);
|
||||||
|
|
||||||
if (maxE <= 0) {
|
if (maxE <= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package com.minelittlepony.unicopia.entity.player;
|
package com.minelittlepony.unicopia.entity.player;
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import com.minelittlepony.unicopia.Race;
|
import com.minelittlepony.unicopia.Race;
|
||||||
import com.minelittlepony.unicopia.USounds;
|
import com.minelittlepony.unicopia.USounds;
|
||||||
import com.minelittlepony.unicopia.ability.FlightPredicate;
|
import com.minelittlepony.unicopia.ability.FlightPredicate;
|
||||||
import com.minelittlepony.unicopia.ability.magic.Spell;
|
import com.minelittlepony.unicopia.ability.magic.Spell;
|
||||||
import com.minelittlepony.unicopia.entity.EntityPhysics;
|
import com.minelittlepony.unicopia.entity.EntityPhysics;
|
||||||
import com.minelittlepony.unicopia.particle.MagicParticleEffect;
|
import com.minelittlepony.unicopia.entity.player.MagicReserves.Bar;
|
||||||
import com.minelittlepony.unicopia.util.NbtSerialisable;
|
import com.minelittlepony.unicopia.util.NbtSerialisable;
|
||||||
import com.minelittlepony.unicopia.util.MutableVector;
|
import com.minelittlepony.unicopia.util.MutableVector;
|
||||||
|
|
||||||
|
@ -21,18 +19,13 @@ import net.minecraft.sound.SoundEvents;
|
||||||
import net.minecraft.util.Tickable;
|
import net.minecraft.util.Tickable;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.util.math.MathHelper;
|
import net.minecraft.util.math.MathHelper;
|
||||||
import net.minecraft.util.math.Vec3d;
|
|
||||||
|
|
||||||
public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Motion, NbtSerialisable {
|
public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Motion, NbtSerialisable {
|
||||||
|
|
||||||
private static final float MAXIMUM_FLIGHT_EXPERIENCE = 1500;
|
private int ticksInAir;
|
||||||
|
|
||||||
public int ticksNextLevel = 0;
|
|
||||||
public float flightExperience = 0;
|
|
||||||
|
|
||||||
public boolean isFlyingEither = false;
|
public boolean isFlyingEither = false;
|
||||||
public boolean isFlyingSurvival = false;
|
public boolean isFlyingSurvival = false;
|
||||||
public boolean isRainbooming = false;
|
|
||||||
|
|
||||||
private double lastTickPosX = 0;
|
private double lastTickPosX = 0;
|
||||||
private double lastTickPosZ = 0;
|
private double lastTickPosZ = 0;
|
||||||
|
@ -78,11 +71,6 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
|
||||||
return dimensions;
|
return dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isExperienceCritical() {
|
|
||||||
return isRainbooming || flightExperience > MAXIMUM_FLIGHT_EXPERIENCE * 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void tick() {
|
public void tick() {
|
||||||
PlayerEntity entity = pony.getOwner();
|
PlayerEntity entity = pony.getOwner();
|
||||||
|
@ -97,20 +85,6 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
|
||||||
|
|
||||||
MutableVector velocity = new MutableVector(entity.getVelocity());
|
MutableVector velocity = new MutableVector(entity.getVelocity());
|
||||||
|
|
||||||
if (isExperienceCritical() && pony.isClient()) {
|
|
||||||
Random rnd = pony.getWorld().random;
|
|
||||||
|
|
||||||
for (int i = 0; i < 360 + getHorizontalMotion(entity); i += 10) {
|
|
||||||
Vec3d pos = pony.getOriginVector().add(
|
|
||||||
rnd.nextGaussian() * entity.getWidth(),
|
|
||||||
rnd.nextGaussian() * entity.getHeight()/2,
|
|
||||||
rnd.nextGaussian() * entity.getWidth()
|
|
||||||
);
|
|
||||||
|
|
||||||
pony.addParticle(MagicParticleEffect.UNICORN, pos, velocity.toImmutable());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean creative = entity.abilities.creativeMode || pony.getOwner().isSpectator();
|
boolean creative = entity.abilities.creativeMode || pony.getOwner().isSpectator();
|
||||||
|
|
||||||
entity.abilities.allowFlying = checkCanFly();
|
entity.abilities.allowFlying = checkCanFly();
|
||||||
|
@ -121,72 +95,44 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
|
||||||
if ((entity.isOnGround() && entity.isSneaking()) || entity.isTouchingWater()) {
|
if ((entity.isOnGround() && entity.isSneaking()) || entity.isTouchingWater()) {
|
||||||
entity.abilities.flying = false;
|
entity.abilities.flying = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isFlyingSurvival = entity.abilities.flying && !creative;
|
isFlyingSurvival = entity.abilities.flying && !creative;
|
||||||
isFlyingEither = isFlyingSurvival || (creative && entity.abilities.flying);
|
isFlyingEither = isFlyingSurvival || (creative && entity.abilities.flying);
|
||||||
|
|
||||||
if (!creative && !entity.isFallFlying()) {
|
if (!creative && !entity.isFallFlying() && isFlyingSurvival && !entity.hasVehicle()) {
|
||||||
if (isFlyingSurvival && !entity.hasVehicle()) {
|
|
||||||
|
|
||||||
if (!isRainbooming && getHorizontalMotion(entity) > 0.2 && flightExperience < MAXIMUM_FLIGHT_EXPERIENCE) {
|
entity.fallDistance = 0;
|
||||||
flightExperience++;
|
|
||||||
}
|
|
||||||
|
|
||||||
entity.fallDistance = 0;
|
if (ticksInAir > 100) {
|
||||||
|
Bar mana = pony.getMagicalReserves().getMana();
|
||||||
|
|
||||||
if (pony.getSpecies() != Race.CHANGELING && entity.world.random.nextInt(100) == 0) {
|
mana.add((int)(-getHorizontalMotion(entity) * 100));
|
||||||
float exhaustion = (0.3F * ticksNextLevel) / 70;
|
|
||||||
if (entity.isSprinting()) {
|
if (mana.getPercentFill() < 0.2) {
|
||||||
exhaustion *= 3.11F;
|
pony.getMagicalReserves().getExertion().add(2);
|
||||||
|
pony.getMagicalReserves().getEnergy().add(2);
|
||||||
|
|
||||||
|
if (mana.getPercentFill() < 0.1 && ticksInAir % 10 == 0) {
|
||||||
|
float exhaustion = (0.3F * ticksInAir) / 70;
|
||||||
|
if (entity.isSprinting()) {
|
||||||
|
exhaustion *= 3.11F;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.addExhaustion(exhaustion);
|
||||||
}
|
}
|
||||||
|
|
||||||
exhaustion *= (1 - flightExperience / MAXIMUM_FLIGHT_EXPERIENCE);
|
|
||||||
|
|
||||||
entity.addExhaustion(exhaustion);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ticksNextLevel++ >= MAXIMUM_FLIGHT_EXPERIENCE) {
|
|
||||||
ticksNextLevel = 0;
|
|
||||||
|
|
||||||
entity.addExperience(1);
|
|
||||||
addFlightExperience(1);
|
|
||||||
entity.playSound(SoundEvents.ENTITY_GUARDIAN_FLOP, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
moveFlying(entity, velocity);
|
|
||||||
|
|
||||||
if (isExperienceCritical()) {
|
|
||||||
|
|
||||||
if (pony.getMagicalReserves().getEnergy() <= 0.25F) {
|
|
||||||
pony.getMagicalReserves().addEnergy(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRainbooming || (entity.isSneaking() && isRainboom())) {
|
|
||||||
performRainboom(entity, velocity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ticksNextLevel > 0 && ticksNextLevel % 30 == 0) {
|
|
||||||
entity.playSound(getWingSound(), 0.5F, 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ticksNextLevel != 0) {
|
|
||||||
entity.playSound(getWingSound(), 0.4F, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ticksNextLevel = 0;
|
|
||||||
|
|
||||||
if (isExperienceCritical()) {
|
|
||||||
addFlightExperience(-0.39991342F);
|
|
||||||
} else {
|
|
||||||
addFlightExperience(-0.019991342F);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flightExperience < 0.02) {
|
|
||||||
isRainbooming = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveFlying(entity, velocity);
|
||||||
|
|
||||||
|
if (ticksInAir++ > 0 && ticksInAir % 30 == 0) {
|
||||||
|
entity.playSound(getWingSound(), 0.5F, 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ticksInAir = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pony.getPhysics().isGravityNegative()) {
|
if (pony.getPhysics().isGravityNegative()) {
|
||||||
|
@ -208,28 +154,9 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
|
||||||
return pony.getSpecies() == Race.CHANGELING ? USounds.CHANGELING_BUZZ : USounds.WING_FLAP;
|
return pony.getSpecies() == Race.CHANGELING ? USounds.CHANGELING_BUZZ : USounds.WING_FLAP;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void performRainboom(Entity entity, MutableVector velocity) {
|
|
||||||
float forward = 0.5F * flightExperience / MAXIMUM_FLIGHT_EXPERIENCE;
|
|
||||||
|
|
||||||
velocity.x += - forward * MathHelper.sin(entity.yaw * 0.017453292F);
|
|
||||||
velocity.z += forward * MathHelper.cos(entity.yaw * 0.017453292F);
|
|
||||||
velocity.y += forward * MathHelper.sin(entity.pitch * 0.017453292F);
|
|
||||||
|
|
||||||
if (!isRainbooming || entity.world.random.nextInt(5) == 0) {
|
|
||||||
entity.playSound(SoundEvents.ENTITY_LIGHTNING_BOLT_THUNDER, 1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flightExperience > 0) {
|
|
||||||
flightExperience -= 13;
|
|
||||||
isRainbooming = true;
|
|
||||||
} else {
|
|
||||||
isRainbooming = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void moveFlying(Entity player, MutableVector velocity) {
|
protected void moveFlying(Entity player, MutableVector velocity) {
|
||||||
|
|
||||||
float forward = 0.000015F * flightExperience * (float)Math.sqrt(getHorizontalMotion(player));
|
float forward = 0.000015F * (float)Math.sqrt(getHorizontalMotion(player));
|
||||||
boolean sneak = !player.isSneaking();
|
boolean sneak = !player.isSneaking();
|
||||||
|
|
||||||
// vertical drop due to gravity
|
// vertical drop due to gravity
|
||||||
|
@ -287,13 +214,6 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
|
||||||
return distance > 4 ? SoundEvents.ENTITY_PLAYER_BIG_FALL : SoundEvents.ENTITY_PLAYER_SMALL_FALL;
|
return distance > 4 ? SoundEvents.ENTITY_PLAYER_BIG_FALL : SoundEvents.ENTITY_PLAYER_SMALL_FALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFlightExperience(float factor) {
|
|
||||||
float maximumGain = MAXIMUM_FLIGHT_EXPERIENCE - flightExperience;
|
|
||||||
float gainSteps = 20;
|
|
||||||
|
|
||||||
flightExperience = Math.max(0, flightExperience + factor * maximumGain / gainSteps);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateFlightStat(boolean flying) {
|
public void updateFlightStat(boolean flying) {
|
||||||
PlayerEntity entity = pony.getOwner();
|
PlayerEntity entity = pony.getOwner();
|
||||||
|
|
||||||
|
@ -303,10 +223,6 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
|
||||||
entity.abilities.flying |= flying;
|
entity.abilities.flying |= flying;
|
||||||
|
|
||||||
isFlyingSurvival = entity.abilities.flying;
|
isFlyingSurvival = entity.abilities.flying;
|
||||||
|
|
||||||
if (isFlyingSurvival) {
|
|
||||||
ticksNextLevel = 0;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
entity.abilities.flying = false;
|
entity.abilities.flying = false;
|
||||||
isFlyingSurvival = false;
|
isFlyingSurvival = false;
|
||||||
|
@ -316,21 +232,17 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
|
||||||
@Override
|
@Override
|
||||||
public void toNBT(CompoundTag compound) {
|
public void toNBT(CompoundTag compound) {
|
||||||
super.toNBT(compound);
|
super.toNBT(compound);
|
||||||
compound.putInt("flightDuration", ticksNextLevel);
|
|
||||||
compound.putFloat("flightExperience", flightExperience);
|
|
||||||
compound.putBoolean("isFlying", isFlyingSurvival);
|
compound.putBoolean("isFlying", isFlyingSurvival);
|
||||||
compound.putBoolean("isFlyingEither", isFlyingEither);
|
compound.putBoolean("isFlyingEither", isFlyingEither);
|
||||||
compound.putBoolean("isRainbooming", isRainbooming);
|
compound.putInt("ticksInAir", ticksInAir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fromNBT(CompoundTag compound) {
|
public void fromNBT(CompoundTag compound) {
|
||||||
super.fromNBT(compound);
|
super.fromNBT(compound);
|
||||||
ticksNextLevel = compound.getInt("flightDuration");
|
|
||||||
flightExperience = compound.getFloat("flightExperience");
|
|
||||||
isFlyingSurvival = compound.getBoolean("isFlying");
|
isFlyingSurvival = compound.getBoolean("isFlying");
|
||||||
isFlyingEither = compound.getBoolean("isFlyingEither");
|
isFlyingEither = compound.getBoolean("isFlyingEither");
|
||||||
isRainbooming = compound.getBoolean("isRainbooming");
|
ticksInAir = compound.getInt("ticksInAir");
|
||||||
|
|
||||||
pony.getOwner().calculateDimensions();
|
pony.getOwner().calculateDimensions();
|
||||||
}
|
}
|
||||||
|
@ -339,14 +251,4 @@ public class PlayerPhysics extends EntityPhysics<Pony> implements Tickable, Moti
|
||||||
public boolean isFlying() {
|
public boolean isFlying() {
|
||||||
return isFlyingSurvival;
|
return isFlyingSurvival;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getFlightExperience() {
|
|
||||||
return flightExperience / MAXIMUM_FLIGHT_EXPERIENCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getFlightDuration() {
|
|
||||||
return ticksNextLevel / MAXIMUM_FLIGHT_EXPERIENCE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,11 @@ import net.minecraft.util.math.Vec3d;
|
||||||
public class Pony implements Caster<PlayerEntity>, Equine<PlayerEntity>, Transmittable, Copieable<Pony> {
|
public class Pony implements Caster<PlayerEntity>, Equine<PlayerEntity>, Transmittable, Copieable<Pony> {
|
||||||
|
|
||||||
private static final TrackedData<Integer> RACE = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER);
|
private static final TrackedData<Integer> RACE = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.INTEGER);
|
||||||
|
|
||||||
static final TrackedData<Float> ENERGY = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT);
|
static final TrackedData<Float> ENERGY = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT);
|
||||||
static final TrackedData<Float> EXERTION = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT);
|
static final TrackedData<Float> EXERTION = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT);
|
||||||
|
static final TrackedData<Float> MANA = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.FLOAT);
|
||||||
|
|
||||||
private static final TrackedData<CompoundTag> EFFECT = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
|
private static final TrackedData<CompoundTag> EFFECT = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
|
||||||
private static final TrackedData<CompoundTag> HELD_EFFECT = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
|
private static final TrackedData<CompoundTag> HELD_EFFECT = DataTracker.registerData(PlayerEntity.class, TrackedDataHandlerRegistry.TAG_COMPOUND);
|
||||||
|
|
||||||
|
@ -273,8 +276,12 @@ public class Pony implements Caster<PlayerEntity>, Equine<PlayerEntity>, Transmi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mana.addExertion(-10);
|
mana.getExertion().add(-10);
|
||||||
mana.addEnergy(-1);
|
mana.getEnergy().add(-1);
|
||||||
|
|
||||||
|
if (!getSpecies().canFly() || !gravity.isFlying()) {
|
||||||
|
mana.getMana().add(60);
|
||||||
|
}
|
||||||
|
|
||||||
attributes.applyAttributes(this);
|
attributes.applyAttributes(this);
|
||||||
|
|
||||||
|
@ -323,13 +330,24 @@ public class Pony implements Caster<PlayerEntity>, Equine<PlayerEntity>, Transmi
|
||||||
@Override
|
@Override
|
||||||
public boolean subtractEnergyCost(double foodSubtract) {
|
public boolean subtractEnergyCost(double foodSubtract) {
|
||||||
if (!entity.abilities.creativeMode) {
|
if (!entity.abilities.creativeMode) {
|
||||||
int food = (int)(entity.getHungerManager().getFoodLevel() - foodSubtract);
|
|
||||||
|
|
||||||
if (food < 0) {
|
float currentMana = mana.getMana().get();
|
||||||
entity.getHungerManager().add(-entity.getHungerManager().getFoodLevel(), 0);
|
float foodManaRatio = 10;
|
||||||
entity.damage(MagicalDamageSource.EXHAUSTION, -food/2);
|
|
||||||
|
if (currentMana >= foodSubtract * foodManaRatio) {
|
||||||
|
mana.getMana().set(currentMana - (float)foodSubtract * foodManaRatio);
|
||||||
} else {
|
} else {
|
||||||
entity.getHungerManager().add((int)-foodSubtract, 0);
|
mana.getMana().set(0);
|
||||||
|
foodSubtract -= currentMana / foodManaRatio;
|
||||||
|
|
||||||
|
int food = (int)(entity.getHungerManager().getFoodLevel() - foodSubtract);
|
||||||
|
|
||||||
|
if (food < 0) {
|
||||||
|
entity.getHungerManager().add(-entity.getHungerManager().getFoodLevel(), 0);
|
||||||
|
entity.damage(MagicalDamageSource.EXHAUSTION, -food/2);
|
||||||
|
} else {
|
||||||
|
entity.getHungerManager().add((int)-foodSubtract, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue