FANDOM


Player hitting

A hit mask in RS3

What we call render masks, or also known as render flags, are simply multiple data structers used to parse settings and mostly physical information of a player or NPC. This article should help people understand exactly what they are, and how they are used in the client.

MasksEdit

The term 'masks' comes the computing term bitmasks, which is data that is used for bitwise operators in a bit field. (Mask)  Each player and NPC has a set of bit masks, the number of them depending on the client revision. The masks are parsed during the rendering packet, when a local player has been requested a mask update by the server. Masks are parsed in order, typically using IF statements by the client. Each mask contains an ordinal, or index, which is denoted by it's position in the method.

Types of MasksEdit

The types of masks varie from revison to revision, however the standard ones remain as follows:

  • Appearance - Loads and applies a player model with his/her equipment data, username, and body looks and colors.
  • Orientation - Changes the orientation of an actor, (north, west, south, or east)
  • Orientation (Actor) - Changes the orientation of the player/NPC dependent on the location of a given actor. This allows for facing other actors in the game without having to manually calculate their location.
  • Movement Type - Signals the client of a movement type change, (run or walk).
  • Animation - Updates the client with a new animation that the actor should perform.
  • Graphics - Updates the client with a new graphic effect that the actor should perform. There are 4 graphic masks, used in layers to perform multiple graphics at once.
  • Forced Movement - Not to be confused with the walking packet, (sent seperatly in the client), this mask is used to signal the client that the actor should move forcibly to a given destination. This mask is mostly used in the agility skill.
  • Forced Talking - Also not to be confused with the chat packet, (sent seperatly in the client), this mask is used to signal the client that the actor should say something forcibly.
  • Hit - Used to show a hit bar and/or a hit splat above an actors head. Also used for the adrenaline bar in the Evolution of Combat.


NOTE: The data structure of the following code may differ depending on your revision, also, these data representations and documentation ONLY APPLY TO PLAYERS. While NPCs have the same masks, besides appearance, and a couple more, the data structures, mask values, and ordinals are different.

Appearance (Player only):Edit

First, a byte is read containing data used to denote certain flags in the player's class. This byte is a mask, which has different values.

Data includes:

  • 0x1 - Denotes the player as a female
  • 0x2 - Signals the client that the player will be rendered using an NPC model.
  • 0x4 - (In later revisions) signals the client to show the player's skill level other than their combat level.
  • 0x40 - Signals the client that the player has a title prefix.
  • 0x80 - Signals the client that the player has a title suffix.

Server sided code for the mask portion of the appearance bock:

           int mask = 0;
 		if (gender.equals(Gender.FEMALE)) {
 		    mask |= 0x1;
 		}
                if (npcId > -1) {
		    mask |= 0x2;
		}
 		if (showSkillLevel) {
 		    mask |= 0x4;
 		}
 		if (title != null) {
 		    if (isTitlePrefix()) {
 			mask |= 0x40;
 			} else {
 			mask |= 0x80;
 		     }
 		}
 		buffer.put(mask);
 		if (title != null) {
 		    buffer.putJagString(title);
                }
 		}

If the player has a title prefix or suffix, a JAG-type string is written to denote their title. Next, a byte is read indicating if the player is hidden or not. If the player is hidden, they will not be visually rendered and also will not show up on the minimap.

Afterwords, the equipment portion is written. This allows the client to display the item models of what the player is currently wearing. First, the client starts by looping 4 times and reading the first 4 equipment items. If the item doesn't exist, then we write a single null byte. Otherwise, we write a short containing the model mask added to the item's ID.

              for (int index = 0; index < 4; index++) {
			Item item = player.getEquipment().get(index);
			if (item != null) {
		            buffer.putShort(APPEND_MODEL_MASK | item.getId());
			} else {
			    buffer.put(0);
			}
		}

Next, the client reads the body data. This is where we send the body looks if there isn't any armour, or we send the armour if the player has any equipped.

The client goes in order, chest, offhand, chest (arm portion), legs, hat, hands, feet, aura, pocket.

