├── ClientSide └── resources │ ├── sarp-streamer-door │ ├── resource.cfg │ ├── client.mjs │ └── door-streamer.mjs │ ├── sarp-streamer-text │ ├── resource.cfg │ ├── client.mjs │ └── textlabel-streamer.mjs │ ├── sarp-streamer-helptext │ ├── resource.cfg │ ├── helptext-streamer.mjs │ └── client.mjs │ ├── sarp-streamer-marker │ ├── resource.cfg │ ├── client.mjs │ └── marker-streamer.mjs │ ├── sarp-streamer-blip │ ├── dynamic-blip-streamer.mjs │ ├── static-blip-streamer.mjs │ ├── resource.cfg │ └── client.mjs │ └── sarp-streamer-object │ ├── resource.cfg │ ├── async-models.mjs │ ├── client.mjs │ └── object-streamer.mjs ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── LICENSE ├── ServerSide ├── Override │ └── CustomSpatialPartition.cs ├── DoorManager.cs ├── HelpTextManager.cs ├── BlipManager.cs ├── PlayerLabelManager.cs ├── PropManager.cs └── MarkerManager.cs └── README.md /ClientSide/resources/sarp-streamer-door/resource.cfg: -------------------------------------------------------------------------------- 1 | type: js 2 | client-main: client.mjs 3 | 4 | client-files: [ 5 | client.mjs, 6 | door-streamer.mjs 7 | ] -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-text/resource.cfg: -------------------------------------------------------------------------------- 1 | type: js 2 | client-main: client.mjs 3 | 4 | client-files: [ 5 | client.mjs, 6 | textlabel-streamer.mjs 7 | ] -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-helptext/resource.cfg: -------------------------------------------------------------------------------- 1 | type: js 2 | client-main: client.mjs 3 | 4 | client-files: [ 5 | client.mjs, 6 | helptext-streamer.mjs 7 | ] 8 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-marker/resource.cfg: -------------------------------------------------------------------------------- 1 | type: js 2 | client-main: client.mjs 3 | 4 | client-files: [ 5 | client.mjs, 6 | marker-streamer.mjs, 7 | ] -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-blip/dynamic-blip-streamer.mjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LameuleFR/altv-csharp-streamer/HEAD/ClientSide/resources/sarp-streamer-blip/dynamic-blip-streamer.mjs -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-blip/static-blip-streamer.mjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LameuleFR/altv-csharp-streamer/HEAD/ClientSide/resources/sarp-streamer-blip/static-blip-streamer.mjs -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-helptext/helptext-streamer.mjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LameuleFR/altv-csharp-streamer/HEAD/ClientSide/resources/sarp-streamer-helptext/helptext-streamer.mjs -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-object/resource.cfg: -------------------------------------------------------------------------------- 1 | type: js 2 | client-main: client.mjs 3 | 4 | client-files: [ 5 | client.mjs, 6 | object-streamer.mjs, 7 | async-models.mjs 8 | ] -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-blip/resource.cfg: -------------------------------------------------------------------------------- 1 | type: js 2 | client-main: client.mjs 3 | 4 | client-files: [ 5 | client.mjs, 6 | dynamic-blip-streamer.mjs 7 | static-blip-streamer.mjs 8 | ] 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Additional context** 24 | Add any other context about the problem here. 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 lameule123 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-helptext/client.mjs: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt'; 2 | import { helpTextStreamer } from "./helptext-streamer"; 3 | 4 | // when an object is streamed in 5 | alt.onServer("entitySync:create", (entityId, entityType, position, currEntityData) => { 6 | if (currEntityData) { 7 | let data = currEntityData; 8 | if (data != undefined) { 9 | if (entityType === 3) { 10 | helpTextStreamer.addHelpText( 11 | +entityId, data.text, position, +entityType 12 | ); 13 | } 14 | } 15 | } else { 16 | if (entityType === 3) { 17 | helpTextStreamer.restoreHelpText(+entityId); 18 | } 19 | } 20 | }); 21 | 22 | // when an object is streamed out 23 | alt.onServer("entitySync:remove", (entityId, entityType) => { 24 | if (entityType === 3) { 25 | helpTextStreamer.removeHelpText(+entityId); 26 | } 27 | }); 28 | 29 | // when a streamed in object changes position data 30 | alt.onServer("entitySync:updatePosition", (entityId, entityType, position) => { 31 | if (entityType === 3) { 32 | helpTextStreamer.setPosition(+entityId, position); 33 | } 34 | }); 35 | 36 | // when a streamed in object changes data 37 | alt.onServer("entitySync:updateData", (entityId, entityType, newEntityData) => { 38 | if (entityType === 3) { 39 | if (newEntityData.hasOwnProperty("text")) 40 | helpTextStreamer.setText(+entityId, newEntityData.text); 41 | } 42 | }); 43 | 44 | // when a streamed in object needs to be removed 45 | alt.onServer("entitySync:clearCache", (entityId, entityType) => { 46 | if (entityType === 3) { 47 | helpTextStreamer.clearHelpText(+entityId); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-door/client.mjs: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt-client'; 2 | import { doorStreamer } from "./door-streamer"; 3 | const ThreadID = 6; 4 | 5 | // when an object is streamed in 6 | alt.onServer("entitySync:create", (entityId, entityType, position, currEntityData) => { 7 | if (currEntityData) { 8 | let data = currEntityData; 9 | if (data != undefined) { 10 | if (entityType === ThreadID) { 11 | doorStreamer.addDoor( 12 | +entityId, position, data.heading, +entityType, data.hash, data.locked 13 | ); 14 | } 15 | } 16 | } else { 17 | if (entityType === ThreadID) { 18 | doorStreamer.restoreDoor(+entityId); 19 | } 20 | } 21 | }); 22 | 23 | // when an object is streamed out 24 | alt.onServer("entitySync:remove", (entityId, entityType) => { 25 | if (entityType === ThreadID) { 26 | doorStreamer.removeDoor(+entityId); 27 | } 28 | }); 29 | 30 | // when a streamed in object changes position data 31 | alt.onServer("entitySync:updatePosition", (entityId, entityType, position) => { 32 | if (entityType === ThreadID) { 33 | doorStreamer.setPosition(+entityId, position); 34 | } 35 | }); 36 | 37 | // when a streamed in object changes data 38 | alt.onServer("entitySync:updateData", (entityId, entityType, newEntityData) => { 39 | if (entityType === ThreadID) { 40 | if (newEntityData.hasOwnProperty("locked")) 41 | doorStreamer.setState(+entityId, newEntityData.locked); 42 | } 43 | }); 44 | 45 | // when a streamed in object needs to be removed 46 | alt.onServer("entitySync:clearCache", (entityId, entityType) => { 47 | if (entityType === ThreadID) { 48 | doorStreamer.clearDoor(+entityId); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /ServerSide/Override/CustomSpatialPartition.cs: -------------------------------------------------------------------------------- 1 | using AltV.Net.EntitySync; 2 | using AltV.Net.EntitySync.SpatialPartitions; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Numerics; 6 | 7 | /// 8 | /// THIS OVERRIDE IS ONLY USED BY BLIPMANAGER(Static) CAUSE THIS TYPE OF BLIP ARE ONLY DESIGNED TO BE STATIC. 9 | /// WE NEED TO VERIFY DIMENSION ONLY INSTEAD OF POSITION + DIMENSION. 10 | /// 11 | namespace EntityStreamer 12 | { 13 | public class GlobalEntity : SpatialPartition 14 | { 15 | private readonly HashSet entities = new HashSet(); 16 | 17 | public GlobalEntity() 18 | { 19 | } 20 | 21 | public override void Add(IEntity entity) 22 | { 23 | entities.Add(entity); 24 | } 25 | 26 | public override void Remove(IEntity entity) 27 | { 28 | entities.Remove(entity); 29 | } 30 | 31 | public override void UpdateEntityPosition(IEntity entity, in Vector3 newPosition) 32 | { 33 | } 34 | 35 | public override void UpdateEntityRange(IEntity entity, uint range) 36 | { 37 | } 38 | 39 | public override void UpdateEntityDimension(IEntity entity, int dimension) 40 | { 41 | } 42 | 43 | private static bool CanSeeOtherDimension(int dimension, int otherDimension) 44 | { 45 | if (dimension > 0) return dimension == otherDimension || otherDimension == int.MinValue; 46 | if (dimension < 0) 47 | return otherDimension == 0 || dimension == otherDimension || otherDimension == int.MinValue; 48 | return otherDimension == 0 || otherDimension == int.MinValue; 49 | } 50 | 51 | public override IList Find(Vector3 position, int dimension) 52 | { 53 | return entities.Where(entity => CanSeeOtherDimension(dimension, entity.Dimension)).ToList(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-object/async-models.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | Developed by Micaww 3 | */ 4 | 5 | import * as alt from 'alt'; 6 | import * as natives from 'natives'; 7 | 8 | class AsyncModel { 9 | constructor( ) { 10 | this.loadingModels = new Set( ); 11 | } 12 | 13 | cancel( entityId, entityType ) { 14 | this.loadingModels.delete( +entityId, +entityType ); 15 | } 16 | 17 | async load( entityId, entityType, model ) { 18 | return new Promise( resolve => { 19 | if( !natives.isModelValid( model ) ) 20 | return done( false ); 21 | 22 | if( natives.hasModelLoaded( model ) ) 23 | return done( true ); 24 | 25 | if( typeof model === 'string' ) 26 | model = alt.hash( model ); 27 | 28 | this.loadingModels.add( +entityId, +entityType ); 29 | 30 | natives.requestModel( model ); 31 | 32 | const interval = alt.setInterval( () => { 33 | if( !this.loadingModels.has( +entityId, +entityType ) ) { 34 | return done( !!natives.hasModelLoaded( model ) ); 35 | } 36 | 37 | if( natives.hasModelLoaded( model ) ) { 38 | return done( true ); 39 | } 40 | }, 2 ); 41 | 42 | const timeout = alt.setTimeout( ( ) => { 43 | return done( !!natives.hasModelLoaded( model ) ); 44 | }, 3000 ); 45 | 46 | const done = result => { 47 | alt.clearInterval( interval ); 48 | alt.clearTimeout( timeout ); 49 | 50 | this.loadingModels.delete( +entityId, +entityType ); 51 | resolve( result ); 52 | }; 53 | } ); 54 | } 55 | } 56 | 57 | export const asyncModel = new AsyncModel(); 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # altv-csharp-streamer 2 | ServerSide Streamer using EntitySync. 3 | Initially Based on @DasNiels Work. 4 | 5 | # Current Streamer Available 6 | - TextLabel 7 | - HelpText 8 | - Marker 9 | - Object 10 | - Blip (Static/Dynamic) 11 | - Door 12 | 13 | # Installation 14 | This resource makes use of the AltV.Net.EntitySync (v1.13.0) and AltV.Net.EntitySync.ServerEvent (v9.0.2) nuget package, make sure to install those prior to using this resource. 15 | 16 | Make sure to add the following code to your gamemode's OnStart() method(the streamer won't work without it!): 17 | // Documentation: https://docs.altv.mp/cs/articles/entity-sync.html 18 | 19 | ``` 20 | AltEntitySync.Init(8, (syncrate) => 200, (threadId) => false, 21 | (threadCount, repository) => new ServerEventNetworkLayer(threadCount, repository), 22 | (entity, threadCount) => entity.Type, 23 | (entityId, entityType, threadCount) => entityType, 24 | (threadId) => 25 | { 26 | return threadId switch 27 | { 28 | //MARKER 29 | 0 => new LimitedGrid3(50_000, 50_000, 75, 10_000, 10_000, 64), 30 | //TEXT 31 | 1 => new LimitedGrid3(50_000, 50_000, 75, 10_000, 10_000, 32), 32 | //PROP 33 | 2 => new LimitedGrid3(50_000, 50_000, 100, 10_000, 10_000, 1500), 34 | //HELP TEXT 35 | 3 => new LimitedGrid3(50_000, 50_000, 100, 10_000, 10_000, 1), 36 | //BLIP 37 | 4 => new EntityStreamer.GlobalEntity(), 38 | //DYNAMIC BLIP 39 | 5 => new LimitedGrid3(50_000, 50_000, 175, 10_000, 10_000, 200), 40 | //DOOR 41 | 6 => new LimitedGrid3(50_000, 50_000, 175, 10_000, 10_000, 50), 42 | _ => new LimitedGrid3(50_000, 50_000, 175, 10_000, 10_000, 115), 43 | }; 44 | }, 45 | new IdProvider()); 46 | ``` 47 | 48 | # Credits 49 | Original Object Streamer: https://github.com/DasNiels/altv-object-streamer/ 50 | Original Text Streamer: https://github.com/DasNiels/altv-textlabel-streamer 51 | Original Marker Streamer: https://github.com/DasNiels/altv-marker-streamer 52 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-door/door-streamer.mjs: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt-client'; 2 | import * as native from 'natives'; 3 | 4 | class DoorStreamer { 5 | constructor() { 6 | this.doors = []; 7 | } 8 | async addDoor(entityId, position, heading, entityType, hash, locked) { 9 | this.clearDoor(+entityId); 10 | let door = { 11 | hash: hash, locked: locked, position: position, 12 | heading: heading, entityId: +entityId, entityType: +entityType, 13 | }; 14 | this.doors[entityId] = door; 15 | this.setState(entityId, locked); 16 | } 17 | getDoor(entityId) { 18 | if (this.doors.hasOwnProperty(entityId)) { 19 | return this.doors[entityId]; 20 | } 21 | else { 22 | return null; 23 | } 24 | } 25 | restoreDoor(entityId) { 26 | if (this.doors.hasOwnProperty(entityId)) { 27 | let door = this.doors[entityId]; 28 | this.setState(entityId, door.locked); 29 | } 30 | } 31 | removeDoor(entityId) { 32 | if (this.doors.hasOwnProperty(entityId)) { 33 | delete this.doors[entityId]; 34 | } 35 | } 36 | clearDoor(entityId) { 37 | if (this.doors.hasOwnProperty(entityId)) { 38 | delete this.doors[entityId]; 39 | } 40 | } 41 | clearAllDoor() { 42 | this.doors = []; 43 | } 44 | setPosition(entityId, pos) { 45 | if (this.doors.hasOwnProperty(entityId)) { 46 | this.doors[entityId].position = pos; 47 | } 48 | } 49 | setState(entityId, locked) { 50 | if (this.doors.hasOwnProperty(entityId)) { 51 | let door = this.doors[entityId]; 52 | native.setStateOfClosestDoorOfType(door.hash, 53 | door.position.x, 54 | door.position.y, 55 | door.position.z, 56 | locked, 57 | door.heading, 58 | false); 59 | native.doorControl(door.hash, door.position.x, door.position.y, door.position.z, door.locked, 0.0, door.heading, 0.0); 60 | } 61 | } 62 | } 63 | export const doorStreamer = new DoorStreamer(); 64 | 65 | alt.on("resourceStop", () => { 66 | doorStreamer.clearAllDoor(); 67 | }); 68 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-text/client.mjs: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt'; 2 | import { textLabelStreamer } from "./textlabel-streamer"; 3 | 4 | // when an object is streamed in 5 | alt.onServer("entitySync:create", (entityId, entityType, position, currEntityData) => { 6 | if( currEntityData ) { 7 | let data = currEntityData; 8 | if(data != undefined) { 9 | if (entityType == 1){ 10 | textLabelStreamer.addTextLabel( 11 | +entityId, data.text, position, data.scale, data.font, data.color, data.dropShadow, data.edge, data.center, data.proportional, +entityType 12 | ); 13 | } 14 | } 15 | }else{ 16 | if (entityType == 1){ 17 | textLabelStreamer.restoreTextLabel( +entityId ); 18 | } 19 | } 20 | }); 21 | 22 | // when an object is streamed out 23 | alt.onServer("entitySync:remove", (entityId, entityType) => { 24 | if (entityType == 1){ 25 | textLabelStreamer.removeTextLabel( +entityId ); 26 | } 27 | } ); 28 | 29 | // when a streamed in object changes position data 30 | alt.onServer("entitySync:updatePosition", (entityId, entityType, position) => { 31 | if (entityType == 1){ 32 | textLabelStreamer.setPosition( +entityId, position ); 33 | } 34 | } ); 35 | 36 | // when a streamed in object changes data 37 | alt.onServer("entitySync:updateData", (entityId, entityType, newEntityData) => { 38 | if (entityType == 1){ 39 | if( newEntityData.hasOwnProperty( "center" ) ) 40 | textLabelStreamer.setCenter( +entityId , newEntityData.center ); 41 | 42 | if( newEntityData.hasOwnProperty( "color" ) ) 43 | textLabelStreamer.setColor( +entityId , newEntityData.color ); 44 | 45 | if( newEntityData.hasOwnProperty( "center" ) ) 46 | textLabelStreamer.setDropShadow( +entityId , newEntityData.center ); 47 | 48 | if( newEntityData.hasOwnProperty( "edge" ) ) 49 | textLabelStreamer.setEdge( +entityId , newEntityData.edge ); 50 | 51 | if( newEntityData.hasOwnProperty( "font" ) ) 52 | textLabelStreamer.setFont( +entityId , newEntityData.font ); 53 | 54 | if( newEntityData.hasOwnProperty( "proportional" ) ) 55 | textLabelStreamer.setProportional( +entityId , newEntityData.proportional ); 56 | 57 | if( newEntityData.hasOwnProperty( "scale" ) ) 58 | textLabelStreamer.setScale( +entityId , newEntityData.scale ); 59 | 60 | if( newEntityData.hasOwnProperty( "text" ) ) 61 | textLabelStreamer.setText( +entityId , newEntityData.text ); 62 | } 63 | } ); 64 | 65 | // when a streamed in object needs to be removed 66 | alt.onServer("entitySync:clearCache", (entityId, entityType) => { 67 | if (entityType == 1){ 68 | textLabelStreamer.clearTextLabel( +entityId ); 69 | } 70 | } ); 71 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-blip/client.mjs: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt'; 2 | import { staticBlipStreamer } from "./static-blip-streamer"; 3 | import { dynamicBlipStreamer } from "./dynamic-blip-streamer"; 4 | const BLIPTYPE = 4; 5 | const BLIPTYPE2 = 5; 6 | 7 | // when an object is streamed in 8 | alt.onServer("entitySync:create", (entityId, entityType, position, currEntityData) => { 9 | if (currEntityData) { 10 | let data = currEntityData; 11 | if (data != undefined) { 12 | if (entityType === BLIPTYPE) { 13 | staticBlipStreamer.addBlip( 14 | +entityId, position, data.name, data.sprite, data.color, data.scale, data.shortRange 15 | ); 16 | }else if (entityType === BLIPTYPE2){ 17 | dynamicBlipStreamer.addBlip( 18 | +entityId, position, data.name, data.sprite, data.color, data.scale, data.shortRange 19 | ); 20 | } 21 | } 22 | } else { 23 | if (entityType === BLIPTYPE) { 24 | staticBlipStreamer.restoreBlip(+entityId); 25 | }else if (entityType === BLIPTYPE2){ 26 | dynamicBlipStreamer.restoreBlip(+entityId); 27 | } 28 | } 29 | }); 30 | 31 | // when an object is streamed out 32 | alt.onServer("entitySync:remove", (entityId, entityType) => { 33 | if (entityType == BLIPTYPE) { 34 | staticBlipStreamer.removeBlip(+entityId, false); 35 | }else if (entityType == BLIPTYPE2){ 36 | dynamicBlipStreamer.removeBlip(+entityId, false); 37 | } 38 | }); 39 | 40 | // when a streamed in object changes position data 41 | alt.onServer("entitySync:updatePosition", (entityId, entityType, position) => { 42 | if (entityType == BLIPTYPE) { 43 | staticBlipStreamer.setPosition(+entityId, position); 44 | }else if (entityType == BLIPTYPE2){ 45 | dynamicBlipStreamer.setPosition(+entityId, position); 46 | } 47 | }); 48 | 49 | // when a streamed in object changes data 50 | alt.onServer("entitySync:updateData", (entityId, entityType, newEntityData) => { 51 | if (entityType == BLIPTYPE) { 52 | if (newEntityData.hasOwnProperty("text")) 53 | staticBlipStreamer.setText(+entityId, newEntityData.text); 54 | 55 | if (newEntityData.hasOwnProperty("name")) 56 | staticBlipStreamer.setName(+entityId, newEntityData.name); 57 | 58 | if (newEntityData.hasOwnProperty("name")) 59 | staticBlipStreamer.setSprite(+entityId, newEntityData.sprite); 60 | 61 | if (newEntityData.hasOwnProperty("scale")) 62 | staticBlipStreamer.setScale(+entityId, newEntityData.scale); 63 | } 64 | if (entityType == BLIPTYPE2) { 65 | if (newEntityData.hasOwnProperty("text")) 66 | dynamicBlipStreamer.setText(+entityId, newEntityData.text); 67 | 68 | if (newEntityData.hasOwnProperty("name")) 69 | dynamicBlipStreamer.setName(+entityId, newEntityData.name); 70 | 71 | if (newEntityData.hasOwnProperty("name")) 72 | dynamicBlipStreamer.setSprite(+entityId, newEntityData.sprite); 73 | 74 | if (newEntityData.hasOwnProperty("scale")) 75 | dynamicBlipStreamer.setScale(+entityId, newEntityData.scale); 76 | } 77 | }); 78 | 79 | // when a streamed in object needs to be removed 80 | alt.onServer("entitySync:clearCache", (entityId, entityType) => { 81 | if (entityType == BLIPTYPE) { 82 | staticBlipStreamer.removeBlip(+entityId, true); 83 | }else if (entityType == BLIPTYPE2){ 84 | dynamicBlipStreamer.removeBlip(+entityId, true); 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-marker/client.mjs: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt'; 2 | import { markerStreamer } from "./marker-streamer"; 3 | 4 | // when an object is streamed in 5 | alt.onServer("entitySync:create", (entityId, entityType, position, currEntityData) => { 6 | if( currEntityData ) { 7 | let data = currEntityData; 8 | if(entityType == 0 ) { 9 | markerStreamer.addMarker( 10 | +entityId, data.markerType, +entityType, 11 | position, data.rotation, 12 | data.direction, data.scale, data.color, data.bobUpDown, data.faceCam, data.rotate, data.textureDict, data.textureName, data.drawOnEnter, 13 | ); 14 | } 15 | } 16 | else 17 | { 18 | if (entityType == 0){ 19 | markerStreamer.restoreMarker( +entityId ); 20 | } 21 | } 22 | } ); 23 | 24 | // when an object is streamed out 25 | alt.onServer("entitySync:remove", (entityId, entityType) => { 26 | if (entityType == 0){ 27 | markerStreamer.removeMarker( +entityId ); 28 | } 29 | } ); 30 | 31 | // when a streamed in object changes position data 32 | alt.onServer("entitySync:updatePosition", (entityId, entityType, position) => { 33 | if (entityType == 0){ 34 | markerStreamer.setPosition( +entityId, position ); 35 | } 36 | } ); 37 | 38 | // when a streamed in object changes data 39 | alt.onServer("entitySync:updateData", (entityId, entityType, newEntityData) => { 40 | if (entityType == 0){ 41 | if( newEntityData.hasOwnProperty( "rotation" ) ) 42 | markerStreamer.setRotation( +entityId, newEntityData.rotation ); 43 | 44 | if( newEntityData.hasOwnProperty( "markerType" ) ) 45 | markerStreamer.setMarkerType( +entityId, newEntityData.markerType ); 46 | 47 | if( newEntityData.hasOwnProperty( "drawOnEnter" ) ) 48 | markerStreamer.setDrawOnEnter( +entityId, newEntityData.drawOnEnter ); 49 | 50 | if( newEntityData.hasOwnProperty( "textureName" ) ) 51 | markerStreamer.setTextureName( +entityId, newEntityData.textureName ); 52 | 53 | if( newEntityData.hasOwnProperty( "textureDict" ) ) 54 | markerStreamer.setTextureDict( +entityId, newEntityData.textureDict ); 55 | 56 | if( newEntityData.hasOwnProperty( "rotate" ) ) 57 | markerStreamer.setRotate( +entityId, newEntityData.rotate ); 58 | 59 | if( newEntityData.hasOwnProperty( "faceCam" ) ) 60 | markerStreamer.setFaceCamera( +entityId, newEntityData.faceCam ); 61 | 62 | if( newEntityData.hasOwnProperty( "bobUpDown" ) ) 63 | markerStreamer.setBobUpDown( +entityId, newEntityData.bobUpDown ); 64 | 65 | if( newEntityData.hasOwnProperty( "color" ) ) 66 | markerStreamer.setColor( +entityId, newEntityData.color ); 67 | 68 | if( newEntityData.hasOwnProperty( "scale" ) ) 69 | markerStreamer.setScale( +entityId, newEntityData.scale ); 70 | 71 | if( newEntityData.hasOwnProperty( "direction" ) ) 72 | markerStreamer.setDirection( +entityId, newEntityData.direction ); 73 | } 74 | } ); 75 | 76 | // when a streamed in object needs to be removed 77 | alt.onServer("entitySync:clearCache", (entityId, entityType) => { 78 | if (entityType == 0){ 79 | markerStreamer.clearMarker(+entityId); 80 | } 81 | } ); 82 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-object/client.mjs: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt'; 2 | 3 | import { objStreamer } from "./object-streamer"; 4 | 5 | // when an object is streamed in 6 | alt.onServer("entitySync:create", (entityId, entityType, position, currEntityData) => { 7 | if( currEntityData ) { 8 | let data = currEntityData; 9 | if(data != undefined ) { 10 | if (entityType == 2){ 11 | //alt.log("Object Position " +position); 12 | objStreamer.addObject( 13 | +entityId, data.model, +entityType, 14 | position, data.rotation, 15 | data.lodDistance, data.textureVariation, data.dynamic, 16 | data.visible, data.onFire, data.freeze, data.lightColor 17 | ); 18 | } 19 | } 20 | } 21 | // this entity has streamed in before, fetch from cache 22 | else 23 | { 24 | if (entityType == 2){ 25 | objStreamer.restoreObject( +entityId ); 26 | } 27 | } 28 | } ); 29 | 30 | // when an object is streamed out 31 | alt.onServer("entitySync:remove", (entityId, entityType) => { 32 | if (entityType == 2){ 33 | objStreamer.removeObject( +entityId ); 34 | } 35 | } ); 36 | 37 | // when a streamed in object changes position data 38 | alt.onServer("entitySync:updatePosition", (entityId, entityType, position) => { 39 | if (entityType == 2){ 40 | objStreamer.setPosition( +entityId, position ); 41 | } 42 | } ); 43 | 44 | // when a streamed in object changes data 45 | alt.onServer("entitySync:updateData", (entityId, entityType, newEntityData) => { 46 | if (entityType == 2){ 47 | if( newEntityData.hasOwnProperty( "rotation" ) ) 48 | objStreamer.setRotation( +entityId, newEntityData.rotation ); 49 | 50 | if( newEntityData.hasOwnProperty( "velocity" ) ) 51 | objStreamer.setVelocity( +entityId, newEntityData.velocity ); 52 | 53 | if( newEntityData.hasOwnProperty( "model" ) ) 54 | objStreamer.setModel( +entityId, newEntityData.model ); 55 | 56 | if( newEntityData.hasOwnProperty( "lodDistance" ) ) 57 | objStreamer.setLodDistance( +entityId, newEntityData.lodDistance ); 58 | 59 | if( newEntityData.hasOwnProperty( "textureVariation" ) ) 60 | objStreamer.setTextureVariation( +entityId, newEntityData.textureVariation ); 61 | 62 | if( newEntityData.hasOwnProperty( "dynamic" ) ) 63 | objStreamer.setDynamic( +entityId, newEntityData.dynamic ); 64 | 65 | if( newEntityData.hasOwnProperty( "visible" ) ) 66 | objStreamer.setVisible( +entityId, newEntityData.visible ); 67 | 68 | if( newEntityData.hasOwnProperty( "onFire" ) ) 69 | objStreamer.setOnFire( +entityId, newEntityData.onFire ); 70 | 71 | if( newEntityData.hasOwnProperty( "freeze" ) ) 72 | objStreamer.setFrozen( +entityId, newEntityData.freeze ); 73 | 74 | if( newEntityData.hasOwnProperty( "lightColor" ) ) 75 | objStreamer.setLightColor( +entityId, newEntityData.lightColor ); 76 | 77 | if( newEntityData.hasOwnProperty( "slideToPosition" ) ) 78 | objStreamer.slideToPosition( +entityId, newEntityData.slideToPosition ); 79 | 80 | } 81 | } ); 82 | 83 | // when a streamed in object needs to be removed 84 | alt.onServer("entitySync:clearCache", (entityId, entityType) => { 85 | if (entityType == 2){ 86 | objStreamer.clearObject( +entityId ); 87 | } 88 | } ); 89 | -------------------------------------------------------------------------------- /ServerSide/DoorManager.cs: -------------------------------------------------------------------------------- 1 | using AltV.Net; 2 | using AltV.Net.EntitySync; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Numerics; 7 | 8 | namespace EntityStreamer 9 | { 10 | /// 11 | /// DynamicObject class that stores all data related to a single object 12 | /// 13 | public class NetworkDoor : Entity, IEntity 14 | { 15 | /// 16 | /// Set or get the current door hash. 17 | /// 18 | public uint Hash 19 | { 20 | get 21 | { 22 | if (!TryGetData("hash", out uint hash)) 23 | return 0; 24 | 25 | return hash; 26 | } 27 | set 28 | { 29 | if (Hash == value) 30 | return; 31 | 32 | SetData("hash", value); 33 | } 34 | } 35 | 36 | public float Heading 37 | { 38 | get 39 | { 40 | if (!TryGetData("heading", out float heading)) 41 | return 0; 42 | 43 | return heading; 44 | } 45 | set 46 | { 47 | if (Math.Abs(Heading - value) < float.Epsilon) 48 | return; 49 | 50 | SetData("heading", value); 51 | } 52 | } 53 | 54 | /// 55 | /// Set the lock statut of the door 56 | /// 57 | public bool? Locked 58 | { 59 | get 60 | { 61 | if (!TryGetData("locked", out bool locked)) 62 | return false; 63 | 64 | return locked; 65 | } 66 | set 67 | { 68 | if (value == null) 69 | { 70 | SetData("locked", null); 71 | return; 72 | } 73 | if (Locked == value) 74 | return; 75 | 76 | SetData("locked", value); 77 | } 78 | } 79 | 80 | public NetworkDoor(Vector3 position, int dimension, uint range, ulong entityType) : base(entityType, position, dimension, range) 81 | { 82 | } 83 | 84 | public void Delete() 85 | { 86 | DoorStreamer.DoorList.TryRemove(this.Id, out NetworkDoor value); 87 | AltEntitySync.RemoveEntity(this); 88 | } 89 | 90 | public void Destroy() 91 | { 92 | DoorStreamer.DoorList.TryRemove(this.Id, out NetworkDoor value); 93 | AltEntitySync.RemoveEntity(this); 94 | } 95 | } 96 | 97 | public class DoorStreamer 98 | { 99 | public static ConcurrentDictionary DoorList = new ConcurrentDictionary(); 100 | 101 | public static NetworkDoor Create( 102 | uint hash, Vector3 position, float heading ,bool locked, uint streamRange = 25 103 | ) 104 | { 105 | NetworkDoor obj = new NetworkDoor(position, 0, streamRange, 6) 106 | { 107 | Hash = hash, 108 | Locked = locked, 109 | Heading = heading, 110 | }; 111 | AltEntitySync.AddEntity(obj); 112 | DoorList.TryAdd(obj.Id, obj); 113 | return obj; 114 | } 115 | 116 | public static bool Delete(ulong dynamicObjectId) 117 | { 118 | NetworkDoor obj = GetDoor(dynamicObjectId); 119 | 120 | if (obj == null) 121 | return false; 122 | DoorList.TryRemove(obj.Id, out NetworkDoor value); 123 | AltEntitySync.RemoveEntity(obj); 124 | return true; 125 | } 126 | 127 | public static void Delete(NetworkDoor obj) 128 | { 129 | DoorList.TryRemove(obj.Id, out NetworkDoor value); 130 | AltEntitySync.RemoveEntity(obj); 131 | } 132 | 133 | public static NetworkDoor GetDoor(ulong dynamicObjectId) 134 | { 135 | return DoorList[dynamicObjectId]; 136 | } 137 | 138 | public static List GetAllDoors() 139 | { 140 | List objects = new List(); 141 | 142 | foreach (KeyValuePair entity in DoorList) 143 | { 144 | objects.Add(entity.Value); 145 | } 146 | 147 | return objects; 148 | } 149 | 150 | public static (NetworkDoor obj, float distance) GetClosestDoor(Vector3 pos) 151 | { 152 | if (GetAllDoors().Count == 0) 153 | return (null, 5000); 154 | 155 | NetworkDoor obj = null; 156 | float distance = 5000; 157 | 158 | foreach (NetworkDoor o in GetAllDoors()) 159 | { 160 | float dist = Vector3.Distance(o.Position, pos); 161 | if (dist < distance) 162 | { 163 | obj = o; 164 | distance = dist; 165 | } 166 | } 167 | 168 | return (obj, distance); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-marker/marker-streamer.mjs: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt'; 2 | import * as natives from 'natives'; 3 | 4 | class MarkerStreamer { 5 | constructor( ) { 6 | this.markers = {}; 7 | } 8 | 9 | async addMarker( entityId, markerType, entityType, pos, rot, dir, scale, color, bobUpDown, faceCam, rotate, textureDict, textureName, drawOnEnter ) { 10 | this.removeMarker( +entityId ); 11 | this.clearMarker( +entityId ); 12 | 13 | let marker = { 14 | onDisplay: true, markerType: markerType, 15 | entityId: +entityId, entityType: +entityType, position: pos 16 | }; 17 | 18 | this.markers[entityId] = marker; 19 | 20 | this.setRotation( +entityId, rot ); 21 | this.setDirection( +entityId, dir ); 22 | this.setScale( +entityId, scale ); 23 | this.setColor( +entityId, color ); 24 | this.setBobUpDown( +entityId, bobUpDown ); 25 | this.setFaceCamera( +entityId, faceCam ); 26 | this.setRotate( +entityId, rotate ); 27 | this.setTextureDict( +entityId, textureDict ); 28 | this.setTextureName( +entityId, textureName ); 29 | this.setDrawOnEnter( +entityId, drawOnEnter ); 30 | } 31 | 32 | getMarker( entityId ) { 33 | if(this.markers.hasOwnProperty(entityId)){ 34 | return this.markers[entityId]; 35 | }else{ 36 | return null; 37 | } 38 | } 39 | 40 | restoreMarker( entityId ) { 41 | if(this.markers.hasOwnProperty(entityId)){ 42 | this.markers[entityId].onDisplay = true; 43 | } 44 | } 45 | 46 | 47 | removeMarker( entityId ) { 48 | if(this.markers.hasOwnProperty(entityId)){ 49 | this.markers[entityId].onDisplay = false; 50 | } 51 | } 52 | 53 | clearMarker( entityId ) { 54 | if(this.markers.hasOwnProperty(entityId)){ 55 | delete this.markers[entityId]; 56 | } 57 | } 58 | 59 | clearAllMarker( ) { 60 | this.markers = {}; 61 | } 62 | 63 | setMarkerType( entityId, type = 0 ) { 64 | if(this.markers.hasOwnProperty(entityId)){ 65 | this.markers[entityId].markerType = type; 66 | } 67 | } 68 | 69 | 70 | setTextureName( entityId, textureName = undefined ) { 71 | if(this.markers.hasOwnProperty(entityId)){ 72 | this.markers[entityId].textureName = textureName; 73 | } 74 | } 75 | 76 | setTextureDict( entityId, textureDict = undefined ) { 77 | if(this.markers.hasOwnProperty(entityId)){ 78 | this.markers[entityId].textureDict = textureDict; 79 | } 80 | } 81 | 82 | setDrawOnEnter( entityId, drawOnEnter = false ) { 83 | if(this.markers.hasOwnProperty(entityId)){ 84 | this.markers[entityId].drawOnEnter = drawOnEnter; 85 | } 86 | } 87 | 88 | setRotate( entityId, rotate = false ) { 89 | if(this.markers.hasOwnProperty(entityId)){ 90 | this.markers[entityId].rotate = rotate; 91 | } 92 | } 93 | 94 | setFaceCamera( entityId, faceCam = false) { 95 | if(this.markers.hasOwnProperty(entityId)){ 96 | this.markers[entityId].faceCam = faceCam; 97 | } 98 | } 99 | 100 | setBobUpDown( entityId, bobUpDown = false) { 101 | if(this.markers.hasOwnProperty(entityId)){ 102 | this.markers[entityId].bobUpDown = bobUpDown; 103 | } 104 | } 105 | 106 | setColor( entityId, color = { r: 255, g: 255, b: 255, a: 255 } ) { 107 | if(this.markers.hasOwnProperty(entityId)){ 108 | this.markers[entityId].color = color; 109 | } 110 | } 111 | 112 | setScale( entityId, scale = { x: 0, y: 0, z: 0 } ) { 113 | if(this.markers.hasOwnProperty(entityId)){ 114 | this.markers[entityId].scale = scale; 115 | } 116 | } 117 | 118 | setDirection( entityId, dir = { x: 0, y: 0, z: 0 } ) { 119 | if(this.markers.hasOwnProperty(entityId)){ 120 | this.markers[entityId].direction = dir; 121 | } 122 | } 123 | 124 | setRotation( entityId, rot = { x: 0, y: 0, z: 0 } ) { 125 | if(this.markers.hasOwnProperty(entityId)){ 126 | this.markers[entityId].rotation = rot; 127 | } 128 | } 129 | 130 | setPosition( entityId, pos = { x: 0, y: 0, z: 0 } ) { 131 | if(this.markers.hasOwnProperty(entityId)){ 132 | this.markers[entityId].position = pos; 133 | } 134 | } 135 | } 136 | 137 | export const markerStreamer = new MarkerStreamer(); 138 | 139 | alt.on( "resourceStop", ( ) => { 140 | markerStreamer.clearAllMarker(); 141 | } ); 142 | 143 | alt.everyTick( ( ) => { 144 | for(var key in markerStreamer.markers) { 145 | let marker = markerStreamer.markers[key]; 146 | if(marker.onDisplay){ 147 | natives.drawMarker( 148 | marker.markerType, marker.position.x, marker.position.y, marker.position.z, 149 | marker.direction.x, marker.direction.y, marker.direction.z, 150 | marker.rotation.x, marker.rotation.y, marker.rotation.z, 151 | marker.scale.x, marker.scale.y, marker.scale.z, 152 | marker.color.r, marker.color.g, marker.color.b, marker.color.a, 153 | !!marker.bobUpDown, !!marker.faceCam, 2, !!marker.rotate, marker.textureDict, marker.textureName, !!marker.drawOnEnter 154 | ); 155 | } 156 | } 157 | } ); 158 | -------------------------------------------------------------------------------- /ServerSide/HelpTextManager.cs: -------------------------------------------------------------------------------- 1 | using AltV.Net.Data; 2 | using AltV.Net.EntitySync; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Numerics; 6 | 7 | namespace EntityStreamer 8 | { 9 | 10 | /// 11 | /// HelpText class that stores all data 12 | /// 13 | public class HelpText : Entity, IEntity 14 | { 15 | 16 | /// 17 | /// Set/get the HelpText text. 18 | /// 19 | public string Text 20 | { 21 | get 22 | { 23 | if (!TryGetData("text", out string text)) 24 | return null; 25 | 26 | return text; 27 | } 28 | set 29 | { 30 | SetData("text", value); 31 | } 32 | } 33 | 34 | public static object HelpTextLockHandle = new object(); 35 | private static List helpTextList = new List(); 36 | public static List HelpTextList 37 | { 38 | get 39 | { 40 | lock (HelpTextLockHandle) 41 | { 42 | return helpTextList; 43 | } 44 | } 45 | set 46 | { 47 | helpTextList = value; 48 | } 49 | } 50 | 51 | public HelpText(Vector3 position, int dimension, uint range, ulong entityType) : base(entityType, position, dimension, range) 52 | { 53 | } 54 | 55 | /// 56 | /// Destroy this textlabel. 57 | /// 58 | public void Delete() 59 | { 60 | HelpText.HelpTextList.Remove(this); 61 | AltEntitySync.RemoveEntity(this); 62 | } 63 | 64 | public void SetText(string text) 65 | { 66 | Text = text; 67 | } 68 | } 69 | 70 | public static class HelpTextStreamer 71 | { 72 | /// 73 | /// Create a new HelpText. 74 | /// 75 | /// The text to be displayed. 76 | /// The newly created dynamic textlabel. 77 | public static HelpText Create( 78 | string text, Vector3 position, int dimension = 0, uint streamRange = 5 79 | ) 80 | { 81 | HelpText helper = new HelpText(position, dimension, streamRange, 3) 82 | { 83 | Text = text, 84 | }; 85 | 86 | HelpText.HelpTextList.Add(helper); 87 | AltEntitySync.AddEntity(helper); 88 | return helper; 89 | } 90 | 91 | /// 92 | /// Destroy HelpText by it's ID. 93 | /// 94 | /// The ID of the text label. 95 | /// True if successful, false otherwise. 96 | public static bool DeleteHelpText(ulong dynamicTextLabelId) 97 | { 98 | HelpText obj = GetHelpText(dynamicTextLabelId); 99 | 100 | if (obj == null) 101 | return false; 102 | 103 | HelpText.HelpTextList.Remove(obj); 104 | AltEntitySync.RemoveEntity(obj); 105 | return true; 106 | } 107 | 108 | /// 109 | /// Destroy an HelpText. 110 | /// 111 | /// The text label instance to destroy. 112 | public static void DeleteHelpText(HelpText dynamicTextLabel) 113 | { 114 | HelpText.HelpTextList.Remove(dynamicTextLabel); 115 | AltEntitySync.RemoveEntity(dynamicTextLabel); 116 | } 117 | 118 | /// 119 | /// Get a HelpText by it's ID. 120 | /// 121 | /// The ID of the textlabel. 122 | /// The dynamic textlabel or null if not found. 123 | public static HelpText GetHelpText(ulong dynamicTextLabelId) 124 | { 125 | if (!AltEntitySync.TryGetEntity(dynamicTextLabelId, 4, out IEntity entity)) 126 | { 127 | Console.WriteLine($"[OBJECT-STREAMER] [GetDynamicTextLabel] ERROR: Entity with ID { dynamicTextLabelId } couldn't be found."); 128 | return null; 129 | } 130 | 131 | return (HelpText)entity; 132 | } 133 | 134 | /// 135 | /// Destroy all HelpText. 136 | /// 137 | public static void DeleteAllHelpText() 138 | { 139 | foreach (HelpText obj in GetAllHelpText()) 140 | { 141 | HelpText.HelpTextList.Remove(obj); 142 | AltEntitySync.RemoveEntity(obj); 143 | } 144 | } 145 | 146 | /// 147 | /// Get all HelpText. 148 | /// 149 | /// A list of dynamic textlabels. 150 | public static List GetAllHelpText() 151 | { 152 | List textLabels = new List(); 153 | 154 | foreach (IEntity entity in HelpText.HelpTextList) 155 | { 156 | HelpText textLabel = GetHelpText(entity.Id); 157 | 158 | if (textLabel != null) 159 | textLabels.Add(textLabel); 160 | } 161 | 162 | return textLabels; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-text/textlabel-streamer.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | Developed by DasNiels/Niels/DingoDongBlueBalls 3 | */ 4 | 5 | import * as alt from 'alt'; 6 | import * as natives from 'natives'; 7 | 8 | class TextLabelStreamer { 9 | constructor() { 10 | this.textLabels = {}; 11 | } 12 | 13 | async addTextLabel( entityId, text, position, scale, font, color, dropShadow, edge, center, proportional, entityType ) { 14 | this.removeTextLabel( +entityId ); 15 | this.clearTextLabel( +entityId ); 16 | 17 | let textLabel = { 18 | onDisplay: true, position: position, 19 | entityId: +entityId, entityType: +entityType, 20 | }; 21 | 22 | this.textLabels[entityId] = textLabel; 23 | 24 | this.setText( +entityId, text ); 25 | this.setScale( +entityId, scale ); 26 | this.setFont( +entityId, font ); 27 | this.setColor( +entityId, color ); 28 | this.setDropShadow( +entityId, dropShadow ); 29 | this.setEdge( +entityId, edge ); 30 | this.setCenter( +entityId, center ); 31 | this.setProportional( +entityId, proportional ); 32 | } 33 | 34 | getTextLabel( entityId ) { 35 | if(this.textLabels.hasOwnProperty(entityId)){ 36 | return this.textLabels[entityId]; 37 | }else{ 38 | return null; 39 | } 40 | } 41 | 42 | restoreTextLabel( entityId ) { 43 | if(this.textLabels.hasOwnProperty(entityId)){ 44 | this.textLabels[entityId].onDisplay = true; 45 | } 46 | } 47 | 48 | removeTextLabel( entityId ) { 49 | if(this.textLabels.hasOwnProperty(entityId)){ 50 | this.textLabels[entityId].onDisplay = false; 51 | } 52 | } 53 | 54 | clearTextLabel( entityId ) { 55 | if(this.textLabels.hasOwnProperty(entityId)){ 56 | delete this.textLabels[entityId]; 57 | } 58 | } 59 | 60 | clearAllTextLabel() { 61 | this.textLabels = {}; 62 | } 63 | 64 | setPosition( entityId, pos ) { 65 | if(this.textLabels.hasOwnProperty(entityId)){ 66 | this.textLabels[entityId].position = pos; 67 | } 68 | } 69 | 70 | setText( entityId, text = "3D Textlabel" ) { 71 | if(this.textLabels.hasOwnProperty(entityId)){ 72 | this.textLabels[entityId].text = text; 73 | } 74 | } 75 | 76 | setScale( entityId, scale = 1 ) { 77 | if(this.textLabels.hasOwnProperty(entityId)){ 78 | this.textLabels[entityId].scale = scale; 79 | } 80 | } 81 | 82 | setFont( entityId, font = 4 ) { 83 | if(this.textLabels.hasOwnProperty(entityId)){ 84 | this.textLabels[entityId].font = font; 85 | } 86 | } 87 | 88 | setColor( entityId, color = { r: 255, g: 255, b: 255, a: 255 } ) { 89 | if(this.textLabels.hasOwnProperty(entityId)){ 90 | this.textLabels[entityId].color = color; 91 | } 92 | } 93 | 94 | setDropShadow( entityId, dropShadow = { distance: 0, r: 0, g: 0, b: 0, a: 255 } ) { 95 | if(this.textLabels.hasOwnProperty(entityId)){ 96 | this.textLabels[entityId].dropShadow = dropShadow; 97 | } 98 | } 99 | 100 | setEdge( entityId, edge = { r: 255, g: 255, b: 255, a: 255 } ) { 101 | if(this.textLabels.hasOwnProperty(entityId)){ 102 | this.textLabels[entityId].edge = edge; 103 | } 104 | } 105 | 106 | setCenter( entityId, center = true ) { 107 | if(this.textLabels.hasOwnProperty(entityId)){ 108 | this.textLabels[entityId].center = center; 109 | } 110 | } 111 | 112 | setProportional( entityId, proportional = true ) { 113 | if(this.textLabels.hasOwnProperty(entityId)){ 114 | this.textLabels[entityId].proportional = proportional; 115 | } 116 | } 117 | } 118 | 119 | export const textLabelStreamer = new TextLabelStreamer(); 120 | 121 | alt.on( "resourceStop", () => { 122 | textLabelStreamer.clearAllTextLabel(); 123 | } ); 124 | 125 | function draw3dText( text, pos, scale, font, color, dropShadow, edge, center, proportional ) { 126 | 127 | const localPlayer = alt.Player.local; 128 | const entity = localPlayer.vehicle ? localPlayer.vehicle.scriptID : localPlayer.scriptID; 129 | const vector = natives.getEntityVelocity(entity); 130 | const frameTime = natives.getFrameTime(); 131 | natives.setDrawOrigin(pos.x + (vector.x * frameTime), pos.y + (vector.y * frameTime), pos.z + (vector.z * frameTime), 0); 132 | natives.beginTextCommandDisplayText( 'STRING' ); 133 | natives.addTextComponentSubstringPlayerName( text ); 134 | natives.setTextFont( font ); 135 | var size = scale / 2.3; 136 | natives.setTextScale( 1, size ); 137 | natives.setTextWrap( 0.0, 1.0 ); 138 | natives.setTextCentre( center ); 139 | natives.setTextProportional( proportional ); 140 | 141 | if(!color || !color.r) 142 | { 143 | color = { r: 0, g:0,b:0,a:0}; 144 | } 145 | natives.setTextColour( color.r, color.g, color.b, color.a ); 146 | natives.setTextOutline(); 147 | natives.setTextDropShadow(); 148 | 149 | natives.endTextCommandDisplayText( 0, 0, 0 ); 150 | natives.clearDrawOrigin(); 151 | } 152 | 153 | alt.everyTick( () => { 154 | for(var key in textLabelStreamer.textLabels) { 155 | let textLabel = textLabelStreamer.textLabels[key]; 156 | if(textLabel.onDisplay){ 157 | draw3dText( 158 | textLabel.text, 159 | textLabel.position, 160 | textLabel.scale, 161 | textLabel.font, 162 | textLabel.color, 163 | textLabel.dropShadow, 164 | textLabel.edge, 165 | textLabel.center, 166 | textLabel.proportional 167 | ); 168 | } 169 | } 170 | } ); 171 | -------------------------------------------------------------------------------- /ServerSide/BlipManager.cs: -------------------------------------------------------------------------------- 1 | using AltV.Net; 2 | using AltV.Net.Async; 3 | using AltV.Net.Data; 4 | using AltV.Net.Elements.Entities; 5 | using AltV.Net.EntitySync; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Numerics; 10 | using System.Runtime.CompilerServices; 11 | using System.Threading.Tasks; 12 | 13 | namespace EntityStreamer 14 | { 15 | /// 16 | /// Blip class that stores all data related to a single blip. 17 | /// 18 | public class Blip : AltV.Net.EntitySync.Entity, AltV.Net.EntitySync.IEntity 19 | { 20 | /// 21 | /// The text to display on the blip in the map menu 22 | /// 23 | public string Name 24 | { 25 | get 26 | { 27 | if (!TryGetData("name", out string name)) 28 | return null; 29 | 30 | return name; 31 | } 32 | set 33 | { 34 | SetData("name", value); 35 | } 36 | } 37 | 38 | /// 39 | /// ID of the sprite to use, can be found on the ALTV wiki 40 | /// 41 | public int Sprite 42 | { 43 | get 44 | { 45 | if (!TryGetData("sprite", out int spriteId)) 46 | return 0; 47 | 48 | return spriteId; 49 | } 50 | set 51 | { 52 | SetData("sprite", value); 53 | } 54 | } 55 | 56 | /// 57 | /// Blip Color code, can also be found on the ALTV wiki 58 | /// 59 | public int Color 60 | { 61 | get 62 | { 63 | if (!TryGetData("color", out int color)) 64 | return 0; 65 | 66 | return color; 67 | } 68 | set 69 | { 70 | SetData("color", value); 71 | } 72 | } 73 | 74 | /// 75 | /// Scale of the blip, 1 is regular size. 76 | /// 77 | public float Scale 78 | { 79 | get 80 | { 81 | if (!TryGetData("scale", out float scale)) 82 | return 1; 83 | 84 | return scale; 85 | } 86 | set 87 | { 88 | SetData("scale", value); 89 | } 90 | } 91 | 92 | /// 93 | /// Whether this blip can be seen on the minimap from anywhere on the map, or only when close to it(it will always show on the main map). 94 | /// 95 | public bool ShortRange 96 | { 97 | get 98 | { 99 | if (!TryGetData("shortRange", out bool shortRange)) 100 | return true; 101 | 102 | return shortRange; 103 | } 104 | set 105 | { 106 | SetData("shortRange", value); 107 | } 108 | } 109 | 110 | public Blip(Vector3 position, int dimension, uint range, ulong entityType) : base(entityType, position, dimension, range) 111 | { 112 | } 113 | 114 | public bool IsStaticBlip { get; set; } 115 | 116 | /// 117 | /// Destroy this blip. 118 | /// 119 | public void Delete() 120 | { 121 | AltEntitySync.RemoveEntity(this); 122 | } 123 | 124 | public void SetPosition(Position pos) 125 | { 126 | this.Position = pos; 127 | } 128 | 129 | public void SetBlipType(int type) 130 | { 131 | //TODO Transformer in runtime un blip static en dynamique et inversement. 132 | } 133 | } 134 | 135 | public static class BlipStreamer 136 | { 137 | public static Dictionary BlipList = new Dictionary(); 138 | 139 | /// 140 | /// Create static blip without any range limit 141 | /// 142 | /// 143 | /// 144 | /// 145 | /// 146 | /// 147 | /// 148 | /// 149 | /// 150 | /// 151 | public static Blip CreateStaticBlip(string name, int color, float scale, bool shortRange, int spriteId, Vector3 position, int dimension, uint range = 100) 152 | { 153 | Blip blip = new Blip(position, dimension, range, 4) 154 | { 155 | Color = color, 156 | Scale = scale, 157 | ShortRange = shortRange, 158 | Sprite = spriteId, 159 | Name = name, 160 | IsStaticBlip = true 161 | }; 162 | AltEntitySync.AddEntity(blip); 163 | BlipList.Add(blip.Id, blip); 164 | return blip; 165 | } 166 | 167 | /// 168 | /// Create Dynamic Blip. 169 | /// 170 | /// 171 | /// 172 | /// 173 | /// 174 | /// 175 | /// 176 | /// 177 | /// 178 | /// 179 | public static Blip CreateDynamicBlip(string name, int color, float scale, bool shortRange, int spriteId, Vector3 position, int dimension, uint range = 200) 180 | { 181 | Blip blip = new Blip(position, dimension, range, 5) 182 | { 183 | Color = color, 184 | Scale = scale, 185 | ShortRange = shortRange, 186 | Sprite = spriteId, 187 | Name = name, 188 | IsStaticBlip = false, 189 | }; 190 | AltEntitySync.AddEntity(blip); 191 | BlipList.Add(blip.Id, blip); 192 | return blip; 193 | } 194 | 195 | /// 196 | /// Destroy a dynamic blip 197 | /// 198 | /// The blip to destroy 199 | public static void DestroyBlip(Blip blip) 200 | { 201 | BlipList.Remove(blip.Id); 202 | AltEntitySync.RemoveEntity(blip); 203 | } 204 | 205 | public static Blip GetBlip(ulong dynamicObjectId) 206 | { 207 | 208 | return BlipList[dynamicObjectId]; 209 | } 210 | 211 | public static List GetAllBlip() 212 | { 213 | List objects = new List(); 214 | 215 | foreach (KeyValuePair entity in BlipStreamer.BlipList) 216 | { 217 | Blip obj = GetBlip(entity.Key); 218 | 219 | if (obj != null) 220 | objects.Add(obj); 221 | } 222 | 223 | return objects; 224 | } 225 | 226 | public static (Blip obj, float distance) GetClosestBlip(Vector3 pos) 227 | { 228 | if (GetAllBlip().Count == 0) 229 | return (null, 5000); 230 | 231 | Blip obj = null; 232 | float distance = 5000; 233 | 234 | foreach (Blip o in GetAllBlip()) 235 | { 236 | float dist = Vector3.Distance(o.Position, pos); 237 | if (dist < distance) 238 | { 239 | obj = o; 240 | distance = dist; 241 | } 242 | } 243 | 244 | return (obj, distance); 245 | } 246 | } 247 | } 248 | 249 | -------------------------------------------------------------------------------- /ClientSide/resources/sarp-streamer-object/object-streamer.mjs: -------------------------------------------------------------------------------- 1 | import * as alt from 'alt'; 2 | import * as natives from 'natives'; 3 | 4 | function waitFor(duration) { 5 | return new Promise(resolve => setTimeout(resolve, duration)); 6 | } 7 | 8 | async function loadModel(model) { 9 | if (typeof model === "string") model = alt.hash(model); 10 | if (!natives.isModelValid(model)) return false; 11 | if(natives.hasModelLoaded(model)) return true; 12 | 13 | natives.requestModel(model); 14 | 15 | let hasModelLoaded = false; 16 | let tries = 0; 17 | 18 | while (!(hasModelLoaded = natives.hasModelLoaded(model)) && tries++ < 10) { 19 | await waitFor(10); 20 | } 21 | 22 | return hasModelLoaded; 23 | } 24 | 25 | class ObjectStreamer { 26 | constructor( ) { 27 | this.objects = {}; 28 | } 29 | 30 | async addObject(entityId, model, entityType, pos, rot, lodDistance, textureVariation, dynamic, visible, onFire, frozen, lightColor ) { 31 | // clear the object incase it still exists. 32 | this.removeObject( +entityId ); 33 | this.clearObject( +entityId ); 34 | 35 | loadModel(model).then(() => 36 | { 37 | let handle = natives.createObjectNoOffset(model, pos.x, pos.y, pos.z, false, false, false ); 38 | let obj = { handle: handle, entityId: entityId, model: model, entityType: entityType, position: pos, frozen: frozen }; 39 | this.objects[entityId] = obj; 40 | this.setRotation( +entityId, rot ); 41 | this.setLodDistance( obj, lodDistance ); 42 | this.setTextureVariation( +entityId, textureVariation ); 43 | this.setDynamic( +entityId, dynamic ); 44 | this.setVisible( obj, visible ); 45 | this.setOnFire( +entityId, onFire ); 46 | this.setFrozen( +entityId, frozen ); 47 | this.setLightColor( +entityId, lightColor ); 48 | }); 49 | } 50 | 51 | getObject( entityId ) { 52 | if(this.objects.hasOwnProperty(entityId)){ 53 | return this.objects[entityId]; 54 | }else{ 55 | return null; 56 | } 57 | } 58 | 59 | async restoreObject( entityId ) { 60 | if(this.objects.hasOwnProperty(entityId)){ 61 | let obj = this.objects[entityId]; 62 | loadModel(obj.model).then(() => 63 | { 64 | this.objects[entityId].handle = natives.createObjectNoOffset( alt.hash( obj.model ), obj.position.x, obj.position.y, obj.position.z, false, false, false ); 65 | this.setRotation( +entityId, obj.rotation ); 66 | this.setLodDistance( obj, obj.lodDistance ); 67 | this.setTextureVariation( +entityId, obj.textureVariation ); 68 | this.setDynamic( +entityId, obj.dynamic ); 69 | this.setVisible( obj, obj.visible ); 70 | this.setOnFire( +entityId, obj.onFire ); 71 | this.setFrozen( +entityId, obj.frozen ); 72 | this.setLightColor( +entityId, obj.lightColor ); 73 | }); 74 | } 75 | } 76 | 77 | removeObject( entityId ) { 78 | if(this.objects.hasOwnProperty(entityId)){ 79 | natives.deleteObject( this.objects[entityId].handle ); 80 | this.objects[entityId].handle = null; 81 | } 82 | } 83 | 84 | clearObject( entityId ) { 85 | if(this.objects.hasOwnProperty(entityId)){ 86 | delete this.objects[entityId]; 87 | } 88 | } 89 | 90 | clearAllObject() { 91 | this.objects= {}; 92 | } 93 | 94 | setRotation( entityId, rot ) { 95 | if(this.objects.hasOwnProperty(entityId)){ 96 | natives.setEntityRotation( this.objects[entityId].handle, rot.x, rot.y, rot.z, 0, true ); 97 | this.objects[entityId].rotation = rot; 98 | } 99 | } 100 | setVelocity( entityId, vel ) { 101 | if(this.objects.hasOwnProperty(entityId)){ 102 | natives.setEntityVelocity( this.objects[entityId].handle, vel.x, vel.y, vel.z); 103 | this.objects[entityId].velocity = vel; 104 | } 105 | } 106 | slideToPosition( entityId, pos ) { 107 | natives.slideObject(this.objects[entityId].handle, pos.x, pos.y, pos.z, 8, 8, 8, true); 108 | } 109 | 110 | setPosition( entityId, pos ) { 111 | if(this.objects.hasOwnProperty(entityId)){ 112 | natives.setEntityCoordsNoOffset( this.objects[entityId].handle, pos.x, pos.y, pos.z, true, true, true ); 113 | this.objects[entityId].position = pos; 114 | } 115 | } 116 | 117 | async setModel( entityId, model ) { 118 | if(this.objects.hasOwnProperty(entityId)){ 119 | this.objects[entityId].model = model; 120 | } 121 | } 122 | 123 | setLodDistance( entityId, lodDistance ) { 124 | if(this.objects.hasOwnProperty(entityId) && lodDistance !== null){ 125 | natives.setEntityLodDist( this.objects[entityId].handle, lodDistance ); 126 | this.objects[entityId].lodDistance = lodDistance; 127 | } 128 | } 129 | 130 | setTextureVariation( entityId, textureVariation = null ) { 131 | if(this.objects.hasOwnProperty(entityId)){ 132 | if(textureVariation == null) 133 | natives.setObjectTextureVariation( this.objects[entityId].handle, textureVariation ); 134 | this.objects[entityId].textureVariation = textureVariation; 135 | } 136 | } 137 | 138 | setDynamic( entityId, dynamic ) { 139 | if(this.objects.hasOwnProperty(entityId) && dynamic !== null){ 140 | natives.setEntityDynamic( this.objects[entityId].handle, !!dynamic ); 141 | this.objects[entityId].dynamic = !!dynamic; 142 | } 143 | } 144 | 145 | setVisible( entityId, visible ) { 146 | if(this.objects.hasOwnProperty(entityId) && visible !== null){ 147 | natives.setEntityVisible( this.objects[entityId].handle, !!visible, false ); 148 | this.objects[entityId].visible = !!visible; 149 | } 150 | } 151 | 152 | setOnFire( entityId, onFire = null ) { 153 | if(this.objects.hasOwnProperty(entityId) && onFire !== null){ 154 | if( !!onFire ) 155 | { 156 | this.objects[entityId].fireHandle = natives.startScriptFire( this.objects[entityId].position.x, this.objects[entityId].position.y, this.objects[entityId].position.z, 1, true ); 157 | } 158 | else 159 | { 160 | if( this.objects[entityId].fireHandle !== null ) 161 | { 162 | natives.removeScriptFire( this.objects[entityId].fireHandle ); 163 | this.objects[entityId].fireHandle = null; 164 | } 165 | } 166 | 167 | this.objects[entityId].onFire = !!onFire; 168 | } 169 | } 170 | 171 | setFrozen( entityId, frozen ) { 172 | if(this.objects.hasOwnProperty(entityId) && frozen !== null){ 173 | natives.freezeEntityPosition( this.objects[entityId].handle, frozen ); 174 | this.objects[entityId].frozen = frozen; 175 | } 176 | } 177 | 178 | setLightColor( entityId, lightColor = {r:0,g:0,b:0} ) { 179 | if(this.objects.hasOwnProperty(entityId) && lightColor !== null){ 180 | natives.setObjectLightColor( this.objects[entityId].handle, true, lightColor.r, lightColor.g, lightColor.b ); 181 | this.objects[entityId].lightColor = lightColor; 182 | }else{ 183 | natives.setObjectLightColor( this.objects[entityId].handle, true, 0, 0, 0 ); 184 | this.objects[entityId].lightColor = lightColor; 185 | } 186 | } 187 | } 188 | 189 | export const objStreamer = new ObjectStreamer(); 190 | 191 | alt.on( "resourceStop", ( ) => { 192 | objStreamer.clearAllObject(); 193 | } ); 194 | -------------------------------------------------------------------------------- /ServerSide/PlayerLabelManager.cs: -------------------------------------------------------------------------------- 1 | using AltV.Net.Data; 2 | using AltV.Net.EntitySync; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Numerics; 6 | 7 | namespace EntityStreamer 8 | { 9 | /// 10 | /// Class to hold drop shadow data. 11 | /// 12 | public class DropShadow 13 | { 14 | public int Distance { get; set; } 15 | public int R { get; set; } 16 | public int G { get; set; } 17 | public int B { get; set; } 18 | public int A { get; set; } 19 | } 20 | 21 | /// 22 | /// DynamicTextLabel class that stores all data related to a single textlabel 23 | /// 24 | public class PlayerLabel : Entity, IEntity 25 | { 26 | private ulong EntityType 27 | { 28 | get 29 | { 30 | if (!TryGetData("entityType", out ulong type)) 31 | return 999; 32 | 33 | return type; 34 | } 35 | set 36 | { 37 | // No data changed 38 | if (EntityType == value) 39 | return; 40 | 41 | SetData("entityType", value); 42 | } 43 | } 44 | 45 | /// 46 | /// Set/get or get the current textlabel's scale. 47 | /// 48 | public float? Scale 49 | { 50 | get 51 | { 52 | if (!TryGetData("scale", out float scale)) 53 | return null; 54 | 55 | return scale; 56 | } 57 | set 58 | { 59 | SetData("scale", value); 60 | } 61 | } 62 | 63 | /// 64 | /// Set/get the textlabel's text. 65 | /// 66 | public string Text 67 | { 68 | get 69 | { 70 | if (!TryGetData("text", out string text)) 71 | return null; 72 | 73 | return text; 74 | } 75 | set 76 | { 77 | SetData("text", value); 78 | } 79 | } 80 | 81 | /// 82 | /// Set/get textlabel center, if true the textlabel will be centered. 83 | /// 84 | public bool Center 85 | { 86 | get 87 | { 88 | if (!TryGetData("center", out bool center)) 89 | return default; 90 | 91 | return center; 92 | } 93 | set 94 | { 95 | SetData("center", value); 96 | } 97 | } 98 | 99 | /// 100 | /// Set/get textlabel proportional. 101 | /// 102 | public bool Proportional 103 | { 104 | get 105 | { 106 | if (!TryGetData("proportional", out bool proportional)) 107 | return default; 108 | 109 | return proportional; 110 | } 111 | set 112 | { 113 | SetData("proportional", value); 114 | } 115 | } 116 | 117 | /// 118 | /// Set/get textlabel's color. 119 | /// 120 | public Rgba Color 121 | { 122 | get 123 | { 124 | if (!TryGetData("color", out Dictionary data)) 125 | return default; 126 | 127 | return new Rgba() 128 | { 129 | R = Convert.ToByte(data["r"]), 130 | G = Convert.ToByte(data["g"]), 131 | B = Convert.ToByte(data["b"]), 132 | A = Convert.ToByte(data["a"]), 133 | }; 134 | } 135 | set 136 | { 137 | // No data changed 138 | if (Color.R == value.R && Color.G == value.G && Color.B == value.B && Color.A == value.A) 139 | return; 140 | 141 | Dictionary dict = new Dictionary() 142 | { 143 | ["r"] = Convert.ToInt32(value.R), 144 | ["g"] = Convert.ToInt32(value.G), 145 | ["b"] = Convert.ToInt32(value.B), 146 | ["a"] = Convert.ToInt32(value.A), 147 | }; 148 | SetData("color", dict); 149 | } 150 | } 151 | 152 | /// 153 | /// Set/get textlabel's edge color. 154 | /// 155 | public Rgba Edge 156 | { 157 | get 158 | { 159 | if (!TryGetData("edge", out Dictionary data)) 160 | return default; 161 | 162 | return new Rgba() 163 | { 164 | R = Convert.ToByte(data["r"]), 165 | G = Convert.ToByte(data["g"]), 166 | B = Convert.ToByte(data["b"]), 167 | A = Convert.ToByte(data["a"]), 168 | }; 169 | } 170 | set 171 | { 172 | // No data changed 173 | if (Edge.R == value.R && Edge.G == value.G && Edge.B == value.B && Edge.A == value.A) 174 | return; 175 | 176 | Dictionary dict = new Dictionary() 177 | { 178 | ["r"] = Convert.ToInt32(value.R), 179 | ["g"] = Convert.ToInt32(value.G), 180 | ["b"] = Convert.ToInt32(value.B), 181 | ["a"] = Convert.ToInt32(value.A), 182 | }; 183 | SetData("edge", dict); 184 | } 185 | } 186 | 187 | /// 188 | /// Set/get textlabel's drop shadow. 189 | /// 190 | public DropShadow DropShadow 191 | { 192 | get 193 | { 194 | if (!TryGetData("dropShadow", out Dictionary data)) 195 | return null; 196 | 197 | return new DropShadow() 198 | { 199 | Distance = Convert.ToInt32(data["distance"]), 200 | R = Convert.ToInt32(data["r"]), 201 | G = Convert.ToInt32(data["g"]), 202 | B = Convert.ToInt32(data["b"]), 203 | A = Convert.ToInt32(data["a"]), 204 | }; 205 | } 206 | set 207 | { 208 | // No data changed 209 | if (DropShadow.Distance == value.Distance && DropShadow.R == value.R && DropShadow.G == value.G && DropShadow.B == value.B && DropShadow.A == value.A) 210 | return; 211 | 212 | Dictionary dict = new Dictionary() 213 | { 214 | ["distance"] = value.Distance, 215 | ["r"] = value.R, 216 | ["g"] = value.G, 217 | ["b"] = value.B, 218 | ["a"] = value.A, 219 | }; 220 | SetData("dropShadow", dict); 221 | } 222 | } 223 | 224 | /// 225 | /// Set/get textlabel's font type. 226 | /// 227 | public int Font 228 | { 229 | get 230 | { 231 | if (!TryGetData("font", out int font)) 232 | return default; 233 | 234 | return font; 235 | } 236 | set 237 | { 238 | SetData("font", value); 239 | } 240 | } 241 | 242 | public static object LabelLockHandle = new object(); 243 | 244 | private static List labelList = new List(); 245 | 246 | public static List LabelList 247 | { 248 | get 249 | { 250 | lock (LabelLockHandle) 251 | { 252 | return labelList; 253 | } 254 | } 255 | set 256 | { 257 | labelList = value; 258 | } 259 | } 260 | 261 | public PlayerLabel(Vector3 position, int dimension, uint range, ulong entityType) : base(entityType, position, dimension, range) 262 | { 263 | EntityType = entityType; 264 | } 265 | 266 | /// 267 | /// Destroy this textlabel. 268 | /// 269 | public void Delete() 270 | { 271 | PlayerLabel.LabelList.Remove(this); 272 | AltEntitySync.RemoveEntity(this); 273 | } 274 | 275 | public void SetText(string text) 276 | { 277 | Text = text; 278 | } 279 | } 280 | 281 | public static class TextLabelStreamer 282 | { 283 | /// 284 | /// Create a new dynamic textlabel. 285 | /// 286 | /// The text to be displayed. 287 | /// The position to spawn it at. 288 | /// The dimension to spawn it in. 289 | /// Center the textlabel. 290 | /// The color of the textlabel. 291 | /// The scale of the textlabel. 292 | /// The drop shadow of the textlabel. 293 | /// The edge color of the textlabel. 294 | /// The font type of the textlabel. 295 | /// Whether to set textlabel proportional. 296 | /// Stream range, default is 30. 297 | /// The newly created dynamic textlabel. 298 | public static PlayerLabel Create( 299 | string text, Vector3 position, int dimension = 0, bool? center = true, Rgba? color = null, float? scale = 0.7f, 300 | DropShadow dropShadow = null, Rgba? edge = null, int? font = null, bool? proportional = null, uint streamRange = 45 301 | ) 302 | { 303 | PlayerLabel textLabel = new PlayerLabel(position, dimension, streamRange, 1) 304 | { 305 | Center = center ?? true, 306 | Color = color ?? new Rgba(255, 255, 255, 255), 307 | DropShadow = dropShadow ?? new DropShadow { Distance = 0, R = 0, G = 0, B = 0, A = 255 }, 308 | Edge = edge ?? new Rgba(0, 0, 0, 150), 309 | Font = font ?? 4, 310 | Text = text, 311 | Proportional = proportional ?? true, 312 | Scale = scale 313 | }; 314 | PlayerLabel.LabelList.Add(textLabel); 315 | AltEntitySync.AddEntity(textLabel); 316 | return textLabel; 317 | } 318 | 319 | /// 320 | /// Destroy a dynamic text label by it's ID. 321 | /// 322 | /// The ID of the text label. 323 | /// True if successful, false otherwise. 324 | public static bool DestroyDynamicTextLabel(ulong dynamicTextLabelId) 325 | { 326 | PlayerLabel obj = GetDynamicTextLabel(dynamicTextLabelId); 327 | 328 | if (obj == null) 329 | return false; 330 | 331 | PlayerLabel.LabelList.Remove(obj); 332 | AltEntitySync.RemoveEntity(obj); 333 | return true; 334 | } 335 | 336 | /// 337 | /// Destroy a dynamic text label. 338 | /// 339 | /// The text label instance to destroy. 340 | public static void DestroyDynamicTextLabel(PlayerLabel dynamicTextLabel) 341 | { 342 | PlayerLabel.LabelList.Remove(dynamicTextLabel); 343 | AltEntitySync.RemoveEntity(dynamicTextLabel); 344 | } 345 | 346 | /// 347 | /// Get a dynamic text label by it's ID. 348 | /// 349 | /// The ID of the textlabel. 350 | /// The dynamic textlabel or null if not found. 351 | public static PlayerLabel GetDynamicTextLabel(ulong dynamicTextLabelId) 352 | { 353 | if (!AltEntitySync.TryGetEntity(dynamicTextLabelId, 1, out IEntity entity)) 354 | { 355 | Console.WriteLine($"[OBJECT-STREAMER] [GetDynamicTextLabel] ERROR: Entity with ID { dynamicTextLabelId } couldn't be found."); 356 | return null; 357 | } 358 | 359 | return (PlayerLabel)entity; 360 | } 361 | 362 | /// 363 | /// Destroy all created dynamic textlabels. 364 | /// 365 | public static void DestroyAllDynamicTextLabels() 366 | { 367 | foreach (PlayerLabel obj in GetAllDynamicTextLabels()) 368 | { 369 | AltEntitySync.RemoveEntity(obj); 370 | } 371 | PlayerLabel.LabelList.Clear(); 372 | } 373 | 374 | /// 375 | /// Get all created dynamic textlabels. 376 | /// 377 | /// A list of dynamic textlabels. 378 | public static List GetAllDynamicTextLabels() 379 | { 380 | List textLabels = new List(); 381 | 382 | foreach (IEntity entity in PlayerLabel.LabelList) 383 | { 384 | PlayerLabel textLabel = GetDynamicTextLabel(entity.Id); 385 | 386 | if (textLabel != null) 387 | textLabels.Add(textLabel); 388 | } 389 | 390 | return textLabels; 391 | } 392 | } 393 | } -------------------------------------------------------------------------------- /ServerSide/PropManager.cs: -------------------------------------------------------------------------------- 1 | using AltV.Net; 2 | using AltV.Net.EntitySync; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Numerics; 6 | 7 | namespace EntityStreamer 8 | { 9 | public enum TextureVariation 10 | { 11 | Pacific = 0, 12 | Azure = 1, 13 | Nautical = 2, 14 | Continental = 3, 15 | Battleship = 4, 16 | Intrepid = 5, 17 | Uniform = 6, 18 | Classico = 7, 19 | Mediterranean = 8, 20 | Command = 9, 21 | Mariner = 10, 22 | Ruby = 11, 23 | Vintage = 12, 24 | Pristine = 13, 25 | Merchant = 14, 26 | Voyager = 15 27 | } 28 | 29 | public class MoveData : IWritable 30 | { 31 | public float X { get; set; } 32 | public float Y { get; set; } 33 | public float Z { get; set; } 34 | public float Speed { get; set; } 35 | 36 | public void OnWrite(IMValueWriter writer) 37 | { 38 | writer.BeginObject(); 39 | writer.Name("X"); 40 | writer.Value(X); 41 | writer.Name("Y"); 42 | writer.Value(Y); 43 | writer.Name("Z"); 44 | writer.Value(Z); 45 | writer.Name("Speed"); 46 | writer.Value(Speed); 47 | writer.EndObject(); 48 | } 49 | } 50 | 51 | public class Rgb : IWritable 52 | { 53 | public int Red { get; set; } 54 | public int Green { get; set; } 55 | public int Blue { get; set; } 56 | 57 | public Rgb(int red, int green, int blue) 58 | { 59 | Red = red; 60 | Green = green; 61 | Blue = blue; 62 | } 63 | 64 | public void OnWrite(IMValueWriter writer) 65 | { 66 | writer.BeginObject(); 67 | writer.Name("Red"); 68 | writer.Value(Red); 69 | writer.Name("Green"); 70 | writer.Value(Green); 71 | writer.Name("Blue"); 72 | writer.Value(Blue); 73 | writer.EndObject(); 74 | } 75 | } 76 | 77 | /// 78 | /// DynamicObject class that stores all data related to a single object 79 | /// 80 | public class Prop : Entity, IEntity 81 | { 82 | private static List propList = new List(); 83 | 84 | public static List PropList 85 | { 86 | get 87 | { 88 | lock (propList) 89 | { 90 | return propList; 91 | } 92 | } 93 | set 94 | { 95 | propList = value; 96 | } 97 | } 98 | 99 | public AltV.Net.Elements.Entities.IColShape colshape { get; set; } 100 | 101 | /// 102 | /// Set or get the current object's rotation (in degrees). 103 | /// 104 | public Vector3 Rotation 105 | { 106 | get 107 | { 108 | if (!TryGetData("rotation", out Dictionary data)) 109 | return default; 110 | 111 | return new Vector3() 112 | { 113 | X = Convert.ToSingle(data["x"]), 114 | Y = Convert.ToSingle(data["y"]), 115 | Z = Convert.ToSingle(data["z"]), 116 | }; 117 | } 118 | set 119 | { 120 | // No data changed 121 | if (Rotation.X == value.X && Rotation.Y == value.Y && Rotation.Z == value.Z && value != new Vector3(0, 0, 0)) 122 | return; 123 | 124 | Dictionary dict = new Dictionary() 125 | { 126 | ["x"] = value.X, 127 | ["y"] = value.Y, 128 | ["z"] = value.Z, 129 | }; 130 | SetData("rotation", dict); 131 | } 132 | } 133 | 134 | public Vector3 Velocity 135 | { 136 | get 137 | { 138 | if (!TryGetData("velocity", out Dictionary data)) 139 | return default; 140 | 141 | return new Vector3() 142 | { 143 | X = Convert.ToSingle(data["x"]), 144 | Y = Convert.ToSingle(data["y"]), 145 | Z = Convert.ToSingle(data["z"]), 146 | }; 147 | } 148 | set 149 | { 150 | // No data changed 151 | if (Velocity.X == value.X && Velocity.Y == value.Y && Velocity.Z == value.Z && value != new Vector3(0, 0, 0)) 152 | return; 153 | 154 | Dictionary dict = new Dictionary() 155 | { 156 | ["x"] = value.X, 157 | ["y"] = value.Y, 158 | ["z"] = value.Z, 159 | }; 160 | SetData("velocity", dict); 161 | } 162 | } 163 | 164 | public Vector3 SlideToPosition 165 | { 166 | get 167 | { 168 | if (!TryGetData("SlideToPosition", out Dictionary data)) 169 | return default; 170 | 171 | return new Vector3() 172 | { 173 | X = Convert.ToSingle(data["x"]), 174 | Y = Convert.ToSingle(data["y"]), 175 | Z = Convert.ToSingle(data["z"]), 176 | }; 177 | } 178 | set 179 | { 180 | // No data changed 181 | 182 | Dictionary dict = new Dictionary() 183 | { 184 | ["x"] = value.X, 185 | ["y"] = value.Y, 186 | ["z"] = value.Z, 187 | }; 188 | //Log.Important("SetData SlideToPosition "); 189 | SetData("SlideToPosition", dict); 190 | } 191 | } 192 | 193 | /// 194 | /// Set or get the current object's model. 195 | /// 196 | public uint? Model 197 | { 198 | get 199 | { 200 | if (!TryGetData("model", out uint model)) 201 | return null; 202 | 203 | return model; 204 | } 205 | set 206 | { 207 | // No data changed 208 | if (Model == value) 209 | return; 210 | 211 | SetData("model", value); 212 | } 213 | } 214 | 215 | /// 216 | /// Set or get LOD Distance of the object. 217 | /// 218 | public uint? LodDistance 219 | { 220 | get 221 | { 222 | if (!TryGetData("lodDistance", out uint lodDist)) 223 | return null; 224 | 225 | return lodDist; 226 | } 227 | set 228 | { 229 | // if value is set to null, reset the data 230 | if (value == null) 231 | { 232 | SetData("lodDistance", null); 233 | return; 234 | } 235 | 236 | // No data changed 237 | if (LodDistance == value) 238 | return; 239 | 240 | SetData("lodDistance", value); 241 | } 242 | } 243 | 244 | /// 245 | /// Get or set the current texture variation, use null to reset it to default. 246 | /// 247 | public TextureVariation? TextureVariation 248 | { 249 | get 250 | { 251 | if (!TryGetData("textureVariation", out int variation)) 252 | return null; 253 | 254 | return (TextureVariation)variation; 255 | } 256 | set 257 | { 258 | // if value is set to null, reset the data 259 | if (value == null) 260 | { 261 | SetData("textureVariation", null); 262 | return; 263 | } 264 | 265 | // No data changed 266 | if (TextureVariation == value) 267 | return; 268 | 269 | SetData("textureVariation", (int)value); 270 | } 271 | } 272 | 273 | /// 274 | /// Get or set the object's dynamic state. Some objects can be moved around by the player when dynamic is set to true. 275 | /// 276 | public bool? Dynamic 277 | { 278 | get 279 | { 280 | if (!TryGetData("dynamic", out bool isDynamic)) 281 | return false; 282 | 283 | return isDynamic; 284 | } 285 | set 286 | { 287 | // if value is set to null, reset the data 288 | if (value == null) 289 | { 290 | SetData("dynamic", null); 291 | return; 292 | } 293 | 294 | // No data changed 295 | if (Dynamic == value) 296 | return; 297 | 298 | SetData("dynamic", value); 299 | } 300 | } 301 | 302 | /// 303 | /// Set/get visibility state of object 304 | /// 305 | public bool? Visible 306 | { 307 | get 308 | { 309 | if (!TryGetData("visible", out bool visible)) 310 | return false; 311 | 312 | return visible; 313 | } 314 | set 315 | { 316 | // if value is set to null, reset the data 317 | if (value == null) 318 | { 319 | SetData("visible", null); 320 | return; 321 | } 322 | 323 | // No data changed 324 | if (Visible == value) 325 | return; 326 | 327 | SetData("visible", value); 328 | } 329 | } 330 | 331 | /// 332 | /// Set/get an object on fire, NOTE: does not work very well as of right now, fire is very small. 333 | /// 334 | public bool? OnFire 335 | { 336 | get 337 | { 338 | if (!TryGetData("onFire", out bool onFire)) 339 | return false; 340 | 341 | return onFire; 342 | } 343 | set 344 | { 345 | // if value is set to null, reset the data 346 | if (value == null) 347 | { 348 | SetData("onFire", null); 349 | return; 350 | } 351 | 352 | // No data changed 353 | if (OnFire == value) 354 | return; 355 | 356 | SetData("onFire", value); 357 | } 358 | } 359 | 360 | /// 361 | /// Freeze an object into it's current position. or get it's status 362 | /// 363 | public bool? Freeze 364 | { 365 | get 366 | { 367 | if (!TryGetData("freeze", out bool frozen)) 368 | return false; 369 | 370 | return frozen; 371 | } 372 | set 373 | { 374 | // if value is set to null, reset the data 375 | if (value == null) 376 | { 377 | SetData("freeze", null); 378 | return; 379 | } 380 | 381 | // No data changed 382 | if (Freeze == value) 383 | return; 384 | 385 | SetData("freeze", value); 386 | } 387 | } 388 | 389 | /// 390 | /// Set the light color of the object, use null to reset it to default. 391 | /// 392 | public Rgb LightColor 393 | { 394 | get 395 | { 396 | if (!TryGetData("lightColor", out Dictionary data)) 397 | return null; 398 | 399 | return new Rgb( 400 | Convert.ToInt32(data["r"]), 401 | Convert.ToInt32(data["g"]), 402 | Convert.ToInt32(data["b"]) 403 | ); 404 | } 405 | set 406 | { 407 | // if value is set to null, reset the data 408 | if (value == null) 409 | { 410 | SetData("lightColor", null); 411 | return; 412 | } 413 | 414 | // No data changed 415 | if (LightColor.Red == value.Red && LightColor.Green == value.Green && LightColor.Blue == value.Blue) 416 | return; 417 | 418 | Dictionary dict = new Dictionary 419 | { 420 | { "r", value.Red }, 421 | { "g", value.Green }, 422 | { "b", value.Blue } 423 | }; 424 | SetData("lightColor", dict); 425 | } 426 | } 427 | 428 | public Vector3 PositionInitial { get; internal set; } 429 | 430 | public Prop(Vector3 position, int dimension, uint range, ulong entityType) : base(entityType, position, dimension, range) 431 | { 432 | } 433 | 434 | public void SetRotation(Vector3 rot) 435 | { 436 | Rotation = rot; 437 | } 438 | 439 | public void SetPosition(Vector3 pos) 440 | { 441 | Position = pos; 442 | } 443 | 444 | public void Delete() 445 | { 446 | Prop.PropList.Remove(this); 447 | AltEntitySync.RemoveEntity(this); 448 | } 449 | 450 | public void Destroy() 451 | { 452 | Prop.PropList.Remove(this); 453 | AltEntitySync.RemoveEntity(this); 454 | } 455 | } 456 | 457 | public static class PropStreamer 458 | { 459 | /// 460 | /// Create a new dynamic object. 461 | /// 462 | /// The object model name. 463 | /// The position to spawn the object at. 464 | /// The rotation to spawn the object at(degrees). 465 | /// The dimension to spawn the object in. 466 | /// (Optional): Set object dynamic or not. 467 | /// (Optional): Set object frozen. 468 | /// (Optional): Set LOD distance. 469 | /// (Optional): set light color. 470 | /// (Optional): set object on fire(DOESN'T WORK PROPERLY YET!) 471 | /// (Optional): Set object texture variation. 472 | /// (Optional): Set object visibility. 473 | /// (Optional): The range that a player has to be in before the object spawns, default value is 400. 474 | /// The newly created dynamic object. 475 | public static Prop Create( 476 | uint model, Vector3 position, Vector3 rotation, int dimension = 0, bool? isDynamic = null, bool? placeObjectOnGroundProperly = false, bool? frozen = null, uint? lodDistance = null, 477 | Rgb lightColor = null, bool? onFire = null, TextureVariation? textureVariation = null, bool? visible = null, uint streamRange = 520 478 | ) 479 | { 480 | Prop obj = new Prop(position, dimension, streamRange, 2) 481 | { 482 | Rotation = rotation, 483 | Model = model, 484 | Dynamic = isDynamic ?? null, 485 | Freeze = frozen ?? null, 486 | LodDistance = lodDistance ?? null, 487 | LightColor = lightColor ?? null, 488 | OnFire = onFire ?? null, 489 | TextureVariation = textureVariation ?? null, 490 | Visible = visible ?? null, 491 | PositionInitial = position, 492 | }; 493 | Prop.PropList.Add(obj); 494 | AltEntitySync.AddEntity(obj); 495 | return obj; 496 | } 497 | 498 | public static bool Delete(ulong dynamicObjectId) 499 | { 500 | Prop obj = GetProp(dynamicObjectId); 501 | 502 | if (obj == null) 503 | return false; 504 | Prop.PropList.Remove(obj); 505 | AltEntitySync.RemoveEntity(obj); 506 | return true; 507 | } 508 | 509 | public static void Delete(Prop obj) 510 | { 511 | Prop.PropList.Remove(obj); 512 | AltEntitySync.RemoveEntity(obj); 513 | } 514 | 515 | public static Prop GetProp(ulong dynamicObjectId) 516 | { 517 | if (!AltEntitySync.TryGetEntity(dynamicObjectId, 2, out IEntity entity)) 518 | { 519 | Console.WriteLine($"[Prop-Stream] [GetProp] ERROR: Entity with ID { dynamicObjectId } couldn't be found."); 520 | return default; 521 | } 522 | 523 | if (!(entity is Prop)) 524 | return default; 525 | 526 | return (Prop)entity; 527 | } 528 | 529 | /// 530 | /// Destroy all created dynamic objects. 531 | /// 532 | public static void DestroyAllDynamicObjects() 533 | { 534 | foreach (Prop obj in GetAllProp()) 535 | { 536 | AltEntitySync.RemoveEntity(obj); 537 | } 538 | Prop.PropList.Clear(); 539 | } 540 | 541 | /// 542 | /// Get all created dynamic objects. 543 | /// 544 | /// A list of dynamic objects. 545 | public static List GetAllProp() 546 | { 547 | List objects = new List(); 548 | 549 | foreach (IEntity entity in Prop.PropList) 550 | { 551 | Prop obj = GetProp(entity.Id); 552 | 553 | if (obj != null) 554 | objects.Add(obj); 555 | } 556 | 557 | return objects; 558 | } 559 | 560 | /// 561 | /// Get the dynamic object that's closest to a specified position. 562 | /// 563 | /// The position from which to check. 564 | /// The closest dynamic object to the specified position, or null if none found. 565 | public static (Prop obj, float distance) GetClosestDynamicObject(Vector3 pos) 566 | { 567 | if (GetAllProp().Count == 0) 568 | return (null, 5000); 569 | 570 | Prop obj = null; 571 | float distance = 5000; 572 | 573 | foreach (Prop o in GetAllProp()) 574 | { 575 | float dist = Vector3.Distance(o.Position, pos); 576 | if (dist < distance) 577 | { 578 | obj = o; 579 | distance = dist; 580 | } 581 | } 582 | 583 | return (obj, distance); 584 | } 585 | } 586 | } -------------------------------------------------------------------------------- /ServerSide/MarkerManager.cs: -------------------------------------------------------------------------------- 1 | using AltV.Net.Data; 2 | using AltV.Net.EntitySync; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Numerics; 6 | 7 | namespace EntityStreamer 8 | { 9 | /// 10 | /// Marker types. 11 | /// 12 | public enum MarkerTypes 13 | { 14 | MarkerTypeUpsideDownCone = 0, 15 | MarkerTypeVerticalCylinder = 1, 16 | MarkerTypeThickChevronUp = 2, 17 | MarkerTypeThinChevronUp = 3, 18 | MarkerTypeCheckeredFlagRect = 4, 19 | MarkerTypeCheckeredFlagCircle = 5, 20 | MarkerTypeVerticleCircle = 6, 21 | MarkerTypePlaneModel = 7, 22 | MarkerTypeLostMCDark = 8, 23 | MarkerTypeLostMCLight = 9, 24 | MarkerTypeNumber0 = 10, 25 | MarkerTypeNumber1 = 11, 26 | MarkerTypeNumber2 = 12, 27 | MarkerTypeNumber3 = 13, 28 | MarkerTypeNumber4 = 14, 29 | MarkerTypeNumber5 = 15, 30 | MarkerTypeNumber6 = 16, 31 | MarkerTypeNumber7 = 17, 32 | MarkerTypeNumber8 = 18, 33 | MarkerTypeNumber9 = 19, 34 | MarkerTypeChevronUpx1 = 20, 35 | MarkerTypeChevronUpx2 = 21, 36 | MarkerTypeChevronUpx3 = 22, 37 | MarkerTypeHorizontalCircleFat = 23, 38 | MarkerTypeReplayIcon = 24, 39 | MarkerTypeHorizontalCircleSkinny = 25, 40 | MarkerTypeHorizontalCircleSkinny_Arrow = 26, 41 | MarkerTypeHorizontalSplitArrowCircle = 27, 42 | MarkerTypeDebugSphere = 28, 43 | MarkerTypeDallorSign = 29, 44 | MarkerTypeHorizontalBars = 30, 45 | MarkerTypeWolfHead = 31 46 | } 47 | 48 | /// 49 | /// DynamicMarker class that stores all data related to a single marker. 50 | /// 51 | public class Marker : Entity, IEntity 52 | { 53 | private ulong EntityType 54 | { 55 | get 56 | { 57 | if (!TryGetData("entityType", out ulong type)) 58 | return 999; 59 | return type; 60 | } 61 | set 62 | { 63 | // No data changed 64 | if (EntityType == value) 65 | return; 66 | 67 | SetData("entityType", value); 68 | } 69 | } 70 | 71 | /// 72 | /// Set or get the current marker's rotation (in degrees). 73 | /// 74 | public Vector3 Rotation 75 | { 76 | get 77 | { 78 | if (!TryGetData("rotation", out Dictionary data)) 79 | return default; 80 | 81 | return new Vector3() 82 | { 83 | X = Convert.ToSingle(data["x"]), 84 | Y = Convert.ToSingle(data["y"]), 85 | Z = Convert.ToSingle(data["z"]), 86 | }; 87 | } 88 | set 89 | { 90 | // No data changed 91 | if (Rotation.X == value.X && Rotation.Y == value.Y && Rotation.Z == value.Z && value != new Vector3(0, 0, 0)) 92 | return; 93 | 94 | Dictionary dict = new Dictionary() 95 | { 96 | ["x"] = value.X, 97 | ["y"] = value.Y, 98 | ["z"] = value.Z, 99 | }; 100 | SetData("rotation", dict); 101 | } 102 | } 103 | 104 | /// 105 | /// Set a texture dictionary, pass null to remove. 106 | /// 107 | public string TextureDict 108 | { 109 | get 110 | { 111 | if (!TryGetData("textureDict", out string textureDict)) 112 | return null; 113 | 114 | return textureDict; 115 | } 116 | set 117 | { 118 | if (value == null) 119 | { 120 | SetData("textureDict", null); 121 | return; 122 | } 123 | 124 | // No data changed 125 | if (TextureDict == value) 126 | return; 127 | 128 | SetData("textureDict", value); 129 | } 130 | } 131 | 132 | /// 133 | /// Texture name, only applicable if TextureDict is set. pass null to reset value. 134 | /// 135 | public string TextureName 136 | { 137 | get 138 | { 139 | if (!TryGetData("textureName", out string textureName)) 140 | return null; 141 | 142 | return textureName; 143 | } 144 | set 145 | { 146 | if (value == null) 147 | { 148 | SetData("textureName", null); 149 | return; 150 | } 151 | 152 | // No data changed 153 | if (TextureName == value) 154 | return; 155 | 156 | SetData("textureName", value); 157 | } 158 | } 159 | 160 | /// 161 | /// Whether the marker should rotate on the Y axis(heading). 162 | /// 163 | public bool? Rotate 164 | { 165 | get 166 | { 167 | if (!TryGetData("rotate", out bool rotate)) 168 | return false; 169 | 170 | return rotate; 171 | } 172 | set 173 | { 174 | // if value is set to null, reset the data 175 | if (value == null) 176 | { 177 | SetData("rotate", false); 178 | return; 179 | } 180 | 181 | // No data changed 182 | if (Rotate == value) 183 | return; 184 | 185 | SetData("rotate", value); 186 | } 187 | } 188 | 189 | /// 190 | /// Whether the marker should be drawn onto the entity when they enter it. 191 | /// 192 | public bool? DrawOnEnter 193 | { 194 | get 195 | { 196 | if (!TryGetData("drawOnEnter", out bool drawOnEnter)) 197 | return false; 198 | 199 | return drawOnEnter; 200 | } 201 | set 202 | { 203 | // if value is set to null, reset the data 204 | if (value == null) 205 | { 206 | SetData("drawOnEnter", false); 207 | return; 208 | } 209 | 210 | // No data changed 211 | if (DrawOnEnter == value) 212 | return; 213 | 214 | SetData("drawOnEnter", value); 215 | } 216 | } 217 | 218 | /// 219 | /// Whether the marker should rotate on the Y axis towards the player's camera. 220 | /// 221 | public bool? FaceCamera 222 | { 223 | get 224 | { 225 | if (!TryGetData("faceCam", out bool faceCamera)) 226 | return false; 227 | 228 | return faceCamera; 229 | } 230 | set 231 | { 232 | // if value is set to null, reset the data 233 | if (value == null) 234 | { 235 | SetData("faceCam", false); 236 | return; 237 | } 238 | 239 | // No data changed 240 | if (FaceCamera == value) 241 | return; 242 | 243 | SetData("faceCam", value); 244 | } 245 | } 246 | 247 | /// 248 | /// Whether the marker should bob up and down. 249 | /// 250 | public bool? BobUpDown 251 | { 252 | get 253 | { 254 | if (!TryGetData("bobUpDown", out bool bobUpDown)) 255 | return false; 256 | 257 | return bobUpDown; 258 | } 259 | set 260 | { 261 | // if value is set to null, reset the data 262 | if (value == null) 263 | { 264 | SetData("bobUpDown", false); 265 | return; 266 | } 267 | 268 | // No data changed 269 | if (BobUpDown == value) 270 | return; 271 | 272 | SetData("bobUpDown", value); 273 | } 274 | } 275 | 276 | /// 277 | /// Set scale of the marker. 278 | /// 279 | public Vector3 Scale 280 | { 281 | get 282 | { 283 | if (!TryGetData("scale", out Dictionary data)) 284 | return default; 285 | 286 | return new Vector3() 287 | { 288 | X = Convert.ToSingle(data["x"]), 289 | Y = Convert.ToSingle(data["y"]), 290 | Z = Convert.ToSingle(data["z"]), 291 | }; 292 | } 293 | set 294 | { 295 | // No data changed 296 | if (Scale.X == value.X && Scale.Y == value.Y && Scale.Z == value.Z && value != new Vector3(0, 0, 0)) 297 | return; 298 | 299 | Dictionary dict = new Dictionary() 300 | { 301 | ["x"] = value.X, 302 | ["y"] = value.Y, 303 | ["z"] = value.Z, 304 | }; 305 | SetData("scale", dict); 306 | } 307 | } 308 | 309 | /// 310 | /// Represents a heading on each axis in which the marker should face, alternatively you can rotate each axis independently with Rotation and set Direction axis to 0. 311 | /// 312 | public Vector3 Direction 313 | { 314 | get 315 | { 316 | if (!TryGetData("direction", out Dictionary data)) 317 | return default; 318 | 319 | return new Vector3() 320 | { 321 | X = Convert.ToSingle(data["x"]), 322 | Y = Convert.ToSingle(data["y"]), 323 | Z = Convert.ToSingle(data["z"]), 324 | }; 325 | } 326 | set 327 | { 328 | // No data changed 329 | if (Direction != null && Direction.X == value.X && Direction.Y == value.Y && Direction.Z == value.Z && value != new Vector3(0, 0, 0)) 330 | return; 331 | 332 | Dictionary dict = new Dictionary() 333 | { 334 | ["x"] = value.X, 335 | ["y"] = value.Y, 336 | ["z"] = value.Z, 337 | }; 338 | SetData("direction", dict); 339 | } 340 | } 341 | 342 | /// 343 | /// Set or get the current marker's type(see MarkerTypes enum). 344 | /// 345 | public MarkerTypes MarkerType 346 | { 347 | get 348 | { 349 | if (!TryGetData("markerType", out int markerType)) 350 | return default; 351 | 352 | return (MarkerTypes)markerType; 353 | } 354 | set 355 | { 356 | // No data changed 357 | if (MarkerType == value) 358 | return; 359 | 360 | SetData("markerType", (int)value); 361 | } 362 | } 363 | 364 | /// 365 | /// Set marker color. 366 | /// 367 | public Rgba? Color 368 | { 369 | get 370 | { 371 | if (!TryGetData("color", out Dictionary data)) 372 | return null; 373 | 374 | return new Rgba( 375 | Convert.ToByte(data["r"]), 376 | Convert.ToByte(data["g"]), 377 | Convert.ToByte(data["b"]), 378 | Convert.ToByte(data["a"]) 379 | ); 380 | } 381 | set 382 | { 383 | // if value is set to null, reset the data 384 | if (value == null) 385 | { 386 | SetData("color", null); 387 | return; 388 | } 389 | 390 | // No data changed 391 | if (Color != null && Color?.R == value?.R && Color?.G == value?.G && Color?.B == value?.B && Color?.A == value?.A) 392 | return; 393 | 394 | Dictionary dict = new Dictionary 395 | { 396 | { "r", Convert.ToInt32( value?.R ) }, 397 | { "g", Convert.ToInt32( value?.G ) }, 398 | { "b", Convert.ToInt32( value?.B ) }, 399 | { "a", Convert.ToInt32( value?.A ) } 400 | }; 401 | 402 | SetData("color", dict); 403 | } 404 | } 405 | 406 | private static List markerList = new List(); 407 | 408 | public static List MarkerList 409 | { 410 | get 411 | { 412 | lock (markerList) 413 | { 414 | return markerList; 415 | } 416 | } 417 | set 418 | { 419 | markerList = value; 420 | } 421 | } 422 | 423 | public Marker(Vector3 position, int dimension, uint range, ulong entityType) : base(entityType, position, dimension, range) 424 | { 425 | EntityType = entityType; 426 | } 427 | 428 | /// 429 | /// Destroy this marker. 430 | /// 431 | public void Destroy() 432 | { 433 | Marker.MarkerList.Remove(this); 434 | AltEntitySync.RemoveEntity(this); 435 | } 436 | } 437 | 438 | public static class MarkerStreamer 439 | { 440 | /// 441 | /// Create a new dynamic marker 442 | /// 443 | /// The type of marker to spawn. 444 | /// The position at which the marker should spawn at. 445 | /// The scale of the marker. 446 | /// The rotation of the marker. 447 | /// The direction of the marker. 448 | /// The color of the marker. 449 | /// Whether the marker should bob up and down. 450 | /// Whether the marker should face the entity's camera. 451 | /// Whether the marker should rotate on the Y axis only. 452 | /// An optional texture dictionary to apply to the marker. 453 | /// An optional texture name to apply to the marker. 454 | /// Whether it should draw the marker onto an entity that intersects with it. 455 | /// The dimension of the marker 456 | /// Stream distance of the marker, default is 100. 457 | /// 458 | public static Marker Create( 459 | MarkerTypes markerType, Vector3 position, Vector3 scale, Vector3? rotation = null, Vector3? direction = null, Rgba? color = null, int dimension = 0, 460 | bool? bobUpDown = false, bool? faceCamera = false, bool? rotate = false, string textureDict = null, string textureName = null, 461 | bool? drawOnEnter = false, uint streamRange = 100 462 | ) 463 | { 464 | Marker marker = new Marker(position, dimension, streamRange, 0) 465 | { 466 | Rotation = rotation ?? new Vector3(0), 467 | MarkerType = markerType, 468 | Direction = direction ?? new Vector3(0), 469 | Scale = scale, 470 | Color = color ?? null, 471 | BobUpDown = bobUpDown ?? null, 472 | FaceCamera = faceCamera ?? null, 473 | Rotate = rotate ?? null, 474 | TextureDict = textureDict ?? null, 475 | TextureName = textureName ?? null, 476 | DrawOnEnter = drawOnEnter ?? null 477 | }; 478 | Marker.MarkerList.Add(marker); 479 | AltEntitySync.AddEntity(marker); 480 | return marker; 481 | } 482 | 483 | /// 484 | /// Destroy a dynamic marker by it's ID. 485 | /// 486 | /// The ID of the marker. 487 | /// True if successful, false otherwise. 488 | public static bool Delete(ulong dynamicMarkerId) 489 | { 490 | Marker marker = GetMarker(dynamicMarkerId); 491 | 492 | if (marker == null) 493 | return false; 494 | 495 | Marker.MarkerList.Remove(marker); 496 | AltEntitySync.RemoveEntity(marker); 497 | return true; 498 | } 499 | 500 | /// 501 | /// Destroy a dynamic marker. 502 | /// 503 | /// The marker instance to destroy. 504 | public static void Delete(Marker marker) 505 | { 506 | Marker.MarkerList.Remove(marker); 507 | AltEntitySync.RemoveEntity(marker); 508 | } 509 | 510 | /// 511 | /// Get a dynamic marker by it's ID. 512 | /// 513 | /// The ID of the marker. 514 | /// The dynamic marker or null if not found. 515 | public static Marker GetMarker(ulong MarkerId) 516 | { 517 | if (!AltEntitySync.TryGetEntity(MarkerId, 0, out IEntity entity)) 518 | { 519 | Console.WriteLine($"[MARKER-STREAMER] [GetDynamicMarker] ERROR: Entity with ID { MarkerId } couldn't be found."); 520 | return null; 521 | } 522 | 523 | if (!(entity is Marker)) 524 | return null; 525 | 526 | return (Marker)entity; 527 | } 528 | 529 | /// 530 | /// Destroy all created dynamic markers. 531 | /// 532 | public static void DestroyAllDynamicMarkers() 533 | { 534 | foreach (Marker marker in GetAllDynamicMarkers()) 535 | { 536 | AltEntitySync.RemoveEntity(marker); 537 | } 538 | Marker.MarkerList.Clear(); 539 | } 540 | 541 | /// 542 | /// Get all created dynamic markers. 543 | /// 544 | /// A list of dynamic markers. 545 | public static List GetAllDynamicMarkers() 546 | { 547 | List markers = new List(); 548 | 549 | foreach (IEntity entity in Marker.MarkerList) 550 | { 551 | Marker obj = GetMarker(entity.Id); 552 | 553 | if (obj != null) 554 | markers.Add(obj); 555 | } 556 | 557 | return markers; 558 | } 559 | } 560 | } --------------------------------------------------------------------------------