As the title suggest this is how you make it so entities (players and npcs) are seen on different height levels.
I am releasing this because I have no intention of developing RSPS any further, and this is something I want to see in the real RuneScape so I figure this would be a good way for it to gain some traction.
If you want some pictures of how this would look, go to the link in my Signature.
This should be a fairly easy modification, literally just copy paste.
I am not responsible for any broken clients/servers. Use at your own risk.
Before you begin my NPC methods are "Mob" just change that, if you cannot and ask for help then I will not help you.
Client Side
First add
Code:
public static final boolean MULTILEVEL_RENDER = true;
Into a configuration file. Mine is Configuration.java
Next in Client.java
In your addNewMob method you will be adding a new area for npc's heightlevel is be recieved from the server.
Code:
private void addNewMob(final int i, final Stream stream) {
while (stream.bitPosition + 21 < i * 8) {
final int k = stream.readBits(14);
if (k == 16383) {
break;
}
if (mobArray[k] == null) {
mobArray[k] = new Mob();
}
final Mob mob = mobArray[k];
mobIndices[mobCount++] = k;
mob.anInt1537 = Client.loopCycle;
int l = stream.readBits(5);
if (l > 15) {
l -= 32;
}
int i1 = stream.readBits(5);
if (i1 > 15) {
i1 -= 32;
}
if (Configuration.MULTILEVEL_RENDER) {
mob.heightLevel = stream.readBits(5);
}
final int j1 = stream.readBits(1);
mob.desc = MobDef.forID(stream.readBits(12));
final int k1 = stream.readBits(1);
if (k1 == 1) {
anIntArray894[anInt893++] = k;
}
mob.anInt1540 = mob.desc.aByte68;
mob.anInt1504 = mob.desc.anInt79;
mob.anInt1554 = mob.desc.anInt67;
mob.anInt1555 = mob.desc.anInt58;
mob.anInt1556 = mob.desc.anInt83;
mob.anInt1557 = mob.desc.anInt55;
mob.standAnimation = mob.desc.anInt77;
mob.setEntityPos(Client.myPlayer.smallX[0] + i1, Client.myPlayer.smallY[0] + l, j1 == 1);
}
stream.finishBitAccess();
}
We will be doing the same for your addNewPlayer method
Code:
private void addNewPlayer(final Stream stream, final int i) {
while (stream.bitPosition + 10 < i * 8) {
final int j = stream.readBits(11);
if (j == 2047) {
break;
}
if (playerArray[j] == null) {
playerArray[j] = new Player();
if (aStreamArray895s[j] != null) {
playerArray[j].updatePlayer(aStreamArray895s[j]);
}
}
playerIndices[playerCount++] = j;
final Player player = playerArray[j];
player.anInt1537 = Client.loopCycle;
final int k = stream.readBits(1);
if (k == 1) {
anIntArray894[anInt893++] = j;
}
final int l = stream.readBits(1);
int i1 = stream.readBits(5);
if (i1 > 15) {
i1 -= 32;
}
int j1 = stream.readBits(5);
if (j1 > 15) {
j1 -= 32;
}
if (Configuration.MULTILEVEL_RENDER) {
player.heightLevel = stream.readBits(5);
}
player.setEntityPos(Client.myPlayer.smallX[0] + j1, Client.myPlayer.smallY[0] + i1, l == 1);
}
stream.finishBitAccess();
}
Optional
We need to make it so you cannot click on entities at different height levels, this can be changed if you want multi level combat.
At buildAtNPCMenu
Code:
private void buildAtMobMenu(MobDef entityDef, final int i, final int j, Entity entity, final int k) {
if (Configuration.MULTILEVEL_RENDER) {
if (entity.heightLevel != Client.myPlayer.heightLevel) {
return;
}
}
if (menuActionRow >= 400) {
return;
}
if (entityDef.childrenIDs != null) {
entityDef = entityDef.method161();
}
if (entityDef == null) {
return;
}
if (!entityDef.aBoolean84) {
return;
}
String s = entityDef.name;
if (entityDef.combatLevel != 0) {
s = s + Client.combatDiffColor(Client.myPlayer.combatLevel, entityDef.combatLevel) + " (level-"
+ entityDef.combatLevel + ")";
}
if (itemSelected == 1) {
menuActionName[menuActionRow] = "Use " + selectedItemName + " with @yel@" + s;
menuActionID[menuActionRow] = 582;
menuActionCmd1[menuActionRow] = i;
menuActionCmd2[menuActionRow] = k;
menuActionCmd3[menuActionRow] = j;
menuActionRow++;
return;
}
if (spellSelected == 1) {
if ((spellUsableOn & 2) == 2) {
menuActionName[menuActionRow] = spellTooltip + " @yel@" + s;
menuActionID[menuActionRow] = 413;
menuActionCmd1[menuActionRow] = i;
menuActionCmd2[menuActionRow] = k;
menuActionCmd3[menuActionRow] = j;
menuActionRow++;
}
} else {
if (entityDef.actions != null) {
for (int l = 4; l >= 0; l--) {
if (entityDef.actions[l] != null && !entityDef.actions[l].equalsIgnoreCase("attack")) {
menuActionName[menuActionRow] = entityDef.actions[l] + " @yel@" + s;
if (l == 0) {
menuActionID[menuActionRow] = 20;
}
if (l == 1) {
menuActionID[menuActionRow] = 412;
}
if (l == 2) {
menuActionID[menuActionRow] = 225;
}
if (l == 3) {
menuActionID[menuActionRow] = 965;
}
if (l == 4) {
menuActionID[menuActionRow] = 478;
}
menuActionCmd1[menuActionRow] = i;
menuActionCmd2[menuActionRow] = k;
menuActionCmd3[menuActionRow] = j;
menuActionRow++;
}
}
}
if (entityDef.actions != null) {
for (int i1 = 4; i1 >= 0; i1--) {
if (entityDef.actions[i1] != null && entityDef.actions[i1].equalsIgnoreCase("attack")) {
char c = '\0';
if (entityDef.combatLevel > Client.myPlayer.combatLevel) {
c = '\u07D0';
}
menuActionName[menuActionRow] = entityDef.actions[i1] + " @yel@" + s;
if (i1 == 0) {
menuActionID[menuActionRow] = 20 + c;
}
if (i1 == 1) {
menuActionID[menuActionRow] = 412 + c;
}
if (i1 == 2) {
menuActionID[menuActionRow] = 225 + c;
}
if (i1 == 3) {
menuActionID[menuActionRow] = 965 + c;
}
if (i1 == 4) {
menuActionID[menuActionRow] = 478 + c;
}
menuActionCmd1[menuActionRow] = i;
menuActionCmd2[menuActionRow] = k;
menuActionCmd3[menuActionRow] = j;
menuActionRow++;
}
}
}
menuActionName[menuActionRow] = "Examine @yel@" + s + (Configuration.debug ? " @gre@(@whi@" + entityDef.type + "@gre@)" : "");
menuActionID[menuActionRow] = 1025;
menuActionCmd1[menuActionRow] = i;
menuActionCmd2[menuActionRow] = k;
menuActionCmd3[menuActionRow] = j;
menuActionRow++;
}
}
And your buildAtPlayerMenu
Code:
private void buildAtPlayerMenu(final int i, final int j, final Player player, final int k) {
if (player == Client.myPlayer) {
return;
}
if (Configuration.MULTILEVEL_RENDER) {
if (player.heightLevel != Client.myPlayer.heightLevel) {
return;
}
}
if (menuActionRow >= 400) {
return;
}
String s;
if (player.skill == 0) {
s = player.name + Client.combatDiffColor(Client.myPlayer.combatLevel, player.combatLevel) + " (level-"
+ player.combatLevel + ")";
} else {
s = player.name + " (skill-" + player.skill + ")";
}
if (itemSelected == 1) {
menuActionName[menuActionRow] = "Use " + selectedItemName + " with @whi@" + s;
menuActionID[menuActionRow] = 491;
menuActionCmd1[menuActionRow] = j;
menuActionCmd2[menuActionRow] = i;
menuActionCmd3[menuActionRow] = k;
menuActionRow++;
} else if (spellSelected == 1) {
if ((spellUsableOn & 8) == 8) {
menuActionName[menuActionRow] = spellTooltip + " @whi@" + s;
menuActionID[menuActionRow] = 365;
menuActionCmd1[menuActionRow] = j;
menuActionCmd2[menuActionRow] = i;
menuActionCmd3[menuActionRow] = k;
menuActionRow++;
}
} else {
for (int l = 4; l >= 0; l--) {
if (atPlayerActions[l] != null) {
menuActionName[menuActionRow] = atPlayerActions[l] + " @whi@" + s;
char c = '\0';
if (atPlayerActions[l].equalsIgnoreCase("attack")) {
if (player.combatLevel > Client.myPlayer.combatLevel) {
c = '\u07D0';
}
if (Client.myPlayer.team != 0 && player.team != 0) {
if (Client.myPlayer.team == player.team) {
c = '\u07D0';
} else {
c = '\0';
}
}
} else if (atPlayerArray[l]) {
c = '\u07D0';
}
if (l == 0) {
menuActionID[menuActionRow] = 561 + c;
}
if (l == 1) {
menuActionID[menuActionRow] = 779 + c;
}
if (l == 2) {
menuActionID[menuActionRow] = 27 + c;
}
if (l == 3) {
menuActionID[menuActionRow] = 577 + c;
}
if (l == 4) {
menuActionID[menuActionRow] = 729 + c;
}
menuActionCmd1[menuActionRow] = j;
menuActionCmd2[menuActionRow] = i;
menuActionCmd3[menuActionRow] = k;
menuActionRow++;
}
}
}
for (int i1 = 0; i1 < menuActionRow; i1++) {
if (menuActionID[i1] == 516) {
menuActionName[i1] = "Walk here @whi@" + s;
return;
}
}
}
Next is minimap, this can be turned on or off depending on if you want it or not, I prefer to have it on so the minimap doesn't look as crowded.
Code:
private void drawMinimap() {
aRSImageProducer_1164.initDrawingArea();
if (anInt1021 == 2) {
final byte abyte0[] = mapBack.aByteArray1450;
final int ai[] = DrawingArea.pixels;
final int k2 = abyte0.length;
for (int i5 = 0; i5 < k2; i5++) {
if (abyte0[i5] == 0) {
ai[i5] = 0;
}
}
compass.method352(33, viewRotation, anIntArray1057, 256, anIntArray968, 25, 0, 0, 33, 25);
aRSImageProducer_1165.initDrawingArea();
return;
}
final int i = viewRotation + minimapRotation & 0x7ff;
final int j = 48 + Client.myPlayer.x / 32;
final int l2 = 464 - Client.myPlayer.y / 32;
aClass30_Sub2_Sub1_Sub1_1263.method352(151, i, anIntArray1229, 256 + minimapZoom, anIntArray1052, l2, 5, 25,
146, j);
compass.method352(33, viewRotation, anIntArray1057, 256, anIntArray968, 25, 0, 0, 33, 25);
for (int j5 = 0; j5 < anInt1071; j5++) {
final int k = anIntArray1072[j5] * 4 + 2 - Client.myPlayer.x / 32;
final int i3 = anIntArray1073[j5] * 4 + 2 - Client.myPlayer.y / 32;
markMinimap(aClass30_Sub2_Sub1_Sub1Array1140[j5], k, i3);
}
for (int k5 = 0; k5 < 104; k5++) {
for (int l5 = 0; l5 < 104; l5++) {
final NodeList class19 = groundArray[plane][k5][l5];
if (class19 != null) {
final int l = k5 * 4 + 2 - Client.myPlayer.x / 32;
final int j3 = l5 * 4 + 2 - Client.myPlayer.y / 32;
markMinimap(mapDotItem, l, j3);
}
}
}
for (int i6 = 0; i6 < mobCount; i6++) {
final Mob mob = mobArray[mobIndices[i6]];
if (Configuration.MULTILEVEL_RENDER) {
if (mob.heightLevel != Client.myPlayer.heightLevel) {
continue;
}
}
if (mob != null && mob.isVisible()) {
MobDef entityDef = mob.desc;
if (entityDef.childrenIDs != null) {
entityDef = entityDef.method161();
}
if (entityDef != null && entityDef.aBoolean87 && entityDef.aBoolean84) {
final int i1 = mob.x / 32 - Client.myPlayer.x / 32;
final int k3 = mob.y / 32 - Client.myPlayer.y / 32;
markMinimap(mapDotMob, i1, k3);
}
}
}
for (int j6 = 0; j6 < playerCount; j6++) {
final Player player = playerArray[playerIndices[j6]];
if (Configuration.MULTILEVEL_RENDER) {
if (player.heightLevel != Client.myPlayer.heightLevel) {
continue;
}
}
if (player != null && player.isVisible()) {
final int j1 = player.x / 32 - Client.myPlayer.x / 32;
final int l3 = player.y / 32 - Client.myPlayer.y / 32;
boolean flag1 = false;
final long l6 = TextClass.longForName(player.name);
for (int k6 = 0; k6 < friendsCount; k6++) {
if (l6 != friendsListAsLongs[k6] || friendsNodeIDs[k6] == 0) {
continue;
}
flag1 = true;
break;
}
boolean flag2 = false;
if (Client.myPlayer.team != 0 && player.team != 0 && Client.myPlayer.team == player.team) {
flag2 = true;
}
if (player.morph != null) {
markMinimap(mapDotMob, j1, l3);
} else if (flag1) {
markMinimap(mapDotFriend, j1, l3);
} else if (flag2) {
markMinimap(mapDotTeam, j1, l3);
} else {
markMinimap(mapDotPlayer, j1, l3);
}
}
}
if (anInt855 != 0 && Client.loopCycle % 20 < 10) {
if (anInt855 == 1 && anInt1222 >= 0 && anInt1222 < mobArray.length) {
final Mob class30_sub2_sub4_sub1_sub1_1 = mobArray[anInt1222];
if (class30_sub2_sub4_sub1_sub1_1 != null) {
final int k1 = class30_sub2_sub4_sub1_sub1_1.x / 32 - Client.myPlayer.x / 32;
final int i4 = class30_sub2_sub4_sub1_sub1_1.y / 32 - Client.myPlayer.y / 32;
method81(mapMarker, i4, k1);
}
}
if (anInt855 == 2) {
final int l1 = (anInt934 - baseX) * 4 + 2 - Client.myPlayer.x / 32;
final int j4 = (anInt935 - baseY) * 4 + 2 - Client.myPlayer.y / 32;
method81(mapMarker, j4, l1);
}
if (anInt855 == 10 && anInt933 >= 0 && anInt933 < playerArray.length) {
final Player class30_sub2_sub4_sub1_sub2_1 = playerArray[anInt933];
if (class30_sub2_sub4_sub1_sub2_1 != null) {
final int i2 = class30_sub2_sub4_sub1_sub2_1.x / 32 - Client.myPlayer.x / 32;
final int k4 = class30_sub2_sub4_sub1_sub2_1.y / 32 - Client.myPlayer.y / 32;
method81(mapMarker, k4, i2);
}
}
}
if (destX != 0) {
final int j2 = destX * 4 + 2 - Client.myPlayer.x / 32;
final int l4 = destY * 4 + 2 - Client.myPlayer.y / 32;
markMinimap(mapFlag, j2, l4);
}
DrawingArea.drawPixels(3, 78, 97, 0xffffff, 3);
aRSImageProducer_1165.initDrawingArea();
}
Now we need to draw the entities
Code:
private void drawMobs(final boolean flag) {
for (int j = 0; j < mobCount; j++) {
final Mob mob = mobArray[mobIndices[j]];
int k = 0x20000000 + (mobIndices[j] << 14);
if (mob == null || !mob.isVisible() || mob.desc.aBoolean93 != flag) {
continue;
}
final int l = mob.x >> 7;
final int i1 = mob.y >> 7;
if (l < 0 || l >= 104 || i1 < 0 || i1 >= 104) {
continue;
}
if (mob.anInt1540 == 1 && (mob.x & 0x7f) == 64 && (mob.y & 0x7f) == 64) {
if (anIntArrayArray929[l][i1] == anInt1265) {
if (Configuration.MULTILEVEL_RENDER && mob.heightLevel == myPlayer.heightLevel) {
continue;
}
}
anIntArrayArray929[l][i1] = anInt1265;
}
if (!mob.desc.aBoolean84) {
k += 0x80000000;
}
if (Configuration.MULTILEVEL_RENDER) {
worldController.add(mob.heightLevel, mob.yaw, method42(mob.heightLevel, mob.y, mob.x), k, mob.y, (mob.anInt1540 - 1) * 64 + 60,
mob.x, mob, mob.canRotate);
} else {
worldController.add(plane, mob.yaw, method42(plane, mob.y, mob.x), k, mob.y, (mob.anInt1540 - 1) * 64 + 60,
mob.x, mob, mob.canRotate);
}
}
}
Code:
private void drawPlayers(final boolean flag) {
if (Client.myPlayer.x >> 7 == destX && Client.myPlayer.y >> 7 == destY) {
destX = 0;
}
int j = playerCount;
if (flag) {
j = 1;
}
for (int l = 0; l < j; l++) {
Player player;
int i1;
if (flag) {
player = Client.myPlayer;
i1 = myPlayerIndex << 14;
} else {
player = playerArray[playerIndices[l]];
i1 = playerIndices[l] << 14;
}
if (player == null || !player.isVisible()) {
continue;
}
player.ignoreSequences = (Client.lowMem && playerCount > 50 || playerCount > 200) && !flag
&& player.moveSequence == player.standAnimation;
final int j1 = player.x >> 7;
final int k1 = player.y >> 7;
if (j1 < 0 || j1 >= 104 || k1 < 0 || k1 >= 104) {
continue;
}
if (player.objectModel != null && Client.loopCycle >= player.objectStartCycle
&& Client.loopCycle < player.objectEndCycle) {
player.ignoreSequences = false;
if (Configuration.MULTILEVEL_RENDER) {
player.z = method42(player.heightLevel, player.y, player.x);
worldController.add(player.heightLevel, player.y, player, player.yaw, player.objectY1, player.x, player.z,
player.objectX0, player.objectY0, i1, player.objectX1);
} else {
player.z = method42(plane, player.y, player.x);
worldController.add(plane, player.y, player, player.yaw, player.objectY1, player.x, player.z,
player.objectX0, player.objectY0, i1, player.objectX1);
}
continue;
}
if ((player.x & 0x7f) == 64 && (player.y & 0x7f) == 64) {
if (anIntArrayArray929[j1][k1] == anInt1265) {
if (Configuration.MULTILEVEL_RENDER && player.heightLevel == myPlayer.heightLevel) {
continue;
}
}
anIntArrayArray929[j1][k1] = anInt1265;
}
if (Configuration.MULTILEVEL_RENDER) {
player.z = method42(player.heightLevel, player.y, player.x);
worldController.add(player.heightLevel, player.yaw, player.z, i1, player.y, 60, player.x, player, player.canRotate);
} else {
player.z = method42(plane, player.y, player.x);
worldController.add(plane, player.yaw, player.z, i1, player.y, 60, player.x, player, player.canRotate);
}
}
}
Code:
private void entityScreenPos(final Entity entity, final int i) {
calcEntityScreenPos(entity.x, i, entity.y, Configuration.MULTILEVEL_RENDER ? entity.heightLevel : plane);
}
In your Entity.java add
Code:
public int heightLevel;
And one last packet change to get player height level
Code:
private void method117(final Stream stream) {
stream.initBitAccess();
final int j = stream.readBits(1);
if (j == 0) {
return;
}
final int k = stream.readBits(2);
if (k == 0) {
anIntArray894[anInt893++] = myPlayerIndex;
return;
}
if (k == 1) {
final int l = stream.readBits(3);
Client.myPlayer.moveInDir(false, l);
final int k1 = stream.readBits(1);
if (k1 == 1) {
anIntArray894[anInt893++] = myPlayerIndex;
}
return;
}
if (k == 2) {
final int i1 = stream.readBits(3);
Client.myPlayer.moveInDir(true, i1);
final int l1 = stream.readBits(3);
Client.myPlayer.moveInDir(true, l1);
final int j2 = stream.readBits(1);
if (j2 == 1) {
anIntArray894[anInt893++] = myPlayerIndex;
}
return;
}
if (k == 3) {
plane = stream.readBits(2);
if (Configuration.MULTILEVEL_RENDER) {
Client.myPlayer.heightLevel = plane;
}
final int j1 = stream.readBits(1);
final int i2 = stream.readBits(1);
if (i2 == 1) {
anIntArray894[anInt893++] = myPlayerIndex;
}
final int k2 = stream.readBits(7);
final int l2 = stream.readBits(7);
Client.myPlayer.setEntityPos(l2, k2, j1 == 1);
}
}
In your Entity.java add
Server Side
Next up is the server side code this will be different dependng on the server you are using, but it should be easy to figure out where to put this stuff.
This is for my own server use at your own risk.
Same as before make a boolean to toggle multi-level rendering
Code:
public static boolean multiLevelRender = true;
This is in my Configuration.java on the server side
In your Entity.java
Code:
public boolean distanceToEntity(final Entity anEntity, final int distance) {
if (!Configuration.multiLevelRender) {
if (height != anEntity.getHeight()) {
return false;
}
}
if (anEntity.size > 1) {
for (int i = 0; i < (anEntity.size - 1); i++) {
final int top = (int) Math.sqrt(Math.pow((coordX - anEntity.getX()) + i, 2)
+ Math.pow((coordY - anEntity.getY()) + (anEntity.size - 1), 2));
final int bottom = (int) Math
.sqrt(Math.pow((coordX - anEntity.getX()) + i, 2) + Math.pow(coordY - anEntity.getY(), 2));
final int right = (int) Math
.sqrt(Math.pow(coordX - anEntity.getX(), 2) + Math.pow((coordY - anEntity.getY()) + i, 2));
final int left = (int) Math.sqrt(Math.pow((coordX - anEntity.getX()) + (anEntity.size - 1), 2)
+ Math.pow((coordY - anEntity.getY()) + i, 2));
if ((top <= distance) || (bottom <= distance) || (right <= distance) || (left <= bottom)) {
return true;
}
}
} else {
final int check = (int) Math
.sqrt(Math.pow(coordX - anEntity.getX(), 2) + Math.pow(coordY - anEntity.getY(), 2));
if (check <= distance) {
return true;
}
}
return false;
}
In your PlayerUpdate.java or whereever you have the code to update your player's client
Code:
public static void addNewMob(final Player player, final Mob mob, final Stream stream, final Stream updateBlock) {
synchronized (player) {
player.getMobInListBitmap()[mob.getEntityId() >> 3] |= 1 << (mob.getEntityId() & 7);
player.mobList[player.mobListSize++] = mob;
stream.writeBits(14, mob.getEntityId());
int z = mob.getY() - player.getY();
if (z < 0) {
z += 32;
}
stream.writeBits(5, z);
z = mob.getX() - player.getX();
if (z < 0) {
z += 32;
}
stream.writeBits(5, z);
if (Configuration.multiLevelRender) {
stream.writeBits(5, mob.getHeight());
}
stream.writeBits(1, 0);
stream.writeBits(12, mob.getMobType());
final boolean savedFlag = mob.getAppearanceUpdate();
final boolean savedUpdateRequired = mob.getUpdate();
mob.setAppearanceUpdate(true);
mob.setUpdate(true);
MobUpdate.appendMobUpdateBlock(mob, updateBlock);
mob.setAppearanceUpdate(savedFlag);
mob.setUpdate(savedUpdateRequired);
stream.writeBits(1, 1);
}
}
public static void addNewPlayer(final Player self, final Player player, final Stream stream,
final Stream updateBlock) {
synchronized (self) {
if (self.playerListSize >= 79) {
return;
}
final int id = player.getEntityId();
self.playerInListBitmap[id >> 3] |= 1 << (id & 7);
self.playerList[self.playerListSize++] = player;
stream.writeBits(11, id);
stream.writeBits(1, 1);
final boolean savedFlag = player.getAppearanceUpdate();
final boolean savedUpdateRequired = player.getUpdate();
player.setAppearanceUpdate(true);
player.setUpdate(true);
PlayerUpdate.appendPlayerUpdateBlock(player, updateBlock);
player.setAppearanceUpdate(savedFlag);
player.setUpdate(savedUpdateRequired);
stream.writeBits(1, 1);
int z = player.getY() - self.getY();
if (z < 0) {
z += 32;
}
stream.writeBits(5, z);
z = player.getX() - self.getX();
if (z < 0) {
z += 32;
}
stream.writeBits(5, z);
if (Configuration.multiLevelRender) {
stream.writeBits(5, player.getHeight());
}
}
}