First, if the player has no offhand item equipped, we send a null byte. Otherwise, we send a short with the model mask added to the item ID. Next is the main chest, we send a short containing the look mask added to the chest body style if there is no chest armour equipped. Otherwise, we send the model mask added to the item. This step is repeated for all of the equipment slots, but some have different data structures. However the pattern will always be a short or a byte. After equipment, extra item data such as cape customizations, clan vexillums, and more are written as slot flag data. I will not go over that stage in this article, since it's extra.

Skipping the next byte and two shorts, for the sake of this article, we move on to the standard player information. The client starts by reading 10 bytes, each byte represents the color of a certain body part. Next, the rendering animation ID is written as a short, then the display username is written as a string. The combat level is then written as a byte, and the next 3 bytes denote the combat level multipliers and differences for summoning and wilderness.

OrientationEdit

Regular orientation is changes by sending a little endian short containing the orientation ID.

Actor orientation is sent by writing a regular short containing the other actor's client index ID added to the character denotion mask.

Movement TypeEdit

Movement type is sent by writing an A-type byte, containing the move type. 0 = idle, 1 = walk, and 2 = run.

AnimationEdit

4 big smarts are written as the animation ID.

GraphicsEdit

Not going to go into much detail, seeing as though there are 4 types, each containing different structures. However, 3 data types are written as the graphics ID, settings, then grid settings.

Forced MovementEdit

An S type byte is written as the first tile X coordinate, a byte is then written as the first tile Y coordinate. Then, 2 bytes are written as the second tile X and Y. After that, an A-type short is written as the first tile delay. Next, a little endian short is written as the second tile delay, (0 if no second tile). Finally, another little endian short is written as the direction.

Forced TalkingEdit

A string is written as the text to say, and a byte is written indicating if the text should appear in the chatbox.

HitEdit

For hits, the client first starts by reading a byte denoting the amount of hits being applied, so the client knows what data to expect. While iterating through the hit queue, the hit splat ID is written as a smart.If the hit is a "double hit", (two hit splats in one), then the hits custom type, damage, seconday damage, and secondary type are written as smarts. If the hit mark ID is 32766, then we write a null byte. This hides the hit splat. Otherwise, we write a smart as the damage, and another smart as the delay. Next, the client reads the hit bar data. The client starts by reading an A-type byte as the amount of bars being applied, again, so the client knows what data to expect. Next, a smart is written as the bar type. After that, another smart is written to indicate if the bar should display or not. (0, 1, or 32767). Then a null smart is written, next, an A-type byte is written as the maximum percentage of the bar, then, if the bar has a difference in max percentage and current percentage, a C-type byte is written as the current percentage.

   @Override
	public void write(IoBuffer buffer, Actor actor) {
		buffer.put(actor.getRenderQueue().getHitQueue().getHits().size());
		if (actor.getRenderQueue().getHitQueue().getHits().size() > 0) {
			while (!actor.getRenderQueue().getHitQueue().getHits().isEmpty()) {
				Hit hit = actor.getRenderQueue().getHitQueue().getHits().poll();
				buffer.putSmart(hit.getMark().getId());
				if (hit.getMark().equals(HitMark.DOUBLE_HIT)) {
					buffer.putSmart(hit.getCustomType());
					buffer.putSmart(hit.getDamage());
					buffer.putSmart(hit.getSecondaryDamage());
					buffer.putSmart(hit.getSecondaryType());
				} else if (hit.getMark().getId() == 32766) {
					buffer.put(0);
				} else {
					buffer.putSmart(hit.getDamage());
				}
				buffer.putSmart(hit.getDelay());
			}
		}
		if (actor instanceof NPC) {
			buffer.putS(actor.getRenderQueue().getHitQueue().getBars().size());
		} else {
			buffer.putA(actor.getRenderQueue().getHitQueue().getBars().size());
		}
		if (actor.getRenderQueue().getHitQueue().getBars().size() > 0) {
			while (!actor.getRenderQueue().getHitQueue().getBars().isEmpty()) {
				HitBar bar = actor.getRenderQueue().getHitQueue().getBars().poll();
				buffer.putSmart(bar.getType());
				int maxPercentage = bar.getMaxPercentage();
				int percentage = bar.getPercentage(actor);
				buffer.putSmart(bar.shouldDisplay(actor) ? maxPercentage != percentage ? 1 : 0 : 32767);
				if (bar.shouldDisplay(actor)) {
				    buffer.putSmart(0);
				    if (actor instanceof NPC) {
					buffer.putC(maxPercentage);
					} else {
					buffer.putA(maxPercentage);
					}
					if (maxPercentage != percentage) {
					    buffer.putC(bar.getPercentage(actor));
					}
				}
			}
		}
	}

