/* * Copyright (c) 2016-2017, Adam * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package net.runelite.mixins; import java.awt.Polygon; import java.awt.Shape; import java.util.ArrayList; import net.runelite.api.HeadIcon; import net.runelite.api.Model; import net.runelite.api.Perspective; import net.runelite.api.SkullIcon; import static net.runelite.api.SkullIcon.DEAD_MAN_FIVE; import static net.runelite.api.SkullIcon.DEAD_MAN_FOUR; import static net.runelite.api.SkullIcon.DEAD_MAN_ONE; import static net.runelite.api.SkullIcon.DEAD_MAN_THREE; import static net.runelite.api.SkullIcon.DEAD_MAN_TWO; import static net.runelite.api.SkullIcon.SKULL; import static net.runelite.api.SkullIcon.SKULL_FIGHT_PIT; import net.runelite.api.coords.LocalPoint; import net.runelite.api.events.OverheadPrayerChanged; import net.runelite.api.events.PlayerChanged; import net.runelite.api.events.PlayerCompositionChanged; import net.runelite.api.events.PlayerSkullChanged; import net.runelite.api.mixins.Copy; import net.runelite.api.mixins.FieldHook; import net.runelite.api.mixins.Inject; import net.runelite.api.mixins.MethodHook; import net.runelite.api.mixins.Mixin; import net.runelite.api.mixins.Replace; import net.runelite.api.mixins.Shadow; import net.runelite.rs.api.RSBuffer; import net.runelite.rs.api.RSClient; import net.runelite.rs.api.RSModel; import net.runelite.rs.api.RSPlayer; import net.runelite.rs.api.RSUsername; @Mixin(RSPlayer.class) public abstract class RSPlayerMixin implements RSPlayer { @Shadow("client") private static RSClient client; @Inject private boolean friended; @Inject private int oldHeadIcon = -2; @Inject private int oldSkullIcon = -2; @Inject @Override public String getName() { final RSUsername rsName = getRsName(); if (rsName == null) { return null; } String name = rsName.getName(); if (name == null) { return null; } return name.replace('\u00A0', ' '); } @Inject @FieldHook("headIconPrayer") public void prayerChanged(int idx) { if (getRsOverheadIcon() != oldHeadIcon) { final HeadIcon headIcon = getHeadIcon(getRsOverheadIcon()); client.getCallbacks().post( new OverheadPrayerChanged(this, getHeadIcon(oldHeadIcon), headIcon)); } oldHeadIcon = getRsOverheadIcon(); } @Inject @Override public HeadIcon getOverheadIcon() { return getHeadIcon(getRsOverheadIcon()); } @Inject @FieldHook("headIconPk") public void skullChanged(int idx) { final SkullIcon skullIcon = skullFromInt(getRsSkullIcon()); if (getRsSkullIcon() != oldSkullIcon) { client.getCallbacks().post( new PlayerSkullChanged(this, skullFromInt(getRsSkullIcon()), skullIcon)); } oldSkullIcon = getRsSkullIcon(); } @Inject @Override public SkullIcon getSkullIcon() { return skullFromInt(getRsSkullIcon()); } @Inject private HeadIcon getHeadIcon(int overheadIcon) { if (overheadIcon == -1) { return null; } return HeadIcon.values()[overheadIcon]; } @Inject private SkullIcon skullFromInt(int skull) { switch (skull) { case 0: return SKULL; case 1: return SKULL_FIGHT_PIT; case 8: return DEAD_MAN_FIVE; case 9: return DEAD_MAN_FOUR; case 10: return DEAD_MAN_THREE; case 11: return DEAD_MAN_TWO; case 12: return DEAD_MAN_ONE; default: return null; } } @Inject @Override public Polygon[] getPolygons() { Model model = getModel(); if (model == null) { return null; } int[] x2d = new int[model.getVerticesCount()]; int[] y2d = new int[model.getVerticesCount()]; int localX = getX(); int localY = getY(); final int tileHeight = Perspective.getTileHeight(client, new LocalPoint(localX, localY), client.getPlane()); Perspective.modelToCanvas(client, model.getVerticesCount(), localX, localY, tileHeight, getOrientation(), model.getVerticesX(), model.getVerticesZ(), model.getVerticesY(), x2d, y2d); ArrayList polys = new ArrayList(model.getFaceCount()); int[] trianglesX = model.getFaceIndices1(); int[] trianglesY = model.getFaceIndices2(); int[] trianglesZ = model.getFaceIndices3(); for (int triangle = 0; triangle < model.getFaceCount(); ++triangle) { int[] xx = { x2d[trianglesX[triangle]], x2d[trianglesY[triangle]], x2d[trianglesZ[triangle]] }; int[] yy = { y2d[trianglesX[triangle]], y2d[trianglesY[triangle]], y2d[trianglesZ[triangle]] }; polys.add(new Polygon(xx, yy, 3)); } return (Polygon[]) polys.toArray(new Polygon[0]); } @Inject @Override public Shape getConvexHull() { RSModel model = getModel(); if (model == null) { return null; } int tileHeight = Perspective.getTileHeight(client, new LocalPoint(getX(), getY()), client.getPlane()); return model.getConvexHull(getX(), getY(), getOrientation(), tileHeight); } @SuppressWarnings("InfiniteRecursion") @Copy("getModel") @Replace("getModel") public RSModel copy$getModel() { if (!client.isInterpolatePlayerAnimations() || this.getPoseAnimation() == 244) { return copy$getModel(); } int actionFrame = getActionFrame(); int poseFrame = getPoseFrame(); int spotAnimFrame = getSpotAnimFrame(); try { // combine the frames with the frame cycle so we can access this information in the sequence methods // without having to change method calls setActionFrame(Integer.MIN_VALUE | getActionFrameCycle() << 16 | actionFrame); setPoseFrame(Integer.MIN_VALUE | getPoseFrameCycle() << 16 | poseFrame); setSpotAnimFrame(Integer.MIN_VALUE | getSpotAnimationFrameCycle() << 16 | spotAnimFrame); return copy$getModel(); } finally { // reset frames setActionFrame(actionFrame); setPoseFrame(poseFrame); setSpotAnimFrame(spotAnimFrame); } } @Inject public boolean isFriended() { return isFriend() || friended; } @Inject @MethodHook(value = "checkIsFriend", end = true) void updateFriended() { friended = client.getFriendManager().isFriended(getRsName(), false); } @Inject @MethodHook(value = "read", end = true) void postRead(RSBuffer var1) { client.getCallbacks().post(new PlayerChanged(this)); } @Copy("read") @Replace("read") @SuppressWarnings("InfiniteRecursion") public void copy$read(RSBuffer buffer) { final long appearanceHash = getPlayerComposition() == null ? 0 : getPlayerComposition().getHash(); this.copy$read(buffer); if (client.isComparingAppearance() && getPlayerComposition().getHash() != appearanceHash) { client.getCallbacks().post(new PlayerCompositionChanged(this)); } } }