Quote from Adam on April 24, 2022, 3:37 pmFigured this out while playing with my client scripts. This will allow you to display a full NPC on an interface model component. Fully animated, and zoom/rotation adjustable. I haven't seen any guides for this so figured I might as well help further the 503+ development. This is done on a Matrix 667 source/client so a few things may be different for you, however I will help if I can.
So basically, there are two methods for displaying a full player model on an interface, the first being displaying your character model and the second being displaying another players character model.
What this does is essentially overwrite that second functionality. That might be a drawback for you, I dont know, but I haven't found anything that actually uses that second method and tbh I find it kinda pointless to display a different players character in most circumstances. But anyway, keep that in mind if you're willing to sacrifice that. There is ways around it, but I can't be asked atm. It was already a grind getting to this point, maybe I'll sort it out in the future.
Lets get started.
IN CLIENT SIDE:1. in IComponentDefinitions.java, create a new boolean variable:
Code:protected boolean isFullNPCModel
Go ahead and set this variable to false in the IComponentDefinitions constructor, which in my source is at the bottom of the file. This condition will be the defining factor that tells the client to display an npc model vs a player model.
--------------------------------------------------------------------------------------------------------2. in Node_Sub38_Sub38.java we'll implement our new widget condition variable.
what it looks like: (Line 538 in my source)
Spoiler for CODE BEFORE:Code:else { IComponentDefinitions widget = Class76.method771((byte) 107, (int) l); int i_32_ = cachenode_sub2.anInt9434; int i_33_ = cachenode_sub2.anInt9432; int i_34_ = cachenode_sub2.anInt9429; if (i_32_ != widget.anInt4844 || i_33_ != widget.anInt4864 || i_34_ != widget.anInt4683) { widget.anInt4683 = i_34_; widget.anInt4844 = i_32_; widget.anInt4864 = i_33_; ClientScript.method2321(-1, widget); } }
how it should look after:
Spoiler for CODE AFTER:Code:else { IComponentDefinitions widget = Class76.method771((byte) 107, (int) l); int i_32_ = cachenode_sub2.anInt9434; int i_33_ = cachenode_sub2.anInt9432; int i_34_ = cachenode_sub2.anInt9429; if (i_32_ != widget.anInt4844 || i_33_ != widget.anInt4864 || i_34_ != widget.anInt4683) { widget.anInt4683 = i_34_; widget.anInt4844 = i_32_; widget.anInt4864 = i_33_; widget.isFullNPCModel = i_34_ == 1; ClientScript.method2321(-1, widget); } }
NOTE: If you'd like to rename a couple of these variables
widget.anInt4844 = modelType
widget.anInt4864 = modelId--------------------------------------------------------------------------------------------------------3. in ClientScriptsExecutor.java, we'll alter OPCODES: 1204, 1211 accordingly. This is done to determine whether or not we can display our own player model which is important for interfaces such as equipment view. Do a CTRL+F for "== 1204)" without the quotation marks.
what it looks like:
Spoiler for CODE BEFORE:Code:if (i == 1204) { widget.anInt4844 = 5; widget.anInt4864 = anIntArray3840[--anInt3846]; if (widget.anInt4687 == -1) { Class52.method528(255, widget.ihash); } return; }
how it should look:
Spoiler for CODE AFTER:Code:if (i == 1204) { widget.anInt4844 = 5; widget.anInt4864 = anIntArray3840[--anInt3846]; widget.isFullNPCModel = true; if (widget.anInt4687 == -1) { Class52.method528(255, widget.ihash); } return; }
now scroll down to opcode == 1211
what it looks like:
Spoiler for CODE BEFORE:Code:if (i == 1211) { widget.anInt4844 = 5; widget.anInt4864 = Class166.myPlayerIndex; widget.anInt4683 = 0; if (widget.anInt4687 == -1) { Class52.method528(255, widget.ihash); } return; }
how it should look:
Spoiler for CODE AFTER:Code:if (i == 1211) { widget.anInt4844 = 5; widget.anInt4864 = Class166.myPlayerIndex; widget.anInt4683 = 0; widget.isFullNPCModel = false; if (widget.anInt4687 == -1) { Class52.method528(255, widget.ihash); } return; }
--------------------------------------------------------------------------------------------------------4. Now go to Node_Sub6.java this is where our model is actually being told to be drawn, we'll be altering a condition that says if our created boolean is true, then take priority to NPC model
do a CTRL+F for "if ((widget.anInt4844 ^ 0xffffffff) == -6) {" without the quotations (line 455 in my source)what it looks like:
Spoiler for CODE BEFORE:Code:if ((widget.anInt4844 ^ 0xffffffff) == -6) { int i_55_ = widget.anInt4864; if (i_55_ >= 0 && i_55_ < 2048) { Player player = Class270_Sub2.LOCAL_PLAYERS[i_55_]; if (player != null && (i_55_ == Class166.myPlayerIndex || (Class51_Sub1.method522(player.aString11142, (byte) -104) ^ 0xffffffff) == (widget.anInt4683 ^ 0xffffffff))) { drawablemodel = player.aPlayerDefinition11137.method3279(EntityNode_Sub3_Sub1.aClass86_9166, widget.anAnimator4755, 97, true, InputStream_Sub2.aClass281_83, Class18.aClass37_306, 2048, null, Class63.aClass363_922, Class42.aClass181_643, Class93.aGraphicsToolkit1241, Class366.aClass279_4526, null, 0, null, Class24.aClass275_442); } } }
how it should look:
Spoiler for CODE AFTER:Code:if (widget.anInt4844 == 5) { int id = widget.anInt4864; Player player = (id >= 0 && id < 2048) ? Class270_Sub2.LOCAL_PLAYERS[id] : null; if (!widget.isFullNPCModel && player != null && (id == Class166.myPlayerIndex || Class51_Sub1.method522(player.aString11142, (byte) -104) == widget.anInt4683)) drawablemodel = player.aPlayerDefinition11137.method3279(EntityNode_Sub3_Sub1.aClass86_9166, widget.anAnimator4755, 97, true, InputStream_Sub2.aClass281_83, Class18.aClass37_306, 2048, null, Class63.aClass363_922, Class42.aClass181_643, Class93.aGraphicsToolkit1241, Class366.aClass279_4526, null, 0, null, Class24.aClass275_442); else drawablemodel = Class366.aClass279_4526.getNPCDefinitions(id, (byte) 107).method3007(Class24.aClass275_442, Class93.aGraphicsToolkit1241, InputStream_Sub2.aClass281_83, (byte) 50, widget.anAnimator4755, 0, null, class361, widget.anAnimator4755, 2048, null); }
NOTE: method3007 might be named "prepareModel" for you
--------------------------------------------------------------------------------------------------------5. in PacketParser.java, Packet 109 (this is display OTHER player full model on interface) will now be used for full npc on interface. We don't need to change anything. Just keep that in mind.
--------------------------------------------------------------------------------------------------------THAT'S IT, now we'll handle the functionality that will take care of zoom and rotation These are already done through existing packets so we'll hop on over to the server side implementation.
IN SERVER SIDE:1. in DefaultGameEncoder.java, or whatever your packets decoder class may be called. We'll create a couple of new functions. If your packet 109 is already being handled, then you can just accommodate it's name for the new functionality:
Spoiler for sendFullNPCOnIComponent():Code:public void sendFullNPCOnIComponent(int interfaceId, int componentId, int npcId, int animationId) { OutputStream stream = new OutputStream(11); stream.writePacket(player, 109); stream.writeIntV1(interfaceId << 16 | componentId); stream.writeShort(npcId); stream.writeIntV2(1); session.write(stream); sendIComponentAnimation(animationId, interfaceId, componentId); }
NOTE: if you don't have packets being handled for animation and rotation/zoom then go ahead and add these two below:
Spoiler for sendIComponentAnimation():Code:public void sendIComponentAnimation(int animationId, int interfaceId, int componentId) { OutputStream stream = new OutputStream(7); stream.writePacket(player, 23); stream.writeShortLE128(animationId); stream.writeIntV1(interfaceId << 16 | componentId); session.write(stream); }
Spoiler for sendIComponent3DView():Code:public void sendIComponent3DView(int interfaceId, int componentId, int zoom, int rotation1, int rotation2) { OutputStream stream = new OutputStream(11); stream.writePacket(player, 122); stream.writeShort128(zoom); stream.writeShortLE128(rotation1); stream.writeIntLE(interfaceId << 16 | componentId); stream.writeShortLE128(rotation2); session.write(stream); }
--------------------------------------------------------------------------------------------------------AND DONE!
--------------------------------------------------------------------------------------------------------Now, if you'd like to test it. You can try it on interface 1279, component 1. You can make some quick commands for this:
Code:if(cmd[0].equals("set3dview")) player.getPackets().sendIComponent3DView(Integer.parseInt(cmd[1]), Integer.parseInt(cmd[2]), Integer.parseInt(cmd[3]), Integer.parseInt(cmd[4]), Integer.parseInt(cmd[4])); else if(cmd[0].equals("setnpc")) player.getPackets().sendFullNPCOnIComponent(Integer.parseInt(cmd[1]), Integer.parseInt(cmd[2]), Integer.parseInt(cmd[3]), Integer.parseInt(cmd[4]));
and then, just open up interface 1279 using your interface command, whatever it may be:
Code:::inter 1279
and then run your commands as so (these are the values I used in the gif below, it will set graardor with his stance emote)
Code:::setnpc 1279 1 6260 7059 ::set3dview 1279 1 2000 150 0
and voila:
--------------------------------------------------------------------------------------------------------I wrote this very quick and I've been awake for like 30 hours so apologies if I missed anything. I'll go over this again tomorrow or something, but I don't think I missed anything. Please let me know otherwise! Enjoy!
Figured this out while playing with my client scripts. This will allow you to display a full NPC on an interface model component. Fully animated, and zoom/rotation adjustable. I haven't seen any guides for this so figured I might as well help further the 503+ development. This is done on a Matrix 667 source/client so a few things may be different for you, however I will help if I can.
So basically, there are two methods for displaying a full player model on an interface, the first being displaying your character model and the second being displaying another players character model.
What this does is essentially overwrite that second functionality. That might be a drawback for you, I dont know, but I haven't found anything that actually uses that second method and tbh I find it kinda pointless to display a different players character in most circumstances. But anyway, keep that in mind if you're willing to sacrifice that. There is ways around it, but I can't be asked atm. It was already a grind getting to this point, maybe I'll sort it out in the future.
Lets get started.
1. in IComponentDefinitions.java, create a new boolean variable:
protected boolean isFullNPCModel
Go ahead and set this variable to false in the IComponentDefinitions constructor, which in my source is at the bottom of the file. This condition will be the defining factor that tells the client to display an npc model vs a player model.
2. in Node_Sub38_Sub38.java we'll implement our new widget condition variable.
what it looks like: (Line 538 in my source)
else {
IComponentDefinitions widget = Class76.method771((byte) 107, (int) l);
int i_32_ = cachenode_sub2.anInt9434;
int i_33_ = cachenode_sub2.anInt9432;
int i_34_ = cachenode_sub2.anInt9429;
if (i_32_ != widget.anInt4844 || i_33_ != widget.anInt4864 || i_34_ != widget.anInt4683) {
widget.anInt4683 = i_34_;
widget.anInt4844 = i_32_;
widget.anInt4864 = i_33_;
ClientScript.method2321(-1, widget);
}
}
how it should look after:
else {
IComponentDefinitions widget = Class76.method771((byte) 107, (int) l);
int i_32_ = cachenode_sub2.anInt9434;
int i_33_ = cachenode_sub2.anInt9432;
int i_34_ = cachenode_sub2.anInt9429;
if (i_32_ != widget.anInt4844 || i_33_ != widget.anInt4864 || i_34_ != widget.anInt4683) {
widget.anInt4683 = i_34_;
widget.anInt4844 = i_32_;
widget.anInt4864 = i_33_;
widget.isFullNPCModel = i_34_ == 1;
ClientScript.method2321(-1, widget);
}
}
NOTE: If you'd like to rename a couple of these variables
widget.anInt4844 = modelType
widget.anInt4864 = modelId
3. in ClientScriptsExecutor.java, we'll alter OPCODES: 1204, 1211 accordingly. This is done to determine whether or not we can display our own player model which is important for interfaces such as equipment view. Do a CTRL+F for "== 1204)" without the quotation marks.
what it looks like:
if (i == 1204) {
widget.anInt4844 = 5;
widget.anInt4864 = anIntArray3840[--anInt3846];
if (widget.anInt4687 == -1) {
Class52.method528(255, widget.ihash);
}
return;
}
how it should look:
if (i == 1204) {
widget.anInt4844 = 5;
widget.anInt4864 = anIntArray3840[--anInt3846];
widget.isFullNPCModel = true;
if (widget.anInt4687 == -1) {
Class52.method528(255, widget.ihash);
}
return;
}
now scroll down to opcode == 1211
what it looks like:
if (i == 1211) {
widget.anInt4844 = 5;
widget.anInt4864 = Class166.myPlayerIndex;
widget.anInt4683 = 0;
if (widget.anInt4687 == -1) {
Class52.method528(255, widget.ihash);
}
return;
}
how it should look:
if (i == 1211) {
widget.anInt4844 = 5;
widget.anInt4864 = Class166.myPlayerIndex;
widget.anInt4683 = 0;
widget.isFullNPCModel = false;
if (widget.anInt4687 == -1) {
Class52.method528(255, widget.ihash);
}
return;
}
4. Now go to Node_Sub6.java this is where our model is actually being told to be drawn, we'll be altering a condition that says if our created boolean is true, then take priority to NPC model
do a CTRL+F for "if ((widget.anInt4844 ^ 0xffffffff) == -6) {" without the quotations (line 455 in my source)
what it looks like:
if ((widget.anInt4844 ^ 0xffffffff) == -6) {
int i_55_ = widget.anInt4864;
if (i_55_ >= 0 && i_55_ < 2048) {
Player player = Class270_Sub2.LOCAL_PLAYERS[i_55_];
if (player != null && (i_55_ == Class166.myPlayerIndex || (Class51_Sub1.method522(player.aString11142, (byte) -104) ^ 0xffffffff) == (widget.anInt4683 ^ 0xffffffff))) {
drawablemodel = player.aPlayerDefinition11137.method3279(EntityNode_Sub3_Sub1.aClass86_9166, widget.anAnimator4755, 97, true, InputStream_Sub2.aClass281_83, Class18.aClass37_306, 2048, null, Class63.aClass363_922, Class42.aClass181_643, Class93.aGraphicsToolkit1241, Class366.aClass279_4526, null, 0, null, Class24.aClass275_442);
}
}
}
how it should look:
if (widget.anInt4844 == 5) {
int id = widget.anInt4864;
Player player = (id >= 0 && id < 2048) ? Class270_Sub2.LOCAL_PLAYERS[id] : null;
if (!widget.isFullNPCModel && player != null && (id == Class166.myPlayerIndex || Class51_Sub1.method522(player.aString11142, (byte) -104) == widget.anInt4683))
drawablemodel = player.aPlayerDefinition11137.method3279(EntityNode_Sub3_Sub1.aClass86_9166, widget.anAnimator4755, 97, true, InputStream_Sub2.aClass281_83, Class18.aClass37_306, 2048, null, Class63.aClass363_922, Class42.aClass181_643, Class93.aGraphicsToolkit1241, Class366.aClass279_4526, null, 0, null, Class24.aClass275_442);
else
drawablemodel = Class366.aClass279_4526.getNPCDefinitions(id, (byte) 107).method3007(Class24.aClass275_442, Class93.aGraphicsToolkit1241, InputStream_Sub2.aClass281_83, (byte) 50, widget.anAnimator4755, 0, null, class361, widget.anAnimator4755, 2048, null);
}
NOTE: method3007 might be named "prepareModel" for you
5. in PacketParser.java, Packet 109 (this is display OTHER player full model on interface) will now be used for full npc on interface. We don't need to change anything. Just keep that in mind.
THAT'S IT, now we'll handle the functionality that will take care of zoom and rotation These are already done through existing packets so we'll hop on over to the server side implementation.
1. in DefaultGameEncoder.java, or whatever your packets decoder class may be called. We'll create a couple of new functions. If your packet 109 is already being handled, then you can just accommodate it's name for the new functionality:
public void sendFullNPCOnIComponent(int interfaceId, int componentId, int npcId, int animationId) {
OutputStream stream = new OutputStream(11);
stream.writePacket(player, 109);
stream.writeIntV1(interfaceId << 16 | componentId);
stream.writeShort(npcId);
stream.writeIntV2(1);
session.write(stream);
sendIComponentAnimation(animationId, interfaceId, componentId);
}
NOTE: if you don't have packets being handled for animation and rotation/zoom then go ahead and add these two below:
public void sendIComponentAnimation(int animationId, int interfaceId, int componentId) {
OutputStream stream = new OutputStream(7);
stream.writePacket(player, 23);
stream.writeShortLE128(animationId);
stream.writeIntV1(interfaceId << 16 | componentId);
session.write(stream);
}
public void sendIComponent3DView(int interfaceId, int componentId, int zoom, int rotation1, int rotation2) {
OutputStream stream = new OutputStream(11);
stream.writePacket(player, 122);
stream.writeShort128(zoom);
stream.writeShortLE128(rotation1);
stream.writeIntLE(interfaceId << 16 | componentId);
stream.writeShortLE128(rotation2);
session.write(stream);
}
AND DONE!
Now, if you'd like to test it. You can try it on interface 1279, component 1. You can make some quick commands for this:
if(cmd[0].equals("set3dview"))
player.getPackets().sendIComponent3DView(Integer.parseInt(cmd[1]), Integer.parseInt(cmd[2]), Integer.parseInt(cmd[3]), Integer.parseInt(cmd[4]), Integer.parseInt(cmd[4]));
else if(cmd[0].equals("setnpc"))
player.getPackets().sendFullNPCOnIComponent(Integer.parseInt(cmd[1]), Integer.parseInt(cmd[2]), Integer.parseInt(cmd[3]), Integer.parseInt(cmd[4]));
and then, just open up interface 1279 using your interface command, whatever it may be:
::inter 1279
and then run your commands as so (these are the values I used in the gif below, it will set graardor with his stance emote)
::setnpc 1279 1 6260 7059
::set3dview 1279 1 2000 150 0
and voila:
I wrote this very quick and I've been awake for like 30 hours so apologies if I missed anything. I'll go over this again tomorrow or something, but I don't think I missed anything. Please let me know otherwise! Enjoy!