Skip to content

Instantly share code, notes, and snippets.

@root-cause
Created October 18, 2024 18:57
Show Gist options
  • Save root-cause/2a829cf29dffa849452d36cc3cdc91c3 to your computer and use it in GitHub Desktop.
Save root-cause/2a829cf29dffa849452d36cc3cdc91c3 to your computer and use it in GitHub Desktop.
const NUM_COMPONENT_TYPES = 16;
const ENTITY_TYPE_PLAYER = 0;
const GET_SHOP_PED_COMPONENT = 0x74C0E2A57EC66760n;
const GET_SHOP_PED_PROP = 0x5D5CAFF661DDF6FCn;
function isBitSet(num, position) {
return (num & (1 << position)) !== 0;
}
function applyComponentsFromBuffer(player, componentBytes) {
if (!mp.players.exists(player) || player.handle === 0 || componentBytes == null) {
// invalid player or no component data
return;
}
const reader = new DataView(componentBytes);
let readOffset = 0;
// read component bits
const componentBits = reader.getInt16(readOffset);
readOffset += Int16Array.BYTES_PER_ELEMENT;
if (componentBits === 0) {
// nothing is set, no need to go further
return;
}
// read and apply set components
// this can be smarter and apply only the changed components... I guess
let buffer = [ new ArrayBuffer(136) ];
for (let componentType = 0; componentType < NUM_COMPONENT_TYPES; componentType++) {
if (!isBitSet(componentBits, componentType)) {
continue;
}
const isProp = componentType >= 11;
const hash = reader.getInt32(readOffset);
readOffset += Int32Array.BYTES_PER_ELEMENT;
if (!mp.game.invoke((isProp ? GET_SHOP_PED_PROP : GET_SHOP_PED_COMPONENT), hash, buffer)) {
continue;
}
const componentView = new DataView(buffer[0]);
const drawable = componentView.getInt32(24, true);
const texture = componentView.getInt32(32, true);
const slot = componentView.getInt32(48, true);
if (isProp) {
player.setPropIndex(slot, drawable, texture, false);
} else {
player.setComponentVariation(slot, drawable, texture, 0);
}
}
}
// Event handlers
mp.events.add("entityStreamIn", (entity) => {
if (entity.typeInt === ENTITY_TYPE_PLAYER) {
applyComponentsFromBuffer(entity, entity.getVariable("_componentBytes"));
}
});
mp.events.addDataHandler("_componentBytes", (entity, newComponentBytes) => {
if (entity.typeInt === ENTITY_TYPE_PLAYER && entity.handle !== 0) {
applyComponentsFromBuffer(entity, newComponentBytes);
}
});
// https://wiki.rage.mp/index.php?title=Clothes except clothing and prop types are combined
const ComponentType = Object.freeze({
// clothes
HEAD_UNUSED: 0, // head
BERD: 1, // mask
HAIR: 2, // hair style
UPPR: 3, // torso
LOWR: 4, // pants
HAND: 5, // bags and parachutes
FEET: 6, // shoes
TEEF: 7, // accessories
ACCS: 8, // undershirts
TASK: 9, // body armors
DECL: 10, // decals
JBIB: 11, // tops
// props
HEAD: 12, // hats
EYES: 13, // glasses
EARS: 14, // earrings
LEFT_WRIST: 15, // watches
RIGHT_WRIST: 16, // bracelets
});
// Give yourself the N.O.O.S.E. outfit (as mp_m_freemode_01)
mp.events.addCommand("swat", player => {
// put on the mask
player.setComponentHash(ComponentType.BERD, mp.joaat("DLC_MP_HEIST_M_BERD_17_0"));
// ...then the rest
player.setComponentHashes([
[ComponentType.UPPR, mp.joaat("DLC_MP_LTS_M_UPPR_0_0")],
[ComponentType.LOWR, mp.joaat("DLC_MP_H3_M_LEGS_1_0")],
[ComponentType.FEET, mp.joaat("DLC_MP_PILOT_M_FEET_0_0")],
[ComponentType.DECL, mp.joaat("DLC_MP_H3_M_DECL_6_1")],
[ComponentType.JBIB, mp.joaat("DLC_MP_H3_M_JBIB_6_0")],
[ComponentType.HEAD, mp.joaat("DLC_MP_H3_M_PHEAD_4_0")]
]);
// since this undershirt doesn't have a hash, we're using its id
player.setClothes(8, 15, 0, 0);
});
// Remove your mask
mp.events.addCommand("face_reveal", player => {
player.setComponentHash(ComponentType.BERD, 0);
// removals still need setClothes/setProp to be called
player.setClothes(ComponentType.BERD, 0, 0, 0);
});
const NUM_COMPONENT_TYPES = 16;
function setBit(num, position) {
return num | (1 << position);
}
function setComponentHashInternal(player, componentType, hash) {
if (!Number.isInteger(componentType)) {
throw new TypeError("'componentType' must be an integer");
} else if (!Number.isInteger(hash)) {
throw new TypeError("'hash' must be an integer");
} else if (componentType < 1 || componentType > NUM_COMPONENT_TYPES) {
throw new RangeError(`'componentType' must be between 1 and ${NUM_COMPONENT_TYPES}`);
}
// component 0 is the head which is set by player.setCustomization, we'll use it to store masks instead
player.__components[componentType - 1] = hash >> 0;
}
function serializeComponents(components) {
let componentBits = 0;
// calculate required buffer size as well as setting active component bits
let bufferSize = Int16Array.BYTES_PER_ELEMENT;
for (let i = 0; i < components.length; i++) {
if (components[i] !== 0) {
componentBits = setBit(componentBits, i);
bufferSize += Int32Array.BYTES_PER_ELEMENT;
}
}
const buffer = new ArrayBuffer(bufferSize);
const writer = new DataView(buffer);
let writerOffset = 0;
// write set bits
writer.setInt16(writerOffset, componentBits);
writerOffset += Int16Array.BYTES_PER_ELEMENT;
// write hashes
for (const componentHash of components) {
if (componentHash !== 0) {
writer.setInt32(writerOffset, componentHash);
writerOffset += Int32Array.BYTES_PER_ELEMENT;
}
}
return buffer;
}
// Player functions
mp.Player.prototype.setComponentHash = function(componentType, hash) {
setComponentHashInternal(this, componentType, hash);
this.setVariable("_componentBytes", serializeComponents(this.__components));
}
mp.Player.prototype.getComponentHash = function(componentType) {
if (!Number.isInteger(componentType)) {
throw new TypeError("'componentType' must be an integer");
} else if (componentType < 1 || componentType > NUM_COMPONENT_TYPES) {
throw new RangeError(`'componentType' must be between 1 and ${NUM_COMPONENT_TYPES}`);
}
return this.__components[componentType - 1];
}
mp.Player.prototype.setComponentHashes = function(components) {
if (!Array.isArray(components)) {
throw new TypeError("'components' must be an array");
}
for (const item of components) {
const [componentType, hash] = item;
setComponentHashInternal(this, componentType, hash);
}
this.setVariable("_componentBytes", serializeComponents(this.__components));
}
// RAGEMP events
function onPlayerJoin(player) {
player.__components = new Int32Array(NUM_COMPONENT_TYPES);
}
function onPlayerQuit(player) {
delete player.__components;
}
mp.events.add({
"playerJoin": onPlayerJoin,
"playerQuit": onPlayerQuit
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment