I have yet to see anybody in the projects section (besides Catanai, Matrix and myself) able to figure out how overhead icons work in RuneScape 3. Assuming Jagex made an update to change overhead icons when RuneScape 3 went live, we all already know overhead icons were handled in the appearance. When decoding the appearance, the client requests 2 different bytes, one for wilderness icons, the others are prayer related. Here is an example from Matrix 718.
Code:
stream.writeByte(player.hasSkull() ? player.getSkullId() : -1);
stream.writeByte(player.getPrayer().getPrayerHeadIcon());
So, why doesn't RuneScape 3 projects have overhead icons? Simple enough, they changed. And to be honest, I like the new change.
There are 2 masks used in player updating that look extremely alike...
Code:
if (0 != (i_19_ & 0x80)) {
int i_21_ = class465_sub29_sub1.readUnsignedByteA(-1144846717);
byte[] is = new byte[i_21_];
Class465_Sub29 class465_sub29 = new Class465_Sub29(is);
class465_sub29_sub1.method16205(is, 0, i_21_, 1395385706);
Class61.aClass465_Sub29Array715[playerIndex] = class465_sub29;
class592_sub1_sub1_sub1_sub1.method18379(class465_sub29, -1965287251);
}
if (0 != (i_19_ & 0x100)) {
int i_36_ = class465_sub29_sub1.readUnsignedByteC((byte) -78);
byte[] is = new byte[i_36_];
Class465_Sub29 class465_sub29 = new Class465_Sub29(is);
class465_sub29_sub1.method16205(is, 0, i_36_, -1427150336);
Class61.aClass465_Sub29Array722[playerIndex] = class465_sub29;
class592_sub1_sub1_sub1_sub1.method18390(class465_sub29, (byte) -49);
}
If you're familiar with your runescape clients, you would recognize the mask, obviously it's the appearance. Atleast, the top one is. Basically, Jagex took it upon themselves to remove the overhead icons from the appearance, and made them into their own separate mask. Thus, why 2 masks look identical.
The way overhead icons are handled, are in the same fashion you would handle appearance. The decoding for overhead icons look like this..
Code:
builder.writeByte(#);
builder.writeByte(#);
builder.writeShort(#);
The first byte sent is still unknown to me, but it is always 4. The second byte sent is the icon id, then the short sent is the sprite index containing the icons. RuneScape takes the icon id sent, and locates that id from inside the sprite id inside the cache. If you dumped all components from sprite 440 inside the cache, you will get every overhead icon in the game. So here is an example of the actual class I used for overhead icons.
Code:
private void sendOverheadIcon(Player player, RS3PacketBuilder builder) {
byte[] data = player.getAnimator().getOverheadIcon().getOverheadIconData();
sentDataLength += data.length;
cachedAppearances[player.getIndex()] = player.getAnimator().getOverheadIcon().getOverheadIconHash();
builder.writeByteC(data.length);
builder.writeReverseA(data, 0, data.length);
}
Code:
package org.astro.rs3.game.node.entity.render.action;
import org.astro.rs3.network.gameserver.codec.RS3PacketBuilder;
import org.astro.utilities.Utilities;
/**
* @author Jordan Abraham <[email protected]>
* @date Dec 29, 2014
*/
public class OverheadIcon {
/**
* Represents the sprite id that contains the overhead icons.
*/
public static final int SPRITE_ID = 440;
/**
* Represents the current prayers icon id of this {@code OverheadIcon}.
*/
private PrayerIcons prayerIcons;
/**
* Represents the current curses icon id of this {@code OverheadIcon}.
*/
private CurseIcons curseIcons;
/**
* Represents the data hash of the {@code OverheadIcon}.
*/
private byte[] overheadIconHash;
/**
* Represents the raw data of the {@code OverheadIcon}.
*/
private byte[] overheadIconData;
/**
* Constructs a new {@code OverheadIcon} {@link Object}.
* @param prayerIcons Represents the id of the prayers icon to construct.
*/
public OverheadIcon(PrayerIcons prayerIcons) {
this.prayerIcons = prayerIcons;
this.curseIcons = null;
}
/**
* Constructs a new {@code OverheadIcon} {@link Object}.
* @param curseIcons Represents the id of the curses icon to construct.
*/
public OverheadIcon(CurseIcons curseIcons) {
this.curseIcons = curseIcons;
this.prayerIcons = null;
}
/**
* Refreshes the {@code OverheadIcon} of the {@link Player}.
* @param iconId Represents the id of the icon to use during refreshing.
*/
public void refreshOverheadIcon() {
RS3PacketBuilder builder = new RS3PacketBuilder();
builder.writeByte(4);
builder.writeByte(prayerIcons == null ? curseIcons.getIconId() : prayerIcons.getIconId());
builder.writeShort(SPRITE_ID);
byte[] data = new byte[builder.getOffset()];
System.arraycopy(builder.getBuffer(), 0, data, 0, data.length);
byte[] hash = Utilities.encrypt(data);
overheadIconData = data;
overheadIconHash = hash;
}
/**
* Creates a {@code OverheadIcon}.
* @param prayerIcons Represents the created id of the {@code OverheadIcon}.
* @return The {@code OverheadIcon} created.
*/
public static OverheadIcon create(PrayerIcons prayerIcons) {
return new OverheadIcon(prayerIcons);
}
/**
* Creates a {@code OverheadIcon}.
* @param curseIcons Represents the created id of the {@code OverheadIcon}.
* @return The {@code OverheadIcon} created.
*/
public static OverheadIcon create(CurseIcons curseIcons) {
return new OverheadIcon(curseIcons);
}
/**
* Gets and returns the hash of the {@code OverheadIcon}.
* @return the iconHash
*/
public byte[] getOverheadIconHash() {
return overheadIconHash;
}
/**
* Sets the hash of the {@code OverheadIcon}.
* @param iconHash the iconHash to set
*/
public void setOverheadIconHash(byte[] iconHash) {
this.overheadIconHash = iconHash;
}
/**
* Gets and returns the data of the {@code OverheadIcon}.
* @return the iconData
*/
public byte[] getOverheadIconData() {
return overheadIconData;
}
/**
* Sets the data of the {@code OverheadIcon}.
* @param iconData the iconData to set
*/
public void setOverheadIconData(byte[] iconData) {
this.overheadIconData = iconData;
}
/**
* @author Jordan Abraham <[email protected]>
* @date Dec 29, 2014
*/
public enum PrayerIcons {
PROTECT_FROM_MELEE(0), PROTECT_FROM_MISSILES(1), PROTECT_FROM_MAGIC(2), RETRIBUTION(3), SMITE(4), REDEMPTION(5), PROTECT_FROM_MAGIC_MISSILES_SUMMONING(6), PROTECT_FROM_SUMMONING(7), PROTECT_FROM_MELEE_SUMMONING(8), PROTECT_FROM_MISSILES_SUMMONING(9), PROTECT_FROM_MAGIC_SUMMONING(10);
/**
* Represents the id of each {@code Prayers}.
*/
private int iconId;
/**
* Constructs a new {@code Icon} {@link Object}.
* @param iconId Represents the id of each {@code Prayers} to construct.
*/
private PrayerIcons(int iconId) {
this.iconId = iconId;
}
/**
* Gets and returns the id of each constructed {@code Prayers}.
* @return the iconId
*/
public int getIconId() {
return iconId;
}
}
/**
* @author Jordan Abraham <[email protected]>
* @date Dec 29, 2014
*/
public enum CurseIcons {
// 29 is the limit
DEFLECT_MELEE(12), DEFLECT_MAGIC(13), DEFLECT_MISSILES(14), DEFLECT_SUMMONING(15), DEFLECT_MELEE_SUMMONING(16), DEFLECT_MISSILES_SUMMONING(17), DEFLECT_MAGIC_SUMMONING(18), WRATH(19), SOUL_SPLIT(20);
/**
* Represents the id of each {@code Curses}.
*/
private int iconId;
/**
* Constructs a new {@code Icon} {@link Object}.
* @param iconId Represents the id of each {@code Curses} to construct.
*/
private CurseIcons(int iconId) {
this.iconId = iconId;
}
/**
* Gets and returns the id of each constructed {@code Curses}.
* @return the iconId
*/
public int getIconId() {
return iconId;
}
}
}
So that is how overhead icons are used in RuneScape 3. Hope this helped you in some way
Originally Posted by
Zephyrr
True, also now doesnt have to send all the bytes from appearance to make them appear.
For anyone wondering how to write headicons on npc :
Its almost same way,
Code:
if ((i_6_ & 0x200000) != 0) { // npc prays
int i_76_ = class464_sub17_sub2.readA(1223467414);
int[] is = new int[8];
short[] is_77_ = new short[8];
for (int i_78_ = 0; i_78_ < 8; i_78_++) {
if (0 != (i_76_ & 1 << i_78_)) {
is[i_78_] = class464_sub17_sub2.method15827(-740808969);
is_77_[i_78_] = (short) class464_sub17_sub2.method15636(1516369682);
} else {
is[i_78_] = -1;
is_77_[i_78_] = (short) -1;
}
}
class601_sub1_sub3_sub1_sub1.aClass272_11776 = new Class272(is, is_77_);
}
^ is for 834 though
Code:
int mask = 0;
for(int i = 0; i < npc.getIcons().size(); i++)
mask |= 1 << i;
stream.putA(mask);
for(Icon icon : npc.getIcons()) {
stream.putIntSmart(icon.getSpriteId());
stream.putSmart(icon.getSpriteIconId());
}
Dont ask me when it came out, Idk.