From c9e912a378ba5337d2d8382edbc5cc954d92049b Mon Sep 17 00:00:00 2001 From: Sollace Date: Fri, 21 Sep 2018 17:53:33 +0200 Subject: [PATCH] Add gem entities, improve networking code --- .../render/model/GlowRenderer.java | 53 +++ .../render/model/ModelGlow.java | 78 +++++ .../render/model/ModelPlane.java | 84 +++++ .../minelittlepony/render/model/Plane.java | 10 + .../render/model/PlaneRenderer.java | 69 ++++ .../render/model/PonyRenderer.java | 21 ++ .../render/model/package-info.java | 7 + .../util/render/AbstractBoxRenderer.java | 212 ++++++++++++ .../com/minelittlepony/util/render/Box.java | 36 ++ .../com/minelittlepony/util/render/Color.java | 21 ++ .../util/render/ITextureSupplier.java | 11 + .../com/minelittlepony/util/render/Quad.java | 37 +++ .../minelittlepony/util/render/Vertex.java | 21 ++ .../util/render/package-info.java | 7 + .../render/model/ModelQuads.java | 32 ++ .../render/model/TexturedShape2d.java | 50 +++ .../minelittlepony/unicopia/UEntities.java | 26 +- .../unicopia/entity/EntitySpell.java | 308 ++++++++++++++++++ .../unicopia/entity/IMagicals.java | 7 + .../unicopia/item/ItemSpell.java | 14 +- .../unicopia/model/ModelGem.java | 126 +++++++ .../unicopia/network/EffectSync.java | 53 +++ .../unicopia/network/MsgPlayerAbility.java | 4 +- .../network/MsgPlayerCapabilities.java | 50 ++- .../network/MsgRequestCapabilities.java | 2 +- .../unicopia/player/IPlayer.java | 24 +- .../unicopia/player/PlayerCapabilities.java | 67 ++-- .../unicopia/player/PlayerSpeciesList.java | 2 +- .../unicopia/render/RenderGem.java | 30 ++ .../unicopia/spell/ICaster.java | 3 + .../unicopia/spell/IDispenceable.java | 8 +- .../unicopia/spell/SpellRegistry.java | 14 +- .../unicopia/spell/SpellShield.java | 2 +- .../assets/unicopia/textures/entity/gem.png | Bin 0 -> 28136 bytes 34 files changed, 1390 insertions(+), 99 deletions(-) create mode 100644 src/api/java/com/minelittlepony/render/model/GlowRenderer.java create mode 100644 src/api/java/com/minelittlepony/render/model/ModelGlow.java create mode 100644 src/api/java/com/minelittlepony/render/model/ModelPlane.java create mode 100644 src/api/java/com/minelittlepony/render/model/Plane.java create mode 100644 src/api/java/com/minelittlepony/render/model/PlaneRenderer.java create mode 100644 src/api/java/com/minelittlepony/render/model/PonyRenderer.java create mode 100644 src/api/java/com/minelittlepony/render/model/package-info.java create mode 100644 src/api/java/com/minelittlepony/util/render/AbstractBoxRenderer.java create mode 100644 src/api/java/com/minelittlepony/util/render/Box.java create mode 100644 src/api/java/com/minelittlepony/util/render/Color.java create mode 100644 src/api/java/com/minelittlepony/util/render/ITextureSupplier.java create mode 100644 src/api/java/com/minelittlepony/util/render/Quad.java create mode 100644 src/api/java/com/minelittlepony/util/render/Vertex.java create mode 100644 src/api/java/com/minelittlepony/util/render/package-info.java create mode 100644 src/main/java/com/minelittlepony/render/model/ModelQuads.java create mode 100644 src/main/java/com/minelittlepony/render/model/TexturedShape2d.java create mode 100644 src/main/java/com/minelittlepony/unicopia/entity/EntitySpell.java create mode 100644 src/main/java/com/minelittlepony/unicopia/entity/IMagicals.java create mode 100644 src/main/java/com/minelittlepony/unicopia/model/ModelGem.java create mode 100644 src/main/java/com/minelittlepony/unicopia/network/EffectSync.java create mode 100644 src/main/java/com/minelittlepony/unicopia/render/RenderGem.java create mode 100644 src/main/resources/assets/unicopia/textures/entity/gem.png diff --git a/src/api/java/com/minelittlepony/render/model/GlowRenderer.java b/src/api/java/com/minelittlepony/render/model/GlowRenderer.java new file mode 100644 index 00000000..0e9becb5 --- /dev/null +++ b/src/api/java/com/minelittlepony/render/model/GlowRenderer.java @@ -0,0 +1,53 @@ +package com.minelittlepony.render.model; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.ModelBase; + +import org.lwjgl.opengl.GL11; + +import com.minelittlepony.util.render.AbstractBoxRenderer; +import com.minelittlepony.util.render.Color; + +public class GlowRenderer extends AbstractBoxRenderer { + + int tint; + float alpha = 1; + + public GlowRenderer(ModelBase model, int x, int y) { + super(model, x, y); + } + + public GlowRenderer setAlpha(float alpha) { + this.alpha = alpha; + + return this; + } + + public GlowRenderer setTint(int tint) { + this.tint = tint; + + return this; + } + + public void applyTint(float alpha) { + Color.glColor(tint, alpha); + } + + @Override + public void createBox(float offX, float offY, float offZ, int width, int height, int depth, float scaleFactor, boolean mirrored) { + cubeList.add(new ModelGlow(this, textureOffsetX, textureOffsetY, offX, offY, offZ, width, height, depth, scaleFactor, alpha)); + } + + @Override + public void render(float scale) { + GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); + Minecraft.getMinecraft().entityRenderer.disableLightmap(); + super.render(scale); + GL11.glPopAttrib(); + } + + @Override + protected GlowRenderer copySelf() { + return new GlowRenderer(baseModel, textureOffsetX, textureOffsetY); + } +} diff --git a/src/api/java/com/minelittlepony/render/model/ModelGlow.java b/src/api/java/com/minelittlepony/render/model/ModelGlow.java new file mode 100644 index 00000000..54289295 --- /dev/null +++ b/src/api/java/com/minelittlepony/render/model/ModelGlow.java @@ -0,0 +1,78 @@ +package com.minelittlepony.render.model; + +import net.minecraft.client.renderer.BufferBuilder; + +import com.minelittlepony.util.render.Box; +import com.minelittlepony.util.render.Quad; +import com.minelittlepony.util.render.Vertex; + +/** + * Like a normal box, but with the top narrowed a bit. + */ +public class ModelGlow extends Box { + + private final float alpha; + + private Quad[] quadList; + + public ModelGlow(GlowRenderer renderer, int texX, int texY, float xMin, float yMin, float zMin, int w, int h, int d, float scale, float alpha) { + super(renderer, texX, texY, xMin, yMin, zMin, w, h, d, scale); + + this.alpha = alpha; + + float xMax = xMin + w + scale; + float yMax = yMin + h + scale; + float zMax = zMin + d + scale; + + xMin -= scale; + yMin -= scale; + zMin -= scale; + + if (renderer.mirror) { + float v = xMax; + xMax = xMin; + xMin = v; + } + + float tipInset = 0.4f; + + float tipXmin = xMin + w * tipInset; + float tipZmin = zMin + d * tipInset; + float tipXMax = xMax - w * tipInset; + float tipZMax = zMax - d * tipInset; + + // w:west e:east d:down u:up s:south n:north + Vertex wds = vert(tipXmin, yMin, tipZmin, 0, 0); + Vertex eds = vert(tipXMax, yMin, tipZmin, 0, 8); + Vertex eus = vert(xMax, yMax, zMin, 8, 8); + Vertex wus = vert(xMin, yMax, zMin, 8, 0); + Vertex wdn = vert(tipXmin, yMin, tipZMax, 0, 0); + Vertex edn = vert(tipXMax, yMin, tipZMax, 0, 8); + Vertex eun = vert(xMax, yMax, zMax, 8, 8); + Vertex wun = vert(xMin, yMax, zMax, 8, 0); + + quadList = new Quad[] { + quad(texX + d + w, d, texY + d, h, edn, eds, eus, eun), + quad(texX, d, texY + d, h, wds, wdn, wun, wus), + quad(texX + d, w, texY, d, edn, wdn, wds, eds), + quad(texX + d + w, w, texY + d, -d, eus, wus, wun, eun), + quad(texX + d, w, texY + d, h, eds, wds, wus, eus), + quad(texX + d + w + d, w, texY + d, h, wdn, edn, eun, wun) + }; + + if (renderer.mirror) { + for (Quad i : quadList) { + i.flipFace(); + } + } + } + + @Override + public void render(BufferBuilder buffer, float scale) { + parent.applyTint(alpha); + + for (Quad i : quadList) { + i.draw(buffer, scale); + } + } +} diff --git a/src/api/java/com/minelittlepony/render/model/ModelPlane.java b/src/api/java/com/minelittlepony/render/model/ModelPlane.java new file mode 100644 index 00000000..444325a2 --- /dev/null +++ b/src/api/java/com/minelittlepony/render/model/ModelPlane.java @@ -0,0 +1,84 @@ +package com.minelittlepony.render.model; + +import net.minecraft.client.renderer.BufferBuilder; + +import com.minelittlepony.util.render.Box; +import com.minelittlepony.util.render.Quad; +import com.minelittlepony.util.render.Vertex; + +import javax.annotation.Nonnull; + +public class ModelPlane extends Box { + + private Quad quad; + + public boolean hidden = false; + + public ModelPlane(PlaneRenderer renderer, int textureX, int textureY, float xMin, float yMin, float zMin, int w, int h, int d, float scale, Plane face) { + super(renderer, textureX, textureY, xMin, yMin, zMin, w, h, d, scale, false); + + float xMax = xMin + w + scale; + float yMax = yMin + h + scale; + float zMax = zMin + d + scale; + + xMin -= scale; + yMin -= scale; + zMin -= scale; + + if (renderer.mirror) { + float v = xMax; + xMax = xMin; + xMin = v; + } + + if (renderer.mirrory) { + float v = yMax; + yMax = yMin; + yMin = v; + } + + if (renderer.mirrorz) { + float v = zMax; + zMax = zMin; + zMin = v; + } + + // w:west e:east d:down u:up s:south n:north + Vertex wds = vert(xMin, yMin, zMin, 0, 0); + Vertex eds = vert(xMax, yMin, zMin, 0, 8); + Vertex eus = vert(xMax, yMax, zMin, 8, 8); + Vertex wus = vert(xMin, yMax, zMin, 8, 0); + Vertex wdn = vert(xMin, yMin, zMax, 0, 0); + Vertex edn = vert(xMax, yMin, zMax, 0, 8); + Vertex eun = vert(xMax, yMax, zMax, 8, 8); + Vertex wun = vert(xMin, yMax, zMax, 8, 0); + + if (face == Plane.EAST) { + quad = quad(textureX, d, textureY, h, edn, eds, eus, eun); + } + if (face == Plane.WEST) { + quad = quad(textureX, d, textureY, h, wds, wdn, wun, wus); + } + if (face == Plane.UP) { + quad = quad(textureX, w, textureY, d, edn, wdn, wds, eds); + } + if (face == Plane.DOWN) { + quad = quad(textureX, w, textureY, d, eus, wus, wun, eun); + } + if (face == Plane.SOUTH) { + quad = quad(textureX, w, textureY, h, eds, wds, wus, eus); + } + if (face == Plane.NORTH) { + quad = quad(textureX, w, textureY, h, wdn, edn, eun, wun); + } + + if (renderer.mirror || renderer.mirrory || renderer.mirrorz) { + quad.flipFace(); + } + } + + @Override + public void render(@Nonnull BufferBuilder buffer, float scale) { + if (!hidden) quad.draw(buffer, scale); + } +} diff --git a/src/api/java/com/minelittlepony/render/model/Plane.java b/src/api/java/com/minelittlepony/render/model/Plane.java new file mode 100644 index 00000000..9e509117 --- /dev/null +++ b/src/api/java/com/minelittlepony/render/model/Plane.java @@ -0,0 +1,10 @@ +package com.minelittlepony.render.model; + +enum Plane { + NORTH, + SOUTH, + UP, + DOWN, + EAST, + WEST +} \ No newline at end of file diff --git a/src/api/java/com/minelittlepony/render/model/PlaneRenderer.java b/src/api/java/com/minelittlepony/render/model/PlaneRenderer.java new file mode 100644 index 00000000..e738e396 --- /dev/null +++ b/src/api/java/com/minelittlepony/render/model/PlaneRenderer.java @@ -0,0 +1,69 @@ +package com.minelittlepony.render.model; + +import net.minecraft.client.model.ModelBase; + +import com.minelittlepony.util.render.AbstractBoxRenderer; + +public class PlaneRenderer extends AbstractBoxRenderer { + + public boolean mirrory, mirrorz; + + public PlaneRenderer(ModelBase model) { + super(model); + } + + public PlaneRenderer(ModelBase model, int x, int y) { + super(model, x, y); + } + + /** + * Flips the Z bit. Any calls to add a plane will be mirrored until this is called again. + */ + public PlaneRenderer flipZ() { + mirrorz = !mirrorz; + return this; + } + + + /** + * Flips the Y bit. Any calls to add a plane will be mirrored until this is called again. + */ + public PlaneRenderer flipY() { + mirrory = !mirrory; + return this; + } + + @Override + protected PlaneRenderer copySelf() { + return new PlaneRenderer(baseModel, textureOffsetX, textureOffsetY); + } + + private PlaneRenderer addPlane(float offX, float offY, float offZ, int width, int height, int depth, float scale, Plane face) { + cubeList.add(new ModelPlane(this, textureOffsetX, textureOffsetY, modelOffsetX + offX, modelOffsetY + offY, modelOffsetZ + offZ, width, height, depth, scale, face)); + return this; + } + + public PlaneRenderer top(float offX, float offY, float offZ, int width, int depth, float scale) { + return addPlane(offX, offY, offZ, width, 0, depth, scale, Plane.UP); + } + + public PlaneRenderer bottom(float offX, float offY, float offZ, int width, int depth, float scale) { + return addPlane(offX, offY, offZ, width, 0, depth, scale, Plane.DOWN); + } + + public PlaneRenderer west(float offX, float offY, float offZ, int height, int depth, float scale) { + return addPlane(offX, offY, offZ, 0, height, depth, scale, Plane.WEST); + } + + public PlaneRenderer east(float offX, float offY, float offZ, int height, int depth, float scale) { + return addPlane(offX, offY, offZ, 0, height, depth, scale, Plane.EAST); + } + + public PlaneRenderer north(float offX, float offY, float offZ, int width, int height, float scale) { + return addPlane(offX, offY, offZ - scale * 2, width, height, 0, scale, Plane.NORTH); + } + + public PlaneRenderer south(float offX, float offY, float offZ, int width, int height, float scale) { + return addPlane(offX, offY, offZ + scale * 2, width, height, 0, scale, Plane.SOUTH); + } +} diff --git a/src/api/java/com/minelittlepony/render/model/PonyRenderer.java b/src/api/java/com/minelittlepony/render/model/PonyRenderer.java new file mode 100644 index 00000000..d656eb10 --- /dev/null +++ b/src/api/java/com/minelittlepony/render/model/PonyRenderer.java @@ -0,0 +1,21 @@ +package com.minelittlepony.render.model; + +import net.minecraft.client.model.ModelBase; + +import com.minelittlepony.util.render.AbstractBoxRenderer; + +public class PonyRenderer extends AbstractBoxRenderer { + + public PonyRenderer(ModelBase model) { + super(model); + } + + public PonyRenderer(ModelBase model, int texX, int texY) { + super(model, texX, texY); + } + + @Override + protected PonyRenderer copySelf() { + return new PonyRenderer(baseModel, textureOffsetX, textureOffsetY); + } +} diff --git a/src/api/java/com/minelittlepony/render/model/package-info.java b/src/api/java/com/minelittlepony/render/model/package-info.java new file mode 100644 index 00000000..79bb8ff0 --- /dev/null +++ b/src/api/java/com/minelittlepony/render/model/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package com.minelittlepony.render.model; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/api/java/com/minelittlepony/util/render/AbstractBoxRenderer.java b/src/api/java/com/minelittlepony/util/render/AbstractBoxRenderer.java new file mode 100644 index 00000000..2bce7b9b --- /dev/null +++ b/src/api/java/com/minelittlepony/util/render/AbstractBoxRenderer.java @@ -0,0 +1,212 @@ +package com.minelittlepony.util.render; + +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelBox; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.client.model.TextureOffset; + +@SuppressWarnings("unchecked") +public abstract class AbstractBoxRenderer> extends ModelRenderer { + + protected final ModelBase baseModel; + + protected int textureOffsetX; + protected int textureOffsetY; + + protected float modelOffsetX; + protected float modelOffsetY; + protected float modelOffsetZ; + + public AbstractBoxRenderer(ModelBase model) { + super(model); + baseModel = model; + } + + public AbstractBoxRenderer(ModelBase model, int texX, int texY) { + super(model, texX, texY); + baseModel = model; + } + + /** + * Called to create a new instance of this renderer (used for child renderers) + */ + protected abstract T copySelf(); + + @Override + public T setTextureOffset(int x, int y) { + this.textureOffsetX = x; + this.textureOffsetY = y; + super.setTextureOffset(x, y); + return (T) this; + } + + /** + * Flips the mirror flag. All faces are mirrored until this is called again. + */ + public T flip() { + return mirror(!mirror); + } + + public T mirror(boolean m) { + mirror = m; + return (T) this; + } + + /** + * Sets the texture offset + */ + public T tex(int x, int y) { + return setTextureOffset(x, y); + } + + /** + * Sets the texture size for this renderer. + */ + public T size(int w, int h) { + return (T) setTextureSize(w, h); + } + + /** + * Positions this model in space. + */ + public T at(float x, float y, float z) { + return (T)at(this, x, y, z); + } + + /** + * Sets an offset to be used on all shapes and children created through this renderer. + */ + public T offset(float x, float y, float z) { + modelOffsetX = x; + modelOffsetY = y; + modelOffsetZ = z; + return (T) this; + } + + /** + * Adjusts the rotation center of the given renderer by the given amounts in each direction. + */ + public static void shiftRotationPoint(ModelRenderer renderer, float x, float y, float z) { + renderer.rotationPointX += x; + renderer.rotationPointY += y; + renderer.rotationPointZ += z; + } + + /** + * Sets this renderer's rotation angles. + */ + public T rotate(float x, float y, float z) { + rotateAngleX = x; + rotateAngleY = y; + rotateAngleZ = z; + return (T) this; + } + + /** + * Positions a given model in space by setting its offset values divided + * by 16 to account for scaling applied inside the model. + */ + public static T at(T renderer, float x, float y, float z) { + renderer.offsetX = x / 16; + renderer.offsetY = y / 16; + renderer.offsetZ = z / 16; + return renderer; + } + + /** + * Rotates this model to align itself with the angles of another. + */ + public void rotateTo(ModelRenderer other) { + rotate(other.rotateAngleX, other.rotateAngleY, other.rotateAngleZ); + } + + /** + * Shifts this model to align its center with the center of another. + */ + public T rotateAt(ModelRenderer other) { + return around(other.rotationPointX, other.rotationPointY, other.rotationPointZ); + } + + /** + * Sets the rotation point. + */ + public T around(float x, float y, float z) { + setRotationPoint(x, y, z); + return (T) this; + } + + /** + * Gets or creates a new child model based on its unique index. + * New children will be of the same type and inherit the same textures and offsets of the original. + */ + public T child(int index) { + if (childModels == null || index >= childModels.size()) { + return child(); + } + return (T)childModels.get(index); + } + + /** + * Returns a brand new child under this renderer. + */ + public T child() { + T copy = copySelf(); + child(copy.offset(modelOffsetX, modelOffsetY, modelOffsetZ)); + copy.textureHeight = textureHeight; + copy.textureWidth = textureWidth; + return copy; + } + + /** + * Adds a new child renderer and returns itself for chaining. + */ + public T child(K child) { + addChild(child); + return (T)this; + } + + @Override + public T addBox(String partName, float offX, float offY, float offZ, int width, int height, int depth) { + partName = boxName + "." + partName; + + TextureOffset tex = baseModel.getTextureOffset(partName); + + setTextureOffset(tex.textureOffsetX, tex.textureOffsetY).addBox(offX, offY, offZ, width, height, depth); + cubeList.get(cubeList.size() - 1).setBoxName(partName); + + return (T) this; + } + + @Override + public T addBox(float offX, float offY, float offZ, int width, int height, int depth) { + addBox(offX, offY, offZ, width, height, depth, 0); + return (T) this; + } + + @Override + public T addBox(float offX, float offY, float offZ, int width, int height, int depth, boolean mirrored) { + addBox(offX, offY, offZ, width, height, depth, 0, mirrored); + return (T)this; + } + + @Override + public void addBox(float offX, float offY, float offZ, int width, int height, int depth, float scaleFactor) { + addBox(offX, offY, offZ, width, height, depth, scaleFactor, mirror); + } + + /** + * Creates a textured box. + */ + public T box(float offX, float offY, float offZ, int width, int height, int depth, float scaleFactor) { + return addBox(offX, offY, offZ, width, height, depth, scaleFactor, mirror); + } + + private T addBox(float offX, float offY, float offZ, int width, int height, int depth, float scaleFactor, boolean mirrored) { + createBox(modelOffsetX + offX, modelOffsetY + offY, modelOffsetZ + offZ, width, height, depth, scaleFactor, mirrored); + return (T)this; + } + + protected void createBox(float offX, float offY, float offZ, int width, int height, int depth, float scaleFactor, boolean mirrored) { + cubeList.add(new ModelBox(this, textureOffsetX, textureOffsetY, offX, offY, offZ, width, height, depth, scaleFactor, mirrored)); + } +} diff --git a/src/api/java/com/minelittlepony/util/render/Box.java b/src/api/java/com/minelittlepony/util/render/Box.java new file mode 100644 index 00000000..0c4ad8f6 --- /dev/null +++ b/src/api/java/com/minelittlepony/util/render/Box.java @@ -0,0 +1,36 @@ +package com.minelittlepony.util.render; + +import net.minecraft.client.model.ModelBox; +import net.minecraft.client.model.ModelRenderer; + +public abstract class Box extends ModelBox { + + protected final T parent; + + public Box(T renderer, int texU, int texV, float x, float y, float z, int dx, int dy, int dz, float delta) { + super(renderer, texU, texV, x, y, z, dx, dy, dz, delta); + parent = renderer; + } + + public Box(T renderer, int texU, int texV, float x, float y, float z, int dx, int dy, int dz, float delta, boolean mirror) { + super(renderer, texU, texV, x, y, z, dx, dy, dz, delta, mirror); + parent = renderer; + } + + /** + * Creates a new vertex mapping the given (x, y, z) coordinates to a texture offset. + */ + protected Vertex vert(float x, float y, float z, int texX, int texY) { + return new Vertex(x, y, z, texX, texY); + } + + /** + * Creates a new quad with the given spacial vertices. + */ + protected Quad quad(int startX, int width, int startY, int height, Vertex ...verts) { + return new Quad(verts, + startX, startY, + startX + width, startY + height, + parent.textureWidth, parent.textureHeight); + } +} \ No newline at end of file diff --git a/src/api/java/com/minelittlepony/util/render/Color.java b/src/api/java/com/minelittlepony/util/render/Color.java new file mode 100644 index 00000000..967e061b --- /dev/null +++ b/src/api/java/com/minelittlepony/util/render/Color.java @@ -0,0 +1,21 @@ +package com.minelittlepony.util.render; + +import net.minecraft.client.renderer.GlStateManager; + +public interface Color { + static float r(int color) { + return (color >> 16 & 255) / 255F; + } + + static float g(int color) { + return (color >> 8 & 255) / 255F; + } + + static float b(int color) { + return (color & 255) / 255F; + } + + static void glColor(int color, float alpha) { + GlStateManager.color(Color.r(color), Color.g(color), Color.b(color), alpha); + } +} diff --git a/src/api/java/com/minelittlepony/util/render/ITextureSupplier.java b/src/api/java/com/minelittlepony/util/render/ITextureSupplier.java new file mode 100644 index 00000000..ce4dc3f2 --- /dev/null +++ b/src/api/java/com/minelittlepony/util/render/ITextureSupplier.java @@ -0,0 +1,11 @@ +package com.minelittlepony.util.render; + +import net.minecraft.util.ResourceLocation; + +/** + * A texture pool for generating multiple associated textures. + */ +@FunctionalInterface +public interface ITextureSupplier { + ResourceLocation supplyTexture(T key); +} diff --git a/src/api/java/com/minelittlepony/util/render/Quad.java b/src/api/java/com/minelittlepony/util/render/Quad.java new file mode 100644 index 00000000..217ac3ae --- /dev/null +++ b/src/api/java/com/minelittlepony/util/render/Quad.java @@ -0,0 +1,37 @@ +package com.minelittlepony.util.render; + +import net.minecraft.client.model.TexturedQuad; + +public class Quad extends TexturedQuad { + + Quad(Vertex[] vertices, int texcoordU1, int texcoordV1, int texcoordU2, int texcoordV2, float textureWidth, float textureHeight) { + super(vertices, texcoordU1, texcoordV1, texcoordU2, texcoordV2, textureWidth, textureHeight); + } + + /** + * Reverses the order of the vertices belonging to this quad. + * Positions of the vertices stay the same but the order of rendering is reversed to go counter-clockwise. + * + * Reversal also affects the cross-product used to calculate texture orientation. + *
+     * Normal:
+     * 0-----1
+     * |\    |
+     * |  \  |
+     * |    \|
+     * 3-----2
+     *
+     * After flipFace:
+     *
+     * 3-----2
+     * |    /|
+     * |  /  |
+     * |/    |
+     * 0-----1
+     * 
+ */ + @Override + public void flipFace() { + super.flipFace(); + } +} \ No newline at end of file diff --git a/src/api/java/com/minelittlepony/util/render/Vertex.java b/src/api/java/com/minelittlepony/util/render/Vertex.java new file mode 100644 index 00000000..dbfb36e3 --- /dev/null +++ b/src/api/java/com/minelittlepony/util/render/Vertex.java @@ -0,0 +1,21 @@ +package com.minelittlepony.util.render; + +import net.minecraft.client.model.PositionTextureVertex; + +public class Vertex extends PositionTextureVertex { + + public Vertex(float x, float y, float z, float texX, float texY) { + super(x, y, z, texX, texY); + } + + private Vertex(Vertex old, float texX, float texY) { + super(old, texX, texY); + } + + // The MCP name is misleading. + // This is meant to return a COPY with the given texture position + @Override + public Vertex setTexturePosition(float texX, float texY) { + return new Vertex(this, texX, texY); + } +} diff --git a/src/api/java/com/minelittlepony/util/render/package-info.java b/src/api/java/com/minelittlepony/util/render/package-info.java new file mode 100644 index 00000000..4e9d3c08 --- /dev/null +++ b/src/api/java/com/minelittlepony/util/render/package-info.java @@ -0,0 +1,7 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package com.minelittlepony.util.render; + +import mcp.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/com/minelittlepony/render/model/ModelQuads.java b/src/main/java/com/minelittlepony/render/model/ModelQuads.java new file mode 100644 index 00000000..a8aadb84 --- /dev/null +++ b/src/main/java/com/minelittlepony/render/model/ModelQuads.java @@ -0,0 +1,32 @@ +package com.minelittlepony.render.model; + +import java.util.ArrayList; +import java.util.List; + +import com.minelittlepony.util.render.Box; + +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.client.model.PositionTextureVertex; +import net.minecraft.client.model.TexturedQuad; +import net.minecraft.client.renderer.BufferBuilder; + +public class ModelQuads extends Box { + + public ModelQuads(ModelRenderer renderer) { + super(renderer, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } + + protected List quadList = new ArrayList(); + + public ModelQuads addFace(PositionTextureVertex... vertices) { + quadList.add(new TexturedShape2d(vertices)); + + return this; + } + + public void render(BufferBuilder renderer, float scale) { + for (TexturedQuad i : quadList) { + i.draw(renderer, scale); + } + } +} diff --git a/src/main/java/com/minelittlepony/render/model/TexturedShape2d.java b/src/main/java/com/minelittlepony/render/model/TexturedShape2d.java new file mode 100644 index 00000000..e4925713 --- /dev/null +++ b/src/main/java/com/minelittlepony/render/model/TexturedShape2d.java @@ -0,0 +1,50 @@ +package com.minelittlepony.render.model; + +import net.minecraft.client.model.PositionTextureVertex; +import net.minecraft.client.model.TexturedQuad; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.math.Vec3d; + +public class TexturedShape2d extends TexturedQuad { + + protected boolean invertNormal; + + public TexturedShape2d(PositionTextureVertex... vertices) { + super(vertices); + } + + public TexturedShape2d(PositionTextureVertex[] vertices, int texcoordU1, int texcoordV1, int texcoordU2, int texcoordV2, float textureWidth, float textureHeight) { + super(vertices, texcoordU1, texcoordV1, texcoordU2, texcoordV2, textureWidth, textureHeight); + } + + public TexturedShape2d setInvertNormal() { + invertNormal = true; + return this; + } + + public void drawQuad(BufferBuilder renderer, float scale) { + Vec3d vec3d = vertexPositions[1].vector3D.subtractReverse(vertexPositions[0].vector3D); + Vec3d vec3d1 = vertexPositions[1].vector3D.subtractReverse(vertexPositions[2].vector3D); + Vec3d vec3d2 = vec3d1.crossProduct(vec3d).normalize(); + float f = (float)vec3d2.x; + float f1 = (float)vec3d2.y; + float f2 = (float)vec3d2.z; + + if (invertNormal) { + f = -f; + f1 = -f1; + f2 = -f2; + } + + renderer.begin(7, DefaultVertexFormats.OLDMODEL_POSITION_TEX_NORMAL); + + for (int i = 0; i < nVertices; ++i) { + PositionTextureVertex positiontexturevertex = vertexPositions[i]; + renderer.pos(positiontexturevertex.vector3D.x * (double)scale, positiontexturevertex.vector3D.y * (double)scale, positiontexturevertex.vector3D.z * (double)scale).tex((double)positiontexturevertex.texturePositionX, (double)positiontexturevertex.texturePositionY).normal(f, f1, f2).endVertex(); + } + + Tessellator.getInstance().draw(); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/UEntities.java b/src/main/java/com/minelittlepony/unicopia/UEntities.java index 8c2baa5d..dcaaae20 100644 --- a/src/main/java/com/minelittlepony/unicopia/UEntities.java +++ b/src/main/java/com/minelittlepony/unicopia/UEntities.java @@ -3,9 +3,12 @@ package com.minelittlepony.unicopia; import com.minelittlepony.unicopia.entity.EntityCloud; import com.minelittlepony.unicopia.entity.EntityConstructionCloud; import com.minelittlepony.unicopia.entity.EntityRacingCloud; +import com.minelittlepony.unicopia.entity.EntitySpell; import com.minelittlepony.unicopia.entity.EntityWildCloud; import com.minelittlepony.unicopia.render.RenderCloud; +import com.minelittlepony.unicopia.render.RenderGem; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityList.EntityEggInfo; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.client.registry.RenderingRegistry; @@ -13,22 +16,29 @@ import net.minecraftforge.fml.common.registry.EntityEntry; import net.minecraftforge.registries.IForgeRegistry; public class UEntities { - private static final int BRUSHES_ROYALBLUE = 4286945; - private static final int BRUSHES_CHARTREUSE = 8388352; + private static final int BRUSHES_ROYALBLUE = 0x4169E1; + private static final int BRUSHES_CHARTREUSE = 0x7FFF00; static void init(IForgeRegistry registry) { - EntityEntry entry = new EntityEntry(EntityCloud.class, "cloud").setRegistryName(Unicopia.MODID, "cloud"); + addEntity(registry, EntityCloud.class, "cloud", true, BRUSHES_ROYALBLUE, BRUSHES_CHARTREUSE); + addEntity(registry, EntityWildCloud.class, "wild_cloud", false, 0, 0); + addEntity(registry, EntityRacingCloud.class, "racing_cloud", false, 0, 0); + addEntity(registry, EntityConstructionCloud.class, "construction_cloud", false, 0, 0); + addEntity(registry, EntitySpell.class, "magic_spell", false, 0, 0); + } - entry.setEgg(new EntityEggInfo(new ResourceLocation("unicopia", "cloud"), BRUSHES_ROYALBLUE, BRUSHES_CHARTREUSE)); + static void addEntity(IForgeRegistry registry, Class type, String name, boolean egg, int a, int b) { + EntityEntry entry = new EntityEntry(type, name).setRegistryName(Unicopia.MODID, name); + + if (egg) { + entry.setEgg(new EntityEggInfo(new ResourceLocation("unicopia", "cloud"), a, a)); + } registry.register(entry); - - registry.register(new EntityEntry(EntityWildCloud.class, "wild_cloud").setRegistryName(Unicopia.MODID, "wild_cloud")); - registry.register(new EntityEntry(EntityRacingCloud.class, "racing_cloud").setRegistryName(Unicopia.MODID, "racing_cloud")); - registry.register(new EntityEntry(EntityConstructionCloud.class, "construction_cloud").setRegistryName(Unicopia.MODID, "construction_cloud")); } static void preInit() { RenderingRegistry.registerEntityRenderingHandler(EntityCloud.class, manager -> new RenderCloud(manager)); + RenderingRegistry.registerEntityRenderingHandler(EntitySpell.class, manager -> new RenderGem(manager)); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/EntitySpell.java b/src/main/java/com/minelittlepony/unicopia/entity/EntitySpell.java new file mode 100644 index 00000000..89a75acd --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/EntitySpell.java @@ -0,0 +1,308 @@ +package com.minelittlepony.unicopia.entity; + +import com.minelittlepony.unicopia.Predicates; +import com.minelittlepony.unicopia.UItems; +import com.minelittlepony.unicopia.item.ItemSpell; +import com.minelittlepony.unicopia.network.EffectSync; +import com.minelittlepony.unicopia.spell.ICaster; +import com.minelittlepony.unicopia.spell.IMagicEffect; +import com.minelittlepony.unicopia.spell.SpellRegistry; + +import net.minecraft.block.SoundType; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.SoundEvents; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.datasync.DataParameter; +import net.minecraft.network.datasync.DataSerializers; +import net.minecraft.network.datasync.EntityDataManager; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.DamageSource; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumHand; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class EntitySpell extends EntityLiving implements IMagicals, ICaster { + + private EntityLivingBase owner = null; + + public float hoverStart; + + private static final DataParameter LEVEL = EntityDataManager + .createKey(EntitySpell.class, DataSerializers.VARINT); + + private static final DataParameter OWNER = EntityDataManager + .createKey(EntitySpell.class, DataSerializers.STRING); + + private static final DataParameter EFFECT = EntityDataManager + .createKey(EntitySpell.class, DataSerializers.COMPOUND_TAG); + + private final EffectSync effectDelegate = new EffectSync<>(this, EFFECT); + + public EntitySpell(World w) { + super(w); + setSize(0.6f, 0.25f); + hoverStart = (float)(Math.random() * Math.PI * 2.0D); + setRenderDistanceWeight(getRenderDistanceWeight() + 1); + preventEntitySpawning = false; + enablePersistence(); + } + + public boolean isInRangeToRenderDist(double distance) { + return super.isInRangeToRenderDist(distance); + } + + public void setEffect(IMagicEffect effect) { + effectDelegate.set(effect); + } + + public IMagicEffect getEffect() { + return effectDelegate.get(); + } + + @Override + protected void entityInit() { + super.entityInit(); + dataManager.register(LEVEL, 0); + dataManager.register(EFFECT, new NBTTagCompound()); + dataManager.register(OWNER, ""); + } + + public ItemStack onPlayerMiddleClick(EntityPlayer player) { + ItemStack stack = new ItemStack(UItems.spell, 1); + SpellRegistry.instance().enchantStack(stack, getEffect().getName()); + return stack; + } + + @Override + protected boolean canTriggerWalking() { + return false; + } + + @Override + public boolean isPushedByWater() { + return false; + } + + @Override + public boolean canRenderOnFire() { + return false; + } + + @Override + public void setOwner(EntityLivingBase owner) { + this.owner = owner; + setOwner(owner.getName()); + } + + protected void setOwner(String ownerName) { + if (ownerName != null && ownerName.length() != 0) { + dataManager.set(OWNER, ownerName); + } + } + + protected String getOwnerName() { + String ownerName = dataManager.get(OWNER); + + if (ownerName == null || ownerName.length() == 0) { + if (owner instanceof EntityPlayer) { + return owner.getName(); + } + + return ""; + } + + return ownerName; + } + + @Override + public EntityLivingBase getOwner() { + if (owner == null) { + String ownerName = dataManager.get(OWNER); + if (ownerName != null && ownerName.length() > 0) { + owner = world.getPlayerEntityByName(ownerName); + } + } + + return owner; + } + + protected void displayTick() { + if (hasEffect()) { + getEffect().renderAt(this, world, posX, posY, posZ, getLevel()); + } + } + + @Override + public void onUpdate() { + if (world.isRemote) { + displayTick(); + } + + if (getEffect() == null) { + setDead(); + } else { + if (getEffect().getDead()) { + setDead(); + onDeath(); + } else { + getEffect().updateAt(this, world, posX, posY, posZ, getLevel()); + } + + if (getEffect().allowAI()) { + super.onUpdate(); + } + } + } + + @Override + public void fall(float distance, float damageMultiplier) { + + } + + @Override + protected void updateFallState(double y, boolean onGround, IBlockState state, BlockPos pos) { + this.onGround = true; + //super.updateFallState(y, onGround = this.onGround = true, state, pos); + } + + public boolean attackEntityFrom(DamageSource source, float amount) { + if (!world.isRemote) { + setDead(); + onDeath(); + } + return false; + } + + protected void onDeath() { + SoundType sound = SoundType.STONE; + + world.playSound(posX, posY, posZ, sound.getBreakSound(), SoundCategory.NEUTRAL, sound.getVolume(), sound.getPitch(), true); + + if (world.getGameRules().getBoolean("doTileDrops")) { + int level = getLevel(); + + ItemStack stack = new ItemStack(UItems.spell, level + 1); + if (hasEffect()) { + SpellRegistry.instance().enchantStack(stack, getEffect().getName()); + } + + entityDropItem(stack, 0); + } + } + + public void setDead() { + if (hasEffect()) { + getEffect().setDead(); + } + super.setDead(); + } + + public int getLevel() { + return dataManager.get(LEVEL); + } + + public void setLevel(int radius) { + dataManager.set(LEVEL, radius); + } + + public boolean tryLevelUp(ItemStack stack) { + + if (SpellRegistry.stackHasEnchantment(stack)) { + if (!getEffect().getName().equals(SpellRegistry.getKeyFromStack(stack))) { + return false; + } + + increaseLevel(); + + if (!world.isRemote) { + if ((rand.nextFloat() * getLevel()) > 10 || overLevelCap()) { + world.createExplosion(this, posX, posY, posZ, getLevel()/2, true); + setDead(); + return false; + } + } + + playSound(SoundEvents.ENTITY_ZOMBIE_VILLAGER_CURE, 0.1f, 1); + + return true; + } + return false; + } + + public EnumActionResult applyPlayerInteraction(EntityPlayer player, Vec3d vec, EnumHand hand) { + if (Predicates.MAGI.test(player)) { + ItemStack currentItem = player.getHeldItem(EnumHand.MAIN_HAND); + + if (currentItem != null && currentItem.getItem() instanceof ItemSpell) { + tryLevelUp(currentItem); + + if (!player.capabilities.isCreativeMode) { + currentItem.shrink(1); + + if (currentItem.isEmpty()) { + player.renderBrokenItemStack(currentItem); + } + } + + return EnumActionResult.SUCCESS; + } + } + + return EnumActionResult.FAIL; + } + + public void increaseLevel() { + setLevel(getLevel() + 1); + } + + public boolean canLevelUp() { + int max = getEffect().getMaxLevel(); + return max < 0 || getLevel() < max; + } + + public boolean overLevelCap() { + int max = getEffect().getMaxLevel(); + return max > 0 && getLevel() >= (max * 1.1); + } + + public void decreaseLevel() { + int level = getLevel() - 1; + if (level < 0) level = 0; + setLevel(level); + } + + @Override + public Entity getEntity() { + return this; + } + + @Override + public void readEntityFromNBT(NBTTagCompound compound) { + super.readEntityFromNBT(compound); + setOwner(compound.getString("ownerName")); + setLevel(compound.getInteger("level")); + + if (compound.hasKey("effect")) { + setEffect(SpellRegistry.instance().createEffectFromNBT(compound.getCompoundTag("effect"))); + } + } + + @Override + public void writeEntityToNBT(NBTTagCompound compound) { + super.writeEntityToNBT(compound); + + compound.setString("ownerName", getOwnerName()); + compound.setInteger("level", getLevel()); + + if (hasEffect()) { + compound.setTag("effect", SpellRegistry.instance().serializeEffectToNBT(getEffect())); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/entity/IMagicals.java b/src/main/java/com/minelittlepony/unicopia/entity/IMagicals.java new file mode 100644 index 00000000..ca27aaa7 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/IMagicals.java @@ -0,0 +1,7 @@ +package com.minelittlepony.unicopia.entity; + +import net.minecraft.entity.passive.IAnimals; + +public interface IMagicals extends IAnimals { + +} diff --git a/src/main/java/com/minelittlepony/unicopia/item/ItemSpell.java b/src/main/java/com/minelittlepony/unicopia/item/ItemSpell.java index 26cde6d6..0b5b3061 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/ItemSpell.java +++ b/src/main/java/com/minelittlepony/unicopia/item/ItemSpell.java @@ -1,6 +1,7 @@ package com.minelittlepony.unicopia.item; import com.minelittlepony.unicopia.Predicates; +import com.minelittlepony.unicopia.entity.EntitySpell; import com.minelittlepony.unicopia.spell.IMagicEffect; import com.minelittlepony.unicopia.spell.IUseAction; import com.minelittlepony.unicopia.spell.SpellCastResult; @@ -43,8 +44,9 @@ public class ItemSpell extends Item implements ICastable { } if (dispenceResult == SpellCastResult.PLACE) { + BlockPos pos = source.getBlockPos(); - // castContainedSpell(source.getWorld(), pos.getX(), pos.getY(), pos.getZ(), stack, effect); + castContainedSpell(source.getWorld(), pos, stack, effect); stack.shrink(1); } @@ -114,7 +116,7 @@ public class ItemSpell extends Item implements ICastable { pos = pos.offset(side); if (result == SpellCastResult.PLACE) { - // castContainedSpell(world, pos.getX(), pos.getY(), pos.getZ(), stack, effect).setOwner(player); + castContainedSpell(world, pos, stack, effect).setOwner(player); } } @@ -169,11 +171,13 @@ public class ItemSpell extends Item implements ICastable { return result; } -/* protected static EntitySpell castContainedSpell(World world, int x, int y, int z, ItemStack stack, IMagicEffect effect) { + protected static EntitySpell castContainedSpell(World world, BlockPos pos, ItemStack stack, IMagicEffect effect) { EntitySpell spell = new EntitySpell(world); + spell.setEffect(effect); - spell.setLocationAndAngles(x + 0.5, y + 0.5, z + 0.5, 0, 0); + spell.setLocationAndAngles(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, 0, 0); world.spawnEntity(spell); + return spell; - } */ + } } diff --git a/src/main/java/com/minelittlepony/unicopia/model/ModelGem.java b/src/main/java/com/minelittlepony/unicopia/model/ModelGem.java new file mode 100644 index 00000000..3b8ea7e9 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/model/ModelGem.java @@ -0,0 +1,126 @@ +package com.minelittlepony.unicopia.model; + +import com.minelittlepony.render.model.ModelQuads; +import com.minelittlepony.unicopia.entity.EntitySpell; +import com.minelittlepony.unicopia.spell.SpellRegistry; +import com.minelittlepony.util.render.Color; +import com.minelittlepony.util.render.Vertex; + +import net.minecraft.client.model.ModelBase; +import net.minecraft.client.model.ModelRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.entity.Entity; +import net.minecraft.util.math.MathHelper; + +public class ModelGem extends ModelBase { + + private ModelRenderer body; + + public ModelGem() { + textureWidth = 256; + textureHeight = 256; + + body = new ModelRenderer(this); + body.offsetY = 1.2f; + + int size = 1; + + body.cubeList.add(new ModelQuads(body).addFace( + new Vertex( size, 0, size, 0, 0.5f), + new Vertex(-size, 0, size, 0.25f, 0.25f), + new Vertex( 0, size * 2, 0, 0, 0.25f), + new Vertex( 0, size * 2, 0, 0, 0.25f) + ).addFace( + new Vertex( size, 0, size, 0, 0.25f), + new Vertex(-size, 0, size, 0.25f, 0), + new Vertex( 0, -size * 2, 0, 0.25f, 0.25f), + new Vertex( 0, -size * 2, 0, 0.25f, 0.25f) + ).addFace( + new Vertex(size, 0, -size, 0.25f, 0.5f), + new Vertex(size, 0, size, 0.5f, 0.25f), + new Vertex(0, size * 2, 0, 0.25f, 0.25f), + new Vertex(0, size * 2, 0, 0.25f, 0.25f) + ).addFace( + new Vertex(size, 0, -size, 0.25f, 0.25f), + new Vertex(size, 0, size, 0.5f, 0), + new Vertex(0, -size * 2, 0, 0.5f, 0.25f), + new Vertex(0, -size * 2, 0, 0.5f, 0.25f) + ).addFace( + new Vertex(-size, 0, -size, 0.5f, 0.5f), + new Vertex( size, 0, -size, 0.75f, 0.25f), + new Vertex( 0, size * 2, 0, 0.5f, 0.25f), + new Vertex( 0, size * 2, 0, 0.5f, 0.25f) + ).addFace( + new Vertex(-size, 0, -size, 0.5f, 0.25f), + new Vertex( size, 0, -size, 0.75f, 0), + new Vertex( 0, -size * 2, 0, 0.75f, 0.25f), + new Vertex( 0, -size * 2, 0, 0.75f, 0.25f) + ).addFace( + new Vertex(-size, 0, size, 0.75f, 0.5f), + new Vertex(-size, 0, -size, 1, 0.25f), + new Vertex( 0, size * 2, 0, 0.75f, 0.25f), + new Vertex( 0, size * 2, 0, 0.75f, 0.25f) + ).addFace( + new Vertex(-size, 0, size, 0.75f, 0.25f), + new Vertex(-size, 0, -size, 1, 0), + new Vertex( 0, -size * 2, 0, 1, 0.25f), + new Vertex( 0, -size * 2, 0, 1, 0.25f) + )); + } + + @Override + public void render(Entity entity, float time, float walkSpeed, float stutter, float yaw, float pitch, float scale) { + + GlStateManager.pushMatrix(); + + EntitySpell spell = (EntitySpell)entity; + + float floatOffset = MathHelper.sin((spell.ticksExisted + stutter) / 10 + spell.hoverStart) / 10 + 0.1F; + GlStateManager.translate(0, floatOffset, 0); + + floatOffset = (spell.ticksExisted + stutter) / 20; + if (spell.getLevel() > 0) { + floatOffset *= spell.getLevel() + 1; + } + + floatOffset += spell.hoverStart; + floatOffset *= 180 / (float)Math.PI; + + GlStateManager.rotate(floatOffset, 0, 1, 0); + + body.render(scale); + + GlStateManager.enableBlend(); + GlStateManager.disableAlpha(); + GlStateManager.blendFunc(1, 1); + + setLightingConditionsBrightness(0xF0F0); + + Color.glColor(SpellRegistry.instance().getSpellTint(spell.getEffect().getName()), 1); + + GlStateManager.scale(1.2F, 1.2F, 1.2F); + GlStateManager.translate(0, -0.2F, 0); + + body.render(scale); + + setLightingConditionsBrightness(entity.getBrightnessForRender()); + + GlStateManager.disableBlend(); + GlStateManager.enableAlpha(); + + GlStateManager.popMatrix(); + } + + private void setLightingConditionsBrightness(int brightness) { + int texX = brightness % 0x10000; + int texY = brightness / 0x10000; + + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, texX, texY); + } + + @Override + public void setRotationAngles(float time, float walkSpeed, float stutter, float yaw, float pitch, float increment, Entity entity) { + + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/EffectSync.java b/src/main/java/com/minelittlepony/unicopia/network/EffectSync.java new file mode 100644 index 00000000..e0cb5138 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/EffectSync.java @@ -0,0 +1,53 @@ +package com.minelittlepony.unicopia.network; + +import com.minelittlepony.unicopia.spell.ICaster; +import com.minelittlepony.unicopia.spell.IMagicEffect; +import com.minelittlepony.unicopia.spell.SpellRegistry; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.datasync.DataParameter; + +public class EffectSync { + private IMagicEffect effect; + + private final ICaster owned; + + private final DataParameter param; + + public EffectSync(ICaster owned, DataParameter param) { + this.owned = owned; + this.param = param; + } + + public boolean has() { + return get() != null; + } + + public IMagicEffect get() { + NBTTagCompound comp = owned.getEntity().getDataManager().get(param); + + if (comp == null || !comp.hasKey("effect_id")) { + effect = null; + } else { + String id = comp.getString("effect_id"); + if (effect == null || id != effect.getName()) { + effect = SpellRegistry.instance().createEffectFromNBT(comp); + } else { + effect.readFromNBT(comp); + } + } + + return effect; + } + + public void set(IMagicEffect effect) { + this.effect = effect; + + if (effect == null) { + owned.getEntity().getDataManager().set(param, new NBTTagCompound()); + } else { + owned.getEntity().getDataManager().set(param, SpellRegistry.instance().serializeEffectToNBT(effect)); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAbility.java b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAbility.java index 78a51a66..261933f1 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAbility.java @@ -32,13 +32,13 @@ public class MsgPlayerAbility implements IMessage, IMessageHandler power, IData data) { - senderId = player.getGameProfile().getId(); + senderId = player.getUniqueID(); powerIdentifier = power.getKeyName(); abilityJson = gson.toJson(data, power.getPackageType()); } private void apply(IPower power) { - EntityPlayer player = IPlayer.getPlayerEntity(senderId); + EntityPlayer player = IPlayer.getPlayerFromServer(senderId); if (player == null) { return; } diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java index 2f1b0b9d..3173e6f5 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerCapabilities.java @@ -15,7 +15,6 @@ import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.player.IPlayer; import com.minelittlepony.unicopia.player.PlayerSpeciesList; -import net.minecraft.client.Minecraft; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; @@ -32,12 +31,14 @@ public class MsgPlayerCapabilities implements IMessage, IMessageHandler 0) { + try (ByteArrayInputStream input = new ByteArrayInputStream(compoundTag)) { + NBTTagCompound nbt = CompressedStreamTools.read(new DataInputStream(input)); + + player.readFromNBT(nbt); + } catch (IOException e) { + + } + } else { + player.setPlayerSpecies(newRace); } - - player = PlayerSpeciesList.instance().getPlayer(found); - } - - if (compoundTag.length > 0) { - try (ByteArrayInputStream input = new ByteArrayInputStream(compoundTag)) { - NBTTagCompound nbt = CompressedStreamTools.read(new DataInputStream(input)); - - player.readFromNBT(nbt); - } catch (IOException e) { - } - } else { - player.setPlayerSpecies(newRace); } } } diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgRequestCapabilities.java b/src/main/java/com/minelittlepony/unicopia/network/MsgRequestCapabilities.java index 38b5c689..3ce8e574 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/MsgRequestCapabilities.java +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgRequestCapabilities.java @@ -25,6 +25,6 @@ public class MsgRequestCapabilities implements IMessage, IMessageHandler, IRaceContainer { - private static final Logger logger = LogManager.getLogger(); - private static final DataParameter PLAYER_RACE = EntityDataManager .createKey(EntityPlayer.class, DataSerializers.VARINT); + private static final DataParameter EXERTION = EntityDataManager .createKey(EntityPlayer.class, DataSerializers.FLOAT); + private static final DataParameter EFFECT = EntityDataManager + .createKey(EntityPlayer.class, DataSerializers.COMPOUND_TAG); + private final PlayerAbilityDelegate powers = new PlayerAbilityDelegate(this); private final PlayerGravityDelegate gravity = new PlayerGravityDelegate(this); private final PlayerAttributes attributes = new PlayerAttributes(); + private final EffectSync effectDelegate = new EffectSync<>(this, EFFECT); + private float nextStepDistance = 1; - private IMagicEffect effect; - private EntityPlayer entity; - private UUID playerId; PlayerCapabilities(EntityPlayer player) { setOwner(player); player.getDataManager().register(PLAYER_RACE, Race.HUMAN.ordinal()); player.getDataManager().register(EXERTION, 0F); + player.getDataManager().register(EFFECT, new NBTTagCompound()); } @Override @@ -66,21 +64,19 @@ class PlayerCapabilities implements IPlayer, ICaster { @Override public void setPlayerSpecies(Race race) { - EntityPlayer self = getOwner(); + EntityPlayer player = getOwner(); - if (!PlayerSpeciesList.instance().speciesPermitted(race, getOwner())) { + if (!PlayerSpeciesList.instance().speciesPermitted(race, player)) { race = Race.HUMAN; } - if (self != null) { - getOwner().getDataManager().set(PLAYER_RACE, race.ordinal()); + player.getDataManager().set(PLAYER_RACE, race.ordinal()); - self.capabilities.allowFlying = race.canFly(); - gravity.updateFlightStat(self, self.capabilities.isFlying); + player.capabilities.allowFlying = race.canFly(); + gravity.updateFlightStat(player, player.capabilities.isFlying); - self.sendPlayerAbilities(); - sendCapabilities(false); - } + player.sendPlayerAbilities(); + sendCapabilities(false); } @Override @@ -96,10 +92,12 @@ class PlayerCapabilities implements IPlayer, ICaster { @Override public void sendCapabilities(boolean full) { if (!getOwner().getEntityWorld().isRemote) { + System.out.println("[SERVER] Sending player capabilities."); + if (full) { Unicopia.channel.broadcast(new MsgPlayerCapabilities(this)); } else { - Unicopia.channel.broadcast(new MsgPlayerCapabilities(getPlayerSpecies(), getOwner().getGameProfile().getId())); + Unicopia.channel.broadcast(new MsgPlayerCapabilities(getPlayerSpecies(), getOwner())); } } } @@ -136,15 +134,15 @@ class PlayerCapabilities implements IPlayer, ICaster { powers.onUpdate(entity); gravity.onUpdate(entity); - if (effect != null) { + if (hasEffect()) { if (!getPlayerSpecies().canCast()) { setEffect(null); } else { if (entity.getEntityWorld().isRemote) { // && entity.getEntityWorld().getWorldTime() % 10 == 0 - effect.render(entity); + getEffect().render(entity); } - if (!effect.update(entity)) { + if (!getEffect().update(entity)) { setEffect(null); } } @@ -196,21 +194,27 @@ class PlayerCapabilities implements IPlayer, ICaster { @Override public void writeToNBT(NBTTagCompound compound) { compound.setString("playerSpecies", getPlayerSpecies().name()); + compound.setTag("powers", powers.toNBT()); compound.setTag("gravity", gravity.toNBT()); + IMagicEffect effect = getEffect(); + if (effect != null) { - compound.setString("effect_id", effect.getName()); - compound.setTag("effect", effect.toNBT()); + compound.setTag("effect", SpellRegistry.instance().serializeEffectToNBT(effect)); } } @Override public void readFromNBT(NBTTagCompound compound) { setPlayerSpecies(Race.fromName(compound.getString("playerSpecies"), Race.HUMAN)); + powers.readFromNBT(compound.getCompoundTag("powers")); gravity.readFromNBT(compound.getCompoundTag("gravity")); - effect = SpellRegistry.instance().createEffectFroNBT(compound); + + if (compound.hasKey("effect")) { + setEffect(SpellRegistry.instance().createEffectFromNBT(compound.getCompoundTag("effect"))); + } } @Override @@ -221,30 +225,23 @@ class PlayerCapabilities implements IPlayer, ICaster { @Override public void setEffect(IMagicEffect effect) { - this.effect = effect; + effectDelegate.set(effect); sendCapabilities(true); } @Override public IMagicEffect getEffect() { - return effect; + return effectDelegate.get(); } @Override public void setOwner(EntityPlayer owner) { entity = owner; - playerId = owner.getGameProfile().getId(); } @Override public EntityPlayer getOwner() { - if (entity == null) { - entity = IPlayer.getPlayerEntity(playerId); - if (entity == null) { - logger.error("Capabilities without player! Mismatched id was" + playerId); - } - } return entity; } } diff --git a/src/main/java/com/minelittlepony/unicopia/player/PlayerSpeciesList.java b/src/main/java/com/minelittlepony/unicopia/player/PlayerSpeciesList.java index 9210c2c0..d0b63e7a 100644 --- a/src/main/java/com/minelittlepony/unicopia/player/PlayerSpeciesList.java +++ b/src/main/java/com/minelittlepony/unicopia/player/PlayerSpeciesList.java @@ -46,7 +46,7 @@ public class PlayerSpeciesList { } public IPlayer getPlayer(UUID playerId) { - return getPlayer(IPlayer.getPlayerEntity(playerId)); + return getPlayer(IPlayer.getPlayerFromServer(playerId)); } public IRaceContainer getEntity(T entity) { diff --git a/src/main/java/com/minelittlepony/unicopia/render/RenderGem.java b/src/main/java/com/minelittlepony/unicopia/render/RenderGem.java new file mode 100644 index 00000000..8ac70871 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/render/RenderGem.java @@ -0,0 +1,30 @@ +package com.minelittlepony.unicopia.render; + +import com.minelittlepony.unicopia.entity.EntitySpell; +import com.minelittlepony.unicopia.model.ModelGem; + +import net.minecraft.client.renderer.entity.RenderLiving; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.util.ResourceLocation; + +public class RenderGem extends RenderLiving { + + private static final ResourceLocation gem = new ResourceLocation("unicopia", "textures/entity/gem.png"); + + public RenderGem(RenderManager rendermanagerIn) { + super(rendermanagerIn, new ModelGem(), 0); + } + + protected ResourceLocation getEntityTexture(EntitySpell entity) { + return gem; + } + + protected float getDeathMaxRotation(EntitySpell entity) { + return 0; + } + + protected boolean canRenderName(EntitySpell targetEntity) { + return super.canRenderName(targetEntity) && (targetEntity.getAlwaysRenderNameTagForRender() + || targetEntity.hasCustomName() && targetEntity == renderManager.pointedEntity); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/spell/ICaster.java b/src/main/java/com/minelittlepony/unicopia/spell/ICaster.java index 1226edbe..fbbb5e90 100644 --- a/src/main/java/com/minelittlepony/unicopia/spell/ICaster.java +++ b/src/main/java/com/minelittlepony/unicopia/spell/ICaster.java @@ -14,6 +14,9 @@ public interface ICaster extends IOwned { return getEffect() != null; } + /** + * Gets the entity directly responsible for casting. + */ default Entity getEntity() { return getOwner(); } diff --git a/src/main/java/com/minelittlepony/unicopia/spell/IDispenceable.java b/src/main/java/com/minelittlepony/unicopia/spell/IDispenceable.java index 9e235f77..ef9f8415 100644 --- a/src/main/java/com/minelittlepony/unicopia/spell/IDispenceable.java +++ b/src/main/java/com/minelittlepony/unicopia/spell/IDispenceable.java @@ -6,13 +6,13 @@ import net.minecraft.util.EnumFacing; /** * Represents an object with an action to perform when dispensed from a dispenser. - * + * */ -public interface IDispenceable { - +public interface IDispenceable extends IMagicEffect { + /** * Called when dispensed. - * + * * @param pos Block position in front of the dispenser * @param facing Direction of the dispenser * @param source The dispenser currently dispensing diff --git a/src/main/java/com/minelittlepony/unicopia/spell/SpellRegistry.java b/src/main/java/com/minelittlepony/unicopia/spell/SpellRegistry.java index 4c84f6f8..57ab22ce 100644 --- a/src/main/java/com/minelittlepony/unicopia/spell/SpellRegistry.java +++ b/src/main/java/com/minelittlepony/unicopia/spell/SpellRegistry.java @@ -34,8 +34,8 @@ public class SpellRegistry { return null; } - public IMagicEffect createEffectFroNBT(NBTTagCompound compound) { - if (compound.hasKey("effect_id") && compound.hasKey("effect")) { + public IMagicEffect createEffectFromNBT(NBTTagCompound compound) { + if (compound.hasKey("effect_id")) { IMagicEffect effect = getSpellFromName(compound.getString("effect_id")); if (effect != null) { @@ -48,6 +48,14 @@ public class SpellRegistry { return null; } + public NBTTagCompound serializeEffectToNBT(IMagicEffect effect) { + NBTTagCompound compound = effect.toNBT(); + + compound.setString("effect_id", effect.getName()); + + return compound; + } + public IDispenceable getDispenseActionFrom(ItemStack stack) { String key = getKeyFromStack(stack); @@ -80,7 +88,7 @@ public class SpellRegistry { return stack; } - private String getKeyFromStack(ItemStack stack) { + public static String getKeyFromStack(ItemStack stack) { if (stack.isEmpty() || !stack.hasTagCompound() || !stack.getTagCompound().hasKey("spell")) { return ""; } diff --git a/src/main/java/com/minelittlepony/unicopia/spell/SpellShield.java b/src/main/java/com/minelittlepony/unicopia/spell/SpellShield.java index 972a12eb..2eb6b473 100644 --- a/src/main/java/com/minelittlepony/unicopia/spell/SpellShield.java +++ b/src/main/java/com/minelittlepony/unicopia/spell/SpellShield.java @@ -57,7 +57,7 @@ public class SpellShield extends AbstractSpell { protected void spawnParticles(World w, double x, double y, double z, int strength) { IShape sphere = new Sphere(true, strength); - for (int i = 0; i < strength; i++) { + for (int i = 0; i < strength * 6; i++) { Vec3d pos = sphere.computePoint(w.rand); Particles.instance().spawnParticle(Unicopia.MAGIC_PARTICLE, false, pos.x + x, pos.y + y, pos.z + z, diff --git a/src/main/resources/assets/unicopia/textures/entity/gem.png b/src/main/resources/assets/unicopia/textures/entity/gem.png new file mode 100644 index 0000000000000000000000000000000000000000..86b1b195cb8f17fb8dbc0013d99eac3a335b6620 GIT binary patch literal 28136 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

5vz0ZhPuA=hE`%X!06^TICnbtrRdC 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 Gi~b*cJWMzM literal 0 HcmV?d00001