diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java index 2f5ad1837..aa7838e1b 100644 --- a/src/main/java/noppes/npcs/CustomNpcs.java +++ b/src/main/java/noppes/npcs/CustomNpcs.java @@ -199,6 +199,8 @@ public class CustomNpcs { ChunkController.Instance.clear(); FactionController.getInstance().load(); // VisibilityController.instance = new VisibilityController(); + new MagicController(); + MagicController.getInstance().load(); new PlayerDataController(); new TagController(); new TransportController(); diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index 85e8063c3..5e91116ba 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -243,7 +243,8 @@ public class ClientProxy extends CommonProxy { return new GuiNpcPather(npc); else if (gui == EnumGuiType.ManageFactions) - return new GuiNPCManageFactions(npc); + return new GuiNPCManageMagic(npc); + // return new GuiNPCManageFactions(npc); else if (gui == EnumGuiType.ManageCustomForms) return DBCClient.Instance.manageCustomForms(npc); diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java b/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java new file mode 100644 index 000000000..9e9888adf --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/global/GuiMagicMap.java @@ -0,0 +1,413 @@ +package noppes.npcs.client.gui.global; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import noppes.npcs.client.gui.util.GuiNPCInterface2; +import noppes.npcs.controllers.MagicController; +import noppes.npcs.controllers.data.Magic; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.entity.RenderItem; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.init.Items; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; + +public class GuiMagicMap extends Gui { + + private final GuiScreen parent; + private final int x, y, width, height; + private final int iconSize = 16; + private final int slotPadding = 4; // extra pixels for the slot background + private final int slotSize = iconSize + slotPadding; // e.g., 20 + private float panX = 0, panY = 0; + private int lastDragX, lastDragY; + private boolean dragging = false; + + private float zoom = 1.0f; // Zoom factor + + // Build our own list from the controller's map. + private List magics; + private Map positions = new HashMap<>(); + + public GuiMagicMap(GuiScreen parent, int x, int y, int width, int height) { + this.parent = parent; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + magics = new ArrayList(); + for (Magic magic : MagicController.getInstance().magics.values()) { + magics.add(magic); + } + calculatePositions(); + } + + // Call this from your scroll wheel event: + public void handleMouseScroll(int scrollDelta) { + zoom += scrollDelta * 0.0009f; + if (zoom < 0.5f) zoom = 0.5f; + if (zoom > 2.0f) zoom = 2.0f; + } + + // Lay out icons evenly in a circle. + private void calculatePositions() { + int count = magics.size(); + int centerX = x + width / 2; + int centerY = y + height / 2; + int radius = Math.min(width, height) / 2 - iconSize - 10; + for (int i = 0; i < count; i++) { + double angle = 2 * Math.PI * i / count; + int posX = (int) (centerX + radius * Math.cos(angle)); + int posY = (int) (centerY + radius * Math.sin(angle)); + positions.put(magics.get(i).id, new Point(posX, posY)); + } + } + + // Helper: distance from point (px,py) to line segment (x1,y1)-(x2,y2) + private double pointLineDistance(double px, double py, double x1, double y1, double x2, double y2) { + double A = px - x1, B = py - y1, C = x2 - x1, D = y2 - y1; + double dot = A * C + B * D; + double lenSq = C * C + D * D; + double param = (lenSq != 0) ? (dot / lenSq) : -1; + double xx, yy; + if (param < 0) { xx = x1; yy = y1; } + else if (param > 1) { xx = x2; yy = y2; } + else { xx = x1 + param * C; yy = y1 + param * D; } + double dx = px - xx, dy = py - yy; + return Math.sqrt(dx * dx + dy * dy); + } + + // Returns an ARGB color for an arrow based on the weakness percentage. + private int getArrowColor(float percent) { + if (percent <= 0.10f) return 0xFF80FF80; // light green + else if (percent <= 0.40f) return 0xFFCCFF66; // green-yellow + else if (percent <= 0.50f) return 0xFFFFA500; // orange + else { + float ratio = (percent - 0.50f) / 0.50f; + ratio = Math.min(1.0f, Math.max(0.0f, ratio)); + int r1 = 0xFF, g1 = 0xA5, b1 = 0x00; + int r2 = 0xFF, g2 = 0x00, b2 = 0x00; + int r = r1 + (int)((r2 - r1) * ratio); + int g = g1 + (int)((g2 - g1) * ratio); + int b = b1 + (int)((b2 - b1) * ratio); + return (0xFF << 24) | (r << 16) | (g << 8) | b; + } + } + + // Main draw method. + public void drawMap(int mouseX, int mouseY) { + // Call this once per draw; adjust if needed. + handleMouseScroll(Mouse.getDWheel()); + + Minecraft mc = Minecraft.getMinecraft(); + ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); + int factor = sr.getScaleFactor(); + + // Recalculate positions. + calculatePositions(); + + // Compute transformed mouse coordinates for hit testing. + int centerX = x + width / 2, centerY = y + height / 2; + int tMouseX = centerX + (int)((mouseX - centerX) / zoom); + int tMouseY = centerY + (int)((mouseY - centerY) / zoom); + + // --- Determine selection state --- + Integer hoveredIconID = null; + int selectedArrowAttacker = -1, selectedArrowDefender = -1; + HashSet selectedIconIDs = new HashSet<>(); + // Check icons first. + for (Magic magic : magics) { + Point pos = positions.get(magic.id); + if (pos == null) continue; + int slotX = pos.x + (int)panX - slotSize / 2; + int slotY = pos.y + (int)panY - slotSize / 2; + if (tMouseX >= slotX && tMouseX < slotX + slotSize && + tMouseY >= slotY && tMouseY < slotY + slotSize) { + hoveredIconID = magic.id; + break; + } + } + if (hoveredIconID != null) { + selectedIconIDs.add(hoveredIconID); + Magic hoveredMagic = null; + for (Magic magic : magics) { + if (magic.id == hoveredIconID) { + hoveredMagic = magic; + break; + } + } + if (hoveredMagic != null) { + // Add connected icons. + for (Magic magic : magics) { + if (magic.weaknesses.containsKey(hoveredIconID)) { + selectedIconIDs.add(magic.id); + } + } + for (Integer otherId : hoveredMagic.weaknesses.keySet()) { + selectedIconIDs.add(otherId); + } + } + } else { + // Otherwise, check for arrow hover. + final double threshold = 5.0; + outer: for (Magic defMagic : magics) { + Point defPos = positions.get(defMagic.id); + if (defPos == null) continue; + for (Integer attackerId : defMagic.weaknesses.keySet()) { + Point attPos = positions.get(attackerId); + if (attPos == null) continue; + double x1 = attPos.x + panX, y1 = attPos.y + panY; + double x2 = defPos.x + panX, y2 = defPos.y + panY; + double dist = pointLineDistance(tMouseX, tMouseY, x1, y1, x2, y2); + if (dist < threshold) { + selectedArrowAttacker = attackerId; + selectedArrowDefender = defMagic.id; + selectedIconIDs.add(attackerId); + selectedIconIDs.add(defMagic.id); + break outer; + } + } + } + } + + // --- Set scissor rectangle (in original coords) --- + GL11.glEnable(GL11.GL_SCISSOR_TEST); + GL11.glScissor(x * factor, (sr.getScaledHeight() - (y + height)) * factor, width * factor, height * factor); + + // --- Draw dark background BEFORE zoom --- + parent.drawRect(x, y, x + width, y + height, 0xFF333333); + + // --- Begin zoom transformation --- + GL11.glPushMatrix(); + GL11.glTranslatef(centerX, centerY, 0); + GL11.glScalef(zoom, zoom, 1.0f); + GL11.glTranslatef(-centerX, -centerY, 0); + + // --- Draw arrow lines first --- + for (Magic defMagic : magics) { + Point defPos = positions.get(defMagic.id); + if (defPos == null) continue; + for (Integer attackerId : defMagic.weaknesses.keySet()) { + Point attPos = positions.get(attackerId); + if (attPos == null) continue; + float percent = defMagic.weaknesses.get(attackerId); + int ax = attPos.x + (int)panX; + int ay = attPos.y + (int)panY; + int dx = defPos.x + (int)panX; + int dy = defPos.y + (int)panY; + boolean dim = false; + if (!selectedIconIDs.isEmpty() && !(selectedIconIDs.contains(attackerId) && selectedIconIDs.contains(defMagic.id))) { + dim = true; + } + drawArrowLine(ax, ay, dx, dy, percent, dim); + } + } + + // --- Draw icons (with larger slot backgrounds) --- + RenderHelper.enableGUIStandardItemLighting(); + RenderItem renderItem = new RenderItem(); + FontRenderer fontRenderer = mc.fontRenderer; + TextureManager textureManager = mc.getTextureManager(); + for (Magic magic : magics) { + Point pos = positions.get(magic.id); + if (pos == null) continue; + int slotX = pos.x + (int)panX - slotSize / 2; + int slotY = pos.y + (int)panY - slotSize / 2; + boolean iconDim = (!selectedIconIDs.isEmpty() && !selectedIconIDs.contains(magic.id)); + if (iconDim) { + GL11.glColor4f(0.4f, 0.4f, 0.4f, 1f); + } + parent.drawRect(slotX, slotY, slotX + slotSize, slotY + slotSize, 0xFF999999); + drawHorizontalLine(slotX, slotX + slotSize, slotY, 0xFF555555); + drawHorizontalLine(slotX, slotX + slotSize, slotY + slotSize - 1, 0xFF555555); + drawVerticalLine(slotX, slotY, slotY + slotSize, 0xFF555555); + drawVerticalLine(slotX + slotSize - 1, slotY, slotY + slotSize, 0xFF555555); + int iconX = pos.x + (int)panX - iconSize / 2; + int iconY = pos.y + (int)panY - iconSize / 2; + if (magic.iconItem != null) { + renderItem.renderItemAndEffectIntoGUI(fontRenderer, textureManager, magic.iconItem, iconX, iconY); + } else if (magic.iconTexture != null && !magic.iconTexture.isEmpty()) { + textureManager.bindTexture(new ResourceLocation(magic.iconTexture)); + parent.drawTexturedModalRect(iconX, iconY, 0, 0, iconSize, iconSize); + } else { + ItemStack sword = new ItemStack(Items.iron_sword); + renderItem.renderItemAndEffectIntoGUI(fontRenderer, textureManager, sword, iconX, iconY); + } + if (iconDim) { + GL11.glColor4f(1f, 1f, 1f, 1f); + } + } + RenderHelper.disableStandardItemLighting(); + GL11.glPopMatrix(); + + // --- Draw arrow heads (on top) using the same zoom transformation --- + GL11.glPushMatrix(); + GL11.glTranslatef(centerX, centerY, 0); + GL11.glScalef(zoom, zoom, 1.0f); + GL11.glTranslatef(-centerX, -centerY, 0); + for (Magic defMagic : magics) { + Point defPos = positions.get(defMagic.id); + if (defPos == null) continue; + for (Integer attackerId : defMagic.weaknesses.keySet()) { + Point attPos = positions.get(attackerId); + if (attPos == null) continue; + float percent = defMagic.weaknesses.get(attackerId); + int ax = attPos.x + (int)panX; + int ay = attPos.y + (int)panY; + int dx = defPos.x + (int)panX; + int dy = defPos.y + (int)panY; + boolean dim = false; + if (!selectedIconIDs.isEmpty() && !(selectedIconIDs.contains(attackerId) && selectedIconIDs.contains(defMagic.id))) { + dim = true; + } + drawArrowHead(ax, ay, dx, dy, percent, dim); + } + } + GL11.glPopMatrix(); + + GL11.glDisable(GL11.GL_SCISSOR_TEST); + + // --- Draw tooltips outside of the zoom transformation --- + if (hoveredIconID != null) { + for (Magic magic : magics) { + if (magic.id == hoveredIconID) { + List tooltip = new ArrayList<>(); + tooltip.add(magic.getName()); + if (parent instanceof GuiNPCInterface2) { + ((GuiNPCInterface2) parent).renderHoveringText(tooltip, mouseX, mouseY, fontRenderer); + } + return; + } + } + } else { + final double threshold = 5.0; + for (Magic defMagic : magics) { + Point defPos = positions.get(defMagic.id); + if (defPos == null) continue; + for (Integer attackerId : defMagic.weaknesses.keySet()) { + Point attPos = positions.get(attackerId); + if (attPos == null) continue; + double x1 = attPos.x + panX, y1 = attPos.y + panY; + double x2 = defPos.x + panX, y2 = defPos.y + panY; + double dist = pointLineDistance(tMouseX, tMouseY, x1, y1, x2, y2); + if (dist < threshold) { + float percent = defMagic.weaknesses.get(attackerId); + List tooltip = new ArrayList<>(); + tooltip.add("+" + (int)(percent * 100) + "%"); + if (parent instanceof GuiNPCInterface2) { + ((GuiNPCInterface2) parent).renderHoveringText(tooltip, mouseX, mouseY, fontRenderer); + } + return; + } + } + } + } + } + + // Draw only the arrow line (without arrow head). + private void drawArrowLine(int x1, int y1, int x2, int y2, float percent, boolean dim) { + int arrowColor = getArrowColor(percent); + GL11.glPushAttrib(GL11.GL_ENABLE_BIT); + GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glLineWidth(2.0F); + float r = ((arrowColor >> 16) & 0xFF) / 255.0F; + float g = ((arrowColor >> 8) & 0xFF) / 255.0F; + float b = (arrowColor & 0xFF) / 255.0F; + if (dim) { r *= 0.4f; g *= 0.4f; b *= 0.4f; } + GL11.glColor4f(r, g, b, 1f); + GL11.glBegin(GL11.GL_LINES); + GL11.glVertex2f(x1, y1); + GL11.glVertex2f(x2, y2); + GL11.glEnd(); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glPopAttrib(); + } + + // Draw the arrow head so that its tip connects at the defender icon edge. + private void drawArrowHead(int x1, int y1, int x2, int y2, float percent, boolean dim) { + // x1,y1 and x2,y2 are the centers (after pan) of the attacker and defender icons. + float dx = x2 - x1; + float dy = y2 - y1; + float len = (float)Math.sqrt(dx * dx + dy * dy); + if(len == 0) return; + // Compute unit vector from attacker to defender. + float ux = dx / len, uy = dy / len; + // Offset defender center by half the slot size to get the edge. + float defenderEdgeX = x2 - ux * (slotSize / 2f); + float defenderEdgeY = y2 - uy * (slotSize / 2f); + + // For testing, try a larger arrow size. + int arrowSize = 5; // adjust as needed + + // Compute the angle from attacker to defender. + double angle = Math.atan2(uy, ux); + // Compute left and right points of the arrow head. + float leftX = (float)(defenderEdgeX - arrowSize * Math.cos(angle - Math.PI / 6)); + float leftY = (float)(defenderEdgeY - arrowSize * Math.sin(angle - Math.PI / 6)); + float rightX = (float)(defenderEdgeX - arrowSize * Math.cos(angle + Math.PI / 6)); + float rightY = (float)(defenderEdgeY - arrowSize * Math.sin(angle + Math.PI / 6)); + + int arrowColor = getArrowColor(percent); + GL11.glPushAttrib(GL11.GL_ENABLE_BIT); + GL11.glDisable(GL11.GL_TEXTURE_2D); + float r = ((arrowColor >> 16) & 0xFF) / 255.0F; + float g = ((arrowColor >> 8) & 0xFF) / 255.0F; + float b = (arrowColor & 0xFF) / 255.0F; + if(dim) { r *= 0.4f; g *= 0.4f; b *= 0.4f; } + GL11.glColor4f(r, g, b, 1f); + + GL11.glDisable(GL11.GL_CULL_FACE); + GL11.glBegin(GL11.GL_TRIANGLES); + GL11.glVertex2f(defenderEdgeX, defenderEdgeY); + GL11.glVertex2f(leftX, leftY); + GL11.glVertex2f(rightX, rightY); + GL11.glEnd(); + GL11.glEnable(GL11.GL_CULL_FACE); + + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glPopAttrib(); + } + + // --- Mouse handling methods --- + public boolean mouseClicked(int mouseX, int mouseY, int mouseButton) { + if (mouseButton == 0 && isWithin(mouseX, mouseY)) { + dragging = true; + lastDragX = mouseX; + lastDragY = mouseY; + return true; + } + return false; + } + + public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + if (dragging) { + int dx = mouseX - lastDragX; + int dy = mouseY - lastDragY; + // Adjust panning speed by zoom so dragging "feels" the same. + panX += dx / zoom * 0.7f; + panY += dy / zoom * 0.7f; + lastDragX = mouseX; + lastDragY = mouseY; + } + } + + public void mouseReleased(int mouseX, int mouseY, int state) { + dragging = false; + } + + private boolean isWithin(int mouseX, int mouseY) { + return mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/global/GuiNPCManageMagic.java b/src/main/java/noppes/npcs/client/gui/global/GuiNPCManageMagic.java new file mode 100644 index 000000000..bfb7df78a --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/global/GuiNPCManageMagic.java @@ -0,0 +1,71 @@ +package noppes.npcs.client.gui.global; + +import noppes.npcs.client.gui.util.GuiNPCInterface2; +import noppes.npcs.entity.EntityNPCInterface; + +public class GuiNPCManageMagic extends GuiNPCInterface2 { + + private GuiMagicMap magicMap; + + public GuiNPCManageMagic(EntityNPCInterface npc) { + super(npc); + } + + @Override + public void initGui() { + super.initGui(); + // Position the map in a designated area (e.g., right panel) + int mapX = guiLeft + xSize - 230 + 10; + int mapY = guiTop + 10; + int mapWidth = 210; + int mapHeight = 200; + magicMap = new GuiMagicMap(this, mapX, mapY, mapWidth, mapHeight); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + super.drawScreen(mouseX, mouseY, partialTicks); + if (magicMap != null) { + magicMap.drawMap(mouseX, mouseY); + } + } + + @Override + public void mouseClicked(int mouseX, int mouseY, int mouseButton) { + if (magicMap != null && magicMap.mouseClicked(mouseX, mouseY, mouseButton)) { + return; + } + super.mouseClicked(mouseX, mouseY, mouseButton); + } + + @Override + protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + if (magicMap != null) { + magicMap.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + } + super.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + } + + @Override + protected void mouseMovedOrUp(int mouseX, int mouseY, int state) { + if (magicMap != null) { + magicMap.mouseReleased(mouseX, mouseY, state); + } + super.mouseMovedOrUp(mouseX, mouseY, state); + } + + @Override + protected void actionPerformed(net.minecraft.client.gui.GuiButton button) { + // your existing action handling + } + + @Override + public void keyTyped(char c, int i) { + super.keyTyped(c, i); + } + + @Override + public void save() { + // your save logic + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface2.java b/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface2.java index da766c681..9e035f830 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface2.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiNPCInterface2.java @@ -1,9 +1,12 @@ package noppes.npcs.client.gui.util; +import net.minecraft.client.gui.FontRenderer; import net.minecraft.util.ResourceLocation; import noppes.npcs.entity.EntityNPCInterface; import org.lwjgl.opengl.GL11; +import java.util.List; + public abstract class GuiNPCInterface2 extends GuiNPCInterface { private ResourceLocation background = new ResourceLocation("customnpcs:textures/gui/menubg.png"); @@ -62,6 +65,10 @@ public abstract class GuiNPCInterface2 extends GuiNPCInterface drawTexturedModalRect(guiLeft + xSize-230, guiTop, 26, 0, 230, 220); } + public void renderHoveringText(List textLines, int x, int y, FontRenderer font) { + this.drawHoveringText(textLines, x, y, font); + } + protected GuiNpcMenu getMenu(){ return this.menu; } diff --git a/src/main/java/noppes/npcs/controllers/MagicController.java b/src/main/java/noppes/npcs/controllers/MagicController.java index a14f01384..b0343bae4 100644 --- a/src/main/java/noppes/npcs/controllers/MagicController.java +++ b/src/main/java/noppes/npcs/controllers/MagicController.java @@ -1,5 +1,6 @@ package noppes.npcs.controllers; +import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompressedStreamTools; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; @@ -9,193 +10,409 @@ import noppes.npcs.api.handler.data.IMagic; import noppes.npcs.controllers.data.Magic; import java.io.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.zip.GZIPInputStream; public class MagicController { - public HashMap magicSync = new HashMap(); - public HashMap magics; + public HashMap magicSync = new HashMap(); + public HashMap magics; - private static MagicController instance = new MagicController(); + private static MagicController instance; - private int lastUsedID = 0; + private int lastUsedID = 0; - public MagicController(){ - instance = this; - magics = new HashMap(); - } + public MagicController() { + instance = this; + magics = new HashMap(); + } - public static MagicController getInstance(){ - return instance; - } + public static MagicController getInstance() { + return instance; + } - public Magic getMagic(int magic) { - return magics.get(magic); - } + public Magic getMagic(int magic) { + return magics.get(magic); + } - public void load(){ + public void load() { magics = new HashMap(); lastUsedID = 0; - File saveDir = CustomNpcs.getWorldSaveDirectory(); - if(saveDir == null){ - return; - } - try { - File file = new File(saveDir, "magic.dat"); - if(file.exists()){ - loadMagicFile(file); - } - } catch (Exception e) { - try { - File file = new File(saveDir, "magic.dat_old"); - if(file.exists()){ - loadMagicFile(file); - } - - } catch (Exception ee) { - } - } - - if(magics.isEmpty()){ - magics.put(0,new Magic(0,"Nature", 0x00DD00)); - magics.put(1,new Magic(1,"Arcane", 0xF2DD00)); - magics.put(2,new Magic(2,"Ice", 0xDD0000)); - magics.put(3,new Magic(3,"Fire", 0xDD0000)); - magics.put(4,new Magic(4,"Dark", 0xDD0000)); - magics.put(5,new Magic(5,"Holy", 0xDD0000)); + File saveDir = CustomNpcs.getWorldSaveDirectory(); + if (saveDir == null) { + return; + } + try { + File file = new File(saveDir, "magic.dat"); + if (file.exists()) { + loadMagicFile(file); + } + } catch (Exception e) { + try { + File file = new File(saveDir, "magic.dat_old"); + if (file.exists()) { + loadMagicFile(file); + } + } catch (Exception ee) { } } - } - private void loadMagicFile(File file) throws IOException{ - DataInputStream var1 = new DataInputStream(new BufferedInputStream(new GZIPInputStream(new FileInputStream(file)))); - loadMagic(var1); - var1.close(); - } + if (magics.isEmpty()) { + // Create default magics + Magic nature = new Magic(0, "Nature", 0x00DD00); + Magic arcane = new Magic(1, "Arcane", 0xF2DD00); + Magic ice = new Magic(2, "Ice", 0xDD0000); + Magic fire = new Magic(3, "Fire", 0xDD0000); + Magic dark = new Magic(4, "Dark", 0xDD0000); + Magic holy = new Magic(5, "Holy", 0xDD0000); - public void loadMagic(DataInputStream stream) throws IOException{ - HashMap magic = new HashMap(); - NBTTagCompound nbttagcompound1 = CompressedStreamTools.read(stream); - lastUsedID = nbttagcompound1.getInteger("lastID"); - NBTTagList list = nbttagcompound1.getTagList("NPCMagic", 10); + // Define weaknesses (extra damage percentages as fractions) + // Cycle: Nature -> Arcane -> Fire -> Ice -> Nature + nature.weaknesses.put(1, 0.20f); // Nature is weak to Arcane (20% extra damage) + arcane.weaknesses.put(3, 0.25f); // Arcane is weak to Fire (25%) + fire.weaknesses.put(2, 0.30f); // Fire is weak to Ice (30%) + ice.weaknesses.put(0, 0.20f); // Ice is weak to Nature (20%) - if(list != null){ - for(int i = 0; i < list.tagCount(); i++) - { - NBTTagCompound nbttagcompound = list.getCompoundTagAt(i); - Magic loadMagic = new Magic(); - loadMagic.readNBT(nbttagcompound); - magic.put(loadMagic.id,loadMagic); - } - } - this.magics = magic; - } - public NBTTagCompound getNBT(){ - NBTTagList list = new NBTTagList(); - for(int slot : magics.keySet()){ - Magic mag = magics.get(slot); - NBTTagCompound nbtfactions = new NBTTagCompound(); - mag.writeNBT(nbtfactions); - list.appendTag(nbtfactions); - } - NBTTagCompound nbttagcompound = new NBTTagCompound(); - nbttagcompound.setInteger("lastID", lastUsedID); - nbttagcompound.setTag("NPCFactions", list); - return nbttagcompound; - } - public void saveFactions(){ - try { - File saveDir = CustomNpcs.getWorldSaveDirectory(); - File file = new File(saveDir, "magic.dat_new"); - File file1 = new File(saveDir, "magic.dat_old"); - File file2 = new File(saveDir, "magic.dat"); - CompressedStreamTools.writeCompressed(getNBT(), new FileOutputStream(file)); - if(file1.exists()) - { - file1.delete(); - } - file2.renameTo(file1); - if(file2.exists()) - { - file2.delete(); - } - file.renameTo(file2); - if(file.exists()) - { - file.delete(); - } - } catch (Exception e) { - LogWriter.except(e); - } - } + // Dark and Holy are opposites + dark.weaknesses.put(5, 0.40f); // Dark is weak to Holy (40%) + holy.weaknesses.put(4, 0.40f); // Holy is weak to Dark (40%) - public Magic get(int magicID) { - return magics.get(magicID); - } + // Add them to the registry + magics.put(nature.id, nature); + magics.put(arcane.id, arcane); + magics.put(ice.id, ice); + magics.put(fire.id, fire); + magics.put(dark.id, dark); + magics.put(holy.id, holy); + } + printAllMagicInteractions(); + } - public List list() { - return new ArrayList(this.magics.values()); - } + private void loadMagicFile(File file) throws IOException { + DataInputStream stream = new DataInputStream( + new BufferedInputStream(new GZIPInputStream(new FileInputStream(file))) + ); + loadMagic(stream); + stream.close(); + } - public void saveMagic(Magic mag) { + public void loadMagic(DataInputStream stream) throws IOException { + HashMap magic = new HashMap(); + NBTTagCompound nbttagcompound1 = CompressedStreamTools.read(stream); + lastUsedID = nbttagcompound1.getInteger("lastID"); + NBTTagList list = nbttagcompound1.getTagList("NPCMagic", 10); // using "NPCMagic" as tag - if(mag.id < 0){ - mag.id = getUnusedId(); - while(hasName(mag.name)) - mag.name += "_"; - } - else{ - Magic existing = magics.get(mag.id); - if(existing != null && !existing.name.equals(mag.name)) - while(hasName(mag.name)) - mag.name += "_"; - } - magics.remove(mag.id); - magics.put(mag.id, mag); + if (list != null) { + for (int i = 0; i < list.tagCount(); i++) { + NBTTagCompound nbttagcompound = list.getCompoundTagAt(i); + Magic loadMagic = new Magic(); + loadMagic.readNBT(nbttagcompound); + magic.put(loadMagic.id, loadMagic); + } + } + this.magics = magic; + } + + public NBTTagCompound getNBT() { + NBTTagList list = new NBTTagList(); + for (int slot : magics.keySet()) { + Magic mag = magics.get(slot); + NBTTagCompound nbtMagic = new NBTTagCompound(); + mag.writeNBT(nbtMagic); + list.appendTag(nbtMagic); + } + NBTTagCompound nbttagcompound = new NBTTagCompound(); + nbttagcompound.setInteger("lastID", lastUsedID); + nbttagcompound.setTag("NPCMagic", list); // use same tag as loadMagic + return nbttagcompound; + } + + public void saveFactions() { + try { + File saveDir = CustomNpcs.getWorldSaveDirectory(); + File file = new File(saveDir, "magic.dat_new"); + File file1 = new File(saveDir, "magic.dat_old"); + File file2 = new File(saveDir, "magic.dat"); + CompressedStreamTools.writeCompressed(getNBT(), new FileOutputStream(file)); + if (file1.exists()) { + file1.delete(); + } + file2.renameTo(file1); + if (file2.exists()) { + file2.delete(); + } + file.renameTo(file2); + if (file.exists()) { + file.delete(); + } + } catch (Exception e) { + LogWriter.except(e); + } + } + + public Magic get(int magicID) { + return magics.get(magicID); + } + + public List list() { + return new ArrayList(this.magics.values()); + } + + public void saveMagic(Magic mag) { + if (mag.id < 0) { + mag.id = getUnusedId(); + while (hasName(mag.name)) + mag.name += "_"; + } else { + Magic existing = magics.get(mag.id); + if (existing != null && !existing.name.equals(mag.name)) + while (hasName(mag.name)) + mag.name += "_"; + } + magics.remove(mag.id); + magics.put(mag.id, mag); NBTTagCompound facCompound = new NBTTagCompound(); mag.writeNBT(facCompound); // Server.sendToAll(EnumPacketClient.SYNC_UPDATE, SyncType.MAGIC, facCompound); - saveFactions(); - } + saveFactions(); + } - public int getUnusedId(){ - if(lastUsedID == 0){ - for(int catid : magics.keySet()) - if(catid > lastUsedID) - lastUsedID = catid; - } - lastUsedID++; - return lastUsedID; - } + public int getUnusedId() { + if (lastUsedID == 0) { + for (int catid : magics.keySet()) + if (catid > lastUsedID) + lastUsedID = catid; + } + lastUsedID++; + return lastUsedID; + } - public boolean hasName(String newName) { - if(newName.trim().isEmpty()) - return true; - for(Magic mag : magics.values()) - if(mag.name.equals(newName)) - return true; - return false; - } + public boolean hasName(String newName) { + if (newName.trim().isEmpty()) + return true; + for (Magic mag : magics.values()) + if (mag.name.equals(newName)) + return true; + return false; + } - public Magic getMagicFromName(String magicName){ - for (Map.Entry entryMag: MagicController.getInstance().magics.entrySet()){ - if (entryMag.getValue().name.equalsIgnoreCase(magicName)){ - return entryMag.getValue(); - } - } - return null; - } + public Magic getMagicFromName(String magicName) { + for (Map.Entry entryMag : MagicController.getInstance().magics.entrySet()) { + if (entryMag.getValue().name.equalsIgnoreCase(magicName)) { + return entryMag.getValue(); + } + } + return null; + } - public String[] getNames() { - String[] names = new String[magics.size()]; - int i = 0; - for(Magic mag : magics.values()){ - names[i] = mag.name.toLowerCase(); - i++; - } - return names; - } + public String[] getNames() { + String[] names = new String[magics.size()]; + int i = 0; + for (Magic mag : magics.values()) { + names[i] = mag.name.toLowerCase(); + i++; + } + return names; + } + + // --- New functions for applying magic NBT stats and calculating damage --- + + /** + * Applies magic-type damage to an item. A weapon/item can have multiple magic damage values. + * The damage is stored under a compound tag "MagicDamage" where each key is the magic ID. + */ + public static void addMagicDamageToItem(ItemStack item, int magicId, float damage) { + if (item == null) return; + if (item.stackTagCompound == null) + item.stackTagCompound = new NBTTagCompound(); + NBTTagCompound compound = item.stackTagCompound; + NBTTagCompound magicDamage; + if (compound.hasKey("MagicDamage")) { + magicDamage = compound.getCompoundTag("MagicDamage"); + } else { + magicDamage = new NBTTagCompound(); + } + magicDamage.setFloat(String.valueOf(magicId), damage); + compound.setTag("MagicDamage", magicDamage); + } + + /** + * Applies magic resistance to an armor piece. + * The resistance is stored under a compound tag "MagicResistances" with the magic ID as key. + */ + public static void addMagicResistanceToArmor(ItemStack armor, int magicId, float resistance) { + if (armor == null) return; + if (armor.stackTagCompound == null) + armor.stackTagCompound = new NBTTagCompound(); + NBTTagCompound compound = armor.stackTagCompound; + NBTTagCompound magicResist; + if (compound.hasKey("MagicResistances")) { + magicResist = compound.getCompoundTag("MagicResistances"); + } else { + magicResist = new NBTTagCompound(); + } + magicResist.setFloat(String.valueOf(magicId), resistance); + compound.setTag("MagicResistances", magicResist); + } + + /** + * Given a collection of armor pieces, sums the resistance value for the specified magic ID. + */ + public static float calculateTotalMagicResistance(int magicId, Iterable armors) { + float totalResistance = 0.0F; + for (ItemStack armor : armors) { + if (armor != null && armor.hasTagCompound() && armor.stackTagCompound.hasKey("MagicResistances")) { + NBTTagCompound magicResist = armor.stackTagCompound.getCompoundTag("MagicResistances"); + if (magicResist.hasKey(String.valueOf(magicId))) { + totalResistance += magicResist.getFloat(String.valueOf(magicId)); + } + } + } + return totalResistance; + } + + /** + * Calculates the final magic damage after applying armor resistances. + * For a given magic type, the resistance is applied. + * + * @param magicId the magic type + * @param baseDamage the base damage value + * @param armors an Iterable collection of armor ItemStacks + * @return the final damage after reduction + */ + public static float calculateFinalMagicDamage(int magicId, float baseDamage, Iterable armors) { + if (getInstance().getMagic(magicId) == null) { + // If the magic type is not defined, treat it as non-magical. + return baseDamage; + } + float resistance = calculateTotalMagicResistance(magicId, armors); + // Resistance is assumed to be a fractional reduction (e.g., 0.2 = 20% reduction). + return baseDamage * (1.0F - resistance); + } + + /** + * Extracts magic damage values from a weapon ItemStack. + * It looks for a compound tag "CNPCMagic" first; if not found, it falls back to "MagicDamage". + * Returns a mapping of magic ID to its damage value. + */ + public static Map getMagicDamageFromItem(ItemStack item) { + Map damageMap = new HashMap(); + if (item == null || item.stackTagCompound == null) return damageMap; + NBTTagCompound compound = item.stackTagCompound; + NBTTagCompound damageCompound = null; + if (compound.hasKey("CNPCMagic")) { + NBTTagCompound magicCompound = compound.getCompoundTag("CNPCMagic"); + if (magicCompound.hasKey("MagicDamage")) { + damageCompound = magicCompound.getCompoundTag("MagicDamage"); + } + } else if (compound.hasKey("MagicDamage")) { + damageCompound = compound.getCompoundTag("MagicDamage"); + } + if (damageCompound != null) { + Set keys = damageCompound.func_150296_c(); + for (String key : keys) { + try { + int magicId = Integer.parseInt(key); + float dmg = damageCompound.getFloat(key); + damageMap.put(magicId, dmg); + } catch (NumberFormatException e) { + // Ignore non-integer keys. + } + } + } + return damageMap; + } + + /** + * Extracts magic resistances from an armor ItemStack. + * It checks for a "CNPCMagic" container first; if not present, it falls back to "MagicResistances". + * Returns a mapping of magic ID to its resistance value. + */ + public static Map getMagicResistancesFromItem(ItemStack item) { + Map resistMap = new HashMap(); + if (item == null || item.stackTagCompound == null) return resistMap; + NBTTagCompound compound = item.stackTagCompound; + NBTTagCompound resistCompound = null; + if (compound.hasKey("CNPCMagic")) { + NBTTagCompound magicCompound = compound.getCompoundTag("CNPCMagic"); + if (magicCompound.hasKey("MagicResistances")) { + resistCompound = magicCompound.getCompoundTag("MagicResistances"); + } + } else if (compound.hasKey("MagicResistances")) { + resistCompound = compound.getCompoundTag("MagicResistances"); + } + if (resistCompound != null) { + Set keys = resistCompound.func_150296_c(); + for (String key : keys) { + try { + int magicId = Integer.parseInt(key); + float res = resistCompound.getFloat(key); + resistMap.put(magicId, res); + } catch (NumberFormatException e) { + // Ignore non-integer keys. + } + } + } + return resistMap; + } + + /** + * Validates whether an item (weapon or armor) contains any magic-related NBT. + */ + public static boolean hasMagicStats(ItemStack item) { + if (item == null || item.stackTagCompound == null) return false; + NBTTagCompound compound = item.stackTagCompound; + return compound.hasKey("CNPCMagic") || compound.hasKey("MagicDamage") || compound.hasKey("MagicResistances"); + } + + /** + * Calculates a damage breakdown for each magic type from a weapon when applied against a set of armors. + * This function will ignore any magic with ID 0 (neutral), so only magic damage is returned. + * Returns a map where keys are magic IDs (non-zero) and values are the final damage for that type. + */ + public static Map calculateDamageBreakdown(ItemStack weapon, Iterable armors) { + Map breakdown = new HashMap(); + Map weaponDamage = getMagicDamageFromItem(weapon); + for (Map.Entry entry : weaponDamage.entrySet()) { + int magicId = entry.getKey(); + // Skip magic ID 0 (neutral) + if (magicId == 0) continue; + float dmg = entry.getValue(); + float finalDmg = calculateFinalMagicDamage(magicId, dmg, armors); + breakdown.put(magicId, finalDmg); + } + return breakdown; + } + + /** + * Calculates the total final magic damage of a weapon after applying the resistances of a set of armor pieces. + * This sums only the magic interactions (magic IDs other than 0). + */ + public static float calculateTotalDamage(ItemStack weapon, Iterable armors) { + float totalDamage = 0.0F; + Map breakdown = calculateDamageBreakdown(weapon, armors); + for (Float dmg : breakdown.values()) { + totalDamage += dmg; + } + return totalDamage; + } + + /** + * Prints all magic interactions (weaknesses) in the registry. + * For each magic (defender) that has weaknesses, prints the corresponding attacker and the extra damage percentage. + * Format: "Magic ID: [attacker id], Name: "[attacker name]" does [percentage*100]% extra damage to Magic ID: [defender id], Name: "[defender name]". + */ + public static void printAllMagicInteractions() { + MagicController mc = getInstance(); + for (Magic defender : mc.magics.values()) { + for (Map.Entry entry : defender.weaknesses.entrySet()) { + int attackerId = entry.getKey(); + float percentage = entry.getValue(); + Magic attacker = mc.getMagic(attackerId); + if (attacker != null) { + System.out.println("Magic ID: " + attacker.id + ", Name: \"" + attacker.name + "\" does " + + (percentage * 100) + "% extra damage to Magic ID: " + defender.id + ", Name: \"" + defender.name + "\""); + } + } + } + } } diff --git a/src/main/java/noppes/npcs/controllers/data/Magic.java b/src/main/java/noppes/npcs/controllers/data/Magic.java index 96811f3ab..eaea2608c 100644 --- a/src/main/java/noppes/npcs/controllers/data/Magic.java +++ b/src/main/java/noppes/npcs/controllers/data/Magic.java @@ -1,54 +1,134 @@ package noppes.npcs.controllers.data; +import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; import noppes.npcs.api.handler.data.IMagic; import noppes.npcs.controllers.MagicController; +import java.util.HashMap; +import java.util.Map; + public class Magic implements IMagic { - public String name = ""; - public int color = Integer.parseInt("FF00", 16); + public String name = ""; + public int color = Integer.parseInt("FF00", 16); + public int id = -1; - public int id = -1; + // Icon as an ItemStack or as a texture file + public ItemStack iconItem = null; + public String iconTexture = ""; - public Magic(){} + // Weaknesses mapping: key = magic ID this magic is weak against, + // value = extra damage percentage (as a fraction, e.g. 0.2 for 20% extra damage) + public Map weaknesses = new HashMap(); - public Magic(int id, String name, int color){ - this.name = name; - this.color = color; - this.id = id; - } - public static String formatName(String name){ - name = name.toLowerCase().trim(); - return name.substring(0, 1).toUpperCase() + name.substring(1); - } - public void readNBT(NBTTagCompound compound){ + public Magic() {} + + public Magic(int id, String name, int color) { + this.name = name; + this.color = color; + this.id = id; + } + + public static String formatName(String name) { + name = name.toLowerCase().trim(); + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + + public void readNBT(NBTTagCompound compound) { name = compound.getString("Name"); color = compound.getInteger("Color"); id = compound.getInteger("Slot"); - } - public void writeNBT(NBTTagCompound compound){ - compound.setInteger("Slot", id); - compound.setString("Name", name); - compound.setInteger("Color", color); - } - public int getId() { - return this.id; - } + if(compound.hasKey("IconItem")){ + NBTTagCompound itemTag = compound.getCompoundTag("IconItem"); + iconItem = ItemStack.loadItemStackFromNBT(itemTag); + } + iconTexture = compound.getString("IconTexture"); - public String getName() { - return this.name; - } + // Read the weaknesses mapping from NBT. + weaknesses.clear(); + if(compound.hasKey("Weaknesses")) { + NBTTagList weaknessList = compound.getTagList("Weaknesses", 10); // compound tags + for(int i = 0; i < weaknessList.tagCount(); i++){ + NBTTagCompound weaknessTag = weaknessList.getCompoundTagAt(i); + int weakMagicId = weaknessTag.getInteger("MagicID"); + float percentage = weaknessTag.getFloat("Percentage"); + weaknesses.put(weakMagicId, percentage); + } + } + } - public void setName(String name) { this.name = name; } + public void writeNBT(NBTTagCompound compound) { + compound.setInteger("Slot", id); + compound.setString("Name", name); + compound.setInteger("Color", color); - public void setColor(int color) { this.color = color; } + if(iconItem != null) { + NBTTagCompound itemTag = new NBTTagCompound(); + iconItem.writeToNBT(itemTag); + compound.setTag("IconItem", itemTag); + } + if(iconTexture != null && !iconTexture.isEmpty()){ + compound.setString("IconTexture", iconTexture); + } - public int getColor() { - return this.color; - } + // Write the weaknesses mapping. + NBTTagList weaknessList = new NBTTagList(); + for(Map.Entry entry : weaknesses.entrySet()){ + NBTTagCompound weaknessTag = new NBTTagCompound(); + weaknessTag.setInteger("MagicID", entry.getKey()); + weaknessTag.setFloat("Percentage", entry.getValue()); + weaknessList.appendTag(weaknessTag); + } + compound.setTag("Weaknesses", weaknessList); + } - public void save() { - MagicController.getInstance().saveMagic(this); - } + public int getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public void setColor(int color) { + this.color = color; + } + + public int getColor() { + return this.color; + } + + public void save() { + MagicController.getInstance().saveMagic(this); + } + + public void setIconItem(ItemStack item) { + this.iconItem = item; + } + + public ItemStack getIconItem() { + return this.iconItem; + } + + public void setIconTexture(String texture) { + this.iconTexture = texture; + } + + public String getIconTexture() { + return this.iconTexture; + } + + public void setWeaknesses(Map weaknesses) { + this.weaknesses = weaknesses; + } + + public Map getWeaknesses() { + return this.weaknesses; + } }