Animated Texture Support

This commit is contained in:
Kam committed 2026-02-28 18:34:21 -05:00
1 parent 988dbe0cd2
commit de2fcea154
17 files changed
+578 -21

No files matched your search

@@ -830,6 +830,15 @@ public abstract class Ability implements IAbility, IAbilityAction {
addIconLayerFields(defs, icon, 0, isCustom);
addIconLayerFields(defs, icon, 1, () -> isCustom.getAsBoolean() && icon.getLayerCount() >= 2);
addIconLayerFields(defs, icon, 2, () -> isCustom.getAsBoolean() && icon.getLayerCount() >= 3);
// Animation
defs.add(FieldDef.section("Animation").tab("Effects").visibleWhen(isCustom));
defs.add(FieldDef.boolField("gui.animated", icon::isAnimated, icon::setAnimated)
.tab("Effects").visibleWhen(isCustom).hover("gui.animated.hover"));
defs.add(FieldDef.intField("gui.frameCount", icon::getFrameCount, icon::setFrameCount)
.tab("Effects").range(1, 256).visibleWhen(() -> isCustom.getAsBoolean() && icon.isAnimated()));
defs.add(FieldDef.intField("gui.frameTime", icon::getFrameTime, icon::setFrameTime)
.tab("Effects").range(1, 100).visibleWhen(() -> isCustom.getAsBoolean() && icon.isAnimated()));
}
// External field providers (e.g., DBC Addon injecting a "DBC" tab)
@@ -32,6 +32,11 @@ public class AbilityIconData {
/** Per-state icon UV overrides for layer 0 (0-indexed, maps to toggle states 1, 2, ...). */
private int[][] stateIcons = null;
// Animation
private boolean animated = false;
private int frameCount = 1;
private int frametime = 2;
// ═══════════════════════════════════════════════════════════════════
// LAYER INNER CLASS
// ═══════════════════════════════════════════════════════════════════
@@ -125,6 +130,10 @@ public class AbilityIconData {
}
nbt.setTag("Layers", layerList);
nbt.setBoolean("Animated", animated);
nbt.setInteger("FrameCount", frameCount);
nbt.setInteger("FrameTime", frametime);
if (stateIcons != null && stateIcons.length > 0) {
NBTTagList list = new NBTTagList();
for (int[] pair : stateIcons) {
@@ -150,6 +159,13 @@ public class AbilityIconData {
if (layerCount < 1) layerCount = 1;
if (layerCount > MAX_LAYERS) layerCount = MAX_LAYERS;
if (nbt.hasKey("Animated"))
animated = nbt.getBoolean("Animated");
if (nbt.hasKey("FrameCount"))
frameCount = Math.max(1, nbt.getInteger("FrameCount"));
if (nbt.hasKey("FrameTime"))
frametime = Math.max(1, nbt.getInteger("FrameTime"));
if (nbt.hasKey("Layers")) {
NBTTagList layerList = nbt.getTagList("Layers", 10);
for (int i = 0; i < MAX_LAYERS; i++) {
@@ -256,6 +272,37 @@ public class AbilityIconData {
save();
}
// ═══════════════════════════════════════════════════════════════════
// ANIMATION
// ═══════════════════════════════════════════════════════════════════
public boolean isAnimated() {
return animated;
}
public void setAnimated(boolean animated) {
this.animated = animated;
save();
}
public int getFrameCount() {
return frameCount;
}
public void setFrameCount(int frameCount) {
this.frameCount = Math.max(1, frameCount);
save();
}
public int getFrameTime() {
return frametime;
}
public void setFrameTime(int frametime) {
this.frametime = Math.max(1, frametime);
save();
}
// ═══════════════════════════════════════════════════════════════════
// PER-STATE ICON OVERRIDES (applies to layer 0)
// ═══════════════════════════════════════════════════════════════════
@@ -8,6 +8,7 @@ import net.minecraft.util.ResourceLocation;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.client.gui.global.GuiNPCManageEffects;
import noppes.npcs.client.gui.script.GuiScriptInterface;
import noppes.npcs.client.renderer.AnimationHelper;
import noppes.npcs.client.gui.util.GuiMenuTopButton;
import noppes.npcs.client.gui.util.GuiNpcButtonYesNo;
import noppes.npcs.client.gui.util.GuiNpcLabel;
@@ -129,6 +130,12 @@ public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldLi
int width = data.getTotalWidth();
int height = data.getTotalHeight();
// Animation V offset for preview
if (data.isAnimated()) {
iconY += (int) (data.getCurrentFrameVOffset() * height);
} else if (effect.animated && effect.frameCount > 1) {
iconY += (int) (AnimationHelper.getFrameVOffset(height, effect.frameCount, effect.frametime) * height);
}
func_152125_a(x, y, iconX, iconY, iconWidth, iconHeight, iconRenderSize, iconRenderSize, width, height);
@@ -183,6 +190,33 @@ public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldLi
effect.height
));
scrollWindow.addLabel(new GuiNpcLabel(9, "effect.editor.height", scrollWindow.getTextField(9).xPosition - 43, y + 6, 0xFFFFFF));
y += 25;
// Animation controls
scrollWindow.addLabel(new GuiNpcLabel(11, "gui.animated", x, y + 5, 0xFFFFFF));
GuiNpcButtonYesNo animBtn = new GuiNpcButtonYesNo(11, x + 70, y, 50, 20, effect.animated);
animBtn.setHoverText("gui.animated.hover");
scrollWindow.addButton(animBtn);
if (effect.animated) {
y += 23;
scrollWindow.addLabel(new GuiNpcLabel(12, "gui.frameCount", x, y + 6, 0xFFFFFF));
scrollWindow.addTextField(setIntegerOnly(
new GuiNpcTextField(12, this, x + 80, y, 60, 20, "" + effect.frameCount),
1,
256,
effect.frameCount
));
y += 23;
scrollWindow.addLabel(new GuiNpcLabel(13, "gui.frameTime", x, y + 6, 0xFFFFFF));
scrollWindow.addTextField(setIntegerOnly(
new GuiNpcTextField(13, this, x + 80, y, 60, 20, "" + effect.frametime),
1,
100,
effect.frametime
));
}
}
@@ -209,6 +243,11 @@ public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldLi
GuiNpcButtonYesNo button = (GuiNpcButtonYesNo) guibutton;
effect.lossOnDeath = button.getBoolean();
}
if (id == 11) {
GuiNpcButtonYesNo button = (GuiNpcButtonYesNo) guibutton;
effect.animated = button.getBoolean();
initGui();
}
}
@Override
@@ -248,6 +287,12 @@ public class SubGuiEffectGeneral extends SubGuiInterface implements ITextfieldLi
case 9:
effect.height = guiNpcTextField.getInteger();
break;
case 12:
effect.frameCount = Math.max(1, guiNpcTextField.getInteger());
break;
case 13:
effect.frametime = Math.max(1, guiNpcTextField.getInteger());
break;
}
}
@@ -193,6 +193,14 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText
.tab("Icon").visibleWhen(layerVisible));
}
// Animation
fieldDefs.add(FieldDef.section("Animation").tab("Icon"));
fieldDefs.add(FieldDef.boolField("gui.animated", chainIcon::isAnimated, chainIcon::setAnimated).tab("Icon").hover("gui.animated.hover"));
fieldDefs.add(FieldDef.intField("gui.frameCount", chainIcon::getFrameCount, chainIcon::setFrameCount)
.tab("Icon").range(1, 256).visibleWhen(chainIcon::isAnimated));
fieldDefs.add(FieldDef.intField("gui.frameTime", chainIcon::getFrameTime, chainIcon::setFrameTime)
.tab("Icon").range(1, 100).visibleWhen(chainIcon::isAnimated));
// External field providers (e.g., DBC Addon injecting a "DBC" tab)
if (AbilityController.Instance != null) {
for (IChainedAbilityFieldProvider provider : AbilityController.Instance.getChainedFieldProviders()) {
@@ -7,6 +7,7 @@ import noppes.npcs.api.gui.ICustomGuiComponent;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.client.gui.custom.GuiCustom;
import noppes.npcs.client.gui.custom.interfaces.IGuiComponent;
import noppes.npcs.client.renderer.AnimationHelper;
import noppes.npcs.client.renderer.ImageData;
import noppes.npcs.scripted.gui.ScriptGuiTexturedRect;
import org.lwjgl.opengl.GL11;
@@ -30,6 +31,11 @@ public class CustomGuiTexturedRect extends Gui implements IGuiComponent {
float alpha;
float rotation;
// Animation
boolean animated;
int frameCount = 1;
int frametime = 2;
public CustomGuiTexturedRect(int id, String texture, int x, int y, int width, int height) {
this(id, texture, x, y, width, height, 0, 0);
}
@@ -64,10 +70,18 @@ public class CustomGuiTexturedRect extends Gui implements IGuiComponent {
int totalWidth = this.imageData.getTotalWidth();
int totalHeight = this.imageData.getTotalHeight();
// Animation V offset
float vOff = 0f;
if (this.imageData.isAnimated()) {
vOff = this.imageData.getCurrentFrameVOffset();
} else if (this.animated && this.frameCount > 1) {
vOff = AnimationHelper.getFrameVOffset(totalHeight, this.frameCount, this.frametime);
}
float u1 = (float) textureX / (float) totalWidth;
float u2 = u1 + (float) width / (float) totalWidth;
float v1 = (float) textureY / (float) totalHeight;
float v2 = v1 + (float) height / (float) totalHeight;
float v1 = vOff + (float) textureY / (float) totalHeight;
float v2 = vOff + ((float) textureY + (float) height) / (float) totalHeight;
GL11.glPushMatrix();
float red = (color >> 16 & 255) / 255f;
@@ -104,6 +118,9 @@ public class CustomGuiTexturedRect extends Gui implements IGuiComponent {
component.setColor(color);
component.setAlpha(alpha);
component.setRotation(rotation);
if (this.animated && this.frameCount > 1) {
component.setAnimation(this.frameCount, this.frametime);
}
return component;
}
@@ -124,6 +141,12 @@ public class CustomGuiTexturedRect extends Gui implements IGuiComponent {
rect.alpha = component.getAlpha();
rect.rotation = component.getRotation();
if (component.isAnimated()) {
rect.animated = true;
rect.frameCount = component.getFrameCount();
rect.frametime = component.getFrameTime();
}
return rect;
}
}
@@ -13,6 +13,7 @@ import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.ResourceLocation;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.client.renderer.AnimationHelper;
import noppes.npcs.client.renderer.ImageData;
import org.lwjgl.opengl.GL11;
@@ -145,10 +146,18 @@ public class AbilityIcon extends Gui {
int ix = (state > 0) ? data.getIconXForState(state) : layer.iconX;
int iy = (state > 0) ? data.getIconYForState(state) : layer.iconY;
// Animation V offset
float vOff = 0f;
if (imageData.isAnimated()) {
vOff = imageData.getCurrentFrameVOffset();
} else if (data.isAnimated()) {
vOff = AnimationHelper.getFrameVOffset((int) texH, data.getFrameCount(), data.getFrameTime());
}
float u1 = ix / texW;
float v1 = iy / texH;
float v1 = vOff + iy / texH;
float u2 = (ix + data.width) / texW;
float v2 = (iy + data.height) / texH;
float v2 = vOff + (iy + data.height) / texH;
Tessellator t = Tessellator.instance;
t.startDrawingQuads();
@@ -10,6 +10,7 @@ import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.client.CustomNpcResourceListener;
import noppes.npcs.client.renderer.AnimationHelper;
import noppes.npcs.client.gui.SubGuiColorSelector;
import noppes.npcs.client.gui.global.GuiNPCManageLinked;
import noppes.npcs.client.gui.script.GuiScriptInterface;
@@ -286,6 +287,31 @@ public class SubGuiLinkedItem extends SubGuiInterface implements ITextfieldListe
scrollWindow.addLabel(new GuiNpcLabel(70, "display.durabilityShow", x, localY + 5, 0xFFFFFF));
GuiNpcButtonYesNo durabilityButton = new GuiNpcButtonYesNo(26, x + 120, localY, linkedItem.display.durabilityShow);
scrollWindow.addButton(durabilityButton);
localY += 30;
// Animation controls
scrollWindow.addLabel(new GuiNpcLabel(80, "gui.animated", x, localY + 5, 0xFFFFFF));
boolean isAnim = linkedItem.display.animated != null && linkedItem.display.animated;
GuiNpcButtonYesNo animBtn = new GuiNpcButtonYesNo(29, x + 120, localY, isAnim);
animBtn.setHoverText("gui.animated.hover");
scrollWindow.addButton(animBtn);
localY += 25;
if (isAnim) {
scrollWindow.addLabel(new GuiNpcLabel(81, "gui.frameCount", x, localY + 5, 0xFFFFFF));
GuiNpcTextField fcField = new GuiNpcTextField(71, this, x + 120, localY, 60, 20,
"" + (linkedItem.display.frameCount != null ? linkedItem.display.frameCount : 1));
fcField.setIntegersOnly().setMinMaxDefault(1, 256, 1);
scrollWindow.addTextField(fcField);
localY += 25;
scrollWindow.addLabel(new GuiNpcLabel(82, "gui.frameTime", x, localY + 5, 0xFFFFFF));
GuiNpcTextField ftField = new GuiNpcTextField(72, this, x + 120, localY, 60, 20,
"" + (linkedItem.display.frametime != null ? linkedItem.display.frametime : 2));
ftField.setIntegersOnly().setMinMaxDefault(1, 100, 2);
scrollWindow.addTextField(ftField);
localY += 25;
}
scrollWindow.scrollY = 0;
scrollWindow.maxScrollY = Math.max(localY - scrollWindow.clipHeight, 0);
@@ -381,6 +407,9 @@ public class SubGuiLinkedItem extends SubGuiInterface implements ITextfieldListe
linkedItem.isNormalItem = ((GuiNpcButtonYesNo) guibutton).getBoolean();
} else if (id == 26 && guibutton instanceof GuiNpcButtonYesNo) {
linkedItem.display.durabilityShow = ((GuiNpcButtonYesNo) guibutton).getBoolean();
} else if (id == 29 && guibutton instanceof GuiNpcButtonYesNo) {
linkedItem.display.animated = ((GuiNpcButtonYesNo) guibutton).getBoolean();
initGui();
}
}
@@ -429,6 +458,10 @@ public class SubGuiLinkedItem extends SubGuiInterface implements ITextfieldListe
linkedItem.display.translateY = textField.getFloat();
} else if (id == 63) {
linkedItem.display.translateZ = textField.getFloat();
} else if (id == 71) {
linkedItem.display.frameCount = Math.max(1, textField.getInteger());
} else if (id == 72) {
linkedItem.display.frametime = Math.max(1, textField.getInteger());
}
}
@@ -472,7 +505,20 @@ public class SubGuiLinkedItem extends SubGuiInterface implements ITextfieldListe
int iconWidth = data.getTotalWidth();
int iconHeight = data.getTotalWidth();
int width = data.getTotalWidth();
int height = data.getTotalWidth();
int height = data.getTotalHeight();
// Animation V offset for preview
if (data.isAnimated()) {
iconY += (int) (data.getCurrentFrameVOffset() * height);
iconHeight = data.getFrameHeight();
} else if (linkedItem.display.animated != null && linkedItem.display.animated
&& linkedItem.display.frameCount != null && linkedItem.display.frameCount > 1) {
iconY += (int) (AnimationHelper.getFrameVOffset(height, linkedItem.display.frameCount, linkedItem.display.frametime) * height);
iconHeight = height / linkedItem.display.frameCount;
} else {
iconHeight = data.getTotalWidth();
height = data.getTotalWidth();
}
float[] colors = ColorUtil.hexToRGB(linkedItem.display.itemColor);
GL11.glColor3f(colors[0], colors[1], colors[2]);
@@ -6,6 +6,7 @@ import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.texture.TextureManager;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.client.renderer.AnimationHelper;
import noppes.npcs.client.renderer.ImageData;
import noppes.npcs.controllers.data.CustomEffect;
import noppes.npcs.controllers.data.PlayerEffect;
@@ -82,6 +83,13 @@ public class GuiEffectBar extends GuiScreen {
int texWidth = imageData.getTotalWidth();
int texHeight = imageData.getTotalHeight();
// Animation V offset
if (imageData.isAnimated()) {
iconV += (int) (imageData.getCurrentFrameVOffset() * texHeight);
} else if (entry.effect.animated && entry.effect.frameCount > 1) {
iconV += (int) (AnimationHelper.getFrameVOffset(texHeight, entry.effect.frameCount, entry.effect.frametime) * texHeight);
}
// Use iconYOffset to vertically center the icon
func_152125_a(x + 2, drawY + iconYOffset, iconU, iconV, iconWidth, iconHeight, iconRenderSize, iconRenderSize, texWidth, texHeight);
@@ -191,7 +199,14 @@ public class GuiEffectBar extends GuiScreen {
if (imageData != null && imageData.imageLoaded()) {
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
imageData.bindTexture();
func_152125_a(tooltipX + (maxWidth - iconSize) / 2, textY + 4, hoveredEffect.iconX, hoveredEffect.iconY, hoveredEffect.getWidth(), hoveredEffect.getHeight(), iconSize, iconSize, imageData.getTotalWidth(), imageData.getTotalHeight());
int tooltipIconV = hoveredEffect.iconY;
int tooltipTexH = imageData.getTotalHeight();
if (imageData.isAnimated()) {
tooltipIconV += (int) (imageData.getCurrentFrameVOffset() * tooltipTexH);
} else if (hoveredEffect.animated && hoveredEffect.frameCount > 1) {
tooltipIconV += (int) (AnimationHelper.getFrameVOffset(tooltipTexH, hoveredEffect.frameCount, hoveredEffect.frametime) * tooltipTexH);
}
func_152125_a(tooltipX + (maxWidth - iconSize) / 2, textY + 4, hoveredEffect.iconX, tooltipIconV, hoveredEffect.getWidth(), hoveredEffect.getHeight(), iconSize, iconSize, imageData.getTotalWidth(), tooltipTexH);
}
textureManager.bindTexture(specialIcons);
@@ -0,0 +1,49 @@
package noppes.npcs.client.renderer;
import net.minecraft.client.Minecraft;
/**
* Static utility for computing animated texture UV offsets.
* Used by renderers when animation parameters come from data class fields
* rather than .mcmeta auto-detection on ImageData.
*/
public class AnimationHelper {
/**
* Returns the V-coordinate offset (0.0-1.0) for the current animation frame.
* Based on system time for consistent sync across all animated textures.
*
* @param totalHeight Total pixel height of the sprite strip
* @param frameCount Number of frames in the strip
* @param frametime Ticks per frame (1 tick = 50ms)
* @return V offset in 0.0-1.0 range, or 0.0 if not animated
*/
public static float getFrameVOffset(int totalHeight, int frameCount, int frametime) {
if (frameCount <= 1 || totalHeight <= 0) {
return 0f;
}
frametime = Math.max(1, frametime);
long millis = Minecraft.getSystemTime();
int tick = (int) (millis / 50L);
int frameIdx = (tick / frametime) % frameCount;
int frameHeight = totalHeight / frameCount;
return (float) (frameIdx * frameHeight) / (float) totalHeight;
}
/**
* Returns the V-coordinate size of a single frame in 0.0-1.0 range.
*
* @param totalHeight Total pixel height of the sprite strip
* @param frameCount Number of frames in the strip
* @return V size of one frame, or 1.0 if not animated
*/
public static float getFrameVSize(int totalHeight, int frameCount) {
if (frameCount <= 1 || totalHeight <= 0) {
return 1f;
}
int frameHeight = totalHeight / frameCount;
return (float) frameHeight / (float) totalHeight;
}
}
@@ -1,5 +1,8 @@
package noppes.npcs.client.renderer;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.IResource;
@@ -11,6 +14,7 @@ import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class ImageData {
private final ResourceLocation location;
@@ -23,6 +27,14 @@ public class ImageData {
private boolean gotWidthHeight;
private boolean invalid;
// Animation fields
private boolean animated = false;
private int frameCount = 1;
private int frametime = 2;
private int[] frames = null;
private boolean interpolate = false;
private boolean mcmetaChecked = false;
public ImageData(String directory) {
this.location = new ResourceLocation(directory);
if (directory.startsWith("https://") || directory.startsWith("http://")) {
@@ -141,6 +153,11 @@ public class ImageData {
private void correctWidthHeight() {
this.totalWidth = Math.max(this.totalWidth, 1);
this.totalHeight = Math.max(this.totalHeight, 1);
if (!this.isUrl && !this.mcmetaChecked) {
this.mcmetaChecked = true;
tryLoadMcmeta();
}
}
public int getTotalWidth() {
@@ -150,4 +167,112 @@ public class ImageData {
public int getTotalHeight() {
return this.gotWidthHeight ? this.totalHeight : -1;
}
// ═══════════════════════════════════════════════════════════════════
// ANIMATION
// ═══════════════════════════════════════════════════════════════════
private void tryLoadMcmeta() {
try {
ResourceLocation mcmetaLoc = new ResourceLocation(
location.getResourceDomain(),
location.getResourcePath() + ".mcmeta"
);
IResource resource = Minecraft.getMinecraft().getResourceManager().getResource(mcmetaLoc);
InputStream stream = resource.getInputStream();
try {
InputStreamReader reader = new InputStreamReader(stream);
JsonObject root = new JsonParser().parse(reader).getAsJsonObject();
if (root.has("animation")) {
JsonObject anim = root.getAsJsonObject("animation");
this.animated = true;
if (anim.has("frametime")) {
this.frametime = Math.max(1, anim.get("frametime").getAsInt());
}
if (anim.has("interpolate")) {
this.interpolate = anim.get("interpolate").getAsBoolean();
}
if (anim.has("frames")) {
JsonArray framesArray = anim.getAsJsonArray("frames");
this.frames = new int[framesArray.size()];
int maxFrame = 0;
for (int i = 0; i < framesArray.size(); i++) {
this.frames[i] = framesArray.get(i).getAsInt();
if (this.frames[i] > maxFrame) {
maxFrame = this.frames[i];
}
}
this.frameCount = maxFrame + 1;
} else if (this.totalWidth > 0) {
this.frameCount = Math.max(1, this.totalHeight / this.totalWidth);
}
}
} finally {
stream.close();
}
} catch (Exception ignored) {
// No .mcmeta found or parse error — not animated
}
}
/**
* Programmatically enable animation on this ImageData.
* Used when animation settings come from data class fields rather than .mcmeta files.
*/
public void setAnimation(int frameCount, int frametime) {
if (frameCount > 1) {
this.animated = true;
this.frameCount = Math.max(1, frameCount);
this.frametime = Math.max(1, frametime);
}
}
public boolean isAnimated() {
return this.animated && this.frameCount > 1;
}
public int getFrameCount() {
return this.frameCount;
}
public int getFrameTime() {
return this.frametime;
}
/**
* Returns the pixel height of a single animation frame.
* For non-animated textures, returns the total height.
*/
public int getFrameHeight() {
if (this.animated && this.frameCount > 1) {
return Math.max(1, this.totalHeight / this.frameCount);
}
return this.gotWidthHeight ? this.totalHeight : -1;
}
/**
* Returns the V-coordinate offset (0.0-1.0) for the current animation frame.
* Based on system time so all instances of the same texture animate in sync.
* Returns 0.0 for non-animated textures.
*/
public float getCurrentFrameVOffset() {
if (!this.animated || this.frameCount <= 1 || this.totalHeight <= 0) {
return 0f;
}
long millis = Minecraft.getSystemTime();
int tick = (int) (millis / 50L);
int frameIdx;
if (this.frames != null && this.frames.length > 0) {
frameIdx = (tick / this.frametime) % this.frames.length;
frameIdx = this.frames[frameIdx];
} else {
frameIdx = (tick / this.frametime) % this.frameCount;
}
int frameHeight = this.totalHeight / this.frameCount;
return (float) (frameIdx * frameHeight) / (float) this.totalHeight;
}
}
@@ -17,6 +17,7 @@ import net.minecraftforge.client.IItemRenderer;
import noppes.npcs.api.item.IItemCustomizable;
import noppes.npcs.api.item.IItemStack;
import noppes.npcs.client.ClientCacheHandler;
import noppes.npcs.client.renderer.AnimationHelper;
import noppes.npcs.client.renderer.ImageData;
import noppes.npcs.items.ItemCustomizable;
import noppes.npcs.scripted.NpcAPI;
@@ -222,7 +223,8 @@ public class ItemCustomRenderer implements IItemRenderer {
int pass = 0;
GL11.glPushMatrix();
ClientCacheHandler.getImageData(scriptCustomItem.getTexture()).bindTexture();
ImageData entityImageData = ClientCacheHandler.getImageData(scriptCustomItem.getTexture());
entityImageData.bindTexture();
if (renderInFrame) {
GL11.glTranslatef(0.0F, -0.05F, 0.0F);
@@ -233,10 +235,22 @@ public class ItemCustomRenderer implements IItemRenderer {
Tessellator tessellator = Tessellator.instance;
// Animation V offset for entity rendering
float entityVOff = 0f;
float entityVEnd = 1f;
if (entityImageData.isAnimated()) {
entityVOff = entityImageData.getCurrentFrameVOffset();
entityVEnd = entityVOff + (float) entityImageData.getFrameHeight() / entityImageData.getTotalHeight();
} else if (scriptCustomItem.isTextureAnimated() != null && scriptCustomItem.isTextureAnimated()
&& scriptCustomItem.getFrameCount() != null && scriptCustomItem.getFrameCount() > 1) {
entityVOff = AnimationHelper.getFrameVOffset(entityImageData.getTotalHeight(), scriptCustomItem.getFrameCount(), scriptCustomItem.getFrameTime());
entityVEnd = entityVOff + AnimationHelper.getFrameVSize(entityImageData.getTotalHeight(), scriptCustomItem.getFrameCount());
}
float f14 = 0.0F;
float f15 = 1.0F;
float f4 = 0.0F;
float f5 = 1.0F;
float f4 = entityVOff;
float f5 = entityVEnd;
float f6 = 1.0F;
float f7 = 0.5F;
float f8 = 0.25F;
@@ -272,8 +286,11 @@ public class ItemCustomRenderer implements IItemRenderer {
GL11.glRotatef(-90.0F, 0.0F, 0.0F, 1.0F);
}*/
ImageData imageData = ClientCacheHandler.getImageData(scriptCustomItem.getTexture());
ItemRenderer.renderItemIn2D(tessellator, f15, f4, f14, f5, imageData.getTotalWidth(), imageData.getTotalHeight(), f9);
ImageData loopImageData = ClientCacheHandler.getImageData(scriptCustomItem.getTexture());
int entityFrameH = loopImageData.isAnimated() ? loopImageData.getFrameHeight()
: (scriptCustomItem.isTextureAnimated() != null && scriptCustomItem.isTextureAnimated() && scriptCustomItem.getFrameCount() != null && scriptCustomItem.getFrameCount() > 1
? loopImageData.getTotalHeight() / scriptCustomItem.getFrameCount() : loopImageData.getTotalHeight());
ItemRenderer.renderItemIn2D(tessellator, f15, f4, f14, f5, loopImageData.getTotalWidth(), entityFrameH, f9);
if (itemStack.hasEffect(pass)) {
GL11.glDepthFunc(GL11.GL_EQUAL);
@@ -359,8 +376,20 @@ public class ItemCustomRenderer implements IItemRenderer {
GL11.glDisable(GL11.GL_LIGHTING); //Forge: Make sure that render states are reset, a renderEffect can derp them up.
GL11.glEnable(GL11.GL_ALPHA_TEST);
ClientCacheHandler.getImageData(scriptCustomItem.getTexture()).bindTexture();
renderCustomItemSlot(0, 0, 16, 16, itemRed, itemGreen, itemBlue);
ImageData imageData = ClientCacheHandler.getImageData(scriptCustomItem.getTexture());
imageData.bindTexture();
float vOff = 0f;
float vEnd = 1f;
if (imageData.isAnimated()) {
vOff = imageData.getCurrentFrameVOffset();
vEnd = vOff + (float) imageData.getFrameHeight() / imageData.getTotalHeight();
} else if (scriptCustomItem.isTextureAnimated() != null && scriptCustomItem.isTextureAnimated()
&& scriptCustomItem.getFrameCount() != null && scriptCustomItem.getFrameCount() > 1) {
vOff = AnimationHelper.getFrameVOffset(imageData.getTotalHeight(), scriptCustomItem.getFrameCount(), scriptCustomItem.getFrameTime());
vEnd = vOff + AnimationHelper.getFrameVSize(imageData.getTotalHeight(), scriptCustomItem.getFrameCount());
}
renderCustomItemSlot(0, 0, 16, 16, itemRed, itemGreen, itemBlue, vOff, vEnd);
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_ALPHA_TEST);
@@ -412,13 +441,17 @@ public class ItemCustomRenderer implements IItemRenderer {
}
public void renderCustomItemSlot(int posX, int posY, int imageWidth, int imageHeight, float itemRed, float itemGreen, float itemBlue) {
renderCustomItemSlot(posX, posY, imageWidth, imageHeight, itemRed, itemGreen, itemBlue, 0f, 1f);
}
public void renderCustomItemSlot(int posX, int posY, int imageWidth, int imageHeight, float itemRed, float itemGreen, float itemBlue, float vOff, float vEnd) {
Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
tessellator.setColorRGBA_F(itemRed, itemGreen, itemBlue, 1.0F);
tessellator.addVertexWithUV((double) (posX), (double) (posY + imageHeight), 0, 0, 1);
tessellator.addVertexWithUV((double) (posX + imageWidth), (double) (posY + imageHeight), 0, 1, 1);
tessellator.addVertexWithUV((double) (posX + imageWidth), (double) (posY), 0, 1, 0);
tessellator.addVertexWithUV((double) (posX), (double) (posY), 0, 0, 0);
tessellator.addVertexWithUV((double) (posX), (double) (posY + imageHeight), 0, 0, vEnd);
tessellator.addVertexWithUV((double) (posX + imageWidth), (double) (posY + imageHeight), 0, 1, vEnd);
tessellator.addVertexWithUV((double) (posX + imageWidth), (double) (posY), 0, 1, vOff);
tessellator.addVertexWithUV((double) (posX), (double) (posY), 0, 0, vOff);
tessellator.draw();
}
@@ -429,7 +462,8 @@ public class ItemCustomRenderer implements IItemRenderer {
TextureManager texturemanager = mc.getTextureManager();
int par3 = 0;
ClientCacheHandler.getImageData(scriptCustomItem.getTexture()).bindTexture();
ImageData item3dImageData = ClientCacheHandler.getImageData(scriptCustomItem.getTexture());
item3dImageData.bindTexture();
Tessellator tessellator = Tessellator.instance;
IIcon icon = entityLivingBase.getItemIcon(itemStack, par3);
@@ -447,7 +481,19 @@ public class ItemCustomRenderer implements IItemRenderer {
GL11.glRotatef(335.0F, 0.0F, 0.0F, 1.0F);
GL11.glTranslatef(-0.9375F, -0.0625F, 0.0F);
renderCustomItemIn2D(scriptCustomItem, tessellator, 1.0F, 0.0F, 0.0F, 1.0F, 0.0625F);
// Animation V offset for 3D handheld rendering
float item3dVOff = 0f;
float item3dVEnd = 1f;
if (item3dImageData.isAnimated()) {
item3dVOff = item3dImageData.getCurrentFrameVOffset();
item3dVEnd = item3dVOff + (float) item3dImageData.getFrameHeight() / item3dImageData.getTotalHeight();
} else if (scriptCustomItem.isTextureAnimated() != null && scriptCustomItem.isTextureAnimated()
&& scriptCustomItem.getFrameCount() != null && scriptCustomItem.getFrameCount() > 1) {
item3dVOff = AnimationHelper.getFrameVOffset(item3dImageData.getTotalHeight(), scriptCustomItem.getFrameCount(), scriptCustomItem.getFrameTime());
item3dVEnd = item3dVOff + AnimationHelper.getFrameVSize(item3dImageData.getTotalHeight(), scriptCustomItem.getFrameCount());
}
renderCustomItemIn2D(scriptCustomItem, tessellator, 1.0F, item3dVOff, 0.0F, item3dVEnd, 0.0625F);
if (itemStack.hasEffect(par3)) {
GL11.glDepthFunc(GL11.GL_EQUAL);
@@ -485,7 +531,10 @@ public class ItemCustomRenderer implements IItemRenderer {
public static void renderCustomItemIn2D(IItemCustomizable wrapper, Tessellator p_78439_0_, float p_78439_1_, float p_78439_2_, float p_78439_3_, float p_78439_4_, float p_78439_7_) {
ImageData imageData = ClientCacheHandler.getImageData(wrapper.getTexture());
int width = imageData.getTotalWidth();
int height = imageData.getTotalHeight();
// Use frame height for edge pixel calculations so animated strips render correctly
int height = imageData.isAnimated() ? imageData.getFrameHeight()
: (wrapper.isTextureAnimated() != null && wrapper.isTextureAnimated() && wrapper.getFrameCount() != null && wrapper.getFrameCount() > 1
? imageData.getTotalHeight() / wrapper.getFrameCount() : imageData.getTotalHeight());
p_78439_0_.startDrawingQuads();
p_78439_0_.setNormal(0.0F, 0.0F, 1.0F);
@@ -29,6 +29,11 @@ public class CustomEffect implements ICustomEffect {
public String menuName = "§aNEW EFFECT";
public int width = 16, height = 16;
// Animation
public boolean animated = false;
public int frameCount = 1;
public int frametime = 2;
public int index = 0;
public CustomEffect() {
@@ -137,6 +142,36 @@ public class CustomEffect implements ICustomEffect {
this.height = height;
}
@Override
public boolean isAnimated() {
return animated;
}
@Override
public void setAnimated(boolean animated) {
this.animated = animated;
}
@Override
public int getFrameCount() {
return frameCount;
}
@Override
public void setFrameCount(int frameCount) {
this.frameCount = Math.max(1, frameCount);
}
@Override
public int getFrameTime() {
return frametime;
}
@Override
public void setFrameTime(int frametime) {
this.frametime = Math.max(1, frametime);
}
@Override
public boolean isLossOnDeath() {
return lossOnDeath;
@@ -222,6 +257,9 @@ public class CustomEffect implements ICustomEffect {
compound.setInteger("iconHeight", height);
compound.setString("icon", icon);
compound.setBoolean("lossOnDeath", lossOnDeath);
compound.setBoolean("iconAnimated", animated);
compound.setInteger("iconFrameCount", frameCount);
compound.setInteger("iconFrameTime", frametime);
if (saveScripts) {
NBTTagCompound scriptData = new NBTTagCompound();
@@ -264,6 +302,13 @@ public class CustomEffect implements ICustomEffect {
icon = compound.getString("icon");
lossOnDeath = compound.getBoolean("lossOnDeath");
if (compound.hasKey("iconAnimated"))
animated = compound.getBoolean("iconAnimated");
if (compound.hasKey("iconFrameCount"))
frameCount = Math.max(1, compound.getInteger("iconFrameCount"));
if (compound.hasKey("iconFrameTime"))
frametime = Math.max(1, compound.getInteger("iconFrameTime"));
if (compound.hasKey("ScriptData", Constants.NBT.TAG_COMPOUND)) {
EffectScript handler = new EffectScript();
handler.readFromNBT(compound.getCompoundTag("ScriptData"));
@@ -12,6 +12,11 @@ public class ItemDisplayData {
public Boolean durabilityShow = false;
public Integer durabilityColor = -1;
// Animation
public Boolean animated = false;
public Integer frameCount = 1;
public Integer frametime = 2;
public NBTTagCompound writeToNBT() {
NBTTagCompound compound = new NBTTagCompound();
this.writeToNBT(compound);
@@ -42,6 +47,10 @@ public class ItemDisplayData {
this.setFloat(compound, "TranslateY", this.translateY);
this.setFloat(compound, "TranslateZ", this.translateZ);
this.setBoolean(compound, "Animated", this.animated);
this.setInteger(compound, "FrameCount", this.frameCount);
this.setInteger(compound, "FrameTime", this.frametime);
return compound;
}
@@ -97,6 +106,16 @@ public class ItemDisplayData {
if (compound.hasKey("TranslateZ")) {
this.translateZ = compound.getFloat("TranslateZ");
}
if (compound.hasKey("Animated")) {
this.animated = compound.getBoolean("Animated");
}
if (compound.hasKey("FrameCount")) {
this.frameCount = Math.max(1, compound.getInteger("FrameCount"));
}
if (compound.hasKey("FrameTime")) {
this.frametime = Math.max(1, compound.getInteger("FrameTime"));
}
}
private void setBoolean(NBTTagCompound compound, String key, Boolean value) {
@@ -11,6 +11,11 @@ public class ScriptGuiTexturedRect extends ScriptGuiComponent implements ITextur
float scale;
String texture;
// Animation
boolean animated = false;
int frameCount = 1;
int frametime = 2;
public ScriptGuiTexturedRect() {
this.textureY = -1;
this.scale = 1.0F;
@@ -76,6 +81,25 @@ public class ScriptGuiTexturedRect extends ScriptGuiComponent implements ITextur
return this;
}
public boolean isAnimated() {
return this.animated && this.frameCount > 1;
}
public int getFrameCount() {
return this.frameCount;
}
public int getFrameTime() {
return this.frametime;
}
public ITexturedRect setAnimation(int frameCount, int frametime) {
this.animated = frameCount > 1;
this.frameCount = Math.max(1, frameCount);
this.frametime = Math.max(1, frametime);
return this;
}
public int getType() {
return 2;
}
@@ -89,6 +113,12 @@ public class ScriptGuiTexturedRect extends ScriptGuiComponent implements ITextur
nbt.setIntArray("texPos", new int[]{this.textureX, this.textureY});
}
if (this.animated) {
nbt.setBoolean("animated", true);
nbt.setInteger("frameCount", this.frameCount);
nbt.setInteger("frameTime", this.frametime);
}
return nbt;
}
@@ -101,6 +131,13 @@ public class ScriptGuiTexturedRect extends ScriptGuiComponent implements ITextur
this.setTextureOffset(nbt.getIntArray("texPos")[0], nbt.getIntArray("texPos")[1]);
}
if (nbt.hasKey("animated") && nbt.getBoolean("animated")) {
this.setAnimation(
nbt.hasKey("frameCount") ? Math.max(1, nbt.getInteger("frameCount")) : 1,
nbt.hasKey("frameTime") ? Math.max(1, nbt.getInteger("frameTime")) : 2
);
}
return this;
}
}
@@ -147,6 +147,33 @@ public abstract class ScriptCustomizableItem extends ScriptItemStack implements
return this.itemDisplay.translateZ != null ? this.itemDisplay.translateZ : 0;
}
public Boolean isTextureAnimated() {
return this.itemDisplay.animated != null ? this.itemDisplay.animated : false;
}
public void setTextureAnimated(Boolean animated) {
this.itemDisplay.animated = animated;
saveItemData();
}
public Integer getFrameCount() {
return this.itemDisplay.frameCount != null ? this.itemDisplay.frameCount : 1;
}
public void setFrameCount(Integer frameCount) {
this.itemDisplay.frameCount = frameCount != null ? Math.max(1, frameCount) : 1;
saveItemData();
}
public Integer getFrameTime() {
return this.itemDisplay.frametime != null ? this.itemDisplay.frametime : 2;
}
public void setFrameTime(Integer frametime) {
this.itemDisplay.frametime = frametime != null ? Math.max(1, frametime) : 2;
saveItemData();
}
public NBTTagCompound getMCNbt() {
NBTTagCompound compound = super.getMCNbt();
compound.setTag("ItemData", this.getItemNBT(new NBTTagCompound()));