References:Edit

Consider the following semi-refactored code pulled from an 801 revision client:


static final void decodePlayerMasks(OutputBuffer buffer, int clientIndex, Player player, int mask) {
		if (0 != (mask & 0x1)) {//ordinal 1
			int i_11_ = buffer.getUnsignedShortA();
			if (i_11_ == 65535)
				i_11_ = -1;
			player.anInt11377 = i_11_ * -473709365;
		}
		if ((mask & 0x100) != 0) {//ordinal 2
			int i_12_ = buffer.getUnsignedByteA();
			byte[] is = new byte[i_12_];
			RSBuffer class592_sub3 = new RSBuffer(is);
			buffer.method14545(is, 0, i_12_, 1036803294);
			Class201.aClass592_Sub3Array2392[clientIndex] = class592_sub3;
			player.method17881(class592_sub3, 119637615);
		}
		if ((mask & 0x40000) != 0) {//ordinal 3
			int i_13_ = buffer.getUnsignedShort();
			int i_14_ = buffer.method14677(314542845);
			if (i_13_ == 65535)
				i_13_ = -1;
			int i_15_ = buffer.getUnsigned128Byte();
			int i_16_ = i_15_ & 0x7;
			int i_17_ = i_15_ >> 3 & 0xf;
			if (i_17_ == 15)
				i_17_ = -1;
			boolean bool = 1 == (i_15_ >> 7 & 0x1);
			player.queueGraphics(i_13_, i_14_, i_16_, i_17_, bool, 3, -2095952282);
		}
		if (0 != (mask & 0x100000)) {//ordinal 4
			int i_18_ = buffer.getUnsignedShortLE();
			int i_19_ = buffer.getIntLE();
			if (65535 == i_18_)
				i_18_ = -1;
			int i_20_ = buffer.getUnsignedByteC();
			int i_21_ = i_20_ & 0x7;
			int i_22_ = i_20_ >> 3 & 0xf;
			if (i_22_ == 15)
				i_22_ = -1;
			boolean bool = (i_20_ >> 7 & 0x1) == 1;
			player.queueGraphics(i_18_, i_19_, i_21_, i_22_, bool, 4, -2037112916);
		}
		if ((mask & 0x200000) != 0)
			player.aBool11521 = buffer.getUnsignedByte() == 1;
		if (0 != (mask & 0x800)) {
			int i_23_ = buffer.getUnsignedByteA();
			int[] is = new int[i_23_];
			int[] is_24_ = new int[i_23_];
			int[] is_25_ = new int[i_23_];
			for (int i_26_ = 0; i_26_ < i_23_; i_26_++) {
				is[i_26_] = buffer.method14668(-1715859383);
				is_24_[i_26_] = buffer.getUnsignedByteA();
				is_25_[i_26_] = buffer.getUnsignedShortLE128();
			}
			Class219.method4416(player, is, is_24_, is_25_, (byte) 3);
		}
		if (0 != (mask & 0x8000))
			player.aBool11519 = buffer.getUnsigned128Byte() == 1;
		if (0 != (mask & 0x20000)) {
			player.aByte11392 = buffer.method14524((byte) -48);
			player.aByte11364 = buffer.get();
			player.aByte11394 = buffer.get();
			player.aByte11395 = (byte) buffer.getUnsignedByteA();
			player.anInt11390 = (client.anInt8431 + buffer.getUnsignedShort()) * -1332008605;
			player.anInt11391 = (client.anInt8431 + buffer.getUnsignedShort()) * 1690835085;
		}
		if ((mask & 0x2000) != 0) {
			String string = buffer.getString();
			if (Class358_Sub2.player == player)
				Class587.method12793(2, 0, player.getDisplayName(true, -938377360), player.method17890(false, -1562161990), player.name, string, 912504044);
			player.method17887(string, 0, 0, -593666249);
		}
		if (0 != (mask & 0x80000)) {
			int i_27_ = buffer.getUnsigned128Byte();
			int[] is = new int[i_27_];
			int[] is_28_ = new int[i_27_];
			for (int i_29_ = 0; i_29_ < i_27_; i_29_++) {
				int i_30_ = buffer.getUnsignedShortA();
				if (49152 == (i_30_ & 0xc000)) {
					int i_31_ = buffer.getUnsignedShortA();
					is[i_29_] = i_30_ << 16 | i_31_;
				} else
					is[i_29_] = i_30_;
				is_28_[i_29_] = buffer.getUnsignedShort();
			}
			player.method17669(is, is_28_, -8502424);
		}
		if (0 != (mask & 0x800000)) {
			player.anInterface45_11418.method326(-554751423);
			buffer.bufferIndex += -1603921642;
			int i_32_ = ((buffer.buffer[((buffer.bufferIndex += 1345522827) * -850658525 - 1)]) & 0xff);
			for (int i_33_ = 0; i_33_ < i_32_; i_33_++) {
				int i_34_ = buffer.getUnsignedByteC();
				Class214 class214 = (Class214) Class567.method12470(Class214.class, i_34_, -1406106005);
				Class258 class258 = (Class99.aClass188_Sub1_Sub2_1413.method16773(buffer, class214, -693621925));
				player.anInterface45_11418.method311(class258.anInt4385 * 998647597, class258.anObject4384, 1153266749);
			}
		}
		if (0 != (mask & 0x2)) {
			player.anInt11509 = buffer.getUnsignedShort() * 1644620107;
			if (469870621 * player.anInt11407 == 0) {
				player.method17667(player.anInt11509 * -2111416221, (byte) 7);
				player.anInt11509 = -1644620107;
			}
		}
		if (0 != (mask & 0x1000)) {
			int i_35_ = buffer.getUnsignedShortLE128();
			int i_36_ = buffer.getIntA();
			if (i_35_ == 65535)
				i_35_ = -1;
			int i_37_ = buffer.getUnsigned128Byte();
			int i_38_ = i_37_ & 0x7;
			int i_39_ = i_37_ >> 3 & 0xf;
			if (15 == i_39_)
				i_39_ = -1;
			boolean bool = (i_37_ >> 7 & 0x1) == 1;
			player.queueGraphics(i_35_, i_36_, i_38_, i_39_, bool, 1, -2058339374);
		}
		if ((mask & 0x10000) != 0) {
			buffer.bufferIndex += -1603921642;
			int i_40_ = ((buffer.buffer[((buffer.bufferIndex += 1345522827) * -850658525 - 1)]) & 0xff);
			for (int i_41_ = 0; i_41_ < i_40_; i_41_++) {
				int i_42_ = buffer.getUnsignedByte();
				Class214 class214 = (Class214) Class567.method12470(Class214.class, i_42_, -1552238320);
				Class258 class258 = (Class99.aClass188_Sub1_Sub2_1413.method16773(buffer, class214, -1828732140));
				player.anInterface45_11418.method311(class258.anInt4385 * 998647597, class258.anObject4384, 1441051695);
			}
		}
		if (0 != (mask & 0x400000)) {
			String string = buffer.getString();
			int i_43_ = buffer.getUnsignedByteC();
			if ((i_43_ & 0x1) != 0)
				Class587.method12793(2, i_43_, player.getDisplayName(true, -1234718114), player.method17890(false, 747275628), player.name, string, 2041986488);
			player.method17887(string, 0, 0, 235237083);
		}
		if (0 != (mask & 0x4)) {
			int[] is = new int[4];
			for (int i_44_ = 0; i_44_ < 4; i_44_++)
				is[i_44_] = buffer.method14668(-1763525296);
			int i_45_ = buffer.getUnsignedByteA();
			Class77.method1928(player, is, i_45_, false, 1260711785);
		}
		if (0 != (mask & 0x400)) {
			int i_46_ = buffer.getUnsignedShortA();
			int i_47_ = buffer.method14677(314542845);
			if (i_46_ == 65535)
				i_46_ = -1;
			int i_48_ = buffer.getUnsigned128Byte();
			int i_49_ = i_48_ & 0x7;
			int i_50_ = i_48_ >> 3 & 0xf;
			if (i_50_ == 15)
				i_50_ = -1;
			boolean bool = 1 == (i_48_ >> 7 & 0x1);
			player.queueGraphics(i_46_, i_47_, i_49_, i_50_, bool, 2, -2060639202);
		}
		if ((mask & 0x20) != 0) {
			int i_51_ = buffer.getUnsignedShort();
			int i_52_ = buffer.getIntA();
			if (65535 == i_51_)
				i_51_ = -1;
			int i_53_ = buffer.getUnsignedByteA();
			int i_54_ = i_53_ & 0x7;
			int i_55_ = i_53_ >> 3 & 0xf;
			if (i_55_ == 15)
				i_55_ = -1;
			boolean bool = 1 == (i_53_ >> 7 & 0x1);
			player.queueGraphics(i_51_, i_52_, i_54_, i_55_, bool, 0, -2050688745);
		}
		if ((mask & 0x80) != 0) {
			int i_56_ = buffer.getUnsigned128Byte();
			if (i_56_ > 0) {
				for (int i_57_ = 0; i_57_ < i_56_; i_57_++) {
					int i_58_ = -1;
					int i_59_ = -1;
					int i_60_ = -1;
					int i_61_ = buffer.method14505(1979922585);
					if (i_61_ == 32767) {
						i_61_ = buffer.method14505(1824858911);
						i_59_ = buffer.method14505(1952542726);
						i_58_ = buffer.method14505(1990773695);
						i_60_ = buffer.method14505(2136891125);
					} else if (i_61_ != 32766)
						i_59_ = buffer.method14505(1859722287);
					else {
						i_61_ = -1;
						i_59_ = buffer.getUnsigned128Byte();
					}
					int i_62_ = buffer.method14505(1795897168);
					player.method17674(i_61_, i_59_, i_58_, i_60_, client.anInt8431, i_62_, -2096728137);
				}
			}
			int i_63_ = buffer.getUnsignedByteC();
			if (i_63_ > 0) {
				for (int i_64_ = 0; i_64_ < i_63_; i_64_++) {
					int i_65_ = buffer.method14505(1862059524);
					int i_66_ = buffer.method14505(1846458886);
					if (i_66_ != 32767) {
						int i_67_ = buffer.method14505(1765212135);
						int i_68_ = buffer.getUnsignedByte();
						int i_69_ = (i_66_ > 0 ? buffer.getUnsignedByte() : i_68_);
						player.method17698(i_65_, (client.anInt8431), i_66_, i_67_, i_68_, i_69_, 746929482);
					} else
						player.method17665(i_65_, 1151417276);
				}
			}
		}
		if (0 != (mask & 0x40)) {
			player.anInt11383 = buffer.getByteS() * 2100438251;
			player.anInt11385 = buffer.method14525(-1913481995) * -224318085;
			player.anInt11382 = buffer.get() * -1495415935;
			player.anInt11399 = buffer.getByteS() * -1454127533;
			player.anInt11387 = (buffer.getUnsignedShort() + client.anInt8431) * 583024051;
			player.anInt11388 = (buffer.getUnsignedShortLE() + client.anInt8431) * 2068753529;
			player.anInt11389 = buffer.getUnsignedShort() * -79116013;
			player.anInt11383 += (2100438251 * player.anIntArray11419[0]);
			player.anInt11385 += (-224318085 * player.anIntArray11409[0]);
			player.anInt11382 += (player.anIntArray11419[0] * -1495415935);
			player.anInt11399 += (player.anIntArray11409[0] * -1454127533);
			player.anInt11407 = -1083811275;
			player.anInt11412 = 0;
		}
		if ((mask & 0x10) != 0) {
			int size = buffer.getUnsigned128Byte();
			byte[] block = new byte[size];
			RSBuffer appearance = new RSBuffer(block);
			buffer.read(block, 0, size);
			Class201.appearanceBlocks[clientIndex] = appearance;
			player.decodeAppearance(appearance);
		}
	}