UNPKG

@nativewrappers/fivem

Version:

Native wrappers and utilities for use with FiveM.

226 lines (225 loc) 6.56 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); import { GlobalData } from "../GlobalData"; class NetworkedMapEventManager { static { __name(this, "NetworkedMapEventManager"); } #syncedCalls = /* @__PURE__ */ new Map(); constructor() { $SERVER: if (GlobalData.IS_SERVER) { on("playerDropped", () => { const src = source; for (const [_k, map] of this.#syncedCalls) { map.removeSubscriber(src); } }); return; } $CLIENT: { RegisterResourceAsEventHandler(`${GlobalData.CurrentResource}:syncChanges`); addRawEventListener(`${GlobalData.CurrentResource}:syncChanges`, (msgpack_data) => { const data = msgpack_unpack(msgpack_data); const syncName = data[0]; const syncData = data[1]; const map = this.#syncedCalls.get(syncName); if (!map) { throw new Error(`Tried to sync changes for a networked map but ${syncName} does't exist.`); } map.handleSync(syncData); }); } } addNetworkedMap(map) { this.#syncedCalls.set(map.SyncName, map); } removeNetworkedMap(syncName) { this.#syncedCalls.delete(syncName); } } const netManager = new NetworkedMapEventManager(); class NetworkedMap extends Map { static { __name(this, "NetworkedMap"); } #syncName; #queuedChanges = []; #changeListeners = /* @__PURE__ */ new Map(); #subscribers = /* @__PURE__ */ new Set(); constructor(syncName, initialValue) { super(initialValue); this.#syncName = syncName; GlobalData.NetworkedTicks.push(this); netManager.addNetworkedMap(this); $SERVER: { if (!GlobalData.NetworkTick && GlobalData.IS_SERVER) { GlobalData.NetworkTick = setTick(() => { for (const networkedThis of GlobalData.NetworkedTicks) { networkedThis.networkTick(); } }); } } } get SyncName() { return this.#syncName; } // handles removing the player from the map whenever they're dropped onPlayerDropped() { this.removeSubscriber(source); } /* * Resyncs the entire map to the client, useful for if there's a mismatch in the clients map (when multiple players change things, in cases like inventories) * * NOTE: This doesn't check that the player is already subscribed to the map, you should do your own due-diligence to only call this for players already subscribed */ resync(source2) { const packed_data = msgpack_pack([this.#syncName, [[4 /* Init */, this.size === 0 ? [] : Array.from(this)]]]); TriggerClientEventInternal( `${GlobalData.CurrentResource}:syncChanges`, source2, packed_data, packed_data.length ); } /* * Adds a new subscriber to the map */ addSubscriber(source2) { this.#subscribers.add(source2); this.resync(source2); } removeSubscriber(sub) { return this.#subscribers.delete(sub); } hasSubscriber(sub) { return this.#subscribers.has(sub); } subscriberCount() { return this.#subscribers.size; } handleSync(data) { for (const [change_type, key, value, possibly_undefined_subvalue] of data) { switch (change_type) { case 1 /* Add */: { this.set(key, value); continue; } case 2 /* Remove */: { super.delete(key); continue; } case 3 /* Reset */: { super.clear(); continue; } case 4 /* Init */: { super.clear(); const key_value = key; for (const [k, v] of key_value) { this.set(k, v); } continue; } case 0 /* SubValueChanged */: { const data2 = this.get(key); data2[value] = possibly_undefined_subvalue; continue; } } } } /* * Listens for the change on the specified key, it will get the resulting * value on the change */ listenForChange(key, fn) { const listener = this.#changeListeners.get(key); listener ? listener.push(fn) : this.#changeListeners.set(key, [fn]); } #triggerEventForSubscribers(data) { const packed_data = msgpack_pack([this.#syncName, data]); for (const sub of this.#subscribers) { TriggerClientEventInternal( `${GlobalData.CurrentResource}:syncChanges`, sub, packed_data, packed_data.length ); } } #pushChangeForListener(key, value) { const listener = this.#changeListeners.get(key); if (!listener) return; for (const ln of listener) { ln(value); } } set(key, value) { let v = value; if (value instanceof Object) { const curMap = this; const objectChangeHandler = { get(target, prop, reciever) { return Reflect.get(target, prop, reciever); }, set(target, p, newValue, receiver) { const success = Reflect.set(target, p, newValue, receiver); if (success) { curMap.#pushChangeForListener(key, target); $SERVER: { curMap.#queuedChanges.push([0 /* SubValueChanged */, key, p, newValue]); } } return success; } }; v = new Proxy(v, objectChangeHandler); } super.set(key, v); this.#pushChangeForListener(key, v); $SERVER: { this.#queuedChanges.push([1 /* Add */, key, v]); } return this; } /* * Resets the map to its default state */ clear() { $CLIENT: if (GlobalData.IS_CLIENT) throw new Error(`Cannot call 'clear' on client`); this.#queuedChanges = []; this.#queuedChanges.push([3 /* Reset */]); super.clear(); } delete(key) { $CLIENT: if (GlobalData.IS_CLIENT) throw new Error(`Cannot call 'delete' on client`); this.#queuedChanges.push([2 /* Remove */, key]); return super.delete(key); } networkTick() { if (this.#queuedChanges.length !== 0) { this.#triggerEventForSubscribers(this.#queuedChanges); this.#queuedChanges = []; } } [Symbol.dispose]() { this.#subscribers.clear(); this.#changeListeners.clear(); this.#queuedChanges = []; netManager.removeNetworkedMap(this.#syncName); GlobalData.NetworkedTicks.filter((v) => v !== this); } /** * Unregisters from the tick handler and removes the event listener */ dispose() { this[Symbol.dispose](); } get [Symbol.toStringTag]() { return "NetworkedMap"; } } export { NetworkedMap };