├── LICENSE ├── README.md ├── client ├── anim-post-fx │ └── cAnimPostFX.lua ├── asset-requester │ └── cAssetRequester.lua ├── blip │ └── cBlip.lua ├── camera │ ├── cCamera.lua │ └── cFilter.lua ├── entity │ └── cEntity.lua ├── enums │ └── ControlEnum.lua ├── explosion │ └── cExplosion.lua ├── hud │ └── cHud.lua ├── keypress │ ├── cKeymap.lua │ └── cKeypress.lua ├── light │ └── cLight.lua ├── localplayer │ └── cLocalPlayer.lua ├── localplayer_behaviors │ └── cBulletDetectionLocalPlayerBehavior.lua ├── map │ ├── cImap.lua │ └── cIpl.lua ├── marker │ └── cMarker.lua ├── network │ └── cNetwork.lua ├── object │ ├── cObject.lua │ └── cObjectManager.lua ├── particle-effect │ └── cParticleEffect.lua ├── pause-menu │ └── cPauseMenu.lua ├── ped │ └── cPed.lua ├── physics │ └── cPhysics.lua ├── player │ ├── cPlayer.lua │ ├── cPlayerManager.lua │ └── cPlayers.lua ├── prompt │ └── cPrompt.lua ├── render │ ├── cRender.lua │ └── cTexture.lua ├── screen-effects │ └── cScreenEffects.lua ├── sound │ └── cSound.lua ├── spectate │ └── cSpectateMode.lua ├── typecheck │ └── cTypeCheck.lua ├── ui │ ├── events.js │ ├── index.html │ ├── jquery.js │ ├── reset.css │ └── ui.lua ├── volume │ └── cVolume.lua ├── water │ └── cWater.lua ├── weapons │ └── cWeapons.lua └── world │ └── cWorld.lua ├── example_fxmanifest.lua ├── server ├── entity │ └── sEntity.lua ├── fs-additions │ └── exists.lua ├── json │ ├── sJSONUtils.lua │ └── sJsonOOF.lua ├── key-value-store │ └── sKeyValueStore.lua ├── mysql-async │ ├── LICENSE │ ├── MySQLAsync.net.dll │ ├── MySqlConnector.dll │ ├── System.Buffers.dll │ ├── System.Threading.Tasks.Extensions.dll │ ├── lib │ │ ├── MySQL.lua │ │ └── init.lua │ └── src │ │ ├── AssemblyInfo.cs │ │ ├── Execute.cs │ │ ├── FetchAll.cs │ │ ├── FetchScalar.cs │ │ ├── FiveMMySQLAsync.csproj │ │ ├── FiveMMySQLAsync.sln │ │ ├── Insert.cs │ │ ├── MySQLAsync.cs │ │ ├── MySQLThread.cs │ │ ├── Operation.cs │ │ ├── Transaction.cs │ │ └── app.config ├── mysql │ └── MySQL.lua ├── network │ └── sNetwork.lua ├── ped │ └── sPed.lua ├── player │ ├── sPlayer.lua │ ├── sPlayerManager.lua │ └── sPlayers.lua ├── sConfig.lua └── world │ └── sWorld.lua └── shared ├── color └── Color.lua ├── csv └── CSV.lua ├── enums ├── shEntityTypeEnum.lua ├── shPedBoneEnum.lua ├── shVehicleEnum.lua ├── shWeaponEnum.lua └── shWeaponTypeEnum.lua ├── events ├── shEvent.lua └── shEvents.lua ├── game ├── IsFiveM.lua └── IsRedM.lua ├── lua-additions └── lua-additions.lua ├── lua-overloads ├── assert.lua ├── error.lua ├── lua-overloads.lua ├── math-randomseed.lua ├── print.lua └── tostring.lua ├── math ├── shVector3Math.lua ├── shXorCipher.lua └── standardized-distance.lua ├── object-oriented ├── LOAD_ABSOLUTELY_LAST.lua ├── class.lua ├── shGetterSetter.lua └── shObjectOrientedUtilities.lua ├── standalone-data-structures ├── shDeque.lua ├── shEnum.lua └── shIdPool.lua ├── timer └── Timer.lua ├── value-storage └── ValueStorage.lua └── xml ├── SLAXML.lua └── XML.lua /client/anim-post-fx/cAnimPostFX.lua: -------------------------------------------------------------------------------- 1 | AnimPostFX = class() 2 | 3 | function AnimPostFX:__init() 4 | 5 | end 6 | 7 | function AnimPostFX:Play(name) 8 | AnimpostfxPlay(name) 9 | end 10 | 11 | function AnimPostFX:Stop(name) 12 | AnimpostfxStop(name) 13 | end 14 | 15 | if IsRedM then 16 | AnimPostFX = AnimPostFX() 17 | end 18 | 19 | -- List of FX names from https://github.com/femga/rdr3_discoveries/blob/master/graphics/animpostfx/animpostfx.lua 20 | --[[local animpostfx = { 21 | 22 | "CameraTakePicture", 23 | "CameraTransitionBlink", 24 | "CameraTransitionFlash", 25 | "CameraTransitionWipe_L", 26 | "CameraTransitionWipe_R", 27 | "CameraViewfinder", 28 | "CamTransition01", 29 | "CamTransitionBlink", 30 | "CamTransitionBlinkSick", 31 | "CamTransitionBlinkSlow", 32 | "ChapterTitle_IntroCh01", 33 | "ChapterTitle_IntroCh02", 34 | "ChapterTitle_IntroCh03", 35 | "ChapterTitle_IntroCh04", 36 | "ChapterTitle_IntroCh05", 37 | "ChapterTitle_IntroCh06", 38 | "ChapterTitle_IntroCh08Epi01", 39 | "ChapterTitle_IntroCh09Epi02", 40 | "cutscene_mar6_train", 41 | "cutscene_rbch2rsc11_bink1", 42 | "cutscene_rbch2rsc11_bink2", 43 | "cutscene_rbch2rsc11_bink3", 44 | "cutscene_rbch2rsc11_bink4", 45 | "cutscene_rbch2rsc11_bink5", 46 | "cutscene_rbch2rsc11_bink6", 47 | "cutscene_rbch2rsc11_bink7", 48 | "cutscene_rbch2rsc11_bink8", 49 | "deadeye", 50 | "DeadEyeEmpty", 51 | "DeathFailMP01", 52 | "Duel", 53 | "EagleEye", 54 | "GunslingerFill", 55 | "killCam", 56 | "KillCamHonorChange", 57 | "KingCastleBlue", 58 | "KingCastleRed", 59 | "MissionChoice", 60 | "MissionFail01", 61 | "Mission_EndCredits", 62 | "Mission_FIN1_RideBad", 63 | "Mission_FIN1_RideGood", 64 | "Mission_GNG0_Ride", 65 | "MP_BountyLagrasSwamp", 66 | "MP_BountySelection", 67 | "MP_CampWipeDown", 68 | "MP_CampWipeL", 69 | "MP_CampWipeR", 70 | "MP_CampWipeUp", 71 | "MP_DM_Annesburg_ReduceDustDensity", 72 | "MP_Downed", 73 | "MP_HealthDrop", 74 | "MP_InRegion_Exit", 75 | "MP_LobbyBW01_Intro", 76 | "MP_OutofAreaDirectional", 77 | "MP_PedKill", 78 | "MP_RaceBoostStart", 79 | "MP_Region", 80 | "MP_RewardsExposureLoop", 81 | "MP_Rhodes_ReduceDustDensity", 82 | "MP_RiderFormation", 83 | "MP_SuddenDeath", 84 | "ODR3_Injured01Loop", 85 | "ODR3_Injured02Loop", 86 | "ODR3_Injured03Loop", 87 | "OJDominoBlur", 88 | "OJDominoValid", 89 | "OJFiveFinger", 90 | "OJPokerPlayerTurn", 91 | "PauseMenuIn", 92 | "PedKill", 93 | "PlayerDrugsPoisonWell", 94 | "PlayerDrunk01", 95 | "PlayerDrunk01_PassOut", 96 | "PlayerDrunkAberdeen", 97 | "PlayerDrunkSaloon1", 98 | "PlayerHealthCrackpot", 99 | "PlayerHealthLow", 100 | "PlayerHealthPoorCS", 101 | "PlayerHealthPoorGuarma", 102 | "PlayerHealthPoorMOB3", 103 | "PlayerHonorChoiceBad", 104 | "PlayerHonorChoiceGood", 105 | "PlayerHonorLevelBad", 106 | "PlayerHonorLevelGood", 107 | "PlayerImpact04", 108 | "PlayerImpactFall", 109 | "PlayerKnockout_SerialKiller", 110 | "PlayerKnockout_WeirdoPat", 111 | "PlayerOverpower", 112 | "PlayerRPGCore", 113 | "PlayerRPGCoreDeadEye", 114 | "PlayerRPGEmptyCoreDeadEye", 115 | "PlayerRPGEmptyCoreHealth", 116 | "PlayerRPGEmptyCoreStamina", 117 | "PlayerRPGWarnHealth", 118 | "PlayerSickDoctorsOpinion", 119 | "PlayerSickDoctorsOpinionOutBad", 120 | "PlayerSickDoctorsOpinionOutGood", 121 | "PlayerWakeUpAberdeen", 122 | "PlayerWakeUpDrunk", 123 | "PlayerWakeUpInterrogation", 124 | "PlayerWakeUpKnockout", 125 | "PoisonDartPassOut", 126 | "POSTFX_CONSUMABLE_STAMINA", 127 | "POSTFX_CONSUMABLE_STAMINA_FORT", 128 | "RespawnEstablish01", 129 | "RespawnMissionCheckpoint", 130 | "RespawnPulse01", 131 | "RespawnSkyWithHonor", 132 | "RespawnWithHonor", 133 | "SkyTimelapse_0600_01", 134 | "skytl_0000_01clear", 135 | "skytl_0600_01clear", 136 | "skytl_0900_01clear", 137 | "skytl_0900_04storm", 138 | "skytl_1200_01clear", 139 | "skytl_1200_03clouds", 140 | "skytl_1500_03clouds", 141 | "skytl_1500_04storm", 142 | "title_ch01_colter", 143 | "Title_Ch03_ClemensPoint", 144 | "Title_GameIntro", 145 | "Title_Gen_coupledayslater", 146 | "Title_Gen_coupledayslater_onblack", 147 | "Title_Gen_couplemonthslater", 148 | "Title_Gen_couplemonthslater_onblack", 149 | "Title_Gen_coupleweekslater", 150 | "Title_Gen_coupleweekslater_onblack", 151 | "Title_Gen_daylater", 152 | "Title_Gen_daylater_onblack", 153 | "Title_Gen_FewDaysLater", 154 | "Title_Gen_FewDaysLater_onblack", 155 | "Title_Gen_FewHoursLater", 156 | "Title_Gen_FewHoursLater_onblack", 157 | "Title_Gen_FewMonthsLater", 158 | "Title_Gen_FewMonthsLater_onblack", 159 | "Title_Gen_FewWeeksLater", 160 | "Title_Gen_FewWeeksLater_onblack", 161 | "Title_Gen_somedaysLater", 162 | "Title_Gen_somedaysLater_onblack", 163 | "Title_Gen_someyearsLater", 164 | "Title_Gen_someyearsLater_onblack", 165 | "UI_PauseTransition", 166 | "UI_PauseTransitionOut", 167 | "WheelHUDIn", 168 | }]] 169 | 170 | --[[ 171 | List of FiveM FX names: https://pastebin.com/dafBAjs0 172 | 173 | LIST OF SCREEN FX 174 | 175 | INFO : Somes effect are directly loop and need to be stop 176 | 177 | START_SCREEN_EFFECT(FxName, 0, false); 178 | STOP_SCREEN_EFFECT(FxName); 179 | 180 | SwitchHUDIn 181 | SwitchHUDOut 182 | FocusIn 183 | FocusOut 184 | MinigameEndNeutral 185 | MinigameEndTrevor 186 | MinigameEndFranklin 187 | MinigameEndMichael 188 | MinigameTransitionOut 189 | MinigameTransitionIn 190 | SwitchShortNeutralIn 191 | SwitchShortFranklinIn 192 | SwitchShortTrevorIn 193 | SwitchShortMichaelIn 194 | SwitchOpenMichaelIn 195 | SwitchOpenFranklinIn 196 | SwitchOpenTrevorIn 197 | SwitchHUDMichaelOut 198 | SwitchHUDFranklinOut 199 | SwitchHUDTrevorOut 200 | SwitchShortFranklinMid 201 | SwitchShortMichaelMid 202 | SwitchShortTrevorMid 203 | DeathFailOut 204 | CamPushInNeutral 205 | CamPushInFranklin 206 | CamPushInMichael 207 | CamPushInTrevor 208 | SwitchOpenMichaelIn 209 | SwitchSceneFranklin 210 | SwitchSceneTrevor 211 | SwitchSceneMichael 212 | SwitchSceneNeutral 213 | MP_Celeb_Win 214 | MP_Celeb_Win_Out 215 | MP_Celeb_Lose 216 | MP_Celeb_Lose_Out 217 | DeathFailNeutralIn 218 | DeathFailMPDark 219 | DeathFailMPIn 220 | MP_Celeb_Preload_Fade 221 | PeyoteEndOut 222 | PeyoteEndIn 223 | PeyoteIn 224 | PeyoteOut 225 | MP_race_crash 226 | SuccessFranklin 227 | SuccessTrevor 228 | SuccessMichael 229 | DrugsMichaelAliensFightIn 230 | DrugsMichaelAliensFight 231 | DrugsMichaelAliensFightOut 232 | DrugsTrevorClownsFightIn 233 | DrugsTrevorClownsFight 234 | DrugsTrevorClownsFightOut 235 | HeistCelebPass 236 | HeistCelebPassBW 237 | HeistCelebEnd 238 | HeistCelebToast 239 | MenuMGHeistIn 240 | MenuMGTournamentIn 241 | MenuMGSelectionIn 242 | ChopVision 243 | DMT_flight_intro 244 | DMT_flight 245 | DrugsDrivingIn 246 | DrugsDrivingOut 247 | SwitchOpenNeutralFIB5 248 | HeistLocate 249 | MP_job_load 250 | RaceTurbo 251 | MP_intro_logo 252 | HeistTripSkipFade 253 | MenuMGHeistOut 254 | MP_corona_switch 255 | MenuMGSelectionTint 256 | SuccessNeutral 257 | ExplosionJosh3 258 | SniperOverlay 259 | RampageOut 260 | Rampage 261 | Dont_tazeme_bro 262 | DeathFailOut 263 | 264 | 265 | ]] -------------------------------------------------------------------------------- /client/asset-requester/cAssetRequester.lua: -------------------------------------------------------------------------------- 1 | function LoadModel(hash, callback) 2 | Citizen.CreateThread(function() 3 | local wait_time = 0 4 | RequestModel(hash) 5 | while not HasModelLoaded(hash) do 6 | Citizen.Wait(100) 7 | wait_time = wait_time + 100 8 | assert(wait_time < 5000, string.format("model with hash %s could not be loaded in time. Did you use the right hash?", hash)) 9 | end 10 | callback() 11 | -- Clear up memory 12 | Citizen.SetTimeout(1000, function() 13 | SetModelAsNoLongerNeeded(hash) 14 | end) 15 | end) 16 | end 17 | 18 | function LoadEffectBank(bank, callback) 19 | Citizen.CreateThread(function() 20 | local wait_time = 0 21 | RequestNamedPtfxAsset(bank) 22 | while not HasNamedPtfxAssetLoaded(bank) do 23 | Citizen.Wait(100) 24 | wait_time = wait_time + 100 25 | assert(wait_time < 5000, string.format("effect bank %s could not be loaded in time. Did you use the right name?", bank)) 26 | end 27 | callback() 28 | -- Clear up memory 29 | Citizen.SetTimeout(1000, function() 30 | RemoveNamedPtfxAsset(bank) 31 | end) 32 | end) 33 | end 34 | 35 | 36 | function LoadAnimSet(anim, callback) 37 | Citizen.CreateThread(function() 38 | local wait_time = 0 39 | RequestAnimSet(anim) 40 | while not HasAnimSetLoaded(anim) do 41 | Citizen.Wait(100) 42 | wait_time = wait_time + 100 43 | assert(wait_time < 5000, string.format("anim set %s could not be loaded in time. Did you use the right name?", anim)) 44 | end 45 | callback() 46 | -- Clear up memory 47 | Citizen.SetTimeout(1000, function() 48 | RemoveAnimSet(anim) 49 | end) 50 | end) 51 | end 52 | 53 | function LoadAnimDict(dict, callback) 54 | Citizen.CreateThread(function() 55 | local wait_time = 0 56 | RequestAnimDict(dict) 57 | while not HasAnimDictLoaded(dict) do 58 | Citizen.Wait(100) 59 | wait_time = wait_time + 100 60 | assert(wait_time < 5000, string.format("anim dict %s could not be loaded in time. Did you use the right name?", dict)) 61 | end 62 | callback() 63 | -- Clear up memory 64 | Citizen.SetTimeout(1000, function() 65 | RemoveAnimDict(dict) 66 | end) 67 | end) 68 | end -------------------------------------------------------------------------------- /client/camera/cCamera.lua: -------------------------------------------------------------------------------- 1 | Camera = class() 2 | 3 | function Camera:__init() 4 | self.cam = GetRenderingCam() 5 | self:FadeIn(0) 6 | end 7 | 8 | CameraViewMode = 9 | { 10 | ThirdPersonClose = 0, 11 | ThirdPersonMiddle = 1, 12 | ThirdPersonFar = 2, 13 | FirstPerson = 4 14 | } 15 | 16 | if IsFiveM then 17 | -- Locks the camera view mode to a specific CameraViewMode 18 | -- Must be called every frame to override manual controls 19 | -- unless you also disable the camera control with: 20 | -- LocalPlayer:RestrictAction(Control.NextCamera, true) 21 | function Camera:LockCameraMode(mode) 22 | if GetFollowPedCamViewMode() ~= mode or 23 | GetFollowVehicleCamViewMode() ~= mode then 24 | SetFollowPedCamViewMode(mode) 25 | SetFollowVehicleCamViewMode(mode) 26 | end 27 | end 28 | end 29 | 30 | -- Interpolates between two positions over duration ms 31 | function Camera:InterpolateBetween(pos1, pos2, rot1, rot2, duration) 32 | assert(self.freecam == nil 33 | and self.from_cam == nil 34 | and self.to_cam == nil, "cannot call Camera:InterpolateBetween multiple times without calling Camera:Reset first") 35 | 36 | ClearFocus() 37 | self.from_cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", pos1.x, pos1.y, pos1.z, rot1.x, rot1.y, rot1.z, self:GetFOV()) 38 | self.to_cam = CreateCamWithParams("DEFAULT_SCRIPTED_CAMERA", pos2.x, pos2.y, pos2.z, rot2.x, rot2.y, rot2.z, self:GetFOV()) 39 | 40 | SetCamActiveWithInterp(self.to_cam, self.from_cam, duration) 41 | RenderScriptCams(true, false, 0, true, false, true) 42 | SetCamAffectsAiming(self.from_cam, false) 43 | SetCamAffectsAiming(self.to_cam, false) 44 | end 45 | 46 | function Camera:SetFOV(fov) 47 | SetCamFov(self:GetCurrentCam(), tofloat(fov)) 48 | end 49 | 50 | function Camera:DetachFromPlayer(position, rotation, ease, ease_time) 51 | assert(self.freecam == nil 52 | and self.from_cam == nil 53 | and self.to_cam == nil, "cannot call Camera:DetachFromPlayer multiple times without calling Camera:Reset first") 54 | 55 | local pos = position or Camera:GetPosition() 56 | local rot = rotation or Camera:GetRotation() 57 | 58 | ClearFocus() 59 | self.freecam = CreateCamWithParams( 60 | "DEFAULT_SCRIPTED_CAMERA", 61 | pos.x, pos.y, pos.z, 62 | rot.y, rot.y, rot.z, 63 | self:GetFOV()) 64 | SetCamActive(self:GetCurrentCam(), true) 65 | RenderScriptCams(true, ease, ease_time, true, false, true) 66 | SetCamAffectsAiming(self:GetCurrentCam(), false) 67 | end 68 | 69 | --[[ 70 | Attaches the camera to an entity. Must use DetachFromPlayer first. 71 | ]] 72 | function Camera:AttachToEntity(entity, offset) 73 | assert(entity:Exists(), "cannot Camera:AttachToEntity on entity that does not exist") 74 | assert(self:GetCurrentCam() ~= self.cam, "must use Camera:DetachFromPlayer before using Camera:AttachToEntity") 75 | 76 | AttachCamToEntity(self:GetCurrentCam(), entity:GetEntityId(), offset.x, offset.y, offset.z, true) 77 | end 78 | 79 | function Camera:Reset(ease, ease_time) 80 | ClearFocus() 81 | RenderScriptCams(false, ease, ease_time or 0, true, false) 82 | DestroyCam(self.freecam, false) 83 | DestroyCam(self.from_cam, false) 84 | DestroyCam(self.to_cam, false) 85 | self.from_cam = nil 86 | self.to_cam = nil 87 | self.freecam = nil 88 | end 89 | 90 | function Camera:GetFOV() 91 | return GetGameplayCamFov() 92 | end 93 | 94 | function Camera:PointAtEntity(entity) 95 | PointCamAtEntity(self:GetCurrentCam(), entity:GetEntityId(), 0.0, 0.0, 0.0, true) 96 | end 97 | 98 | function Camera:PointAtCoord(pos) 99 | PointCamAtCoord(self:GetCurrentCam(), pos.x, pos.y, pos.z) 100 | end 101 | 102 | function Camera:SetPosition(pos) 103 | SetCamCoord(self:GetCurrentCam(), pos.x, pos.y, pos.z) 104 | end 105 | 106 | function Camera:SetRotation(rot) 107 | SetCamRot(self:GetCurrentCam(), rot.x, rot.y, rot.z, 2) 108 | end 109 | 110 | function Camera:SetGameplayCamShakeAmplitude(amount) 111 | SetGameplayCamShakeAmplitude(tofloat(amount)) 112 | end 113 | 114 | CameraShakeType = 115 | { 116 | DEATH_FAIL_IN_EFFECT_SHAKE = "DEATH_FAIL_IN_EFFECT_SHAKE", 117 | DRUNK_SHAKE = "DRUNK_SHAKE", 118 | FAMILY5_DRUG_TRIP_SHAKE = "FAMILY5_DRUG_TRIP_SHAKE", 119 | HAND_SHAKE = "HAND_SHAKE", 120 | JOLT_SHAKE = "JOLT_SHAKE", 121 | LARGE_EXPLOSION_SHAKE = "LARGE_EXPLOSION_SHAKE", 122 | MEDIUM_EXPLOSION_SHAKE = "MEDIUM_EXPLOSION_SHAKE", 123 | SMALL_EXPLOSION_SHAKE = "SMALL_EXPLOSION_SHAKE", 124 | ROAD_VIBRATION_SHAKE = "ROAD_VIBRATION_SHAKE", 125 | SKY_DIVING_SHAKE = "SKY_DIVING_SHAKE", 126 | VIBRATE_SHAKE = "VIBRATE_SHAKE" 127 | } 128 | --[[ 129 | Shakes the Camera with intensity. 130 | 131 | shakeType is a CameraShakeType enum. Intensity is a number 132 | ]] 133 | function Camera:Shake(shakeType, intensity) 134 | ShakeGameplayCam(shakeType, tofloat(intensity)) 135 | end 136 | 137 | --[[ 138 | Fades in the camera from black over time in ms 139 | ]] 140 | function Camera:FadeIn(time) 141 | DoScreenFadeIn(time) 142 | end 143 | 144 | --[[ 145 | Fades out the camera from black over time in ms 146 | ]] 147 | function Camera:FadeOut(time) 148 | DoScreenFadeOut(time) 149 | end 150 | 151 | --[[ 152 | Returns whether the screen is black or not. 153 | ]] 154 | function Camera:IsFadedOut() 155 | return IsScreenFadedOut() 156 | end 157 | 158 | --[[ 159 | Returns true if the screen isn't faded out to black. 160 | ]] 161 | function Camera:IsFadedIn() 162 | return IsScreenFadedIn() 163 | end 164 | 165 | function Camera:GetPosition() 166 | --return GetCamCoord(self:GetCurrentCam()) 167 | return GetGameplayCamCoord() 168 | end 169 | 170 | function Camera:GetRotation() 171 | --return Vector3Math:RotationToDirection(GetCamRot(self:GetCurrentCam())) 172 | return Vector3Math:RotationToDirection(GetGameplayCamRot(0)) 173 | end 174 | 175 | function Camera:GetCurrentCam() 176 | if self.freecam then return self.freecam end 177 | return self.cam 178 | end 179 | 180 | --[[ 181 | rightVector --[[ vector3, forwardVector --[[ vector3, upVector --[[ vector3, position --[[ vector3 182 | Returns the world matrix of the specified camera. To turn this into a view matrix, calculate the inverse. 183 | ]] 184 | function Camera:GetMatrix() 185 | return GetCamMatrix(self:GetCurrentCam()) 186 | end 187 | 188 | Camera = Camera() -------------------------------------------------------------------------------- /client/explosion/cExplosion.lua: -------------------------------------------------------------------------------- 1 | Explosion = class() 2 | 3 | --[[ 4 | Creates an explosion 5 | 6 | args (in table): 7 | owner (ped id, optional): ped id of the owner of this explosion 8 | position (vector3): position of the explosion 9 | type (ExplosionTypes): explosion type 10 | damageScale (number, optional): damage scale of explosion 11 | isAudible (bool, optional): whether or not this explosion can be heard 12 | isInvisible (bool, optional): whether or not this explosion can be seen 13 | cameraShake (number, optional): amount of camera shake this explosion has 14 | noDamage (bool, optional): if the explosion does damage or not 15 | ]] 16 | function Explosion:Create(args) 17 | if args.owner ~= nil then 18 | AddOwnedExplosion( 19 | args.owner, 20 | args.position.x, args.position.y, args.position.z, 21 | args.type, 22 | args.damageScale == nil and 1.0 or args.damageScale, 23 | args.isAudible == nil and true or args.isAudible, 24 | args.isInvisible == nil and false or args.isInvisible, 25 | args.cameraShake == nil and 1.0 or args.cameraShake, 26 | args.noDamage == nil and false or args.noDamage) 27 | else 28 | AddExplosion( 29 | args.position.x, args.position.y, args.position.z, 30 | args.type, 31 | args.damageScale == nil and 1.0 or args.damageScale, 32 | args.isAudible == nil and true or args.isAudible, 33 | args.isInvisible == nil and false or args.isInvisible, 34 | args.cameraShake == nil and 1.0 or args.cameraShake, 35 | args.noDamage == nil and false or args.noDamage) 36 | end 37 | end 38 | 39 | Explosion = Explosion() 40 | 41 | if IsRedM then 42 | ExplosionTypes = 43 | { 44 | EXP_TAG_DONTCARE = -1, 45 | EXP_TAG_GRENADE = 0, 46 | EXP_TAG_STICKYBOMB = 1, 47 | EXP_TAG_MOLOTOV = 2, 48 | EXP_TAG_MOLOTOV_VOLATILE = 3, 49 | EXP_TAG_HI_OCTANE = 4, 50 | EXP_TAG_CAR = 5, 51 | EXP_TAG_PLANE = 6, 52 | EXP_TAG_PETROL_PUMP = 7, 53 | EXP_TAG_DIR_STEAM = 8, 54 | EXP_TAG_DIR_FLAME = 9, 55 | EXP_TAG_DIR_WATER_HYDRANT = 10, 56 | EXP_TAG_BOAT = 11, 57 | EXP_TAG_BULLET = 12, 58 | EXP_TAG_SMOKEGRENADE = 13, 59 | EXP_TAG_BZGAS = 14, 60 | EXP_TAG_GAS_CANISTER = 15, 61 | EXP_TAG_EXTINGUISHER = 16, 62 | EXP_TAG_TRAIN = 17, 63 | EXP_TAG_DIR_FLAME_EXPLODE = 18, 64 | EXP_TAG_VEHICLE_BULLET = 19, 65 | EXP_TAG_BIRD_CRAP = 20, 66 | EXP_TAG_FIREWORK = 21, 67 | EXP_TAG_TORPEDO = 22, 68 | EXP_TAG_TORPEDO_UNDERWATER = 23, 69 | EXP_TAG_LANTERN = 24, 70 | EXP_TAG_DYNAMITE = 25, 71 | EXP_TAG_DYNAMITESTACK = 26, 72 | EXP_TAG_DYNAMITE_VOLATILE = 27, 73 | EXP_TAG_RIVER_BLAST = 28, 74 | EXP_TAG_PLACED_DYNAMITE = 29, 75 | EXP_TAG_FIRE_ARROW = 30, 76 | EXP_TAG_DYNAMITE_ARROW = 31, 77 | EXP_TAG_PHOSPHOROUS_BULLET = 32, 78 | EXP_TAG_LIGHTNING_STRIKE = 33, 79 | EXP_TAG_TRACKING_ARROW = 34, 80 | EXP_TAG_POISON_BOTTLE = 35 81 | } 82 | end -------------------------------------------------------------------------------- /client/hud/cHud.lua: -------------------------------------------------------------------------------- 1 | HUD = class() 2 | 3 | function HUD:__init() 4 | self.render = Events:Subscribe("Render", function() self:Render() end) 5 | end 6 | 7 | --[[ 8 | Draws a frontend alert, like that seen here: http://www.kronzky.info/fivemwiki/index.php?title=DrawFrontendAlert 9 | 10 | args (in table): 11 | title (string) - title of the message 12 | subtitle (string) - (optional) subtitle of the message 13 | subtitle2 (string) - (optional) second subtitle 14 | no_bg (bool) - (optional) default false, set to true to remove the background 15 | time (number) - how long the alert should display for in seconds 16 | ]] 17 | function HUD:DrawFrontendAlert(args) 18 | assert(type(args.time) == 'number', 'args.time expected to be a number') 19 | Citizen.CreateThread(function() 20 | AddTextEntry("FACES_WARNH2", args.title or '') 21 | AddTextEntry("QM_NO_0", args.subtitle or '') 22 | AddTextEntry("QM_NO_3", args.subtitle2 or '') 23 | local timer = Timer() 24 | while timer:GetSeconds() < args.time do 25 | Citizen.Wait(0) 26 | DrawFrontendAlert("FACES_WARNH2", "QM_NO_0", 3, 3, "QM_NO_3", 2, -1, false, "FM_NXT_RAC", "QM_NO_1", no_bg ~= true, false) 27 | end 28 | end) 29 | end 30 | 31 | --[[ 32 | If the radar/minimap should be enabled or not 33 | ]] 34 | function HUD:SetDisplayRadar(enabled) 35 | DisplayRadar(enabled) 36 | end 37 | 38 | if IsRedM then 39 | function HUD:ShowComponent(hash) 40 | Citizen.InvokeNative(0x8BC7C1F929D07BF3, hash) 41 | end 42 | 43 | function HUD:HideComponent(hash) 44 | Citizen.InvokeNative(0x4CC5F2FC1332577F, hash) 45 | end 46 | elseif IsFiveM then 47 | --[[ 48 | 49 | HUD Component ids 50 | 1 : WANTED_STARS 51 | 2 : WEAPON_ICON 52 | 3 : CASH 53 | 4 : MP_CASH 54 | 5 : MP_MESSAGE 55 | 6 : VEHICLE_NAME 56 | 7 : AREA_NAME 57 | 8 : VEHICLE_CLASS 58 | 9 : STREET_NAME 59 | 10 : HELP_TEXT 60 | 11 : FLOATING_HELP_TEXT_1 61 | 12 : FLOATING_HELP_TEXT_2 62 | 13 : CASH_CHANGE 63 | 14 : RETICLE 64 | 15 : SUBTITLE_TEXT 65 | 16 : RADIO_STATIONS 66 | 17 : SAVING_GAME 67 | 18 : GAME_STREAM 68 | 19 : WEAPON_WHEEL 69 | 20 : WEAPON_WHEEL_STATS 70 | 21 : HUD_COMPONENTS 71 | 22 : HUD_WEAPONS 72 | 73 | ]] 74 | 75 | function HUD:HideComponentThisFrame(id) 76 | HideHudComponentThisFrame(id) 77 | end 78 | 79 | function HUD:HideMinimapExteriorThisFrame() 80 | HideMinimapExteriorMapThisFrame() 81 | end 82 | 83 | function HUD:HideHudAndRadarThisFrame() 84 | HideHudAndRadarThisFrame() 85 | end 86 | end 87 | 88 | --[[ 89 | Toggles the display of the ammo in the top right 90 | ]] 91 | function HUD:SetDisplayAmmo(enabled) 92 | self.display_ammo = enabled 93 | end 94 | 95 | --[[ 96 | Toggles the display of the cash hud in the top right 97 | ]] 98 | function HUD:SetDisplayCash(enabled) 99 | self.display_cash = enabled 100 | end 101 | 102 | --[[ 103 | Toggles the display of the bank hud in the top right 104 | ]] 105 | function HUD:SetDisplayBank(enabled) 106 | self.display_bank = enabled 107 | end 108 | 109 | function HUD:Render() 110 | --DisplayAmmoThisFrame(self.display_ammo) -- native does not exist 111 | --if not self.display_bank then RemoveMultiplayerBankCash() end -- native does not exist 112 | --if not self.display_cash then RemoveMultiplayerHudCash() end -- native does not exist 113 | end 114 | 115 | if IsFiveM then 116 | function HUD:SetVisible(visible) 117 | DisplayHud(visible) 118 | end 119 | end 120 | 121 | HUD = HUD() 122 | 123 | if IsRedM then 124 | HudComponent = { 125 | everything = -1679307491, 126 | minimapHonorAndCards = 724769646, 127 | minimap = 474191950, 128 | forceHonor = 121713391, 129 | onlyActionWheel = 2011163970, 130 | actionWheelWeapons = -1249243147, 131 | skillCards = 1058184710, 132 | onlyMoney = 1920936087, 133 | actionWheelFishing = 100665617, 134 | onlyFishingBait = -859384195, 135 | unkSpMoney = -950624750, 136 | unkSpMoneyReplace = 1670279562, 137 | mpMoney = -66088566, 138 | unkInfoBox = 36547242, 139 | campTraderInfo = -782493871, 140 | honorMoneyCards = -2124237476, 141 | forceSkillCards = 1533515944, 142 | actionWheelItems = -2106452847 143 | } 144 | end -------------------------------------------------------------------------------- /client/keypress/cKeymap.lua: -------------------------------------------------------------------------------- 1 | Keymap = class() 2 | 3 | -- See https://cookbook.fivem.net/2020/01/06/using-the-new-console-key-bindings/ for details 4 | function Keymap:__init() 5 | 6 | self.maps = {} 7 | self.id_pool = IdPool() 8 | 9 | end 10 | 11 | function Keymap:Register(key, keytype, name, cb) 12 | 13 | local id = self.id_pool:GetNextId() 14 | 15 | if not self.maps[key] then 16 | self.maps[key] = {} 17 | 18 | local keymap_name = string.format("keymap_%s", name:gsub(" ", "_")) 19 | RegisterKeyMapping("+" .. keymap_name, name, keytype, key) 20 | 21 | RegisterCommand("+" .. keymap_name, function() 22 | for id, callback in pairs(self.maps[key]) do 23 | callback({down = true}) 24 | end 25 | end) 26 | 27 | RegisterCommand("-" .. keymap_name, function() 28 | for id, callback in pairs(self.maps[key]) do 29 | callback({up = true}) 30 | end 31 | end) 32 | 33 | end 34 | 35 | self.maps[key][id] = cb 36 | 37 | return id 38 | 39 | end 40 | 41 | function Keymap:Unregister(key, id) 42 | if self.maps[key] and self.maps[key][id] then 43 | self.maps[key][id] = nil 44 | end 45 | end 46 | 47 | Keymap = Keymap() -------------------------------------------------------------------------------- /client/light/cLight.lua: -------------------------------------------------------------------------------- 1 | Light = class() 2 | 3 | LightIds = 0 -- Track light ids 4 | 5 | function GenerateLightId() 6 | LightIds = LightIds + 1 7 | return LightIds 8 | end 9 | 10 | --[[ 11 | Creates a light. 12 | 13 | args (in table): 14 | 15 | position - (vector3) position 16 | color - (Color) the color of the light 17 | type - (string) can be either LightTypes.Spot or LightTypes.Point 18 | shadow - (bool) whether or not this light creates shadows 19 | 20 | args required for LightTypes.Spot: 21 | direction - (vector3) which way the light is pointing 22 | very finicky - if you have a 0 as the z component it may not work 23 | brightness - (number) how bright it is 24 | hardness - (number) how hard the edges of the light are 25 | radius - (number) how wide the radius is 26 | falloff - (number) how much the light fades with distance 27 | 28 | args required for LightTypes.Point: 29 | range - (number) range of the light 30 | intensity - (number) how bright/intense the light is 31 | 32 | ]] 33 | function Light:__init(args) 34 | 35 | assert(args.type == LightTypes.Point and args.shadow == false, 36 | "Unsupported light type (currently). Try LightTypes.Point and shadow = false") 37 | 38 | assert(type(args.position) == "vector3", "args.position expected to be a vector3") 39 | TypeCheck:Color(args.color) 40 | assert(args.type == LightTypes.Spot or args.type == LightTypes.Point, "args.type expected to be a LightTypes") 41 | 42 | self.position = args.position 43 | self.color = args.color 44 | self.type = args.type 45 | self.shadow = args.shadow 46 | self.id = GenerateLightId() 47 | 48 | if self.type == LightTypes.Spot then 49 | assert(type(args.direction) == "vector3", "args.direction expected to be a vector3") 50 | TypeCheck:Number(args.distance) 51 | TypeCheck:Number(args.brightness) 52 | TypeCheck:Number(args.hardness) 53 | TypeCheck:Number(args.radius) 54 | TypeCheck:Number(args.falloff) 55 | 56 | self.direction = tofloat(args.direction) 57 | self.distance = tofloat(args.distance) 58 | self.brightness = tofloat(args.brightness) 59 | self.hardness = tofloat(args.hardness) 60 | self.radius = tofloat(args.radius) 61 | self.falloff = tofloat(args.falloff) 62 | else 63 | TypeCheck:Number(args.range) 64 | TypeCheck:Number(args.intensity) 65 | 66 | self.range = tofloat(args.range) 67 | self.intensity = tofloat(args.intensity) 68 | end 69 | 70 | self.enabled = true 71 | 72 | self:Draw() 73 | 74 | end 75 | 76 | function Light:GetPosition() 77 | return self.position 78 | end 79 | 80 | function Light:SetPosition(pos) 81 | assert(type(pos) == "vector3", "pos expected to be a vector3") 82 | self.position = pos 83 | end 84 | 85 | function Light:GetColor() 86 | return self.color 87 | end 88 | 89 | function Light:SetColor(col) 90 | TypeCheck:Color(col) 91 | self.color = col 92 | end 93 | 94 | function Light:SetDirection(dir) 95 | assert(type(dir) == "vector3", "direction expected to be a vector3") 96 | self.direction = dir 97 | end 98 | 99 | function Light:GetDirection() 100 | return self.direction 101 | end 102 | 103 | function Light:GetBrightness() 104 | return self.brightness 105 | end 106 | 107 | function Light:SetBrightness(b) 108 | TypeCheck:Number(b) 109 | self.brightness = b 110 | end 111 | 112 | function Light:GetHardness() 113 | return self.hardness 114 | end 115 | 116 | function Light:SetHardness(h) 117 | TypeCheck:Number(h) 118 | self.hardness = h 119 | end 120 | 121 | function Light:GetRadius() 122 | return self.radius 123 | end 124 | 125 | function Light:SetRadius(radius) 126 | TypeCheck:Number(radius) 127 | self.radius = radius 128 | end 129 | 130 | function Light:SetFalloff(falloff) 131 | TypeCheck:Number(falloff) 132 | self.falloff = falloff 133 | end 134 | 135 | function Light:GetFalloff() 136 | return self.falloff 137 | end 138 | 139 | function Light:GetRange() 140 | return self.range 141 | end 142 | 143 | function Light:SetRange(range) 144 | TypeCheck:Number(range) 145 | self.range = range 146 | end 147 | 148 | function Light:GetIntensity() 149 | return self.intensity 150 | end 151 | 152 | function Light:SetIntensity(intensity) 153 | TypeCheck:Number(intensity) 154 | self.intensity = intensity 155 | end 156 | 157 | function Light:SetEnabled(enabled) 158 | assert(type(enabled) == "bool", "enabled expected to be a bool") 159 | self.enabled = enabled 160 | if self.enabled then 161 | self:Draw() -- Draw call to remake coroutine 162 | end 163 | end 164 | 165 | function Light:GetShadowEnabled() 166 | return self.shadow 167 | end 168 | 169 | function Light:SetShadowEnabled(shadow) 170 | assert(type(shadow) == "bool", "shadow expected to be a bool") 171 | self.shadow = shadow 172 | end 173 | 174 | function Light:Draw() 175 | local type = self.type 176 | local id = self.id 177 | 178 | CreateThread(function() 179 | while self.enabled do 180 | 181 | Wait(1) 182 | 183 | if type == LightTypes.Spot then 184 | if self.shadow then 185 | DrawSpotLightWithShadow( 186 | self.position.x, self.position.y, self.position.z, 187 | self.direction.x, self.direction.y, self.direction.z, 188 | self.color.r, self.color.g, self.color.b, 189 | self.distance, self.brightness, self.hardness, 190 | self.radius, self.falloff, self.id 191 | ) 192 | else 193 | DrawSpotLight( 194 | self.position.x, self.position.y, self.position.z, 195 | self.direction.x, self.direction.y, self.direction.z, 196 | self.color.r, self.color.g, self.color.b, 197 | self.distance, self.brightness, self.hardness, 198 | self.radius, self.falloff 199 | ) 200 | end 201 | else 202 | if self.shadow then 203 | DrawLightWithRangeAndShadow( 204 | self.position.x, self.position.y, self.position.z, 205 | self.color.r, self.color.g, self.color.b, 206 | self.range, self.intensity, self.id 207 | ) 208 | else 209 | DrawLightWithRange( 210 | self.position.x, self.position.y, self.position.z, 211 | self.color.r, self.color.g, self.color.b, 212 | self.range, self.intensity 213 | ) 214 | end 215 | end 216 | end 217 | end) 218 | end 219 | 220 | function Light:Remove() 221 | self.enabled = false 222 | end 223 | 224 | LightTypes = {Spot = 1, Point = 2} -------------------------------------------------------------------------------- /client/localplayer_behaviors/cBulletDetectionLocalPlayerBehavior.lua: -------------------------------------------------------------------------------- 1 | BulletDetectionLocalPlayerBehavior = class() 2 | BulletDetectionLocalPlayerBehavior.name = "BulletDetectionLocalPlayerBehavior" 3 | 4 | --[[ 5 | BulletDetectionLocalPlayerBehavior: 6 | Detects and tracks bullets fired by the LocalPlayer 7 | ]] 8 | 9 | function BulletDetectionLocalPlayerBehavior:__init() 10 | self.active = true 11 | self.stored_clip_ammo = 0 12 | 13 | self:CheckForBulletsFired() 14 | end 15 | 16 | function BulletDetectionLocalPlayerBehavior:CheckForBulletsFired() 17 | Citizen.CreateThread(function() 18 | local stored_clip_ammo, current_clip_ammo 19 | local localplayer_has_weapon, localplayer_weapon_enum 20 | 21 | while self.active do 22 | Wait(1) 23 | 24 | localplayer_has_weapon, localplayer_weapon_enum = LocalPlayer:GetEquippedWeapon() 25 | 26 | if localplayer_has_weapon then 27 | stored_clip_ammo = self.stored_clip_ammo 28 | current_clip_ammo = LocalPlayer:GetCurrentClipAmmoForWeapon(localplayer_weapon_enum) 29 | 30 | if stored_clip_ammo ~= current_clip_ammo then 31 | print("Detected Clip Ammo Change") 32 | if stored_clip_ammo - 1 == current_clip_ammo then 33 | print("Detected Bullet Fired") 34 | end 35 | 36 | local aiming_at_entity, entity_id = GetEntityPlayerIsFreeAimingAt(PlayerId()) 37 | if IsEntityAPed(entity_id) and aiming_at_entity then 38 | Chat:Debug("entity: " .. tostring(entity_id)) 39 | 40 | -- verify that localplayer weapon is the last weapon that hit the ped 41 | --local weapon_damage_validation_passed = HasPedBeenDamagedByWeapon(entity_id, WeaponEnum:GetWeaponHash(localplayer_weapon_enum), 0) 42 | --Chat:Print("HasPedBeenDamagedByWeapon(): " .. tostring(weapon_damage_validation)) 43 | --if weapon_damage_validation_passed then 44 | -- detected damage 45 | --end 46 | end 47 | 48 | -- this comes last 49 | self.stored_clip_ammo = current_clip_ammo 50 | end 51 | end 52 | end 53 | end) 54 | end 55 | -------------------------------------------------------------------------------- /client/marker/cMarker.lua: -------------------------------------------------------------------------------- 1 | Marker = class() 2 | 3 | if IsRedM then 4 | -- Partially from here https://github.com/femga/rdr3_discoveries/blob/master/graphics/markers/marker_types.lua 5 | MarkerTypes = 6 | { 7 | Box = 1857541051, 8 | Cylinder = -1795314153, 9 | Sphere = 0x50638AB9, 10 | Ring = 0xEC032ADD, 11 | Halo = 0x6903B113, 12 | RaceCheckpoint = 0xE60FF3B9, 13 | RaceFinish = 0x664669A6, 14 | CanoePole = 0xE03A92AE, 15 | Buoy = 0x751F27D6 16 | } 17 | elseif IsFiveM then 18 | -- Add more markers from this list as needed: https://docs.fivem.net/docs/game-references/markers/ 19 | MarkerTypes = 20 | { 21 | UpsideDownCone = 0, 22 | VerticalCylinder = 1, 23 | DebugSphere = 28 24 | } 25 | end 26 | 27 | --[[ 28 | Creates a new marker in the world, aka a glowly circle thing. 29 | 30 | args (in table): 31 | type (MarkerTypes): shape/type of the marker 32 | position (vector3): position of the marker in the world 33 | direction (vector3): direction of the marker 34 | rotation (vector3): rotation of the marker 35 | scale (vector3): scale of the marker 36 | color (Color): color of the marker 37 | 38 | -- optional: 39 | bob_up_and_down (bool, default false): if the marker bobs up and down 40 | face_camera (bool, default false): if the marker faces the camera 41 | is_rotating (bool, default false): if the marker rotates 42 | texture_dict (string, default nil): if the marker uses a texture 43 | texture_name (string, default nil): if the marker uses a texture 44 | draw_on_entity (bool, default false): if the marker draws on entities 45 | rotation_order (number 0-2, default 1): rotation order of the marker 46 | 47 | ]] 48 | function Marker:__init(args) 49 | 50 | self.type = args.type 51 | self.position = args.position 52 | self.direction = args.direction 53 | self.rotation = args.rotation 54 | self.scale = args.scale 55 | self.color = Color(args.color.r, args.color.g, args.color.b, args.color.a) 56 | 57 | self.bob_up_and_down = args.bob_up_and_down ~= nil and args.bob_up_and_down or false 58 | self.face_camera = args.face_camera ~= nil and args.face_camera or false 59 | self.is_rotating = args.is_rotating ~= nil and args.is_rotating or false 60 | self.texture_dict = args.texture_dict 61 | self.texture_name = args.texture_name 62 | self.draw_on_entity = args.draw_on_entity ~= nil and args.draw_on_entity or false 63 | 64 | self.rotation_order = args.rotation_order or 1 65 | 66 | self.visible = true 67 | 68 | self.render = Events:Subscribe("Render", self, self.Draw) 69 | end 70 | 71 | function Marker:SetVisible(visible) 72 | self.visible = visible 73 | end 74 | 75 | function Marker:SetColor(color) 76 | self.color = color 77 | end 78 | 79 | function Marker:FadeOut() 80 | self.fadeout = true 81 | end 82 | 83 | function Marker:Draw() 84 | if not self.visible then return end 85 | Citizen.InvokeNative(IsFiveM and 0x28477EC23D892089 or 0x2A32FAA57B937173, 86 | self.type, 87 | self.position.x, self.position.y, self.position.z, 88 | self.direction.x, self.direction.z, self.direction.z, 89 | self.rotation.x, self.rotation.y, self.rotation.z, 90 | self.scale.x, self.scale.y, self.scale.z, 91 | self.color.r, self.color.g, self.color.b, self.color.a, 92 | self.bob_up_and_down, self.face_camera, 93 | self.rotation_order, self.is_rotating, 94 | self.texture_dict, self.texture_name, 95 | self.draw_on_entity 96 | ) 97 | 98 | if self.fadeout then 99 | self.color.a = self.color.a - 1 100 | if self.color.a <= 0 then 101 | self:Remove() 102 | end 103 | end 104 | end 105 | 106 | function Marker:Remove() 107 | if self.render then self.render:Unsubscribe() end 108 | self.render = nil 109 | end -------------------------------------------------------------------------------- /client/network/cNetwork.lua: -------------------------------------------------------------------------------- 1 | local NetworkEvent = class() 2 | 3 | local NetworkEvent_id = 0 4 | function NetworkEvent:__init(name, instance, callback) 5 | self.name = name 6 | self.instance = instance 7 | self.callback = callback 8 | self.id = NetworkEvent_id 9 | NetworkEvent_id = NetworkEvent_id + 1 10 | end 11 | 12 | function NetworkEvent:Unsubscribe() 13 | Network:Unsubscribe(self.name, self.id) 14 | end 15 | 16 | function NetworkEvent:Receive(args, name) 17 | -- source is nil if this is clientside, otherwise it is the player who sent it 18 | local return_args = {} 19 | local is_fetch = false 20 | local fetch_id 21 | 22 | if args then 23 | is_fetch = args.__is_fetch 24 | fetch_id = args.__fetch_id 25 | args.__is_fetch = nil 26 | args.__fetch_id = nil 27 | for k,v in pairs(args) do 28 | return_args[k] = v 29 | end 30 | end 31 | 32 | local return_value 33 | if self.callback then 34 | return_value = self.callback(self.instance, return_args) 35 | else 36 | local callback = self.instance 37 | return_value = callback(return_args) 38 | end 39 | 40 | if is_fetch then 41 | assert(return_value and type(return_value) == "table", "A Network:Fetch handler function must return a table!") 42 | Network:Send(name .. "__FetchCallback" .. tostring(fetch_id), return_value) 43 | end 44 | end 45 | 46 | Network = class() 47 | 48 | function Network:__init() 49 | self.subs = {} 50 | self.handlers = {} 51 | self.current_fetch_id = 1 52 | self.fetch_data = {} 53 | end 54 | 55 | function Network:Send(name, args) 56 | assert(name ~= nil and type(name) == "string", "cannot Network:Send without valid networkevent") 57 | 58 | TriggerServerEvent(name, args) 59 | end 60 | 61 | --[[ 62 | Blocks the current Thread until a response is received from the server 63 | ]] 64 | function Network:Fetch(name, args) 65 | self.current_fetch_id = self.current_fetch_id + 1 66 | local fetch_id = self.current_fetch_id 67 | local fetch_args = args 68 | if not args then 69 | fetch_args = {} 70 | end 71 | fetch_args.__is_fetch = true 72 | fetch_args.__fetch_id = fetch_id 73 | Network:Send(name, fetch_args) 74 | fetch_args.__is_fetch = nil 75 | fetch_args.__fetch_id = nil 76 | 77 | local subscription = Network:Subscribe(name .. "__FetchCallback" .. tostring(fetch_id), function(args) 78 | self.fetch_data[fetch_id] = args 79 | end) 80 | 81 | 82 | local response_timer = Timer() 83 | local fetched_data 84 | while not self.fetch_data[fetch_id] do 85 | Wait(10) 86 | if response_timer:GetSeconds() > 4 then 87 | print("Network:Fetch timed out! No response received from the server within 4 seconds. Did you forget to add a Network:Subscribe for this Fetch in the server-side code?") 88 | break 89 | end 90 | end 91 | fetched_data = self.fetch_data[fetch_id] 92 | self.fetch_data[fetch_id] = nil 93 | subscription:Unsubscribe() 94 | 95 | return fetched_data 96 | end 97 | 98 | --[[ 99 | Subscribe to a network event. 100 | 101 | Example usage: 102 | Network:Subscribe("PlayerLoaded", function(args) 103 | end) 104 | ]] 105 | function Network:Subscribe(name, instance, callback) 106 | assert(name ~= nil, "cannot subscribe networkevent without name") 107 | assert(type(instance) == "table" or type(instance) == "function", "callback function non-existant or no callback instance provided. Function usage is Network:Subscribe(name, instance, callback) or Network:Subscribe(name, callback)") 108 | 109 | if not self.subs[name] then 110 | self.subs[name] = {} 111 | 112 | RegisterNetEvent(name) 113 | self.handlers[name] = AddEventHandler(name, function(args) 114 | for _, networkevent in pairs(self.subs[name]) do 115 | networkevent:Receive(args, name) 116 | end 117 | end) 118 | end 119 | 120 | local networkevent = NetworkEvent(name, instance, callback) 121 | self.subs[name][networkevent.id] = networkevent 122 | 123 | return networkevent 124 | end 125 | 126 | function Network:Unsubscribe(name, id) 127 | assert(name ~= nil, "cannot unsubscribe networkevent without name") 128 | 129 | assert(self.subs[name] ~= nil and self.subs[name][id] ~= nil, "cannot unsubscribe NetworkEvent that does not exist") 130 | self.subs[name][id] = nil 131 | 132 | if count_table(self.subs[name]) == 0 then 133 | RemoveEventHandler(self.handlers[name]) 134 | self.subs[name] = nil 135 | self.handlers[name] = nil 136 | end 137 | end 138 | 139 | Network = Network() -------------------------------------------------------------------------------- /client/object/cObject.lua: -------------------------------------------------------------------------------- 1 | Object = class(Entity) 2 | 3 | Objects = {} -- Global table to keep track of all the objects, indexed by entity id 4 | 5 | --[[ 6 | Spawns a new object. 7 | 8 | args (in table) 9 | 10 | model - string of model name 11 | position - vector3 12 | rotation (optional) - vector3 of rotation 13 | isNetwork (optional) - (bool) whether you want this object to be synced across the network 14 | quaternion (optional) - quat 15 | kinematic (optional) - if the object is kinematic or not 16 | callback (optional) - callback function that is called after the object is spawned 17 | ]] 18 | function Object:__init(args) 19 | 20 | assert(type(args.model) == "string" or type(args.model) == "number", 21 | "args.model expected to be a string or hash (number)") 22 | assert(type(args.position) == "vector3", "args.position expected to be a vector3") 23 | 24 | self.hash = type(args.model) == "string" and GetHashKey(args.model) or args.model 25 | self.model = args.model 26 | 27 | self:InitializeValueStorage(self) 28 | 29 | LoadModel(self.hash, function() 30 | self.entity = CreateObject(self.hash, args.position.x, args.position.y, args.position.z, args.isNetwork == true, true, true, true, true) 31 | self:SetQuaternion(type(args.quaternion) == "quat" and args.quaternion or quat(0,0,0,0)) 32 | self:SetKinematic(args.kinematic or false) 33 | self:SetAsPersistent() 34 | if args.rotation then self:SetRotation(args.rotation) end 35 | 36 | self:SetPosition(args.position) -- Reset position again because sometimes they move 37 | 38 | Objects[self:GetEntityId()] = self 39 | 40 | if args.callback then 41 | args.callback(self) 42 | end 43 | end) 44 | 45 | end 46 | 47 | function Object:PlaceOnGroundProperly() 48 | PlaceObjectOnGroundProperly(self.entity) 49 | end 50 | 51 | function Object:GetModel() 52 | return self.model 53 | end 54 | 55 | function Object:GetHash() 56 | return self.hash 57 | end 58 | 59 | function Object:Slide(to_pos, speed, has_collision) 60 | SlideObject(self:GetEntityId(), to_pos.x, to_pos.y, to_pos.z, speed.x, speed.y, speed.z, has_collision) 61 | end 62 | 63 | function Object:Equals(object) 64 | return object ~= nil and self:GetEntityId() == object:GetEntityId() 65 | end 66 | 67 | function Object:Destroy() 68 | if not self:GetEntityId() then 69 | print("[WARNING] Tried to destroy object without valid entity id. (did you already remove it?)") 70 | return 71 | end 72 | Objects[self:GetEntityId()] = nil 73 | self:Remove() 74 | end -------------------------------------------------------------------------------- /client/object/cObjectManager.lua: -------------------------------------------------------------------------------- 1 | ObjectManager = class() 2 | 3 | function ObjectManager:__init() 4 | Events:Subscribe("onResourceStop", self, self.OnResourceStop) 5 | end 6 | 7 | function ObjectManager:FindObjectByEntityId(id) 8 | return Objects[id] 9 | end 10 | 11 | -- Clean up all spawned objects on resource stop 12 | function ObjectManager:OnResourceStop(resource_name) 13 | if GetCurrentResourceName() == resource_name then 14 | self:RemoveAllObjects() 15 | end 16 | end 17 | 18 | function ObjectManager:RemoveAllObjects() 19 | for id, object in pairs(Objects) do 20 | object:Destroy() 21 | end 22 | Objects = {} 23 | end 24 | 25 | ObjectManager = ObjectManager() 26 | 27 | -------------------------------------------------------------------------------- /client/particle-effect/cParticleEffect.lua: -------------------------------------------------------------------------------- 1 | ParticleEffect = class() 2 | 3 | --[[ 4 | Creates a particle. 5 | 6 | args (in table): 7 | 8 | There are 3 ways you can create a particle: 9 | at a position (ParticleEffectTypes.Position) 10 | attached to an entity (with an offset) (ParticleEffectTypes.Entity) 11 | attached to an entity bone (with an offset) (ParticleEffectTypes.Bone) 12 | 13 | bank - (string) effect bank of the effect you want to play. List below 14 | effect - (string) name of the effect you want to play. List here: https://pastebin.com/N9unUFWY 15 | type - (ParticleEffectTypes) 16 | if ParticleEffectTypes.Position, requires these args: 17 | position - (vector3) the position to create the particle at 18 | if ParticleEffectTypes.Entity, requires these args: 19 | entity - (entity) the entity to attach the effect to 20 | offset - (vector3) the offset from the entity to play the effect 21 | if ParticleEffectTypes.Entity, requires these args: 22 | entity - (entity) the entity to attach the effect to 23 | offset - (vector3) the offset from the entity to play the effect 24 | bone - (number) the index of the bone to play the effect on 25 | 26 | scale - (number) how big the particle is 27 | rotation (optional) - (vector3) the rotation of the particle 28 | loop (optional) - (bool) if you want the effect to loop, default false 29 | axis (optional) - (vector3) if you want to flip axes and get wild (Invert Axis Flags) 30 | callback (optional) - (func) if you want to do something after the effect is spawned 31 | 32 | ]] 33 | function ParticleEffect:__init(args) 34 | assert(args.effect ~= nil, "cannot create ParticleEffect without effect") 35 | assert(args.bank ~= nil, "cannot create ParticleEffect without bank") 36 | assert(args.type ~= nil, "cannot create ParticleEffect without type") 37 | assert(args.scale ~= nil, "cannot create ParticleEffect without scale") 38 | 39 | self.effect = args.effect 40 | self.bank = args.bank 41 | self.type = args.type 42 | self.scale = tofloat(args.scale) 43 | self.rotation = args.rotation or vector3(0, 0, 0) 44 | self.loop = args.loop 45 | self.axis = axis or vector3(0, 0, 0) 46 | self.callback = args.callback 47 | 48 | if self.type == ParticleEffectTypes.Position then 49 | assert(args.position ~= nil, "ParticleEffectTypes.Position specified, but no position given") 50 | self.position = args.position 51 | else 52 | assert(args.entity ~= nil, "ParticleEffectTypes.Entity or Bone specified, but no entity given") 53 | assert(args.offset ~= nil, "ParticleEffectTypes.Entity or Bone specified, but no offset given") 54 | self.offset = args.offset 55 | self.entity = args.entity 56 | if self.type == ParticleEffectTypes.Bone then 57 | assert(args.bone ~= nil, "ParticleEffectTypes.Bone specified, but no bone given") 58 | self.bone = args.bone 59 | end 60 | end 61 | 62 | LoadEffectBank(self.bank, function() self:CreateEffect() end) 63 | 64 | end 65 | 66 | function ParticleEffect:GetEffect() 67 | return self.effect 68 | end 69 | 70 | function ParticleEffect:GetType() 71 | return self.type 72 | end 73 | 74 | function ParticleEffect:GetScale() 75 | return self.scale 76 | end 77 | 78 | function ParticleEffect:GetRotation() 79 | return self.rotation 80 | end 81 | 82 | function ParticleEffect:GetIsLooped() 83 | return self.loop 84 | end 85 | 86 | function ParticleEffect:GetAxis() 87 | return self.axis 88 | end 89 | 90 | function ParticleEffect:GetPosition() 91 | return self.position 92 | end 93 | 94 | function ParticleEffect:GetOffset() 95 | return self.offset 96 | end 97 | 98 | function ParticleEffect:GetEntity() 99 | return self.entity 100 | end 101 | 102 | function ParticleEffect:GetBone() 103 | return self.bone 104 | end 105 | 106 | function ParticleEffect:GetColor() 107 | return self.color 108 | end 109 | 110 | function ParticleEffect:GetHandle() 111 | return self.handle 112 | end 113 | 114 | function ParticleEffect:Exists() 115 | return DoesParticleFxLoopedExist(self.handle) 116 | end 117 | 118 | --[[ 119 | Only works for looped effects 120 | ]] 121 | function ParticleEffect:SetScale(scale) 122 | if self.loop then 123 | self.scale = scale 124 | SetParticleFxLoopedScale(self.handle, scale) 125 | end 126 | end 127 | 128 | --[[ 129 | Only works for looped effects 130 | ]] 131 | function ParticleEffect:SetProperty(propertyName, amount) 132 | if self.loop then 133 | SetParticleFxLoopedEvolution(self.handle, propertyName, amount, 0) 134 | end 135 | end 136 | 137 | --[[ 138 | Only works on some particle effects, and only works on looped effects 139 | ]] 140 | function ParticleEffect:SetColor(color) 141 | if self.loop then 142 | self.color = color 143 | SetParticleFxLoopedColour(self.handle, color.r, color.g, color.b, 0) 144 | end 145 | end 146 | 147 | function ParticleEffect:Remove() 148 | RemoveParticleFx(self.handle, true) 149 | end 150 | 151 | function ParticleEffect:CreateEffect() 152 | 153 | UseParticleFxAssetNextCall(self.bank) 154 | if self.type == ParticleEffectTypes.Position then 155 | if self.loop then 156 | self.handle = StartParticleFxLoopedAtCoord( 157 | self.effect, 158 | self.position.x, self.position.y, self.position.z, 159 | self.rotation.x, self.rotation.y, self.rotation.z, 160 | self.scale, self.axis.x, self.axis.y, self.axis.z, 0 161 | ) 162 | else 163 | self.handle = StartParticleFxNonLoopedAtCoord( 164 | self.effect, 165 | self.position.x, self.position.y, self.position.z, 166 | self.rotation.x, self.rotation.y, self.rotation.z, 167 | self.scale, self.axis.x, self.axis.y, self.axis.z, 0 168 | ) 169 | end 170 | elseif self.type == ParticleEffectTypes.Entity then 171 | if self.loop then 172 | self.handle = StartParticleFxLoopedOnEntity( 173 | self.effect, self.entity, 174 | self.offset.x, self.offset.y, self.offset.z, 175 | self.rotation.x, self.rotation.y, self.rotation.z, 176 | self.scale, self.axis.x, self.axis.y, self.axis.z 177 | ) 178 | else 179 | self.handle = StartParticleFxNonLoopedOnEntity( 180 | self.effect, self.entity, 181 | self.offset.x, self.offset.y, self.offset.z, 182 | self.rotation.x, self.rotation.y, self.rotation.z, 183 | self.scale, self.axis.x, self.axis.y, self.axis.z 184 | ) 185 | end 186 | elseif self.type == ParticleEffectTypes.Bone then 187 | if self.loop then 188 | self.handle = StartParticleFxLoopedOnEntityBone( 189 | self.effect, self.entity, 190 | self.offset.x, self.offset.y, self.offset.z, 191 | self.rotation.x, self.rotation.y, self.rotation.z, self.bone, 192 | self.scale, self.axis.x, self.axis.y, self.axis.z 193 | ) 194 | else 195 | -- IF something breaks, you used loop = false with type = bone on a non ped bone 196 | self.handle = StartParticleFxNonLoopedOnPedBone( 197 | self.effect, self.entity, 198 | self.offset.x, self.offset.y, self.offset.z, 199 | self.rotation.x, self.rotation.y, self.rotation.z, self.bone, 200 | self.scale, self.axis.x, self.axis.y, self.axis.z 201 | ) 202 | end 203 | end 204 | 205 | if self.callback then 206 | self.callback(self) 207 | end 208 | end 209 | 210 | ParticleEffectTypes = {Position = 1, Entity = 2, Bone = 3} -------------------------------------------------------------------------------- /client/pause-menu/cPauseMenu.lua: -------------------------------------------------------------------------------- 1 | PauseMenu = class() 2 | 3 | function PauseMenu:__init() 4 | self.enabled_while_dead = false -- If the pause menu can be opened while the player is dead 5 | end 6 | 7 | function PauseMenu:SetEnabledWhileDead(enabled) 8 | self.enabled_while_dead = enabled 9 | 10 | Citizen.CreateThread(function() 11 | while self.enabled_while_dead do 12 | --N_0xcc3fdded67bcfc63() 13 | Wait(0) 14 | end 15 | end) 16 | end 17 | 18 | function PauseMenu:SetTitle(title) 19 | Citizen.CreateThread(function() 20 | AddTextEntry('FE_THDR_GTAO', title) 21 | end) 22 | end 23 | 24 | function PauseMenu:IsActive() 25 | return IsPauseMenuActive() 26 | end 27 | 28 | PauseMenu = PauseMenu() -------------------------------------------------------------------------------- /client/physics/cPhysics.lua: -------------------------------------------------------------------------------- 1 | Physics = class() 2 | 3 | --[[ 4 | Casts a ray from a point to another point. 5 | 6 | startpos - vector3 7 | endpos - vector3 8 | flags - intersection bit flags of what to hit. -1 presumably hits everything 9 | 1: Intersect with map 10 | 2: Intersect with vehicles (used to be mission entities?) (includes train) 11 | 4: Intersect with peds? (same as 8) 12 | 8: Intersect with peds? (same as 4) 13 | 16: Intersect with objects 14 | 32: Water? 15 | 64: Unknown 16 | 128: Unknown 17 | 256: Intersect with vegetation (plants, coral. trees not included) 18 | NOTE: Raycasts that intersect with mission_entites (flag = 2) has limited range and will not register for far away entites. 19 | The range seems to be about 30 metres. 20 | ignore_entity (optional) - an entity to ignore when raycasting 21 | 22 | returns - table containing: 23 | retval - (int) 24 | hit - (bool) whether it hit anything. False if it reached its destination 25 | position - (vector3) the position hit by the ray. if it does not hit anything, it will be equal to endpos 26 | normal - (vector3) surface normal coords of where the ray hit 27 | entity - (entity) handle of the entity hit by the ray 28 | 29 | ]] 30 | function Physics:Raycast(startpos, endpos, flags, ignore_entity) 31 | TypeCheck:Position3D(startpos) 32 | TypeCheck:Position3D(endpos) 33 | 34 | local result = self:GetShapeTestResult(StartShapeTestRay(startpos.x, startpos.y, startpos.z, endpos.x, endpos.y, endpos.z, flags or -1, ignore_entity or 0, 1)) 35 | result.hit = result.hit == 1 36 | 37 | if not result.hit then 38 | result.position = endpos 39 | end 40 | 41 | return result 42 | end 43 | 44 | --[[ 45 | Casts a ray with radius from a point to another point. Basically a cylinder. 46 | 47 | 48 | startpos - vector3 49 | endpos - vector3 50 | flags - intersection bit flags of what to hit 51 | vehicles=10 52 | peds =12 53 | Iterating through flags yields many ped / vehicle/ object combinations 54 | ignore_entity - an entity to ignore when raycasting 55 | radius - number of the radius of the ray 56 | 57 | returns - table containing: 58 | retval - (int) 59 | hit - (bool) whether it hit anything. False if it reached its destination 60 | position - (vector3) the position hit by the ray 61 | normal - (vector3) surface normal coords of where the ray hit 62 | entity - (entity) handle of the entity hit by the ray 63 | ]] 64 | function Physics:RaycastWithRadius(startpos, endpos, flags, ignore_entity, radius) 65 | TypeCheck:Position(startpos) 66 | TypeCheck:Position(endpos) 67 | TypeCheck:Number(flags) 68 | TypeCheck:Number(radius) 69 | return self:GetShapeTestResult(StartShapeTestCapsule(startpos.x, startpos.y, startpos.z, endpos.x, endpos.y, endpos.z, radius, flags or -1, ignore_entity or 0, 7)) 70 | end 71 | 72 | function Physics:GetShapeTestResult(handle) 73 | local retval, hit, position, normal, entity = GetShapeTestResult(handle) 74 | return {retval = retval, hit = hit, position = position, normal = normal, entity = entity} 75 | end 76 | 77 | Physics = Physics() -------------------------------------------------------------------------------- /client/player/cPlayer.lua: -------------------------------------------------------------------------------- 1 | -- a Player instance represents a single player in the game 2 | Player = class(ValueStorage) 3 | 4 | function Player:__init(source_id, steam_id, server_id, unique_id) 5 | self.source_id = source_id 6 | self.steam_id = steam_id 7 | self.server_id = server_id 8 | self.unique_id = unique_id 9 | self.__is_player_instance = true 10 | 11 | -- Wait until player is valid to get player id 12 | Citizen.CreateThread(function() 13 | while not self.player_id or self.player_id == -1 do 14 | self.player_id = GetPlayerFromServerId(self.source_id) 15 | Wait(500) 16 | end 17 | end) 18 | 19 | self:InitializeValueStorage(self) 20 | self:SetValueStorageNetworkId(self.server_id) 21 | end 22 | 23 | function Player:SetControl(enabled) 24 | SetPlayerControl(self.player_id, enabled, false) 25 | end 26 | 27 | --[[ 28 | Changes the model of the player. 29 | Warning: this function is not a wrapped in a thread 30 | 31 | WARNING: THIS WILL DESTROY THE CURRENT PED AND CREATE A NEW 32 | ONE FOR THIS PLAYER. 33 | ]] 34 | function Player:SetModel(model, cb) 35 | local hashkey = GetHashKey(model) 36 | LoadModel(hashkey, function() 37 | -- change the player model 38 | SetPlayerModel(self.player_id, hashkey) 39 | 40 | -- release the player model 41 | SetModelAsNoLongerNeeded(hashkey) 42 | 43 | -- RDR3 player model bits 44 | if N_0x283978a15512b2fe then 45 | N_0x283978a15512b2fe(PlayerPedId(), true) 46 | end 47 | cb() 48 | end) 49 | end 50 | 51 | --[[ 52 | Updates the Ped class so that the Ped is still valid for this player. 53 | Useful for respawning or changing model. 54 | ]] 55 | function Player:GetPed() 56 | if not self.player_id or self.player_id == -1 then 57 | self.player_id = GetPlayerFromServerId(self.source_id) 58 | end 59 | 60 | local player_ped_id = GetPlayerPed(self.player_id) 61 | 62 | if not self.ped then 63 | self.ped = Ped({ped = player_ped_id}) 64 | end 65 | 66 | if self.ped:GetEntityId() ~= player_ped_id then 67 | self.ped:UpdatePedId(player_ped_id) 68 | end 69 | 70 | if not self.ped:Exists() then 71 | self.ped = Ped({ped = player_ped_id}) 72 | end 73 | 74 | return self.ped 75 | end 76 | 77 | function Player:GetEntity() 78 | return self:GetPed() 79 | end 80 | 81 | function Player:GetEntityId() 82 | return GetPlayerPed(self.player_id) 83 | end 84 | 85 | function Player:GetPedId() 86 | return GetPlayerPed(self.player_id) 87 | end 88 | 89 | --[[ 90 | Freezes the player, including movement controls. 91 | 92 | Same as original scripts. 93 | ]] 94 | function Player:Freeze(freeze) 95 | self:SetControl(not freeze) 96 | 97 | local ped = self:GetPed() 98 | local entity = Entity(ped:GetPedId()) 99 | 100 | if not freeze then 101 | if not entity:IsVisible() then 102 | entity:SetVisible(true) 103 | end 104 | 105 | if not ped:InVehicle() then 106 | entity:ToggleCollision(true) 107 | end 108 | 109 | entity:SetKinematic(false) 110 | entity:SetInvincible(false) 111 | else 112 | if entity:IsVisible() then 113 | entity:SetVisible(false) 114 | end 115 | 116 | entity:ToggleCollision(false) 117 | entity:SetKinematic(true) 118 | entity:SetInvincible(true) 119 | 120 | if not ped:IsFatallyInjured() then 121 | ClearPedTasksImmediately(ped.ped_id) 122 | end 123 | end 124 | end 125 | 126 | function Player:SetWeaponDamageModifier(modifier) 127 | SetPlayerWeaponDamageModifier(self:GetPlayerId(), tofloat(modifier)) 128 | end 129 | 130 | function Player:SetMeleeWeaponDamageModifier(modifier) 131 | SetPlayerMeleeWeaponDamageModifier(self:GetPlayerId(), tofloat(modifier)) 132 | end 133 | 134 | function Player:SetRunSprintMultiplier(multiplier) 135 | SetRunSprintMultiplierForPlayer(self:GetPlayerId(), tofloat(multiplier)) 136 | end 137 | 138 | function Player:ResetStamina() 139 | ResetPlayerStamina(self.player_id) 140 | end 141 | 142 | function Player:SetMinFallDistance(distance) 143 | SetPlayerFallDistance(self.player_id, tofloat(distance)) 144 | end 145 | 146 | function Player:DisableFiring(disabled) 147 | self.firing_disabled = disabled 148 | 149 | Citizen.CreateThread(function() 150 | while self.firing_disabled do 151 | DisablePlayerFiring(self.player_id, true) 152 | Wait(1) 153 | end 154 | end) 155 | 156 | end 157 | 158 | function Player:GetPosition() 159 | return self:GetPed():GetPosition() 160 | end 161 | 162 | function Player:SetName(name) 163 | self.name = name 164 | end 165 | 166 | function Player:GetName() 167 | return self.name 168 | end 169 | 170 | function Player:GetId() 171 | return self.server_id 172 | end 173 | 174 | function Player:GetPlayerId() 175 | return self.player_id 176 | end 177 | 178 | function Player:GetSteamId() 179 | return self.steam_id 180 | end 181 | 182 | function Player:GetUniqueId() 183 | return self.unique_id 184 | end 185 | 186 | function Player:Disconnected() 187 | 188 | end 189 | 190 | 191 | 192 | function Player:tostring() 193 | return "Player (" .. self.name .. ")" 194 | end -------------------------------------------------------------------------------- /client/player/cPlayerManager.lua: -------------------------------------------------------------------------------- 1 | PlayerManager = class() 2 | 3 | function PlayerManager:__init() 4 | self:SubscribeToEvents() 5 | self:ListenForNetworkEvents() 6 | end 7 | 8 | function PlayerManager:SubscribeToEvents() 9 | Events:Subscribe("LocalPlayerSpawn", function() 10 | Network:Send("__PlayerSpawned") 11 | end) 12 | end 13 | 14 | function PlayerManager:ListenForNetworkEvents() 15 | Network:Subscribe("PlayerRemoved", self, self.PlayerRemoved) 16 | end 17 | 18 | function PlayerManager:PlayerRemoved(args) 19 | local player = cPlayers:GetByUniqueId(args.player_unique_id) 20 | assert(cPlayers:GetByUniqueId(args.player_unique_id) ~= nil, "PlayerManager:PlayerRemoved tried to remove player that wasn't being stored") 21 | 22 | if player then 23 | Events:Fire("PlayerQuit", {player = player}) 24 | 25 | player:Disconnected() 26 | 27 | cPlayers:RemovePlayer(args.player_unique_id) 28 | end 29 | end 30 | 31 | PlayerManager = PlayerManager() -------------------------------------------------------------------------------- /client/player/cPlayers.lua: -------------------------------------------------------------------------------- 1 | cPlayers = class() 2 | 3 | function cPlayers:__init() 4 | self.players_by_unique_id = {} 5 | self.received_data = false 6 | 7 | Network:Subscribe("__SyncConnectedPlayers", function(data) 8 | self:SyncConnectedPlayers(data, not self.received_data) 9 | self.received_data = true 10 | end) 11 | 12 | Network:Subscribe("__SyncConnectedPlayer", function(sync_data) 13 | if not self.players_by_unique_id[player_unique_id] then 14 | self:AddPlayer(sync_data) 15 | 16 | local player = self:GetByUniqueId(sync_data.unique_id) 17 | Events:Fire("PlayerJoined", {player = player}) 18 | else 19 | self:AddPlayer(sync_data) 20 | end 21 | end) 22 | 23 | Network:Send("__RequestApiPlayerData") 24 | 25 | while not self.received_data do 26 | Wait(10) 27 | end 28 | end 29 | 30 | function cPlayers:__postLoad() 31 | 32 | end 33 | 34 | -- getting all the players from the server 35 | function cPlayers:SyncConnectedPlayers(data, on_init) 36 | -- print("--- syncing Players ---") 37 | 38 | for player_unique_id, sync_data in pairs(data) do 39 | if not self.players_by_unique_id[player_unique_id] then 40 | self:AddPlayer(sync_data) 41 | 42 | local player = self:GetByUniqueId(sync_data.unique_id) 43 | if not on_init then 44 | Events:Fire("PlayerJoined", {player = player}) 45 | end 46 | 47 | -- print("Player Added: ", player) 48 | else 49 | self:AddPlayer(sync_data) 50 | end 51 | end 52 | 53 | -- print("------------------------------------") 54 | end 55 | 56 | function cPlayers:GetLocalPlayer() 57 | for player_unique_id, player in pairs(self.players_by_unique_id) do 58 | if LocalPlayer:IsPlayer(player) then 59 | return player 60 | end 61 | end 62 | end 63 | 64 | function cPlayers:GetNearestPlayer(position) 65 | local closest_player 66 | local closest_distance = 99999 67 | 68 | for player_unique_id, player in pairs(self.players_by_unique_id) do 69 | local distance = #(position - player:GetPosition()) 70 | 71 | if distance < closest_distance then 72 | closest_distance = distance 73 | closest_player = player 74 | end 75 | end 76 | 77 | return closest_player, closest_distance 78 | end 79 | 80 | function cPlayers:AddPlayer(sync_data) 81 | local player = Player(sync_data.source_id, sync_data.steam_id, sync_data.source_id, sync_data.unique_id) 82 | player:SetName(sync_data.name) 83 | 84 | for name, value in pairs(sync_data.network_values) do 85 | player:SetValue(name, value) 86 | end 87 | 88 | self.players_by_unique_id[player:GetUniqueId()] = player 89 | end 90 | 91 | function cPlayers:RemovePlayer(player_unique_id) 92 | self.players_by_unique_id[player_unique_id] = nil 93 | end 94 | 95 | function cPlayers:GetByUniqueId(player_unique_id) 96 | return self.players_by_unique_id[player_unique_id] 97 | end 98 | 99 | function cPlayers:GetByPlayerId(id) 100 | for player_unique_id, player in pairs(self.players_by_unique_id) do 101 | if player:GetPlayerId() == id then 102 | return player 103 | end 104 | end 105 | end 106 | 107 | function cPlayers:GetByServerId(server_id) 108 | for player_unique_id, player in pairs(self.players_by_unique_id) do 109 | if player:GetId() == server_id then 110 | return player 111 | end 112 | end 113 | end 114 | 115 | function cPlayers:GetNumPlayers() 116 | return count_table(self.players_by_unique_id) 117 | end 118 | 119 | function cPlayers:GetPlayers() 120 | -- todo: write new iterator 121 | return self.players_by_unique_id 122 | end 123 | 124 | 125 | cPlayers = cPlayers() -------------------------------------------------------------------------------- /client/prompt/cPrompt.lua: -------------------------------------------------------------------------------- 1 | if IsRedM then 2 | 3 | Prompt = class() 4 | 5 | --[[ 6 | Creates a new UI prompt. 7 | 8 | args (in table): 9 | text (string): text to display 10 | position (vector3) (optional): position to trigger the prompt at 11 | size (number): size of prompt trigger zone 12 | control (Control enum): control to trigger this prompt 13 | 14 | ]] 15 | function Prompt:__init(args) 16 | self.str = CreateVarString(10, "LITERAL_STRING", args.text) 17 | self.position = args.position 18 | self.size = tofloat(args.size) 19 | self.control = args.control 20 | 21 | self:Create() 22 | self:SetEnabled(true) 23 | self:SetVisible(true) 24 | self:SetContextPoint(self.position) 25 | self:SetContextSize(self.size) 26 | 27 | Events:Subscribe("onResourceStop", self, self.OnResourceStop) 28 | end 29 | 30 | function Prompt:Create() 31 | self.prompt = Citizen.InvokeNative(0x29FA7910726C3889, self.control, self.str, 6, 1, 1, -1, Citizen.ResultAsInteger()) 32 | end 33 | 34 | function Prompt:SetEnabled(enabled) 35 | Citizen.InvokeNative(0x8A0FB4D03A630D21, self.prompt, enabled) 36 | end 37 | 38 | function Prompt:SetVisible(visible) 39 | Citizen.InvokeNative(0x71215ACCFDE075EE, self.prompt, visible) 40 | end 41 | 42 | function Prompt:SetHoldMode(seconds_to_hold) 43 | Citizen.InvokeNative(0x74C7D7B72ED0D3CF, self.prompt, seconds_to_hold) 44 | end 45 | 46 | function Prompt:HasHoldModeCompleted() 47 | return Citizen.InvokeNative(0xE0F65F0640EF0617, self.prompt) 48 | end 49 | 50 | function Prompt:SetHoldIndefinitelyMode() 51 | Citizen.InvokeNative(0xEA5CCF4EEB2F82D1, self.prompt) 52 | end 53 | 54 | function Prompt:SetContextPoint(pos) 55 | self.position = pos 56 | Citizen.InvokeNative(0xAE84C5EE2C384FB3, self.prompt, self.position.x, self.position.y, self.position.z) 57 | end 58 | 59 | function Prompt:IsHoldModeRunning() 60 | return Citizen.InvokeNative(0xC7D70EAEF92EFF48, self.prompt) 61 | end 62 | 63 | function Prompt:IsPressed() 64 | return Citizen.InvokeNative(0x21E60E230086697F, self.prompt) 65 | end 66 | 67 | function Prompt:SetContextSize(size) 68 | self.size = size 69 | Citizen.InvokeNative(0x0C718001B77CA468, self.prompt, size) 70 | end 71 | 72 | function Prompt:OnResourceStop(name) 73 | if name == GetCurrentResourceName() then 74 | self:Remove() 75 | end 76 | end 77 | 78 | function Prompt:Remove() 79 | Citizen.InvokeNative(0x00EDE88D4D13CF59, self.prompt) 80 | end 81 | 82 | end -------------------------------------------------------------------------------- /client/render/cTexture.lua: -------------------------------------------------------------------------------- 1 | if not IsFiveM then return end 2 | 3 | Texture = class() 4 | 5 | TextureTypes = {Dui = 1, Image = 2} 6 | local texture_id = 0 7 | local TEXTURE_DICT = CreateRuntimeTxd("Texture_OOF_Dict") 8 | 9 | local function GetTextureName() 10 | texture_id = texture_id + 1 11 | return "Texture_" .. tostring(texture_id) 12 | end 13 | 14 | --[[ 15 | Creates a new texture that can be used with Render:DrawTexture 16 | 17 | args (in table): 18 | width (int): width of the texture 19 | height (int): height of the texture 20 | type (TextureTypes): type of the source of the texture 21 | source (string): path to the texture. Can be png, jpg, html, etc 22 | position (vector2): position of texture on screen 23 | rotation (number) (optional): rotation of the texture 24 | ]] 25 | function Texture:__init(args) 26 | 27 | self.height = args.height 28 | self.width = args.width 29 | self.size = vector2(self.width, self.height) 30 | self.type = args.type 31 | self.source = args.source 32 | self.position = args.position or vector2(0, 0) 33 | 34 | Citizen.CreateThread(function() 35 | self:Create() 36 | end) 37 | end 38 | 39 | function Texture:Create() 40 | print("Creating texture...") 41 | Citizen.Wait(1000) 42 | self.txn = GetTextureName() 43 | Citizen.Wait(1000) 44 | print("Texture name: " .. self.txn) 45 | Citizen.Wait(1000) 46 | 47 | if self.type == TextureTypes.Dui then 48 | print("Creating dui object with source " .. tostring(self.source) .. " width " .. tostring(self.width) .. " height " .. tostring(self.height)) 49 | Citizen.Wait(1000) 50 | self.dui_obj = Citizen.InvokeNative(0x23EAF899, self.source, self.width, self.height, Citizen.ResultAsLong()) 51 | Citizen.Wait(1000) 52 | print("Getting dui handle") 53 | Citizen.Wait(1000) 54 | self.dui = GetDuiHandle(self.dui_obj) 55 | Citizen.Wait(1000) 56 | print("CreateRuntimeTextureFromDuiHandle") 57 | Citizen.Wait(1000) 58 | self.tx = CreateRuntimeTextureFromDuiHandle(TEXTURE_DICT, self.txn, self.dui) 59 | Citizen.Wait(1000) 60 | 61 | elseif self.type == TextureTypes.Image then 62 | self.tx = CreateRuntimeTextureFromImage(TEXTURE_DICT, self.txn, self.source) 63 | end 64 | 65 | print("Done") 66 | end 67 | 68 | function Texture:SetPosition(pos) 69 | self.position = pos 70 | end 71 | 72 | function Texture:SetRotation(rot) 73 | self.rotation = rot 74 | end 75 | 76 | function Texture:Draw() 77 | Render:DrawSprite(self.position, self.size, self.rotation, Colors.White) 78 | end 79 | 80 | function Texture:Destroy() 81 | if self.type == TextureTypes.Dui then DestroyDui(self.dui_obj) end 82 | end -------------------------------------------------------------------------------- /client/screen-effects/cScreenEffects.lua: -------------------------------------------------------------------------------- 1 | if IsFiveM then 2 | ScreenEffects = class() 3 | 4 | --[[ 5 | toggle blurred screen 6 | ]] 7 | function ScreenEffects:Blur(enabled) 8 | assert(type(enabled) == "bool", "enabled expected to be a bool") 9 | if enabled then 10 | TransitionToBlurred(1) 11 | else 12 | TransitionFromBlurred(1) 13 | end 14 | end 15 | 16 | --[[ 17 | Starts a screen effect. 18 | Name is from ScreenEffect list, time is in ms, and loop is bool 19 | ]] 20 | function ScreenEffects:Start(name, time, loop) 21 | assert(type(name) == "string", "name expected to be a string") 22 | assert(type(time) == "number", "time expected to be a number") 23 | assert(type(loop) == "bool", "loop expected to be a bool") 24 | StartScreenEffect(name, time, loop) 25 | end 26 | 27 | --[[ 28 | Stops a screen effect with specified name 29 | ]] 30 | function ScreenEffects:Stop(name) 31 | assert(type(name) == "string", "name expected to be a string") 32 | StopScreenEffect(name) 33 | end 34 | 35 | --[[ 36 | Stops all screen effects. 37 | ]] 38 | function ScreenEffects:StopAll() 39 | StopAllScreenEffects() 40 | end 41 | 42 | ScreenEffects = ScreenEffects() 43 | 44 | ScreenEffect = { 45 | "SwitchHUDIn", 46 | "SwitchHUDOut", 47 | "FocusIn", 48 | "FocusOut", 49 | "MinigameEndNeutral", 50 | "MinigameEndTrevor", 51 | "MinigameEndFranklin", 52 | "MinigameEndMichael", 53 | "MinigameTransitionOut", 54 | "MinigameTransitionIn", 55 | "SwitchShortNeutralIn", 56 | "SwitchShortFranklinIn", 57 | "SwitchShortTrevorIn", 58 | "SwitchShortMichaelIn", 59 | "SwitchOpenMichaelIn", 60 | "SwitchOpenFranklinIn", 61 | "SwitchOpenTrevorIn", 62 | "SwitchHUDMichaelOut", 63 | "SwitchHUDFranklinOut", 64 | "SwitchHUDTrevorOut", 65 | "SwitchShortFranklinMid", 66 | "SwitchShortMichaelMid", 67 | "SwitchShortTrevorMid", 68 | "DeathFailOut", 69 | "CamPushInNeutral", 70 | "CamPushInFranklin", 71 | "CamPushInMichael", 72 | "CamPushInTrevor", 73 | "SwitchOpenMichaelIn", 74 | "SwitchSceneFranklin", 75 | "SwitchSceneTrevor", 76 | "SwitchSceneMichael", 77 | "SwitchSceneNeutral", 78 | "MP_Celeb_Win", 79 | "MP_Celeb_Win_Out", 80 | "MP_Celeb_Lose", 81 | "MP_Celeb_Lose_Out", 82 | "DeathFailNeutralIn", 83 | "DeathFailMPDark", 84 | "DeathFailMPIn", 85 | "MP_Celeb_Preload_Fade", 86 | "PeyoteEndOut", 87 | "PeyoteEndIn", 88 | "PeyoteIn", 89 | "PeyoteOut", 90 | "MP_race_crash", 91 | "SuccessFranklin", 92 | "SuccessTrevor", 93 | "SuccessMichael", 94 | "DrugsMichaelAliensFightIn", 95 | "DrugsMichaelAliensFight", 96 | "DrugsMichaelAliensFightOut", 97 | "DrugsTrevorClownsFightIn", 98 | "DrugsTrevorClownsFight", 99 | "DrugsTrevorClownsFightOut", 100 | "HeistCelebPass", 101 | "HeistCelebPassBW", 102 | "HeistCelebEnd", 103 | "HeistCelebToast", 104 | "MenuMGHeistIn", 105 | "MenuMGTournamentIn", 106 | "MenuMGSelectionIn", 107 | "ChopVision", 108 | "DMT_flight_intro", 109 | "DMT_flight", 110 | "DrugsDrivingIn", 111 | "DrugsDrivingOut", 112 | "SwitchOpenNeutralFIB5", 113 | "HeistLocate", 114 | "MP_job_load", 115 | "RaceTurbo", 116 | "MP_intro_logo", 117 | "HeistTripSkipFade", 118 | "MenuMGHeistOut", 119 | "MP_corona_switch", 120 | "MenuMGSelectionTint", 121 | "SuccessNeutral", 122 | "ExplosionJosh3", 123 | "SniperOverlay", 124 | "RampageOut", 125 | "Rampage", 126 | "Dont_tazeme_bro", 127 | "DeathFailOut" 128 | } 129 | end -------------------------------------------------------------------------------- /client/sound/cSound.lua: -------------------------------------------------------------------------------- 1 | Sound = class() 2 | 3 | --[[ 4 | Creates a new sound instance so you can play sounds. 5 | 6 | args: 7 | 8 | position - (optional) vector3 where you want it to be played from 9 | entity - (optional) entity you want it to be played from 10 | audioName - the name of the audio file from the audioRef 11 | audioRef - the audio bank with the file you want to play 12 | autoplay - (optional) bool of whether you want the sound to play automatically (good for one-shots) 13 | ]] 14 | function Sound:__init(args) 15 | assert(type(args.audioName) == "string", "args.audioName expected to be a string") 16 | assert(type(args.audioRef) == "string", "args.audioRef expected to be a string") 17 | assert(args.position == nil and args.entity == nil, "args.position or args.entity expected, none given") 18 | 19 | self.id = GetSoundId() 20 | self.position = args.position or vector3(0,0,0) 21 | 22 | self.entity = args.entity 23 | 24 | self.audioName = args.audioName 25 | self.audioRef = args.audioRef 26 | 27 | if args.autoplay then 28 | self:Play() 29 | end 30 | end 31 | 32 | function Sound:SetPosition(pos) 33 | assert(type(pos) == "vector3", "position expected to be a vector3") 34 | self.position = pos 35 | end 36 | 37 | function Sound:GetPosition() 38 | return self.position 39 | end 40 | 41 | function Sound:PlayFromCoord(pos) 42 | PlaySoundFromCoord(self.id, self.audioName, pos.x, pos.y, pos.z + 0.5 , self.audioRef, 1, 100, 0) 43 | end 44 | 45 | function Sound:PlayFromEntity(entity) 46 | PlaySoundFromEntity(self.id, self.audioName, entity, self.audioRef, 1, 1) 47 | end 48 | 49 | function Sound:PlayFrontend() 50 | PlaySoundFrontend(self.id, self.audioName, self.audioRef, 1) 51 | end 52 | 53 | --[[ 54 | Plays the sound. 55 | ]] 56 | function Sound:Play() 57 | if self.entity then 58 | self:PlayFromEntity(self.entity) 59 | elseif self.position then 60 | self:PlayFromCoord(self.position) 61 | else 62 | self:PlayFrontend() 63 | end 64 | end 65 | 66 | function Sound:SetVariable(var, val) 67 | SetVariableOnSound(self.id, var, val) 68 | end 69 | 70 | function Sound:IsFinishedPlaying() 71 | return HasSoundFinished(self.id) 72 | end 73 | 74 | function Sound:Stop() 75 | StopSound(self.id) 76 | end 77 | 78 | function Sound:Remove() 79 | self:Stop() 80 | ReleaseSoundId(self.id) 81 | end -------------------------------------------------------------------------------- /client/typecheck/cTypeCheck.lua: -------------------------------------------------------------------------------- 1 | TypeCheck = class() 2 | 3 | 4 | function TypeCheck:Number(n) 5 | assert(type(n) == "number", "arg expected to be a number") 6 | end 7 | 8 | function TypeCheck:Position(pos) 9 | assert(type(pos) == "vector2", "position expected to be a vector2") 10 | end 11 | 12 | function TypeCheck:Position3D(pos) 13 | assert(type(pos) == "vector3", "position3d expected to be a vector3") 14 | end 15 | 16 | function TypeCheck:Text(text) 17 | assert(type(text) == "string", "text expected to be a string") 18 | return text 19 | end 20 | 21 | function TypeCheck:Color(color) 22 | assert(is_class_instance(color, Color), "color expected to be a Color") 23 | assert(color.r ~= nil and color.g ~= nil and color.b ~= nil and color.a ~= nil, "color missing a component") 24 | end 25 | 26 | function TypeCheck:Font(font) 27 | font = math.round(font) 28 | assert(type(font) == "number" and font >= 0 and font < 5, "font expected to be a number 0-4") 29 | end 30 | 31 | function TypeCheck:Scale(scale) 32 | assert(type(scale) == "table", "scale expected to be a table") 33 | assert(scale.x ~= nil and scale.y ~= nil, "scale missing a component") 34 | end 35 | 36 | function TypeCheck:Size(size) 37 | assert(type(size) == "table", "size expected to be a table") 38 | assert(size.x ~= nil and size.y ~= nil, "size missing a component") 39 | end 40 | 41 | TypeCheck = TypeCheck() -------------------------------------------------------------------------------- /client/ui/events.js: -------------------------------------------------------------------------------- 1 | const res_name = GetParentResourceName() 2 | 3 | $(document).ready(function() 4 | { 5 | // Register frames here to call NUI events on them 6 | const frames = {} 7 | let z = 10 8 | 9 | // Listen for NUI events 10 | window.addEventListener('message', function(event) 11 | { 12 | const iframe = $(`#FRAME_${event.data.name}`); 13 | const event_name = event.data.nui_event; 14 | delete event.data.nui_event; 15 | 16 | if (event_name == "ui/hide" && iframe != null) 17 | { 18 | iframe.hide() 19 | EventCalled('Hide', null, event.data.name); 20 | } 21 | else if (event_name == "ui/show") 22 | { 23 | iframe.show() 24 | EventCalled('Show', null, event.data.name); 25 | } 26 | else if (event_name == "ui/event") 27 | { 28 | const nui_event = event.data.data.nui_event; 29 | delete event.data.data.nui_event; 30 | EventCalled(nui_event, event.data.data, event.data.name); 31 | } 32 | else if (event_name == "ui/create") 33 | { 34 | const iframe_new = $(``) 35 | 36 | Object.keys(event.data.css).forEach((key, index) => 37 | { 38 | iframe_new.css(key, event.data.css[key]); 39 | }) 40 | 41 | $('body').append(iframe_new) 42 | frames[event.data.name] = iframe_new 43 | z++; 44 | 45 | if (!event.data.visible) 46 | { 47 | iframe_new.hide() 48 | } 49 | 50 | iframe_new[0].contentWindow.onkeyup = (e) => {OnKey(e, 'keyup')} 51 | iframe_new[0].contentWindow.onkeydown = (e) => {OnKey(e, 'keydown')} 52 | iframe_new[0].contentWindow.onkeypress = (e) => {OnKey(e, 'keypress')} 53 | iframe_new[0].contentWindow.OOF = new OOF(event.data.name); 54 | } 55 | else if (event_name == "ui/remove" && frames[event.data.name] != null) 56 | { 57 | frames[event.data.name].remove() 58 | delete frames[event.data.name] 59 | } 60 | else if (event_name == "ui/bring_to_front" && frames[event.data.name] != null) 61 | { 62 | frames[event.data.name].css('z-index', z++) 63 | } 64 | else if (event_name == "ui/send_to_back" && frames[event.data.name] != null) 65 | { 66 | frames[event.data.name].css('z-index', 0) 67 | } 68 | else if (event_name == "ui/lua_ready") 69 | { 70 | $.post(`http://${res_name}/ui/ready`, 71 | JSON.stringify({size: {x: $(window).width(), y: $(window).height()}})); 72 | } 73 | }) 74 | 75 | function EventCalled(event_name, data, ui_name) 76 | { 77 | if (events[event_name] != null) 78 | { 79 | events[event_name].forEach((entry) => 80 | { 81 | if (entry.callback != null && (ui_name == null || ui_name == entry.name)) 82 | { 83 | entry.callback(data); 84 | } 85 | }) 86 | } 87 | } 88 | 89 | const events = {}; 90 | 91 | // Additional OOF functions 92 | class OOF 93 | { 94 | constructor (name) 95 | { 96 | this.name = name 97 | } 98 | 99 | GetFocus(elem) 100 | { 101 | elem.focus(); 102 | } 103 | 104 | CallEvent(event_name, args) 105 | { 106 | if (event_name == null || event_name.length == 0) 107 | { 108 | this.console.error(`Cannot CallEvent without a valid event_name`) 109 | return; 110 | } 111 | args = args || {} 112 | args = JSON.stringify(args) 113 | 114 | $.post(`http://${res_name}/${this.name}_${event_name}`, args); 115 | } 116 | 117 | Subscribe(event_name, callback) 118 | { 119 | if (events[event_name] == null) 120 | { 121 | events[event_name] = [] 122 | } 123 | events[event_name].push({callback: callback, name: this.name}); 124 | } 125 | } 126 | 127 | const key_types = 128 | { 129 | 'keyup': 'KeyUp', 130 | 'keydown': 'KeyDown', 131 | 'keypress': 'KeyPress', 132 | } 133 | function OnKey(e, type) 134 | { 135 | const keycode = (typeof e.which === 'number') ? e.which : e.keyCode; 136 | 137 | $.post(`http://${res_name}/ui/keypress`, JSON.stringify({key: keycode, type: type})); 138 | 139 | const returns = [] 140 | 141 | const event_name = key_types[type]; 142 | if (events[event_name] != null) 143 | { 144 | events[event_name].forEach((entry) => 145 | { 146 | returns.push(entry.callback({key: keycode, event: e})); 147 | }) 148 | } 149 | 150 | returns.forEach((return_val) => 151 | { 152 | if (return_val != undefined) 153 | { 154 | return return_val; 155 | } 156 | }) 157 | 158 | } 159 | 160 | document.onkeyup = (e) => {OnKey(e, 'keyup');} 161 | document.onkeydown = (e) => {OnKey(e, 'keydown');} 162 | document.onkeypress = (e) => {OnKey(e, 'keypress');} 163 | 164 | }) 165 | -------------------------------------------------------------------------------- /client/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /client/ui/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /client/ui/ui.lua: -------------------------------------------------------------------------------- 1 | UIInstance = class() 2 | 3 | function UIInstance:__init(args) 4 | self.name = args.name 5 | self.path = args.path 6 | self.css = args.css 7 | self.visible = args.visible or true 8 | end 9 | 10 | --[[ 11 | Subscribe to UI events! 12 | ]] 13 | function UIInstance:Subscribe(event_name, instance, callback) 14 | RegisterNUICallback(string.format("%s_%s", self.name, event_name), function(args) 15 | -- Use a thread to get proper callstack on future errors 16 | Citizen.CreateThread(function() 17 | if not callback then 18 | instance(args) 19 | else 20 | callback(instance, args) 21 | end 22 | end) 23 | end) 24 | end 25 | 26 | function UIInstance:BringToFront() 27 | SendNUIMessage({nui_event = "ui/bring_to_front", name = self.name}) 28 | end 29 | 30 | function UIInstance:SendToBack() 31 | SendNUIMessage({nui_event = "ui/send_to_back", name = self.name}) 32 | end 33 | 34 | --[[ 35 | Calls an event on a specific UI instance. 36 | 37 | data - (table) table of data you want to send with the event 38 | ]] 39 | function UIInstance:CallEvent(event_name, data) 40 | assert(type(event_name) == "string", "event_name expected to be a string") 41 | if not data then data = {} end 42 | data.nui_event = event_name 43 | -- data = EnsureStringKeys(data) 44 | SendNUIMessage({nui_event = "ui/event", name = self.name, data = data}) 45 | end 46 | 47 | -- Loops through all tables in a table and converts keys to strings if they are not already 48 | function EnsureStringKeys(input) 49 | if type(input) == "table" then 50 | local input_string_keys = {} 51 | for key, value in pairs(input) do 52 | input_string_keys[tostring(key)] = EnsureStringKeys(value) 53 | end 54 | return input_string_keys 55 | end 56 | return input 57 | end 58 | 59 | --[[ 60 | If the UI is visible or not 61 | ]] 62 | function UIInstance:GetVisible() 63 | return self.visible 64 | end 65 | 66 | --[[ 67 | Hides the UI instance. 68 | ]] 69 | function UIInstance:Hide() 70 | SendNUIMessage({nui_event = "ui/hide", name = self.name}) 71 | self.visible = false 72 | end 73 | 74 | --[[ 75 | Shows the UI instance. 76 | ]] 77 | function UIInstance:Show() 78 | SendNUIMessage({nui_event = "ui/show", name = self.name}) 79 | self.visible = true 80 | end 81 | 82 | --[[ 83 | Removes a UI instance 84 | ]] 85 | function UIInstance:Remove() 86 | UI:Remove(self.name) 87 | end 88 | 89 | -- -------------------------------------------------- 90 | 91 | UI = class() 92 | 93 | function UI:__init() 94 | self.uis = {} 95 | 96 | -- ref count nui focus/cursor 97 | self.ref = {focus = 0, cursor = 0} 98 | self.size = {x = 0, y = 0} 99 | 100 | self.ui_ready = false 101 | RegisterNUICallback("ui/ready", function(args) 102 | self.ui_ready = true 103 | self.size.x = args.size.x 104 | self.size.y = args.size.y 105 | end) 106 | 107 | --[[ In order to use this event, please make sure to have these two lines in your code: 108 | UI:KeepInput(true) 109 | UI:SetFocus(true) 110 | 111 | This will ensure that the UI picks up keypresses even when not visible 112 | --]] 113 | RegisterNUICallback("ui/keypress", function(args) 114 | Events:Fire("UIKeyPress", args) 115 | end) 116 | 117 | self:SetNuiFocus() 118 | 119 | SendNUIMessage({nui_event = "ui/lua_ready"}) 120 | end 121 | 122 | function UI:KeepInput(keep_input) 123 | SetNuiFocusKeepInput(keep_input) 124 | end 125 | 126 | function UI:GetSize() 127 | return self.size 128 | end 129 | 130 | function UI:SetCursor(enabled) 131 | self.ref.cursor = math.max(0, enabled and self.ref.cursor + 1 or self.ref.cursor - 1) 132 | self:SetNuiFocus() 133 | end 134 | 135 | function UI:SetFocus(enabled) 136 | self.ref.focus = math.max(0, enabled and self.ref.focus + 1 or self.ref.focus - 1) 137 | self:SetNuiFocus() 138 | end 139 | 140 | function UI:GetFocus() 141 | return self.ref.focus 142 | end 143 | 144 | function UI:GetCursor() 145 | return self.ref.cursor 146 | end 147 | 148 | function UI:SetNuiFocus() 149 | SetNuiFocus(self.ref.focus > 0, self.ref.cursor > 0) 150 | end 151 | 152 | --[[ 153 | Creates a new UIInstance. 154 | 155 | args: (in table) 156 | 157 | name - (string) name of ui instance. Must be unique. 158 | path - (string) path to html page. Must be relative to resource, eg: lobby/client/html/index.html 159 | ]] 160 | function UI:Create(args) 161 | assert(type(args.name) == "string", "name must be a string") 162 | assert(type(args.path) == "string", "path must be a string") 163 | 164 | local ui = UIInstance(args) 165 | 166 | Citizen.CreateThread(function() 167 | while not self.ui_ready do 168 | Wait(100) 169 | end 170 | SendNUIMessage({ 171 | nui_event = "ui/create", 172 | name = args.name, 173 | path = args.path, 174 | css = args.css or {}, 175 | visible = not (args.visible == false) 176 | }) 177 | end) 178 | 179 | self.uis[args.name] = ui 180 | return ui 181 | end 182 | 183 | --[[ 184 | Calls an event on a single or all UI instances. 185 | 186 | args: (in table) 187 | data - (table) the table of data you want to send 188 | event_name - (string) event name 189 | name (optional) - name of the UI instance you want to send to, nil for all 190 | ]] 191 | function UI:CallEvent(args) 192 | if args.name and self.uis[args.name] then 193 | self.uis[args.name]:CallEvent(args.event_name, args.data) 194 | else 195 | for name, ui in pairs(self.uis) do 196 | ui:CallEvent(args.event_name, args.data) 197 | end 198 | end 199 | end 200 | 201 | --[[ 202 | Removes a UI instance by name 203 | ]] 204 | function UI:Remove(name) 205 | if self.uis[name] then 206 | SendNUIMessage({nui_event = "ui/remove", name = name}) 207 | self.uis[name] = nil 208 | end 209 | end 210 | 211 | UI = UI() 212 | -------------------------------------------------------------------------------- /client/volume/cVolume.lua: -------------------------------------------------------------------------------- 1 | if IsRedM then 2 | 3 | Volume = class() 4 | 5 | VolumeType = 6 | { 7 | Sphere = 1, 8 | Box = 2, 9 | Cylinder = 3 10 | } 11 | 12 | --[[ 13 | Creates a new Volume, aka an area within the game. Use Entity:IsInVolume(Volume) to detect 14 | if an entity is within the volume. 15 | 16 | args (in table): 17 | position (vector3): position of the volume 18 | rotation (vector3): rotation of the volume 19 | size (vector3): size of the volume 20 | type (VolumeType): type of the volume 21 | ]] 22 | function Volume:__init(args) 23 | 24 | local volume_func_hash 25 | 26 | if args.type == VolumeType.Sphere then 27 | volume_func_hash = 0xB3FB80A32BAE3065 28 | elseif args.type == VolumeType.Box then 29 | volume_func_hash = 0xDF85637F22706891 30 | elseif args.type == VolumeType.Cylinder then 31 | volume_func_hash = 0x0522D4774B82E3E6 32 | end 33 | 34 | self.volume = Citizen.InvokeNative(volume_func_hash, 35 | args.position.x, args.position.y, args.position.z, 36 | args.rotation.x, args.rotation.y, args.rotation.z, 37 | args.size.x, args.size.y, args.size.z 38 | ) 39 | 40 | end 41 | 42 | function Volume:GetHandle() 43 | return self.volume 44 | end 45 | 46 | function Volume:Remove() 47 | Citizen.InvokeNative(0x43F867EF5C463A53, self.volume) 48 | end 49 | 50 | end -------------------------------------------------------------------------------- /client/water/cWater.lua: -------------------------------------------------------------------------------- 1 | if IsFiveM then 2 | 3 | Water = class() 4 | 5 | function Water:__init() 6 | 7 | end 8 | 9 | --[[ 10 | This seems to edit the water wave, intensity around your current location. 11 | 0.0f = Normal 12 | 1.0f = So Calm and Smooth, a boat will stay still. 13 | 3.0f = Really Intense. 14 | 15 | You can also set the value as high as you want for some crazy looking water. 16 | ]] 17 | function Water:SetWaveIntensity(intensity) 18 | WaterOverrideSetStrength(tofloat(intensity)) 19 | end 20 | 21 | function Water:SetOceanWaveAmplitude(amplitude) 22 | WaterOverrideSetOceanwaveamplitude(amplitude) 23 | end 24 | 25 | --[[ 26 | This function set height to the value of z-axis of the water surface. 27 | This function works with sea and lake. However it does not work with shallow rivers (e.g. raton canyon will return -100000.0f) 28 | note: seems to return true when you are in water 29 | 30 | set ignore_waves to true if you want to ignore waves in this calculation 31 | ]] 32 | function Water:GetHeightAtPos(pos, ignore_waves) 33 | if not ignore_waves then 34 | local water_exists, height = GetWaterHeight(pos.x, pos.y, pos.z) 35 | return height, water_exists 36 | else 37 | local water_exists, height = GetWaterHeightNoWaves(pos.x, pos.y, pos.z) 38 | return height, water_exists 39 | end 40 | end 41 | 42 | --[[ 43 | Sets the water height at specified position. 44 | 45 | Seems to only make a small impact if done every frame. 46 | ]] 47 | function Water:SetHeightAtPos(pos, radius, height) 48 | ModifyWater(pos.x, pos.y, tofloat(radius), tofloat(height)) 49 | end 50 | 51 | -- Does not seem do to anything 52 | function Water:AddCurrentRise(x_low, y_low, x_high, y_high, height) 53 | AddCurrentRise(x_low, y_low, x_high, y_high, height) 54 | end 55 | 56 | Water = Water() 57 | 58 | end -------------------------------------------------------------------------------- /client/weapons/cWeapons.lua: -------------------------------------------------------------------------------- 1 | Weapons = class() 2 | 3 | function Weapons:__init() 4 | 5 | self.current_modifier = 1.0 6 | end 7 | 8 | 9 | function Weapons:SetDamageModifier(modifier) 10 | self.current_modifier = modifier 11 | 12 | local success, localplayer_weapon_hash = GetCurrentPedWeapon(LocalPlayer:GetPedId()) 13 | if success then 14 | SetWeaponDamageModifier(localplayer_weapon_hash, tofloat(self.current_modifier)) 15 | end 16 | end 17 | 18 | function Weapons:GetAllPossibleBaseDamages(weapon_enum) 19 | return self.base_damages[weapon_enum] 20 | end 21 | 22 | 23 | Weapons = Weapons() 24 | -------------------------------------------------------------------------------- /example_fxmanifest.lua: -------------------------------------------------------------------------------- 1 | ui_page 'oof/client/ui/index.html' 2 | 3 | client_scripts { 4 | -- OOF module, nothing should precede this module 5 | 6 | -- Uncomment ONE of these depending on the game this is running on 7 | --'oof/shared/game/IsRedM.lua', 8 | --'oof/shared/game/IsFiveM.lua', 9 | 10 | 'oof/shared/lua-overloads/*.lua', 11 | 'oof/shared/lua-additions/*.lua', 12 | 'oof/shared/object-oriented/class.lua', -- no class instances on initial frame before this file 13 | 'oof/shared/object-oriented/shGetterSetter.lua', -- getter_setter, getter_setter_encrypted 14 | 'oof/shared/object-oriented/shObjectOrientedUtilities.lua', -- is_class_instance 15 | 'oof/shared/standalone-data-structures/*', -- Enum, IdPool 16 | 'oof/shared/math/*.lua', 17 | '**/shared/enums/*Enum.lua', -- load all Enums 18 | '**/client/enums/*Enum.lua', 19 | 'oof/shared/events/*.lua', -- Events class 20 | 'oof/client/network/*.lua', -- Network class 21 | 'oof/shared/value-storage/*.lua', -- ValueStorage class 22 | 'oof/client/typecheck/*.lua', -- TypeCheck class 23 | 'oof/client/asset-requester/*.lua', 24 | 'oof/shared/timer/*.lua', -- Timer class 25 | 'oof/shared/xml/*.lua', -- XML class 26 | 'oof/shared/csv/*.lua', -- CSV class 27 | 'oof/client/entity/*.lua', -- Entity class 28 | 'oof/client/player/cPlayer.lua', 29 | 'oof/client/player/cPlayers.lua', 30 | 'oof/client/player/cPlayerManager.lua', 31 | 'oof/client/ped/*.lua', -- Ped class 32 | 'oof/client/physics/*.lua', 33 | 'oof/client/localplayer/*.lua', -- LocalPlayer class 34 | 'oof/shared/color/*.lua', 35 | 'oof/client/render/*.lua', 36 | 'oof/client/camera/*.lua', -- Camera class 37 | 'oof/client/blip/*.lua', -- Blip class 38 | 'oof/client/object/*.lua', -- Object, ObjectManager classes 39 | 'oof/client/ScreenEffects.lua', 40 | 'oof/client/world/*.lua', -- World class 41 | 'oof/client/sound/*.lua', -- Sound class 42 | 'oof/client/light/*.lua', -- Light class 43 | 'oof/client/particle-effect/*.lua', -- ParticleEffect class 44 | 'oof/client/anim-post-fx/*.lua', -- AnimPostFX class 45 | 'oof/client/volume/*.lua', -- Volume class 46 | 'oof/client/explosion/*.lua', -- Explosion class 47 | 'oof/client/pause-menu/*.lua', -- PauseMenu class 48 | 'oof/client/hud/*.lua', -- HUD class 49 | 'oof/client/keypress/*.lua', 50 | 'oof/client/prompt/*.lua', -- Prompt class 51 | 'oof/client/map/*.lua', -- Imap/Ipl class 52 | 'oof/client/marker/*.lua', -- Marker class 53 | 'oof/client/water/*.lua', -- Water class 54 | 'oof/client/apitest.lua', 55 | 'oof/client/localplayer_behaviors/*.lua', 56 | 'oof/client/weapons/*.lua', 57 | 'oof/client/ui/ui.lua', 58 | 59 | -- Add other modules here (client and shared) 60 | 61 | -- LOAD LAST 62 | 'oof/shared/object-oriented/LOAD_ABSOLUTELY_LAST.lua' 63 | } 64 | 65 | server_scripts { 66 | -- OOF module, nothing should precede this module 67 | 68 | -- Uncomment ONE of these depending on the game this is running on 69 | --'oof/shared/game/IsRedM.lua', 70 | --'oof/shared/game/IsFiveM.lua', 71 | 72 | 'oof/server/sConfig.lua', 73 | 'oof/shared/lua-overloads/*.lua', 74 | 'oof/shared/lua-additions/*.lua', 75 | 'oof/shared/object-oriented/class.lua', -- no class instances on initial frame before this file 76 | 'oof/shared/object-oriented/shGetterSetter.lua', 77 | 'oof/shared/object-oriented/shObjectOrientedUtilities.lua', -- is_class_instance function 78 | 'oof/shared/math/*.lua', 79 | 'oof/shared/standalone-data-structures/*', -- Enum, IdPool 80 | '**/shared/enums/*Enum.lua', -- load all the enums from all the modules 81 | '**/server/enums/*Enum.lua', 82 | 'oof/shared/color/*.lua', 83 | 'oof/shared/events/*.lua', -- Events class 84 | 'oof/server/network/*.lua', -- Network class 85 | 'oof/server/json/*.lua', -- JsonOOF, JsonUtils class 86 | -- mysql enabler 87 | 'oof/server/mysql-async/MySQLAsync.net.dll', 88 | 'oof/server/mysql-async/lib/init.lua', 89 | 'oof/server/mysql-async/lib/MySQL.lua', 90 | -- mysql wrapper 91 | 'oof/server/mysql/MySQL.lua', 92 | 'oof/server/key-value-store/*.lua', 93 | 'oof/shared/value-storage/*.lua', -- ValueStorage class 94 | 'oof/shared/timer/*.lua', -- Timer class 95 | 'oof/shared/xml/*.lua', -- XML class 96 | 'oof/shared/csv/*.lua', -- CSV class 97 | 'oof/server/fs-additions/*.lua', -- Directory/file exists helper functions 98 | 'oof/server/player/sPlayer.lua', -- Player class 99 | 'oof/server/player/sPlayers.lua', -- Players class 100 | 'oof/server/player/sPlayerManager.lua', -- PlayerManager class 101 | 'oof/server/entity/sEntity.lua', -- Entity class 102 | 'oof/server/ped/sPed.lua', -- Ped class 103 | 'oof/server/world/*.lua', -- World class 104 | 105 | -- Add other modules here (server and shared) 106 | 107 | -- Load last 108 | 'oof/shared/object-oriented/LOAD_ABSOLUTELY_LAST.lua' 109 | } 110 | 111 | files { 112 | -- general ui 113 | 'oof/client/ui/reset.css', 114 | 'oof/client/ui/jquery.js', 115 | 'oof/client/ui/events.js', 116 | 'oof/client/ui/index.html' 117 | -- Add files that you need here, like html/css/js for UI 118 | } 119 | 120 | fx_version 'adamant' 121 | games { 'rdr3', 'gta5' } 122 | rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.' -------------------------------------------------------------------------------- /server/entity/sEntity.lua: -------------------------------------------------------------------------------- 1 | Entity = class(ValueStorage) 2 | 3 | function Entity:__init(entity_id) 4 | self.entity = entity_id 5 | self:InitializeEntity(self.entity) 6 | end 7 | 8 | function Entity:InitializeEntity(ent) 9 | self.entity = ent 10 | self:InitializeValueStorage(self) 11 | end 12 | 13 | function Entity:GetEntity() 14 | return self.entity 15 | end 16 | 17 | function Entity:GetEntityId() 18 | return self.entity 19 | end 20 | 21 | function Entity:Remove() 22 | DeleteEntity(self.entity) 23 | end 24 | 25 | function Entity:Exists() 26 | return DoesEntityExist(self.entity) 27 | end 28 | 29 | function Entity:GetNetworkId() 30 | return NetworkGetNetworkIdFromEntity(self.entity) 31 | end 32 | 33 | function Entity:GetNetworkOwner() 34 | return NetworkGetEntityOwner(self.entity) 35 | end 36 | 37 | function Entity:NetworkGetFirstEntityOwner() 38 | -- Get first entity owner - with fallback in case of older server version 39 | return NetworkGetFirstEntityOwner and NetworkGetFirstEntityOwner(self.entity) or NetworkGetEntityOwner(self.entity) 40 | end 41 | 42 | function Entity:GetPosition() 43 | return GetEntityCoords(self.entity) 44 | end 45 | 46 | function Entity:GetHeading() 47 | return GetEntityHeading(self.entity) 48 | end 49 | 50 | function Entity:GetHealth() 51 | return GetEntityHealth(self.entity) 52 | end 53 | 54 | function Entity:GetMaxHealth() 55 | return GetEntityMaxHealth(self.entity) 56 | end 57 | 58 | function Entity:GetModel() 59 | return GetEntityModel(self.entity) 60 | end 61 | 62 | function Entity:GetRotation() 63 | return GetEntityRotation(self.entity) 64 | end 65 | 66 | function Entity:GetRotationVelocity() 67 | return GetEntityRotationVelocity(self.entity) 68 | end 69 | 70 | function Entity:GetRoutingBucket() 71 | return GetEntityRoutingBucket(self.entity) 72 | end 73 | 74 | function Entity:SetRoutingBucket(bucket) 75 | SetEntityRoutingBucket(self.entity, bucket) 76 | end 77 | 78 | function Entity:GetType() 79 | return GetEntityType(self.entity) 80 | end 81 | 82 | function Entity:GetVelocity() 83 | return GetEntityVelocity(self.entity) 84 | end 85 | -------------------------------------------------------------------------------- /server/fs-additions/exists.lua: -------------------------------------------------------------------------------- 1 | -- https://stackoverflow.com/a/40195356 2 | --- Check if a file or directory exists in this path 3 | function fs_exists(file) 4 | local ok, err, code = os.rename(file, file) 5 | if not ok then 6 | if code == 13 then 7 | -- Permission denied, but it exists 8 | return true 9 | end 10 | end 11 | return ok, err 12 | end 13 | 14 | --- Check if a directory exists in this path 15 | function fs_isdir(path) 16 | -- "/" works on both Unix and Windows 17 | return fs_exists(path.."/") 18 | end 19 | -------------------------------------------------------------------------------- /server/json/sJSONUtils.lua: -------------------------------------------------------------------------------- 1 | JsonUtils = class() 2 | 3 | function JsonUtils:LoadJSON(path) 4 | local contents = LoadResourceFile(GetCurrentResourceName(), path) 5 | if not contents then return end 6 | return json.decode(contents) 7 | end 8 | 9 | function JsonUtils:SaveJSON(data, path) 10 | SaveResourceFile(GetCurrentResourceName(), path, json.encode(data, {indent = true}), -1) 11 | return true 12 | end 13 | 14 | JsonUtils = JsonUtils() -------------------------------------------------------------------------------- /server/mysql-async/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2017, Joel Wurtz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the “Software”), to deal in the Software without restriction, including without limitation the 5 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 6 | persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 9 | Software. 10 | 11 | The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the 12 | warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or 13 | copyright holders X be liable for any claim, damages or other liability, whether in an action of contract, tort or 14 | otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software. 15 | -------------------------------------------------------------------------------- /server/mysql-async/MySQLAsync.net.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paradigm-MP/oof/e22d484c63ad7d780f5e1f0aa8c50ae9383596a7/server/mysql-async/MySQLAsync.net.dll -------------------------------------------------------------------------------- /server/mysql-async/MySqlConnector.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paradigm-MP/oof/e22d484c63ad7d780f5e1f0aa8c50ae9383596a7/server/mysql-async/MySqlConnector.dll -------------------------------------------------------------------------------- /server/mysql-async/System.Buffers.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paradigm-MP/oof/e22d484c63ad7d780f5e1f0aa8c50ae9383596a7/server/mysql-async/System.Buffers.dll -------------------------------------------------------------------------------- /server/mysql-async/System.Threading.Tasks.Extensions.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Paradigm-MP/oof/e22d484c63ad7d780f5e1f0aa8c50ae9383596a7/server/mysql-async/System.Threading.Tasks.Extensions.dll -------------------------------------------------------------------------------- /server/mysql-async/lib/MySQL.lua: -------------------------------------------------------------------------------- 1 | MySQL = { 2 | Async = {}, 3 | Sync = {}, 4 | Threaded = {} 5 | } 6 | 7 | local function safeParameters(params) 8 | if nil == params then 9 | return {[''] = ''} 10 | end 11 | 12 | assert(type(params) == "table", "A table is expected") 13 | assert(params[1] == nil, "Parameters should not be an array, but a map (key / value pair) instead") 14 | 15 | if next(params) == nil then 16 | return {[''] = ''} 17 | end 18 | 19 | return params 20 | end 21 | 22 | local function safeCallback(callback) 23 | if nil == callback then 24 | return function() end 25 | end 26 | 27 | assert(type(callback) == "function", "A callback is expected") 28 | 29 | return callback 30 | end 31 | 32 | --- 33 | -- Execute a query with no result required, sync version 34 | -- 35 | -- @param query 36 | -- @param params 37 | -- 38 | -- @return int Number of rows updated 39 | -- 40 | function MySQL.Sync.execute(query, params) 41 | assert(type(query) == "string", "The SQL Query must be a string") 42 | 43 | return exports[GetCurrentResourceName()]:mysql_sync_execute(query, safeParameters(params)) 44 | end 45 | 46 | --- 47 | -- Execute a query and fetch all results in an sync way 48 | -- 49 | -- @param query 50 | -- @param params 51 | -- 52 | -- @return table Query results 53 | -- 54 | function MySQL.Sync.fetchAll(query, params) 55 | assert(type(query) == "string", "The SQL Query must be a string") 56 | 57 | return exports[GetCurrentResourceName()]:mysql_sync_fetch_all(query, safeParameters(params)) 58 | end 59 | 60 | --- 61 | -- Execute a query and fetch the first column of the first row, sync version 62 | -- Useful for count function by example 63 | -- 64 | -- @param query 65 | -- @param params 66 | -- 67 | -- @return mixed Value of the first column in the first row 68 | -- 69 | function MySQL.Sync.fetchScalar(query, params) 70 | assert(type(query) == "string", "The SQL Query must be a string") 71 | 72 | return exports[GetCurrentResourceName()]:mysql_sync_fetch_scalar(query, safeParameters(params)) 73 | end 74 | 75 | --- 76 | -- Execute a query and retrieve the last id insert, sync version 77 | -- 78 | -- @param query 79 | -- @param params 80 | -- 81 | -- @return mixed Value of the last insert id 82 | -- 83 | function MySQL.Sync.insert(query, params) 84 | assert(type(query) == "string", "The SQL Query must be a string") 85 | 86 | return exports[GetCurrentResourceName()]:mysql_sync_insert(query, safeParameters(params)) 87 | end 88 | 89 | --- 90 | -- Execute a List of querys and returns bool true when all are executed successfully 91 | -- 92 | -- @param querys 93 | -- @param params 94 | -- 95 | -- @return bool if the transaction was successful 96 | -- 97 | function MySQL.Sync.transaction(querys, params) 98 | assert(type(querys) == "table", "The SQL Query must be a table of strings") 99 | 100 | return exports[GetCurrentResourceName()]:mysql_sync_transaction(querys, safeParameters(params)) 101 | end 102 | 103 | --- 104 | -- Execute a query with no result required, async version 105 | -- 106 | -- @param query 107 | -- @param params 108 | -- @param func(int) 109 | -- 110 | function MySQL.Async.execute(query, params, func) 111 | assert(type(query) == "string", "The SQL Query must be a string") 112 | 113 | exports[GetCurrentResourceName()]:mysql_execute(query, safeParameters(params), safeCallback(func)) 114 | end 115 | 116 | --- 117 | -- Execute a query and fetch all results in an async way 118 | -- 119 | -- @param query 120 | -- @param params 121 | -- @param func(table) 122 | -- 123 | function MySQL.Async.fetchAll(query, params, func) 124 | assert(type(query) == "string", "The SQL Query must be a string") 125 | 126 | exports[GetCurrentResourceName()]:mysql_fetch_all(query, safeParameters(params), safeCallback(func)) 127 | end 128 | 129 | --- 130 | -- Execute a query and fetch the first column of the first row, async version 131 | -- Useful for count function by example 132 | -- 133 | -- @param query 134 | -- @param params 135 | -- @param func(mixed) 136 | -- 137 | function MySQL.Async.fetchScalar(query, params, func) 138 | assert(type(query) == "string", "The SQL Query must be a string") 139 | 140 | exports[GetCurrentResourceName()]:mysql_fetch_scalar(query, safeParameters(params), safeCallback(func)) 141 | end 142 | 143 | --- 144 | -- Execute a query and retrieve the last id insert, async version 145 | -- 146 | -- @param query 147 | -- @param params 148 | -- @param func(string) 149 | -- 150 | function MySQL.Async.insert(query, params, func) 151 | assert(type(query) == "string", "The SQL Query must be a string") 152 | 153 | exports[GetCurrentResourceName()]:mysql_insert(query, safeParameters(params), safeCallback(func)) 154 | end 155 | 156 | --- 157 | -- Execute a List of querys and returns bool true when all are executed successfully 158 | -- 159 | -- @param querys 160 | -- @param params 161 | -- @param func(bool) 162 | -- 163 | function MySQL.Async.transaction(querys, params, func) 164 | assert(type(querys) == "table", "The SQL Query must be a table of strings") 165 | 166 | return exports[GetCurrentResourceName()]:mysql_transaction(querys, safeParameters(params), safeCallback(func)) 167 | end 168 | 169 | --- 170 | -- Execute a query with no result required, Threaded version 171 | -- 172 | -- @param query 173 | -- @param params 174 | -- 175 | -- @return int Number of rows updated 176 | -- 177 | function MySQL.Threaded.execute(query, params) 178 | assert(type(query) == "string", "The SQL Query must be a string") 179 | 180 | return exports[GetCurrentResourceName()]:mysql_threaded_execute(query, safeParameters(params)) 181 | end 182 | 183 | --- 184 | -- Execute a query and fetch all results in an Threaded way 185 | -- 186 | -- @param query 187 | -- @param params 188 | -- 189 | -- @return table Query results 190 | -- 191 | function MySQL.Threaded.fetchAll(query, params) 192 | assert(type(query) == "string", "The SQL Query must be a string") 193 | 194 | return exports[GetCurrentResourceName()]:mysql_threaded_fetch_all(query, safeParameters(params)) 195 | end 196 | 197 | --- 198 | -- Execute a query and fetch the first column of the first row, Threaded version 199 | -- Useful for count function by example 200 | -- 201 | -- @param query 202 | -- @param params 203 | -- 204 | -- @return mixed Value of the first column in the first row 205 | -- 206 | function MySQL.Threaded.fetchScalar(query, params) 207 | assert(type(query) == "string", "The SQL Query must be a string") 208 | 209 | return exports[GetCurrentResourceName()]:mysql_threaded_fetch_scalar(query, safeParameters(params)) 210 | end 211 | 212 | --- 213 | -- Execute a query and retrieve the last id insert, Threaded version 214 | -- 215 | -- @param query 216 | -- @param params 217 | -- 218 | -- @return mixed Value of the last insert id 219 | -- 220 | function MySQL.Threaded.insert(query, params) 221 | assert(type(query) == "string", "The SQL Query must be a string") 222 | 223 | return exports[GetCurrentResourceName()]:mysql_threaded_insert(query, safeParameters(params)) 224 | end 225 | 226 | 227 | local isReady = false 228 | 229 | AddEventHandler('onMySQLReady', function () 230 | isReady = true 231 | end) 232 | 233 | function MySQL.ready(callback) 234 | if isReady then 235 | callback() 236 | 237 | return 238 | end 239 | 240 | AddEventHandler('onMySQLReady', callback) 241 | end 242 | -------------------------------------------------------------------------------- /server/mysql-async/lib/init.lua: -------------------------------------------------------------------------------- 1 | AddEventHandler('onServerResourceStart', function (resource) 2 | if resource == GetCurrentResourceName() then 3 | exports[GetCurrentResourceName()]:mysql_configure() 4 | 5 | Citizen.CreateThread(function () 6 | Citizen.Wait(0) 7 | TriggerEvent('onMySQLReady') 8 | end) 9 | end 10 | end) 11 | 12 | 13 | -------------------------------------------------------------------------------- /server/mysql-async/src/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // Les informations générales relatives à un assembly dépendent de 6 | // l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations 7 | // associées à un assembly. 8 | [assembly: AssemblyTitle("MySQLAsync")] 9 | [assembly: AssemblyDescription("FiveM MySQL Async Integration for LUA")] 10 | [assembly: AssemblyConfiguration("x64")] 11 | [assembly: AssemblyCompany("Brouznouf")] 12 | [assembly: AssemblyProduct("MySQLAsync")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly 18 | // aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de 19 | // COM, affectez la valeur true à l'attribut ComVisible sur ce type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM 23 | [assembly: Guid("fbe7b949-3e81-41ca-a836-e576fab1baf4")] 24 | 25 | // Les informations de version pour un assembly se composent des quatre valeurs suivantes : 26 | // 27 | // Version principale 28 | // Version secondaire 29 | // Numéro de build 30 | // Révision 31 | // 32 | [assembly: AssemblyVersion("2.1.2.0")] 33 | [assembly: AssemblyFileVersion("2.1.2.0")] 34 | -------------------------------------------------------------------------------- /server/mysql-async/src/Execute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MySql.Data.MySqlClient; 7 | 8 | namespace MySQLAsync 9 | { 10 | class Execute : Operation 11 | { 12 | public Execute(string connectionString) : base(connectionString) 13 | { 14 | } 15 | 16 | protected override int Reader(MySqlCommand command) 17 | { 18 | return command.ExecuteNonQuery(); 19 | } 20 | 21 | protected override Task ReaderAsync(MySqlCommand command) 22 | { 23 | return command.ExecuteNonQueryAsync(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/mysql-async/src/FetchAll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MySql.Data.MySqlClient; 7 | 8 | namespace MySQLAsync 9 | { 10 | class FetchAll : Operation>> 11 | { 12 | public FetchAll(string connectionString) : base(connectionString) 13 | { 14 | } 15 | 16 | protected override List> Reader(MySqlCommand command) 17 | { 18 | var results = new List>(); 19 | 20 | using (var reader = command.ExecuteReader()) 21 | { 22 | if (reader.HasRows) 23 | { 24 | while (reader.Read()) 25 | { 26 | var line = new Dictionary(); 27 | 28 | for (int i = 0; i < reader.FieldCount; i++) 29 | { 30 | if (reader.IsDBNull(i)) 31 | { 32 | line.Add(reader.GetName(i), null); 33 | } 34 | else 35 | { 36 | line.Add(reader.GetName(i), reader.GetValue(i)); 37 | } 38 | } 39 | 40 | results.Add(line); 41 | } 42 | } 43 | } 44 | 45 | return results; 46 | } 47 | 48 | protected async override Task>> ReaderAsync(MySqlCommand command) 49 | { 50 | var results = new List>(); 51 | 52 | using (var reader = await command.ExecuteReaderAsync()) 53 | { 54 | if (reader.HasRows) 55 | { 56 | while (await reader.ReadAsync()) 57 | { 58 | var line = new Dictionary(); 59 | 60 | for (int i = 0; i < reader.FieldCount; i++) 61 | { 62 | if (reader.IsDBNull(i)) 63 | { 64 | line.Add(reader.GetName(i), null); 65 | } 66 | else 67 | { 68 | line.Add(reader.GetName(i), reader.GetValue(i)); 69 | } 70 | } 71 | 72 | results.Add(line); 73 | } 74 | } 75 | } 76 | 77 | return results; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /server/mysql-async/src/FetchScalar.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MySql.Data.MySqlClient; 7 | 8 | namespace MySQLAsync 9 | { 10 | class FetchScalar : Operation 11 | { 12 | public FetchScalar(string connectionString) : base(connectionString) 13 | { 14 | } 15 | 16 | protected override object Reader(MySqlCommand command) 17 | { 18 | var result = command.ExecuteScalar(); 19 | 20 | if (result != null && result.GetType() == typeof(DBNull)) 21 | { 22 | result = null; 23 | } 24 | 25 | return result; 26 | } 27 | 28 | protected override async Task ReaderAsync(MySqlCommand command) 29 | { 30 | var result = await command.ExecuteScalarAsync(); 31 | 32 | if (result != null && result.GetType() == typeof(DBNull)) 33 | { 34 | result = null; 35 | } 36 | 37 | return result; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/mysql-async/src/FiveMMySQLAsync.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {98F7FA9E-6F30-464D-8771-EC765FFF74BC} 8 | Library 9 | Properties 10 | MySQLAsync 11 | MySQLAsync.net 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | x64 24 | 25 | 26 | pdbonly 27 | true 28 | ..\ 29 | TRACE 30 | prompt 31 | 4 32 | x64 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | E:\fivem-test-server\citizen\clr2\lib\mono\4.5\CitizenFX.Core.dll 48 | 49 | 50 | 51 | False 52 | ..\..\MySqlConnector\src\MySqlConnector\bin\Release\net45\MySqlConnector.dll 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /server/mysql-async/src/FiveMMySQLAsync.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2036 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FiveMMySQLAsync", "FiveMMySQLAsync.csproj", "{98F7FA9E-6F30-464D-8771-EC765FFF74BC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySqlConnector", "..\..\MySqlConnector\src\MySqlConnector\MySqlConnector.csproj", "{2BE1CF31-58E8-47A7-BB89-55F2EE572097}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {98F7FA9E-6F30-464D-8771-EC765FFF74BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {98F7FA9E-6F30-464D-8771-EC765FFF74BC}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {98F7FA9E-6F30-464D-8771-EC765FFF74BC}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {98F7FA9E-6F30-464D-8771-EC765FFF74BC}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {2BE1CF31-58E8-47A7-BB89-55F2EE572097}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {2BE1CF31-58E8-47A7-BB89-55F2EE572097}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {2BE1CF31-58E8-47A7-BB89-55F2EE572097}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {2BE1CF31-58E8-47A7-BB89-55F2EE572097}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {9539478F-741C-4262-832E-CDA907EF7786} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /server/mysql-async/src/Insert.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MySql.Data.MySqlClient; 7 | 8 | namespace MySQLAsync 9 | { 10 | class Insert : Operation 11 | { 12 | public Insert(string connectionString) : base(connectionString) 13 | { 14 | } 15 | 16 | protected override object Reader(MySqlCommand command) 17 | { 18 | command.ExecuteNonQuery(); 19 | 20 | using (var lastInsertCommand = command.Connection.CreateCommand()) 21 | { 22 | lastInsertCommand.CommandText = "SELECT LAST_INSERT_ID()"; 23 | 24 | return lastInsertCommand.ExecuteScalar(); 25 | } 26 | } 27 | 28 | protected async override Task ReaderAsync(MySqlCommand command) 29 | { 30 | await command.ExecuteNonQueryAsync(); 31 | 32 | using (var lastInsertCommand = command.Connection.CreateCommand()) 33 | { 34 | lastInsertCommand.CommandText = "SELECT LAST_INSERT_ID()"; 35 | 36 | return await lastInsertCommand.ExecuteScalarAsync(); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/mysql-async/src/MySQLAsync.cs: -------------------------------------------------------------------------------- 1 | using CitizenFX.Core; 2 | using CitizenFX.Core.Native; 3 | using System; 4 | using System.Collections.Generic; 5 | using MySql.Data.MySqlClient; 6 | using System.Threading.Tasks; 7 | using System.Linq; 8 | 9 | namespace MySQLAsync 10 | { 11 | public class MySQLAsync : BaseScript 12 | { 13 | internal String ConnectionString; 14 | internal bool debug; 15 | 16 | public MySQLAsync() 17 | { 18 | Exports.Add("mysql_configure", new Action(() => 19 | { 20 | Configure( 21 | API.GetConvar("mysql_connection_string", ""), 22 | API.GetConvar("mysql_debug", "false") == "true" 23 | ); 24 | })); 25 | 26 | Exports.Add("mysql_execute", new Action, CallbackDelegate>((query, parameters, callback) => 27 | { 28 | (new Execute(ConnectionString)).ExecuteAsync(query, parameters, callback, debug); 29 | })); 30 | Exports.Add("mysql_sync_execute", new Func, int>((query, parameters) => 31 | { 32 | return (new Execute(ConnectionString)).Execute(query, parameters, debug); 33 | })); 34 | Exports.Add("mysql_threaded_execute", new Func, Task>(async (query, parameters) => 35 | { 36 | return await (new Execute(ConnectionString)).ExecuteThreaded(query, parameters, debug); 37 | })); 38 | 39 | Exports.Add("mysql_fetch_all", new Action, CallbackDelegate>((query, parameters, callback) => 40 | { 41 | (new FetchAll(ConnectionString)).ExecuteAsync(query, parameters, callback, debug); 42 | })); 43 | Exports.Add("mysql_sync_fetch_all", new Func, List>>((query, parameters) => 44 | { 45 | return (new FetchAll(ConnectionString)).Execute(query, parameters, debug); 46 | })); 47 | Exports.Add("mysql_threaded_fetch_all", new Func, Task>(async (query, parameters) => 48 | { 49 | return await (new FetchAll(ConnectionString)).ExecuteThreaded(query, parameters, debug); 50 | })); 51 | 52 | Exports.Add("mysql_fetch_scalar", new Action, CallbackDelegate>((query, parameters, callback) => 53 | { 54 | (new FetchScalar(ConnectionString)).ExecuteAsync(query, parameters, callback, debug); 55 | })); 56 | Exports.Add("mysql_sync_fetch_scalar", new Func, Object>((query, parameters) => 57 | { 58 | return (new FetchScalar(ConnectionString)).Execute(query, parameters, debug); 59 | })); 60 | Exports.Add("mysql_threaded_fetch_scalar", new Func, Task>(async (query, parameters) => 61 | { 62 | return await (new FetchScalar(ConnectionString)).ExecuteThreaded(query, parameters, debug); 63 | })); 64 | 65 | Exports.Add("mysql_insert", new Action, CallbackDelegate>((query, parameters, callback) => 66 | { 67 | (new Insert(ConnectionString)).ExecuteAsync(query, parameters, callback, debug); 68 | })); 69 | Exports.Add("mysql_sync_insert", new Func, Object>((query, parameters) => 70 | { 71 | return (new Insert(ConnectionString)).Execute(query, parameters, debug); 72 | })); 73 | Exports.Add("mysql_threaded_insert", new Func, Task>(async (query, parameters) => 74 | { 75 | return await (new Insert(ConnectionString)).ExecuteThreaded(query, parameters, debug); 76 | })); 77 | 78 | Exports.Add("mysql_transaction", new Action, IDictionary, CallbackDelegate>((querys, parameters, callback) => 79 | { 80 | (new Transaction(ConnectionString)).ExecuteTransactionAsync(querys.Select(q => q.ToString()).ToList(), parameters, callback, debug); 81 | })); 82 | Exports.Add("mysql_sync_transaction", new Func, IDictionary, bool>((querys, parameters) => 83 | { 84 | return (new Transaction(ConnectionString)).ExecuteTransaction(querys.Select(q => q.ToString()).ToList(), parameters, debug); 85 | })); 86 | } 87 | 88 | private void Configure(string connectionStringConfig, bool debug) 89 | { 90 | var connectionStringBuilder = new MySqlConnectionStringBuilder(connectionStringConfig); 91 | connectionStringBuilder.AllowUserVariables = true; 92 | connectionStringBuilder.SslMode = MySqlSslMode.None; 93 | 94 | this.debug = debug; 95 | ConnectionString = connectionStringBuilder.ToString(); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /server/mysql-async/src/MySQLThread.cs: -------------------------------------------------------------------------------- 1 | using CitizenFX.Core; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | 5 | namespace MySQLAsync 6 | { 7 | public class MySQLThread : BaseScript 8 | { 9 | internal BlockingCollection queryCollection = new BlockingCollection(); 10 | internal ConcurrentDictionary resultCollection = new ConcurrentDictionary(); 11 | internal uint NextId { get { uint result = nextId; nextId++; return result; } } 12 | private uint nextId = 0; 13 | private readonly Thread queryThread = null; 14 | 15 | private static MySQLThread instance; 16 | 17 | public MySQLThread() 18 | { 19 | instance = this; 20 | queryThread = new Thread(new ThreadStart(Execute)); 21 | if (!queryThread.IsAlive) 22 | queryThread.Start(); 23 | } 24 | 25 | public static MySQLThread GetInstance() => instance; 26 | 27 | private void Execute() 28 | { 29 | foreach (dynamic query in queryCollection.GetConsumingEnumerable()) 30 | resultCollection[query.ThreadedId] = (object)query.Execute(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/mysql-async/src/Operation.cs: -------------------------------------------------------------------------------- 1 | using CitizenFX.Core; 2 | using MySql.Data.MySqlClient; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Debug = CitizenFX.Core.Debug; 9 | 10 | namespace MySQLAsync 11 | { 12 | abstract class Operation 13 | { 14 | internal string ConnectionString; 15 | 16 | internal string query = ""; 17 | internal IDictionary parameters = null; 18 | internal bool debug = false; 19 | internal uint ThreadedId = 0; 20 | 21 | public Operation(string connectionString) 22 | { 23 | ConnectionString = connectionString; 24 | } 25 | 26 | public TResult Execute(string query = null, IDictionary parameters = null, bool debug = false) 27 | { 28 | if (string.IsNullOrEmpty(query)) 29 | { 30 | this.query = query; 31 | this.parameters = parameters; 32 | this.debug = debug; 33 | } 34 | 35 | TResult result = default(TResult); 36 | Stopwatch stopwatch = new Stopwatch(); 37 | 38 | try 39 | { 40 | stopwatch.Start(); 41 | 42 | using (var connection = new MySqlConnection(ConnectionString)) 43 | { 44 | connection.Open(); 45 | var ConnectionTime = stopwatch.ElapsedMilliseconds; 46 | stopwatch.Restart(); 47 | 48 | using (var command = CreateCommand(query, parameters, connection)) 49 | { 50 | var QueryTime = stopwatch.ElapsedMilliseconds; 51 | stopwatch.Restart(); 52 | 53 | result = Reader(command); 54 | stopwatch.Stop(); 55 | 56 | if (debug) 57 | { 58 | Debug.WriteLine(string.Format("[{0}] [C: {1}ms, Q: {2}ms, R: {3}ms] {4}", "MySQL", ConnectionTime, QueryTime, stopwatch.ElapsedMilliseconds, QueryToString(query, parameters))); 59 | } 60 | } 61 | } 62 | } 63 | catch (AggregateException aggregateException) 64 | { 65 | var firstException = aggregateException.InnerExceptions.First(); 66 | 67 | if (!(firstException is MySqlException)) 68 | { 69 | throw; 70 | } 71 | 72 | Debug.Write(string.Format("[ERROR] [{0}] An error happens on MySQL for query \"{1}\": {2}\n", "MySQL", QueryToString(query, parameters), firstException.Message)); 73 | } 74 | catch (MySqlException mysqlException) 75 | { 76 | Debug.Write(string.Format("[ERROR] [{0}] An error happens on MySQL for query \"{1}\": {2}\n", "MySQL", QueryToString(query, parameters), mysqlException.Message)); 77 | } 78 | catch (Exception exception) 79 | { 80 | Debug.Write(string.Format("[ERROR] [{0}] An critical error happens on MySQL for query \"{1}\": {2} {3}\n", "MySQL", QueryToString(query, parameters), exception.Message, exception.StackTrace)); 81 | } 82 | 83 | return result; 84 | } 85 | 86 | public async void ExecuteAsync(string query, IDictionary parameters, CallbackDelegate callback, bool debug = false) 87 | { 88 | TResult result = default(TResult); 89 | Stopwatch stopwatch = new Stopwatch(); 90 | 91 | try 92 | { 93 | stopwatch.Start(); 94 | 95 | using (var connection = new MySqlConnection(ConnectionString)) 96 | { 97 | await connection.OpenAsync(); 98 | var ConnectionTime = stopwatch.ElapsedMilliseconds; 99 | stopwatch.Restart(); 100 | 101 | using (var command = CreateCommand(query, parameters, connection)) 102 | { 103 | var QueryTime = stopwatch.ElapsedMilliseconds; 104 | stopwatch.Restart(); 105 | 106 | result = await ReaderAsync(command); 107 | stopwatch.Stop(); 108 | 109 | if (debug) 110 | { 111 | Debug.WriteLine(string.Format("[{0}] [C: {1}ms, Q: {2}ms, R: {3}ms] {4}", "MySQL", ConnectionTime, QueryTime, stopwatch.ElapsedMilliseconds, QueryToString(query, parameters))); 112 | } 113 | 114 | callback.Invoke(result); 115 | } 116 | } 117 | } 118 | catch (AggregateException aggregateException) 119 | { 120 | var firstException = aggregateException.InnerExceptions.First(); 121 | 122 | if (!(firstException is MySqlException)) 123 | { 124 | throw aggregateException; 125 | } 126 | 127 | Debug.Write(string.Format("[ERROR] [{0}] An error happens on MySQL for query \"{1}\": {2}\n", "MySQL", QueryToString(query, parameters), firstException.Message)); 128 | } 129 | catch (MySqlException mysqlException) 130 | { 131 | Debug.Write(string.Format("[ERROR] [{0}] An error happens on MySQL for query \"{1}\": {2}\n", "MySQL", QueryToString(query, parameters), mysqlException.Message)); 132 | } 133 | catch (ArgumentNullException) 134 | { 135 | Debug.Write(string.Format("[ERROR] [{0}] Check the error above, an error happens when executing the callback from the query : \"{1}\"\n", "MySQL", QueryToString(query, parameters))); 136 | } 137 | catch (Exception exception) 138 | { 139 | Debug.Write(string.Format("[ERROR] [{0}] An critical error happens on MySQL for query \"{1}\": {2} {3}\n", "MySQL", QueryToString(query, parameters), exception.Message, exception.StackTrace)); 140 | } 141 | } 142 | 143 | public async Task ExecuteThreaded(string query, IDictionary parameters, bool debug = false) 144 | { 145 | this.query = query; 146 | this.parameters = parameters; 147 | this.debug = debug; 148 | MySQLThread mysqlAsync = MySQLThread.GetInstance(); 149 | ThreadedId = mysqlAsync.NextId; 150 | mysqlAsync.queryCollection.TryAdd(this); 151 | 152 | while (!mysqlAsync.resultCollection.ContainsKey(ThreadedId)) 153 | await BaseScript.Delay(0); 154 | 155 | mysqlAsync.resultCollection.TryRemove(ThreadedId, out dynamic result); 156 | 157 | return result; 158 | } 159 | 160 | abstract protected TResult Reader(MySqlCommand command); 161 | 162 | abstract protected Task ReaderAsync(MySqlCommand command); 163 | 164 | private MySqlCommand CreateCommand(string query, IDictionary parameters, MySqlConnection connection) 165 | { 166 | MySqlCommand command = connection.CreateCommand(); 167 | command.CommandText = query; 168 | 169 | foreach (var parameter in parameters ?? Enumerable.Empty>()) 170 | { 171 | command.Parameters.AddWithValue(parameter.Key, parameter.Value); 172 | } 173 | 174 | return command; 175 | } 176 | 177 | internal string QueryToString(string query, IDictionary parameters) 178 | { 179 | return query + " {" + string.Join(";", parameters.Select(x => x.Key + "=" + x.Value).ToArray()) + "}"; 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /server/mysql-async/src/Transaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using CitizenFX.Core; 7 | using MySql.Data.MySqlClient; 8 | 9 | namespace MySQLAsync 10 | { 11 | class Transaction : Operation 12 | { 13 | public Transaction(string connectionString) : base(connectionString) { } 14 | 15 | public bool ExecuteTransaction(IList querys, IDictionary parameters = null, bool debug = false) 16 | { 17 | bool result = false; 18 | Stopwatch stopwatch = new Stopwatch(); 19 | 20 | try 21 | { 22 | stopwatch.Start(); 23 | 24 | using (var connection = new MySqlConnection(ConnectionString)) 25 | { 26 | connection.Open(); 27 | var ConnectionTime = stopwatch.ElapsedMilliseconds; 28 | stopwatch.Restart(); 29 | 30 | using (var command = connection.CreateCommand()) 31 | { 32 | foreach (var parameter in parameters ?? Enumerable.Empty>()) 33 | command.Parameters.AddWithValue(parameter.Key, parameter.Value); 34 | var QueryTime = stopwatch.ElapsedMilliseconds; 35 | stopwatch.Restart(); 36 | 37 | using (var transaction = connection.BeginTransaction()) 38 | { 39 | command.Transaction = transaction; 40 | 41 | try 42 | { 43 | foreach (string commandText in querys) 44 | { 45 | command.CommandText = commandText; 46 | command.ExecuteNonQuery(); 47 | } 48 | transaction.Commit(); 49 | result = true; 50 | } 51 | catch (Exception ex) 52 | { 53 | transaction.Rollback(); 54 | CitizenFX.Core.Debug.Write(string.Format("[ERROR] [{0}] [{1}] {2}\n", "MySQL", "Transaction", ex.Message)); 55 | } 56 | } 57 | 58 | stopwatch.Stop(); 59 | 60 | if (debug) 61 | { 62 | Console.WriteLine(string.Format("[{0}] [C: {1}ms, Q: {2}ms, R: {3}ms] {4}", "MySQL", ConnectionTime, QueryTime, stopwatch.ElapsedMilliseconds, QueryToString("Transaction", parameters))); 63 | } 64 | } 65 | } 66 | } 67 | catch (AggregateException aggregateException) 68 | { 69 | var firstException = aggregateException.InnerExceptions.First(); 70 | 71 | if (!(firstException is MySqlException)) 72 | { 73 | throw; 74 | } 75 | 76 | CitizenFX.Core.Debug.Write(string.Format("[ERROR] [{0}] An error happens on MySQL for query \"{1}\": {2}\n", "MySQL", QueryToString("Transaction", parameters), firstException.Message)); 77 | } 78 | catch (MySqlException mysqlException) 79 | { 80 | CitizenFX.Core.Debug.Write(string.Format("[ERROR] [{0}] An error happens on MySQL for query \"{1}\": {2}\n", "MySQL", QueryToString("Transaction", parameters), mysqlException.Message)); 81 | } 82 | catch (Exception exception) 83 | { 84 | CitizenFX.Core.Debug.Write(string.Format("[ERROR] [{0}] An critical error happens on MySQL for query \"{1}\": {2} {3}\n", "MySQL", QueryToString("Transaction", parameters), exception.Message, exception.StackTrace)); 85 | } 86 | 87 | return result; 88 | } 89 | 90 | public async void ExecuteTransactionAsync(IList querys, IDictionary parameters = null, CallbackDelegate callback = null, bool debug = false) 91 | { 92 | bool result = false; 93 | Stopwatch stopwatch = new Stopwatch(); 94 | 95 | try 96 | { 97 | stopwatch.Start(); 98 | 99 | using (var connection = new MySqlConnection(ConnectionString)) 100 | { 101 | await connection.OpenAsync(); 102 | var ConnectionTime = stopwatch.ElapsedMilliseconds; 103 | stopwatch.Restart(); 104 | 105 | using (var command = connection.CreateCommand()) 106 | { 107 | foreach (var parameter in parameters ?? Enumerable.Empty>()) 108 | command.Parameters.AddWithValue(parameter.Key, parameter.Value); 109 | var QueryTime = stopwatch.ElapsedMilliseconds; 110 | stopwatch.Restart(); 111 | 112 | using (var transaction = await connection.BeginTransactionAsync()) 113 | { 114 | command.Transaction = transaction; 115 | 116 | try 117 | { 118 | foreach (string commandText in querys) 119 | { 120 | command.CommandText = commandText; 121 | await command.ExecuteNonQueryAsync(); 122 | } 123 | await transaction.CommitAsync(); 124 | result = true; 125 | } 126 | catch (Exception ex) 127 | { 128 | await transaction.RollbackAsync(); 129 | CitizenFX.Core.Debug.Write(string.Format("[ERROR] [{0}] [{1}] {2}\n", "MySQL", "Transaction", ex.Message)); 130 | } 131 | } 132 | 133 | stopwatch.Stop(); 134 | 135 | if (debug) 136 | { 137 | Console.WriteLine(string.Format("[{0}] [C: {1}ms, Q: {2}ms, R: {3}ms] {4}", "MySQL", ConnectionTime, QueryTime, stopwatch.ElapsedMilliseconds, QueryToString("Transaction", parameters))); 138 | } 139 | } 140 | } 141 | } 142 | catch (AggregateException aggregateException) 143 | { 144 | var firstException = aggregateException.InnerExceptions.First(); 145 | 146 | if (!(firstException is MySqlException)) 147 | { 148 | throw; 149 | } 150 | 151 | CitizenFX.Core.Debug.Write(string.Format("[ERROR] [{0}] An error happens on MySQL for query \"{1}\": {2}\n", "MySQL", QueryToString("Transaction", parameters), firstException.Message)); 152 | } 153 | catch (MySqlException mysqlException) 154 | { 155 | CitizenFX.Core.Debug.Write(string.Format("[ERROR] [{0}] An error happens on MySQL for query \"{1}\": {2}\n", "MySQL", QueryToString("Transaction", parameters), mysqlException.Message)); 156 | } 157 | catch (Exception exception) 158 | { 159 | CitizenFX.Core.Debug.Write(string.Format("[ERROR] [{0}] An critical error happens on MySQL for query \"{1}\": {2} {3}\n", "MySQL", QueryToString("Transaction", parameters), exception.Message, exception.StackTrace)); 160 | } 161 | 162 | callback?.Invoke(result); 163 | } 164 | 165 | protected override bool Reader(MySqlCommand command) => throw new NotImplementedException(); 166 | protected override Task ReaderAsync(MySqlCommand command) => throw new NotImplementedException(); 167 | 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /server/mysql-async/src/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /server/mysql/MySQL.lua: -------------------------------------------------------------------------------- 1 | MySQLWrapper = class() 2 | 3 | 4 | function MySQLWrapper:__init() 5 | 6 | MySQL.ready(function () 7 | self:Ready() 8 | end) 9 | 10 | end 11 | 12 | function MySQLWrapper:Ready() 13 | self.ready = true 14 | Events:Fire("mysql/Ready") -- Let other modules know that MySQL is ready 15 | print(Colors.Console.Green .. "MySQL ready!" .. Colors.Console.Default) 16 | end 17 | 18 | -- SQL.Execute("UPDATE player SET name=@name WHERE id=@id", {['@id'] = 10, ['@name'] = 'foo'}, function(data) end) 19 | function MySQLWrapper:Execute(query, params, callback) 20 | MySQL.Async.execute(query, params, function(rowsChanged) 21 | if callback then 22 | callback(rowsChanged) 23 | end 24 | end) 25 | end 26 | 27 | -- SELECT ... where id=@id ... (see above) 28 | function MySQLWrapper:Fetch(query, params, callback) 29 | MySQL.Async.fetchAll(query, params, function(data) 30 | callback(data) 31 | end) 32 | end 33 | 34 | SQL = MySQLWrapper() -------------------------------------------------------------------------------- /server/network/sNetwork.lua: -------------------------------------------------------------------------------- 1 | local NetworkEvent = class() 2 | 3 | local NetworkEvent_id = 0 4 | function NetworkEvent:__init(name, instance, callback) 5 | self.name = name 6 | self.instance = instance 7 | self.callback = callback 8 | self.id = NetworkEvent_id 9 | NetworkEvent_id = NetworkEvent_id + 1 10 | end 11 | 12 | function NetworkEvent:Unsubscribe() 13 | Network:Unsubscribe(self.name, self.id) 14 | end 15 | 16 | function NetworkEvent:Receive(source, args, name) 17 | -- source is the player who sent it 18 | local player = sPlayers:GetById(source) 19 | local return_args = {source = source, player = player} 20 | local is_fetch = false 21 | local fetch_id 22 | 23 | if args then 24 | is_fetch = args.__is_fetch 25 | fetch_id = args.__fetch_id 26 | args.__is_fetch = nil 27 | args.__fetch_id = nil 28 | for k, v in pairs(args) do 29 | return_args[k] = v 30 | end 31 | end 32 | 33 | local return_value 34 | if self.callback then 35 | return_value = self.callback(self.instance, return_args) 36 | else 37 | local callback = self.instance 38 | return_value = callback(return_args) 39 | end 40 | 41 | if is_fetch then 42 | assert(return_value and type(return_value) == "table", "A Network:Fetch handler function must return a table!") 43 | Network:Send(name .. "__FetchCallback" .. tostring(fetch_id), player, return_value) 44 | end 45 | end 46 | 47 | Network = class() 48 | 49 | function Network:__init() 50 | self.subs = {} 51 | self.handlers = {} 52 | self.current_fetch_id = 1 53 | self.fetch_data = {} 54 | end 55 | 56 | function Network:Send(name, players, args) 57 | assert(name ~= nil and type(name) == "string", "cannot Network:Send without valid name (check your args!)") 58 | 59 | assert(type(players) == "number" or type(players) == "table" or is_class_instance(players, Player), 60 | string.format("cannot Network:Send without valid player id(s). Specify -1 for all, one id, or a table. Given: %s", 61 | tostring(type(players)))) 62 | 63 | if type(players) == "number" then 64 | TriggerClientEvent(name, players, args) 65 | elseif is_class_instance(players, Player) then 66 | TriggerClientEvent(name, players:GetId(), args) 67 | elseif type(players) == "table" then 68 | for _, player in pairs(players) do 69 | if type(player) == "number" then 70 | TriggerClientEvent(name, player, args) 71 | elseif is_class_instance(player, Player) then 72 | TriggerClientEvent(name, player:GetId(), args) 73 | end 74 | end 75 | end 76 | end 77 | 78 | --[[ 79 | Blocks the current Thread until a response is received from the client 80 | ]] 81 | function Network:Fetch(name, players, args) 82 | self.current_fetch_id = self.current_fetch_id + 1 83 | local fetch_id = self.current_fetch_id 84 | local fetch_args = args 85 | if not args then 86 | fetch_args = {} 87 | end 88 | fetch_args.__is_fetch = true 89 | fetch_args.__fetch_id = fetch_id 90 | Network:Send(name, players, fetch_args) 91 | fetch_args.__is_fetch = nil 92 | fetch_args.__fetch_id = nil 93 | 94 | local subscription = Network:Subscribe(name .. "__FetchCallback" .. tostring(fetch_id), function(args) 95 | self.fetch_data[fetch_id] = args 96 | end) 97 | 98 | local response_timer = Timer() 99 | local fetched_data 100 | while not self.fetch_data[fetch_id] do 101 | Wait(10) 102 | if response_timer:GetSeconds() > 4 then 103 | print("Network:Fetch timed out! No response received from the client within 4 seconds. Did you forget to add a Network:Subscribe for this Fetch in the client-side code?") 104 | break 105 | end 106 | end 107 | fetched_data = self.fetch_data[fetch_id] 108 | self.fetch_data[fetch_id] = nil 109 | subscription:Unsubscribe() 110 | 111 | return fetched_data 112 | end 113 | 114 | function Network:Broadcast(name, args) 115 | self:Send(name, -1, args) 116 | end 117 | 118 | --[[ 119 | Subscribe to a network event. 120 | 121 | Example usage: 122 | Network:Subscribe("PlayerLoaded", function(args) 123 | end) 124 | ]] 125 | function Network:Subscribe(name, instance, callback) 126 | assert(name ~= nil, "cannot subscribe networkevent without name") 127 | assert(type(instance) == "table" or type(instance) == "function", "callback function non-existant or no callback instance provided. Function usage is Network:Subscribe(name, instance, callback) or Network:Subscribe(name, callback)") 128 | 129 | if not self.subs[name] then 130 | self.subs[name] = {} 131 | RegisterNetEvent(name) 132 | self.handlers[name] = AddEventHandler(name, function(args) 133 | for _, networkevent in pairs(self.subs[name]) do 134 | networkevent:Receive(source, args, name) 135 | end 136 | end) 137 | end 138 | 139 | local networkevent = NetworkEvent(name, instance, callback) 140 | self.subs[name][networkevent.id] = networkevent 141 | 142 | return networkevent 143 | end 144 | 145 | function Network:Unsubscribe(name, id) 146 | assert(name ~= nil, "cannot unsubscribe networkevent without name") 147 | 148 | assert(self.subs[name] ~= nil and self.subs[name][id] ~= nil, "cannot unsubscribe NetworkEvent that does not exist") 149 | self.subs[name][id] = nil 150 | 151 | if count_table(self.subs[name]) == 0 then 152 | RemoveEventHandler(self.handlers[name]) 153 | self.subs[name] = nil 154 | self.handlers[name] = nil 155 | end 156 | end 157 | 158 | Network = Network() -------------------------------------------------------------------------------- /server/ped/sPed.lua: -------------------------------------------------------------------------------- 1 | Ped = class(Entity) 2 | 3 | function Ped:__init(ped_id) 4 | self.ped_id = ped_id 5 | self:InitializeEntity(self.ped_id) 6 | end 7 | 8 | function Ped:GetPedId() 9 | return self.ped_id 10 | end 11 | 12 | -------------------------------------------------------------------------------- /server/player/sPlayer.lua: -------------------------------------------------------------------------------- 1 | -- a Player instance represents a single player in the game 2 | Player = class(ValueStorage) 3 | 4 | function Player:__init(player_id, ids) 5 | self.player_id = tonumber(player_id) 6 | self.ids = ids -- steam, discord, license, live (xbox live), fivem, ip 7 | self.name = GetPlayerName(player_id) 8 | self.ep = GetPlayerEP(player_id) 9 | self.__is_player_instance = true 10 | 11 | math.randomseed(self:GetUniqueId()) 12 | self.color = Color:FromHSV( 13 | math.random(), 14 | 0.7 + (math.random() * 0.3), 15 | 0.8 + (math.random() * 0.2) 16 | ) 17 | 18 | -- Reset randomseed 19 | math.randomseed(GetGameTimer()) 20 | 21 | self:InitializeValueStorage(self) 22 | self:SetValueStorageNetworkId(self.player_id) 23 | end 24 | 25 | function Player:IsValid() 26 | return GetPlayerEP(self.player_id) ~= nil 27 | end 28 | 29 | function Player:GetInvincible() 30 | return GetPlayerInvincible(self.player_id) 31 | end 32 | 33 | function Player:GetRoutingBucket() 34 | return GetPlayerRoutingBucket(self.player_id) 35 | end 36 | 37 | function Player:SetRoutingBucket(bucket) 38 | return SetPlayerRoutingBucket(self.player_id, bucket) 39 | end 40 | 41 | function Player:GetPed() 42 | return Ped(GetPlayerPed(self.player_id)) 43 | end 44 | 45 | function Player:GetPosition() 46 | return self:GetPed():GetPosition() 47 | end 48 | 49 | function Player:GetNumTokens() 50 | return GetNumPlayerTokens(self.player_id) 51 | end 52 | 53 | function Player:GetToken(index) 54 | return GetPlayerToken(self.player_id, index) 55 | end 56 | 57 | function Player:Kick(reason) 58 | DropPlayer(self.player_id, reason or "You have been kicked from the server") 59 | end 60 | 61 | --[[ 62 | Returns player endpoint 63 | ]] 64 | function Player:GetEP() 65 | return self.ep 66 | end 67 | 68 | function Player:GetIP() 69 | return self.ids.ip 70 | end 71 | 72 | function Player:GetXBoxLiveId() 73 | return self.ids.live 74 | end 75 | 76 | function Player:GetDiscordId() 77 | return self.ids.discord 78 | end 79 | 80 | function Player:GetLicense() 81 | return self.ids.license 82 | end 83 | 84 | function Player:GetName() 85 | return self.name 86 | end 87 | 88 | function Player:GetId() 89 | return self.player_id 90 | end 91 | 92 | function Player:GetUniqueId() 93 | return self:GetSteamId() 94 | end 95 | 96 | function Player:GetSteamId() 97 | return self.ids.steam 98 | end 99 | 100 | function Player:GetColor() 101 | return self.color 102 | end 103 | 104 | function Player:Disconnected() 105 | -- handle disconnect stuff here 106 | -- do not delete data yet as this instance is being passed with Events:Fire("PlayerQuit") 107 | end 108 | 109 | function Player:StoreValue(args) 110 | assert(type(args) == "table", "Player:StoreValue requires a table of arguments") 111 | assert(type(args.key) == "string", "Player:StoreValue 'key' argument must be a string") 112 | assert(not (args.synchronous and args.callback), "Player:StoreValue does not accept a 'callback' argument if the 'synchronous' argument is true") 113 | 114 | if args.synchronous then 115 | return KeyValueStore:Set({key = "Player_" .. tostring(self:GetUniqueId()) .. args.key, value = args.value, synchronous = true}) 116 | else 117 | KeyValueStore:Set({key = "Player_" .. tostring(self:GetUniqueId()) .. args.key, value = args.value, callback = args.callback}) 118 | end 119 | end 120 | 121 | function Player:GetStoredValue(args) 122 | assert(type(args) == "table", "Player:GetStoredValue requires a table of arguments") 123 | assert((type(args.key) == "string" or type(args.keys) == "table") and not (args.key and args.keys), "Player:GetStoredValue requires either a 'key' parameter of type string, or a 'keys' parameter of type table") 124 | assert(args.synchronous or args.callback, "Player:GetStoredValue requires a 'callback' parameter of type function when the 'synchronous' parameter is nil or false") 125 | local kvs_args = {} 126 | 127 | if args.key then 128 | kvs_args.key = "Player_" .. tostring(self:GetUniqueId()) .. args.key 129 | end 130 | 131 | if args.keys then 132 | kvs_args.keys = {} 133 | for _, key in ipairs(args.keys) do 134 | table.insert(kvs_args.keys, "Player_" .. tostring(self:GetUniqueId()) .. key) 135 | end 136 | end 137 | 138 | if args.synchronous then 139 | kvs_args.synchronous = true 140 | 141 | return KeyValueStore:Get(kvs_args) 142 | else 143 | kvs_args.callback = function(value) 144 | args.callback(value) 145 | end 146 | 147 | KeyValueStore:Get(kvs_args) 148 | end 149 | end 150 | 151 | function Player:tostring() 152 | return "Player (" .. self.name .. ")" 153 | end -------------------------------------------------------------------------------- /server/player/sPlayerManager.lua: -------------------------------------------------------------------------------- 1 | PlayerManager = class() 2 | 3 | function PlayerManager:__init() 4 | -- If whitelist is enabled or not 5 | self.whitelist_enabled = GetConvarInt("whitelist_enabled", 0) == 1 6 | print("Whitelist enabled: " .. tostring(self.whitelist_enabled)) 7 | 8 | self.max_players = GetConvarInt('sv_maxclients', 32) 9 | self.queue = {} -- Queue of players who are waiting to get into the server 10 | self.num_players_connected = #GetPlayers() 11 | 12 | self:ListenForLocalEvents() 13 | self:ListenForNetworkEvents() 14 | end 15 | 16 | function PlayerManager:ListenForLocalEvents() 17 | -- when a player disconnects 18 | Events:Subscribe("playerDropped", function(reason) 19 | self:PlayerDisconnect(source, reason) 20 | end) 21 | 22 | -- handle their connection request 23 | Events:Subscribe("playerConnecting", function(name, setKickReason, deferrals) 24 | self:PlayerConnect(source, name, setKickReason, deferrals) 25 | end) 26 | 27 | end 28 | 29 | function PlayerManager:ListenForNetworkEvents() 30 | Network:Subscribe("__RequestApiPlayerData", self, self.PlayerInitialDataRequest) 31 | Network:Subscribe("__ClientReady", self, self.ClientReady) 32 | end 33 | 34 | -- this comes from cPlayers:__loadFirst() 35 | function PlayerManager:PlayerInitialDataRequest(args) 36 | sPlayers:AddPlayer(args.source) 37 | local player = sPlayers:GetById(args.source) 38 | self:SyncConnectedPlayers(player) -- send the updated players list to this client 39 | self:SyncNewPlayer(player) 40 | end 41 | 42 | function PlayerManager:ClientReady(args) 43 | -- fired after a client has executed all the inits & postloads from all their classes 44 | Events:Fire("ClientModulesLoaded", {player = args.player}) 45 | end 46 | 47 | function PlayerManager:PlayerDisconnect(source, reason) 48 | self.num_players_connected = self.num_players_connected - 1 49 | local player = sPlayers:GetById(source) 50 | if not player then -- can be caused by connect (not fully), and then disconnect 51 | return 52 | end 53 | local player_unique_id = player:GetUniqueId() 54 | 55 | print(string.format("%s%s (%i | %s | %s) left the server.%s", 56 | Colors.Console.Yellow, player:GetName(), player:GetId(), player:GetUniqueId(), tostring(player:GetIP()), Colors.Console.Default)) 57 | 58 | player:Disconnected() 59 | 60 | sPlayers:RemovePlayer(player_unique_id) 61 | 62 | Events:Fire("PlayerQuit", {player = player, reason = reason}) 63 | Network:Send("PlayerRemoved", -1, {player_unique_id = player_unique_id}) 64 | end 65 | 66 | --[[ 67 | Returns the number of players who are either fully connected to the server or 68 | are connecting past the queue. 69 | ]] 70 | function PlayerManager:GetNumPlayersConnected() 71 | return self.num_players_connected 72 | end 73 | 74 | function PlayerManager:PlayerConnect(source, name, setKickReason, deferrals) 75 | 76 | -- First, check steam id 77 | local ids = sPlayers:GetPlayerIdentifiers(source) 78 | deferrals.defer() 79 | 80 | deferrals.update(OOF_Config.Deferrals.CheckingSteamId) 81 | 82 | if not ids.steam then 83 | deferrals.done(OOF_Config.Deferrals.NotConnectedToSteam) 84 | CancelEvent() 85 | return 86 | else 87 | if not self.whitelist_enabled or IsPlayerAceAllowed(source, "whitelist") then 88 | deferrals.update(OOF_Config.Deferrals.Connecting) 89 | else 90 | deferrals.done(OOF_Config.Deferrals.NotWhitelisted) 91 | CancelEvent() 92 | return 93 | end 94 | end 95 | 96 | local name = GetPlayerName(source) 97 | 98 | if not name then 99 | deferrals.done() 100 | CancelEvent() 101 | return 102 | end 103 | 104 | -- Check if someone with the same steam id is already on the server 105 | local name_lower = string.lower(name) 106 | for id, player in pairs(sPlayers:GetPlayers()) do 107 | if player:GetSteamId() == ids.steam then 108 | deferrals.done(string.format(OOF_Config.Deferrals.DuplicateClient)) 109 | CancelEvent() 110 | return 111 | end 112 | end 113 | 114 | Citizen.CreateThread(function() 115 | -- Check if the server is full or if there's a queue 116 | if self:GetNumPlayersConnected() >= self.max_players or #self.queue > 0 then 117 | table.insert(self.queue, source) 118 | local time_elapsed = 0 119 | 120 | -- While the server is full or they aren't front of the queue, keep them in the queue 121 | while self:GetNumPlayersConnected() >= self.max_players or self:GetPositionInQueue(source) > 1 do 122 | 123 | -- Stopped connecting to the queue 124 | if not GetPlayerEndpoint(source) then 125 | table.remove(self.queue, self:GetPositionInQueue(source)) 126 | deferrals.done(OOF_Config.Deferrals.ConnectionCancelled) 127 | CancelEvent() 128 | return 129 | end 130 | 131 | deferrals.update(string.format(OOF_Config.Deferrals.WaitingInQueue, 132 | name, self:GetLoadingIcons(time_elapsed), tostring(self:GetPositionInQueue(source)))) 133 | 134 | time_elapsed = time_elapsed + 1 135 | Wait(250) 136 | end 137 | 138 | table.remove(self.queue, 1) 139 | end 140 | 141 | 142 | deferrals.update(OOF_Config.Deferrals.Connected) 143 | deferrals.done() 144 | self.num_players_connected = self.num_players_connected + 1 145 | end) 146 | 147 | end 148 | 149 | -- Makes a fun little loading animation for those in the queue 150 | function PlayerManager:GetLoadingIcons(time) 151 | local str = "" 152 | for i = 1, time % 6 do 153 | str = str .. OOF_Config.Deferrals.LoadingIcon 154 | end 155 | return str 156 | end 157 | 158 | function PlayerManager:GetPositionInQueue(source) 159 | for pos, src in ipairs(self.queue) do 160 | if src == source then return pos end 161 | end 162 | return '???' 163 | end 164 | 165 | function PlayerManager:SyncNewPlayer(player) 166 | local sync_data = sPlayers:GetPlayerSyncData(player) 167 | 168 | Network:Broadcast("__SyncConnectedPlayer", sync_data) 169 | end 170 | 171 | function PlayerManager:SyncConnectedPlayers(player) 172 | local sync_data = sPlayers:GetAllSyncData() 173 | 174 | Network:Send("__SyncConnectedPlayers", player, sync_data) 175 | end 176 | 177 | PlayerManager = PlayerManager() -------------------------------------------------------------------------------- /server/player/sPlayers.lua: -------------------------------------------------------------------------------- 1 | sPlayers = class() 2 | 3 | function sPlayers:__init() 4 | self.players_by_unique_id = {} 5 | 6 | for _, player_id in pairs(GetPlayers()) do 7 | self:AddPlayer(player_id, true) 8 | end 9 | end 10 | 11 | -- !! this function must be __loadFirst compatible. ask Dev_34 if confused 12 | function sPlayers:AddPlayer(source, already_connected) 13 | if self:GetById(source) then return end 14 | 15 | local ids = self:GetPlayerIdentifiers(source) 16 | 17 | -- Getting license failed 18 | if not ids.license then 19 | DropPlayer(source, "Could not retrieve license id") 20 | return 21 | end 22 | 23 | local player = Player(tonumber(source), ids) -- Player is an immediate_class 24 | local player_unique_id = player:GetUniqueId() 25 | 26 | if self.players_by_unique_id[player_unique_id] then 27 | DropPlayer(source, "A player with your unique id already exists on the server") 28 | return 29 | end 30 | 31 | self.players_by_unique_id[player_unique_id] = player 32 | 33 | if not already_connected then 34 | print(string.format("%s%s (%i | %s | %s) joined the server.%s", 35 | Colors.Console.Yellow, player:GetName(), player:GetId(), player:GetUniqueId(), player:GetIP(), Colors.Console.Default)) 36 | Events:Fire("PlayerJoined", {player = player}) 37 | end 38 | end 39 | 40 | function sPlayers:RemovePlayer(player_unique_id) 41 | assert(self.players_by_unique_id[player_unique_id] ~= nil, "sPlayers:RemovePlayer tried to remove player that wasn't stored") 42 | 43 | self.players_by_unique_id[player_unique_id] = nil 44 | end 45 | 46 | function sPlayers:GetPlayerSyncData(player) 47 | return { 48 | source_id = player:GetId(), 49 | steam_id = player:GetSteamId(), 50 | unique_id = player:GetUniqueId(), 51 | name = player:GetName(), 52 | network_values = player:GetNetworkValues() 53 | } 54 | end 55 | 56 | -- return the sync data for every player we have stored 57 | function sPlayers:GetAllSyncData() 58 | local sync_data = {} 59 | 60 | for unique_id, player in pairs(self.players_by_unique_id) do 61 | sync_data[unique_id] = self:GetPlayerSyncData(player) 62 | end 63 | 64 | return sync_data 65 | end 66 | 67 | -- !! must be __loadFirst compatible. ask Dev_34 if confused 68 | function sPlayers:GetPlayerIdentifiers(source) 69 | local ids = 70 | { 71 | steam = "", 72 | license = "", 73 | live = "", 74 | discord = "", 75 | fivem = "", 76 | ip = "" 77 | } 78 | local identifiers, steamIdentifier = GetPlayerIdentifiers(source) 79 | 80 | for _, v in pairs(identifiers) do 81 | if string.find(v, "steam") then 82 | ids.steam = v:gsub("steam:", "") 83 | end 84 | if string.find(v, "license") then 85 | ids.license = v:gsub("license:", ""):gsub("license2:", "") 86 | end 87 | if string.find(v, "live") then 88 | ids.live = v:gsub("live:", "") 89 | end 90 | if string.find(v, "discord") then 91 | ids.discord = v:gsub("discord:", "") 92 | end 93 | if string.find(v, "fivem") then 94 | ids.fivem = v:gsub("fivem:", "") 95 | end 96 | if string.find(v, "ip") then 97 | ids.ip = v:gsub("ip:", "") 98 | end 99 | end 100 | 101 | return ids 102 | end 103 | 104 | function sPlayers:GetPlayers() 105 | return self.players_by_unique_id 106 | end 107 | 108 | function sPlayers:GetNumPlayers() 109 | return count_table(self.players_by_unique_id) 110 | end 111 | 112 | -- "source" id: number 113 | function sPlayers:GetById(id) 114 | id = tonumber(id) 115 | for player_unique_id, player in pairs(self.players_by_unique_id) do 116 | if player:GetId() == id then 117 | return player 118 | end 119 | end 120 | end 121 | 122 | function sPlayers:GetByUniqueId(unique_id) 123 | return self.players_by_unique_id[unique_id] 124 | end 125 | 126 | function sPlayers:GetBySteamId(steam_id) 127 | for player_unique_id, player in pairs(self.players_by_unique_id) do 128 | if player:GetSteamId() == steam_id then 129 | return player 130 | end 131 | end 132 | end 133 | 134 | 135 | sPlayers = sPlayers() -------------------------------------------------------------------------------- /server/sConfig.lua: -------------------------------------------------------------------------------- 1 | OOF_Config = 2 | { 3 | Deferrals = 4 | { 5 | CheckingSteamId = "Checking Steam ID...", 6 | NotConnectedToSteam = "You are not connected to steam.", 7 | Connecting = "Connecting to awesomeness...", 8 | NotWhitelisted = "You are not whitelisted.", 9 | DuplicateClient = "You are already connected to the server somewhere else. Please disconnect and try again.", 10 | ConnectionCancelled = "Connection cancelled.", 11 | WaitingInQueue = "Hey there, %s. Sorry to inform you, but the server is full! Hang on for a few moments while we get you connected.\n%s\n" .. 12 | "Your position in queue: %s", 13 | Connected = "Connected! Welcome!", 14 | LoadingIcon = "🔥" 15 | } 16 | } -------------------------------------------------------------------------------- /server/world/sWorld.lua: -------------------------------------------------------------------------------- 1 | World = class() 2 | 3 | function World:__init() 4 | self.timestep_enabled = false 5 | self.time = {hours = 12, minutes = 0, seconds = 0} 6 | getter_setter(self, "weather") 7 | self:SetWorldWeather("SUNNY") 8 | self.timescale = 1 9 | self.blackout = false 10 | 11 | Events:Subscribe("PlayerJoined", self, self.PlayerJoined) 12 | end 13 | 14 | --[[ 15 | Keep track of time here so we can sync accurate time to new clients 16 | ]] 17 | function World:TimeStep() 18 | 19 | Citizen.CreateThread(function() 20 | 21 | while self.timestep_enabled do 22 | Wait(1000) 23 | self.time.seconds = self.time.seconds + 30 24 | if self.time.seconds == 60 then 25 | self.time.seconds = 0 26 | self.time.minutes = self.time.minutes + 1 27 | end 28 | 29 | if self.time.minutes == 60 then 30 | self.time.minutes = 0 31 | self.time.hours = self.time.hours + 1 32 | end 33 | 34 | if self.time.hours == 24 then 35 | self.time.hours = 0 36 | end 37 | 38 | end 39 | 40 | end) 41 | 42 | end 43 | 44 | function World:PlayerJoined(args) 45 | 46 | Network:Send("__World/InitialSync", args.player:GetId(), { 47 | timestep_enabled = self:GetTimestepEnabled(), 48 | time = self:GetTime(), 49 | blackout = self:GetBlackout(), 50 | weather = self:GetWeather() 51 | }) 52 | end 53 | 54 | function World:SetTime(hours, minutes, seconds) 55 | --print("Setting time to {", hours, "}, {", minutes, "}, {", seconds, "}") 56 | self.time = {hours = hours, minutes = minutes, seconds = seconds} 57 | Network:Broadcast("__World/SetTime", {time = self.time}) 58 | end 59 | 60 | function World:SetTimestepEnabled(enabled) 61 | self.timestep_enabled = enabled 62 | self:TimeStep() 63 | Network:Send("__World/SetTimestepEnabled", -1, {enabled = self:GetTimestepEnabled()}) 64 | end 65 | 66 | function World:SetTimeScale(scale) 67 | self.timescale = scale 68 | Network:Send("__World/SetTimeScale", -1, {scale = self:GetTimeScale()}) 69 | end 70 | 71 | function World:SetBlackout(blackout) 72 | self.blackout = blackout 73 | Network:Send("__World/SetBlackout", -1, {blackout = self:GetBlackout()}) 74 | end 75 | 76 | function World:SetWorldWeather(weather) 77 | self:SetWeather(weather) 78 | Network:Broadcast("__World/SetWeather", {weather = self:GetWeather()}) 79 | end 80 | 81 | function World:GetTimestepEnabled() 82 | return self.timestep_enabled 83 | end 84 | 85 | function World:GetTimeScale() 86 | return self.timescale 87 | end 88 | 89 | function World:GetTime() 90 | return self.time 91 | end 92 | 93 | function World:GetBlackout() 94 | return self.blackout 95 | end 96 | 97 | World = World() -------------------------------------------------------------------------------- /shared/csv/CSV.lua: -------------------------------------------------------------------------------- 1 | CSV = class() 2 | 3 | --[[ 4 | Parses a CSV file 5 | 6 | args: 7 | filename (string): name of the file, including the path starting after resource name 8 | wait_after (number, optional): after X number of lines, this will call Wait(1). Call CSV:Parse in a thread to use this 9 | progress_callback (function, optional): will callback with its lines processed and total lines as it processes. Good to use with wait_after 10 | 11 | 12 | Returns a list of entries, with each entry containing fields for each column 13 | ]] 14 | function CSV:Parse(filename, wait_after, progress_callback) 15 | local data = LoadResourceFile(GetCurrentResourceName(), filename) 16 | 17 | local insert, remove = table.insert, table.remove 18 | 19 | if data then 20 | 21 | local parsed = {} 22 | local lines = split(data, "\n") 23 | local headers = {} 24 | local done = 0 25 | local count = count_table(lines) - 1 26 | 27 | for line_index, line in pairs(lines) do 28 | local line_split = split(line, ",") 29 | local line_data = {} 30 | 31 | for data_index, data_string in pairs(line_split) do 32 | data_string = trim(data_string) 33 | 34 | if line_index == 1 then 35 | headers[data_index] = data_string 36 | else 37 | line_data[headers[data_index]] = data_string 38 | end 39 | end 40 | 41 | if line_index > 1 then 42 | done = done + 1 43 | insert(parsed, line_data) 44 | end 45 | 46 | if progress_callback then 47 | progress_callback(done, count) 48 | end 49 | end 50 | 51 | return parsed 52 | 53 | else 54 | error(string.format("File %s not found in CSV parse", tostring(filename))) 55 | end 56 | 57 | end 58 | 59 | CSV = CSV() -------------------------------------------------------------------------------- /shared/enums/shEntityTypeEnum.lua: -------------------------------------------------------------------------------- 1 | EntityTypeEnum = class(Enum) 2 | 3 | function EntityTypeEnum:__init() 4 | self:EnumInit() 5 | 6 | self.None = 0 7 | self:SetDescription(self.None, "None") 8 | 9 | self.Ped = 1 10 | self:SetDescription(self.Ped, "Ped") 11 | 12 | self.Vehicle = 2 13 | self:SetDescription(self.Vehicle, "Vehicle") 14 | 15 | self.Object = 3 16 | self:SetDescription(self.Object, "Object") 17 | end 18 | 19 | EntityTypeEnum = EntityTypeEnum() -------------------------------------------------------------------------------- /shared/enums/shPedBoneEnum.lua: -------------------------------------------------------------------------------- 1 | PedBoneEnum = class(Enum) 2 | 3 | function PedBoneEnum:__init() 4 | self:EnumInit() 5 | 6 | -- Enum values 7 | self.RightHandThumbFirstKnuckle = 1 8 | self.RightHandIndexFingerSecondKnuckle = 2 9 | self.RightHandIndexFingerThirdKnuckle = 3 10 | self.RightHandMiddleFingerLastKnuckle = 4 11 | self.Spine1 = 5 12 | self.Spine2 = 6 13 | self.Spine3 = 7 14 | self.Head = 8 15 | self.RightElbow = 9 16 | self.RightHand = 10 17 | 18 | 19 | 20 | self.Test = 99 21 | 22 | 23 | self.bone_map = { 24 | [self.RightHandThumbFirstKnuckle] = "BONETAG_R_FINGER01", 25 | [self.RightHandIndexFingerSecondKnuckle] = "BONETAG_R_FINGER11", 26 | [self.RightHandIndexFingerThirdKnuckle] = "BONETAG_R_FINGER12", 27 | [self.RightHandMiddleFingerLastKnuckle] = "BONETAG_R_FINGER22", 28 | [self.Spine1] = "BONETAG_SPINE1", 29 | [self.Spine2] = "BONETAG_SPINE2", 30 | [self.Spine3] = "BONETAG_SPINE3", 31 | [self.Head] = "BONETAG_HEAD", 32 | [self.RightElbow] = "BONETAG_R_FOREARM", 33 | [self.RightHand] = "BONETAG_R_HAND" 34 | } 35 | 36 | self.reverse_bone_map = {} 37 | for k, v in pairs(self.bone_map) do 38 | self.reverse_bone_map[v] = k 39 | end 40 | end 41 | 42 | function PedBoneEnum:MapToBoneName(enum_value) 43 | return self.bone_map[enum_value] 44 | end 45 | 46 | function PedBoneEnum:FromBone(bone) 47 | return self.reverse_bone_map[bone] 48 | end 49 | 50 | PedBoneEnum = PedBoneEnum() -------------------------------------------------------------------------------- /shared/enums/shVehicleEnum.lua: -------------------------------------------------------------------------------- 1 | VehicleEnum = class(Enum) 2 | 3 | function VehicleEnum:__init() 4 | self:EnumInit() 5 | 6 | --[[ 7 | CART01 8 | CART02 9 | CART03 10 | CART04 11 | CART05 12 | CART06 13 | CART07 14 | CART08 15 | ARMYSUPPLYWAGON 16 | BUGGY01 17 | BUGGY02 18 | BUGGY03 19 | CHUCKWAGON000X 20 | CHUCKWAGON002X 21 | COACH2 22 | COACH3 23 | COACH4 24 | COACH5 25 | COACH6 26 | coal_wagon 27 | OILWAGON01X 28 | POLICEWAGON01X Police Patrol Wagon 29 | WAGON02X 30 | WAGON04X 31 | LOGWAGON 32 | WAGON03X 33 | WAGON05X 34 | WAGON06X 35 | WAGONPRISON01X 36 | STAGECOACH001X 37 | STAGECOACH002X 38 | STAGECOACH003X 39 | STAGECOACH004X 40 | STAGECOACH005X 41 | STAGECOACH006X 42 | UTILLIWAG 43 | GATCHUCK 44 | GATCHUCK_2 45 | wagonCircus01x 46 | wagonDairy01x 47 | wagonWork01x 48 | wagonTraveller01x 49 | supplywagon 50 | CABOOSE01X 51 | northpassenger01x 52 | NORTHSTEAMER01X 53 | HANDCART 54 | KEELBOAT 55 | CANOE 56 | CANOETREETRUNK 57 | PIROGUE 58 | RCBOAT 59 | rowboat 60 | ROWBOATSWAMP 61 | SKIFF 62 | SHIP_GUAMA02 63 | SHIP_NBDGUAMA 64 | horseBoat 65 | BREACH_CANNON Breaching Cannon 66 | GATLING_GUN Gattling Gun 67 | GATLINGMAXIM02 68 | SMUGGLER02 69 | turbineboat 70 | HOTAIRBALLOON01 71 | hotchkiss_cannon 72 | wagonCircus02x 73 | wagonDoc01x 74 | PIROGUE2 75 | PRIVATECOALCAR01X 76 | PRIVATESTEAMER01X 77 | PRIVATEDINING01X 78 | ROWBOATSWAMP02 79 | midlandboxcar05x 80 | coach3_cutscene 81 | privateflatcar01x 82 | privateboxcar04x 83 | privatebaggage01X 84 | privatepassenger01x 85 | trolley01x 86 | northflatcar01x 87 | supplywagon2 88 | northcoalcar01x 89 | northpassenger03x 90 | privateboxcar02x 91 | armoredcar03x 92 | privateopensleeper02x 93 | WINTERSTEAMER 94 | wintercoalcar 95 | privateboxcar01x 96 | privateobservationcar 97 | privatearmoured 98 | ]] 99 | 100 | self.HorseBoat = 1 101 | self:SetDescription(self.HorseBoat, "Horse Boat") 102 | 103 | self.hash_vehicle_map = { 104 | [GetHashKey("horseBoat")] = self.HorseBoat, 105 | } 106 | 107 | self.vehicle_hash_map = {} 108 | for k, v in pairs(self.hash_vehicle_map) do 109 | self.vehicle_hash_map[v] = k 110 | end 111 | 112 | end 113 | 114 | function VehicleEnum:GetFromVehicleHash(vehicle_hash) 115 | return self.hash_vehicle_map[vehicle_hash] 116 | end 117 | 118 | function VehicleEnum:GetVehicleHash(vehicle_enum) 119 | return self.vehicle_hash_map[vehicle_enum] 120 | end 121 | 122 | function VehicleEnum:GetRandomVehicleEnum() 123 | return random_table_value(self.hash_vehicle_map) 124 | end 125 | 126 | VehicleEnum = VehicleEnum() -------------------------------------------------------------------------------- /shared/enums/shWeaponTypeEnum.lua: -------------------------------------------------------------------------------- 1 | WeaponTypeEnum = class(Enum) 2 | 3 | function WeaponTypeEnum:__init() 4 | self:EnumInit() 5 | 6 | 7 | self.Pistol = 1 8 | self:SetDescription(self.Pistol, "Pistol") 9 | 10 | self.Repeater = 2 11 | self:SetDescription(self.Repeater, "Repeater") 12 | 13 | self.Shotgun = 3 14 | self:SetDescription(self.Shotgun, "Shotgun") 15 | 16 | self.Rifle = 4 17 | self:SetDescription(self.Rifle, "Rifle") 18 | 19 | self.Sniper = 5 20 | self:SetDescription(self.Sniper, "Sniper") 21 | 22 | self.Melee = 6 23 | self:SetDescription(self.Melee, "Melee") 24 | end 25 | 26 | WeaponTypeEnum = WeaponTypeEnum() -------------------------------------------------------------------------------- /shared/events/shEvent.lua: -------------------------------------------------------------------------------- 1 | Event = class() 2 | 3 | local event_id = 0 4 | function Event:__init(name, instance, callback) 5 | self.name = name 6 | self.instance = instance 7 | self.callback = callback 8 | self.id = event_id 9 | event_id = event_id + 1 10 | 11 | self.default_event = AddEventHandler(name, function(...) 12 | self:Fire(...) 13 | end) 14 | end 15 | 16 | function Event:Unsubscribe() 17 | Events:Unsubscribe(self.name, self.id) 18 | RemoveEventHandler(self.default_event) 19 | end 20 | 21 | function Event:Fire(...) 22 | if self.callback then 23 | return self.callback(self.instance, ...) 24 | else 25 | local callback = self.instance 26 | return callback(...) 27 | end 28 | end -------------------------------------------------------------------------------- /shared/events/shEvents.lua: -------------------------------------------------------------------------------- 1 | Events = class() 2 | 3 | function Events:__init() 4 | self.subs = {} 5 | end 6 | 7 | function Events:Fire(event, ...) 8 | assert(event ~= nil and type(event) == "string", "cannot fire event without valid event") 9 | -- Nothing subscribed 10 | if not self.subs[event] then return {} end 11 | 12 | local return_vals = {} 13 | for id, cb in pairs(self.subs[event]) do 14 | local return_val = cb:Fire(...) 15 | if return_val ~= nil then 16 | table.insert(return_vals, return_val) 17 | end 18 | end 19 | return return_vals 20 | end 21 | 22 | function Events:Subscribe(name, instance, callback) 23 | assert(name ~= nil, "cannot subscribe event without name") 24 | if type(instance) ~= "function" and type(instance) ~= "table" then 25 | error("callback function non-existant or no callback instance provided. Function usage is Events:Subscribe(name, instance, callback) or Events:Subscribe(name, callback)") 26 | end 27 | 28 | if not self.subs[name] then 29 | self.subs[name] = {} 30 | end 31 | 32 | local event = Event(name, instance, callback) 33 | self.subs[name][event.id] = event 34 | 35 | return event 36 | end 37 | 38 | function Events:Unsubscribe(name, id) 39 | assert(self.subs[name] ~= nil and self.subs[name][id] ~= nil, "cannot unsubscribe event that does not exist") 40 | self.subs[name][id] = nil 41 | if count_table(self.subs[name]) == 0 then 42 | self.subs[name] = nil 43 | end 44 | end 45 | 46 | Events = Events() 47 | -------------------------------------------------------------------------------- /shared/game/IsFiveM.lua: -------------------------------------------------------------------------------- 1 | IsFiveM = true 2 | IsRedM = false -------------------------------------------------------------------------------- /shared/game/IsRedM.lua: -------------------------------------------------------------------------------- 1 | IsRedM = true 2 | IsFiveM = false -------------------------------------------------------------------------------- /shared/lua-additions/lua-additions.lua: -------------------------------------------------------------------------------- 1 | -- Pure Lua global utility functions (considered as additions to the vanilla Lua environment) 2 | -- no class instances can be instantiated in this file 3 | 4 | IsServer = IsDuplicityVersion() -- true if the machine this code runs on is the (single) server 5 | IsClient = not IsServer -- true if the machine this code runs on is one of the clients / players connected to the server 6 | 7 | CreateThread = Citizen.CreateThread 8 | 9 | function unpack(t, i) 10 | i = i or 1 11 | if t[i] ~= nil then 12 | return t[i], unpack(t, i + 1) 13 | end 14 | end 15 | 16 | -- counts an associative Lua table (use #table for sequential tables) 17 | function count_table(table) 18 | local count = 0 19 | 20 | for k, v in pairs(table) do 21 | count = count + 1 22 | end 23 | 24 | return count 25 | end 26 | 27 | function tofloat(num) 28 | return num / 1.0 29 | end 30 | 31 | -- general linear interpolation function 32 | -- parameter t should be between 0.0 and 1.0 33 | -- (0.0 is a and 1.0 is b) 34 | function lerp(a, b, t) 35 | return a * (1 - t) + (b * t) 36 | end 37 | 38 | -- splits a string given a seperator 39 | -- returns a sequential table of tokens, which could be empty 40 | function split(input_str, seperator) 41 | if seperator == nil then 42 | sep = "%s" 43 | end 44 | local t = {} 45 | for str in string.gmatch(input_str, "([^" .. seperator .. "]+)") do 46 | table.insert(t, str) 47 | end 48 | return t 49 | end 50 | 51 | function math.round(num, decimal_places) 52 | return tonumber(string.format("%." .. (decimal_places or 0) .. "f", num)) 53 | end 54 | 55 | function math.clamp(value, lower, upper) 56 | return math.min(math.max(value, lower), upper) 57 | end 58 | 59 | function trim(s) 60 | return s:match'^()%s*$' and '' or s:match'^%s*(.*%S)' 61 | end 62 | 63 | -- Allows string indexing with s[0], from http://lua-users.org/wiki/StringIndexing 64 | getmetatable('').__index = function(str,i) 65 | if type(i) == 'number' then 66 | return string.sub(str,i,i) 67 | else 68 | return string[i] 69 | end 70 | end 71 | 72 | function sequence_contains(t, v) 73 | for _, _v in pairs(t) do 74 | if v == _v then return true end 75 | end 76 | return false 77 | end 78 | 79 | function random_table_value(t) 80 | local keys = {} 81 | for k in pairs(t) do table.insert(keys, k) end 82 | return t[keys[math.random(#keys)]] 83 | end 84 | 85 | function deepcopy(orig) 86 | local orig_type = type(orig) 87 | local copy 88 | if orig_type == 'table' then 89 | copy = {} 90 | for orig_key, orig_value in next, orig, nil do 91 | copy[deepcopy(orig_key)] = deepcopy(orig_value) 92 | end 93 | setmetatable(copy, deepcopy(getmetatable(orig))) 94 | else -- number, string, boolean, etc 95 | copy = orig 96 | end 97 | return copy 98 | end 99 | 100 | -- does not keep same order 101 | function shallow_copy(t) 102 | local new_table = {} 103 | for k, v in pairs(t) do 104 | new_table[k] = v 105 | end 106 | return new_table 107 | end 108 | 109 | function output_table(t) 110 | print("-----", t, "-----") 111 | for key, value in pairs(t) do 112 | if type(value) ~= "table" or (type(value) == "table" and value.__class_instance) then 113 | print("[", key, "]: ", value) 114 | else 115 | print(key .. " {") 116 | for k, v in pairs(value) do 117 | if type(v) ~= "table" or (type(value) == "table" and value.__class_instance) then 118 | print("[", k, "]: ", v) 119 | else 120 | print(k .. " {") 121 | for k2, v2 in pairs(v) do 122 | print("[", k2, "]: ", v2) 123 | end 124 | print("}") 125 | end 126 | end 127 | print("}") 128 | end 129 | end 130 | print("------------------------") 131 | end 132 | 133 | -- omitted keys is a table where key is index and value can be anything except false or 0 134 | function random_weighted_table_value(weighted_table, omitted_keys) 135 | if not omitted_keys then 136 | omitted_keys = {} 137 | end 138 | 139 | local sum = 0 140 | for key, weight in pairs(weighted_table) do 141 | if not omitted_keys[key] then 142 | sum = sum + weight 143 | end 144 | end 145 | 146 | local rand = math.random() * sum 147 | local found 148 | for key, weight in pairs(weighted_table) do 149 | if not omitted_keys[key] then 150 | rand = rand - weight 151 | if rand < 0 then 152 | found = key 153 | break 154 | end 155 | end 156 | end 157 | 158 | return found 159 | end -------------------------------------------------------------------------------- /shared/lua-overloads/assert.lua: -------------------------------------------------------------------------------- 1 | local _assert = assert 2 | function assert(condition, message) 3 | return _assert(condition, debug.traceback(tostring(message))) 4 | end -------------------------------------------------------------------------------- /shared/lua-overloads/error.lua: -------------------------------------------------------------------------------- 1 | local _error = error 2 | function error(...) 3 | return _error(debug.traceback(...)) 4 | end -------------------------------------------------------------------------------- /shared/lua-overloads/lua-overloads.lua: -------------------------------------------------------------------------------- 1 | -- This file is where changes to existing superglobals (either in platform API or Lua itself) can be added via overloading 2 | -- This allows us to change the behavior of these superglobals while keeping our code's vocabulary simple. It also makes refactoring much simpler in some cases. 3 | -- adds print(), tostring(), and type() support for the object-oriented structure 4 | 5 | -- TODO: move IsTest somewhere else, in some sort of config file 6 | IsTest = true 7 | 8 | -------------------------------------------------------------------------------- /shared/lua-overloads/math-randomseed.lua: -------------------------------------------------------------------------------- 1 | local OgMathRandomseed = math.randomseed 2 | 3 | local function string_to_number(str) 4 | local insert = table.insert 5 | if str:len() < 6 then 6 | local numeric_representations = {} 7 | local digits = 0 8 | for c in str:gmatch('.') do 9 | insert(numeric_representations, c:byte()) 10 | end 11 | return tonumber(table.concat(numeric_representations)) 12 | else 13 | local numeric_representation = 0 14 | for c in str:gmatch('.') do 15 | numeric_representation = numeric_representation + c:byte() 16 | end 17 | return numeric_representation 18 | end 19 | end 20 | 21 | function math.randomseed(x) 22 | local number_form = x 23 | if type(x) == 'string' then 24 | number_form = string_to_number(x) 25 | end 26 | 27 | OgMathRandomseed(number_form) 28 | end 29 | -------------------------------------------------------------------------------- /shared/lua-overloads/print.lua: -------------------------------------------------------------------------------- 1 | local OgLuaPrint = print 2 | 3 | -- format time to have a 0 in front if its short 4 | local function f_time(time) 5 | return time < 10 and "0" .. time or time 6 | end 7 | 8 | function print(...) 9 | local args = {...} 10 | local output_str = string.format("%s[%s]%s", 11 | "^5", tostring(GetCurrentResourceName()), "^7") 12 | 13 | if IsServer then 14 | local t = os.date("*t", os.time()) 15 | output_str = output_str .. " [" .. f_time(t.hour) .. ":" .. f_time(t.min) .. ":" .. f_time(t.sec) .. "]: " 16 | elseif IsClient then 17 | local year, month, day, hour, min, sec = GetPosixTime() 18 | output_str = output_str .. " [" .. f_time(hour) .. ":" .. f_time(min) .. ":" .. f_time(sec) .. "]: " 19 | end 20 | 21 | for index, arg in ipairs(args) do 22 | output_str = output_str .. tostring(arg) 23 | end 24 | 25 | OgLuaPrint(output_str) 26 | end 27 | -------------------------------------------------------------------------------- /shared/lua-overloads/tostring.lua: -------------------------------------------------------------------------------- 1 | local OgLuaTostring = tostring 2 | function tostring(variable) 3 | if type(variable) == "table" and variable.__class_instance then 4 | return variable:tostring() 5 | else 6 | return OgLuaTostring(variable) 7 | end 8 | end -------------------------------------------------------------------------------- /shared/math/shVector3Math.lua: -------------------------------------------------------------------------------- 1 | Vector3Math = class() 2 | -- Static Vector3 class for vector3 math 3 | 4 | function Vector3Math:Distance(position_a, position_b) 5 | return #(position_a - position_b) 6 | end 7 | 8 | function Vector3Math:RotationToDirection(rotation) 9 | local adjustedRotation = 10 | { 11 | x = (math.pi / 180) * rotation.x, 12 | y = (math.pi / 180) * rotation.y, 13 | z = (math.pi / 180) * rotation.z 14 | } 15 | local direction = 16 | { 17 | x = -math.sin(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)), 18 | y = math.cos(adjustedRotation.z) * math.abs(math.cos(adjustedRotation.x)), 19 | z = math.sin(adjustedRotation.x) 20 | } 21 | 22 | return vector3(direction.x, direction.y, direction.z) 23 | end 24 | 25 | function Vector3Math:RadToDeg(rad) 26 | return rad * 180 / math.pi 27 | end 28 | 29 | -- Adapted from https://github.com/citizenfx/fivem/blob/master/code/client/clrcore/Math/GameMath.cs 30 | function Vector3Math:RotationToDirection2(dir, roll) 31 | dir = dir / Vector3Math:Length(dir) 32 | local rotval_z = -Vector3Math:RadToDeg(math.atan2(dir.x, dir.y)); 33 | local rotpos = vector3(dir.z, Vector3Math:Length(vector3(dir.x, dir.y, 0.0)), 0.0) 34 | rotpos = rotpos / Vector3Math:Length(rotpos) 35 | local rotval_x = Vector3Math:RadToDeg(math.atan2(rotpos.x, rotpos.y)) 36 | local rotval_y = roll or 0 37 | return vector3(rotval_x, rotval_y, rotval_z) 38 | end 39 | 40 | function Vector3Math:Lerp(a, b, factor) 41 | local diff = b - a 42 | return a + factor * diff 43 | end 44 | 45 | function Vector3Math:Length(a) 46 | return math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z) 47 | end 48 | 49 | function Vector3Math:LerpOverTime(a, b, time, callback) 50 | local timer = Timer() 51 | Citizen.CreateThread(function() 52 | local ms = timer:GetMilliseconds() 53 | while ms < time do 54 | ms = timer:GetMilliseconds() 55 | callback(Vector3Math:Lerp(a, b, ms / time), false) 56 | Wait(1) 57 | end 58 | callback(Vector3Math:Lerp(a, b, ms / time), true) 59 | end) 60 | end 61 | 62 | Vector3Math = Vector3Math() 63 | -------------------------------------------------------------------------------- /shared/math/shXorCipher.lua: -------------------------------------------------------------------------------- 1 | -- xor_cipher() can be used to encrypt lua state variables 2 | -- pure Lua bitwise xor implementation largely based on https://github.com/davidm/lua-bit-numberlua/blob/master/lmod/bit/numberlua.lua 3 | --[[ 4 | lua-bit-numberlua License 5 | 6 | =============================================================================== 7 | 8 | Copyright (C) 2008, David Manura. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in 18 | all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | THE SOFTWARE. 27 | 28 | =============================================================================== 29 | ]] 30 | 31 | local MOD = 2^32 32 | 33 | local function memoize(f) 34 | local mt = {} 35 | local t = setmetatable({}, mt) 36 | function mt:__index(k) 37 | local v = f(k); t[k] = v 38 | return v 39 | end 40 | return t 41 | end 42 | 43 | local function make_bitop_uncached(t, m) 44 | local function bitop(a, b) 45 | local res,p = 0,1 46 | while a ~= 0 and b ~= 0 do 47 | local am, bm = a%m, b%m 48 | res = res + t[am][bm]*p 49 | a = (a - am) / m 50 | b = (b - bm) / m 51 | p = p*m 52 | end 53 | res = res + (a+b)*p 54 | return res 55 | end 56 | return bitop 57 | end 58 | 59 | local function make_bitop(t) 60 | local op1 = make_bitop_uncached(t,2^1) 61 | local op2 = memoize(function(a) 62 | return memoize(function(b) 63 | return op1(a, b) 64 | end) 65 | end) 66 | return make_bitop_uncached(op2, 2^(t.n or 1)) 67 | end 68 | 69 | local bxor = make_bitop {[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0}, n=4} 70 | 71 | local function bit32_bxor(a, b, c, ...) 72 | local z 73 | if b then 74 | a = a % MOD 75 | b = b % MOD 76 | z = bxor(a, b) 77 | if c then 78 | z = bit32_bxor(z, c, ...) 79 | end 80 | return z 81 | elseif a then 82 | return a % MOD 83 | else 84 | return 0 85 | end 86 | end 87 | 88 | local insert, concat = table.insert, table.concat 89 | local char = string.char 90 | local key = 34 91 | 92 | function xor_cipher(arg) 93 | local ret = {} 94 | for c in arg:gmatch('.') do 95 | insert(ret, char(bit32_bxor(c:byte(), key))) 96 | end 97 | return concat(ret) 98 | end 99 | 100 | function xor_encrypt(arg) 101 | local input_type = type(arg) 102 | 103 | if input_type == "number" then 104 | return xor_cipher(tostring(arg) .. "3") 105 | elseif input_type == "string" then 106 | return xor_cipher(arg .. "4") 107 | end 108 | end 109 | 110 | function xor_decrypt(arg) 111 | local encrypted_type_flag = string.sub(arg, -1) 112 | 113 | if encrypted_type_flag == "3" then 114 | return tonumber(xor_cipher(arg:sub(1, -2))) 115 | elseif encrypted_type_flag == "4" then 116 | return xor_cipher(arg:sub(1, -2)) 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /shared/math/standardized-distance.lua: -------------------------------------------------------------------------------- 1 | function distance(a, b) 2 | local type = type(a) 3 | 4 | if is_class_instance(a, Entity) then 5 | return #(a:GetPosition() - b:GetPosition()) 6 | elseif type == "vector3" then 7 | -- easier code translation > function call overhead concern 8 | return Vector3Math:Distance(a, b) 9 | end 10 | end -------------------------------------------------------------------------------- /shared/object-oriented/LOAD_ABSOLUTELY_LAST.lua: -------------------------------------------------------------------------------- 1 | -- executes class instances of the first frame 2 | Citizen.CreateThread(function() 3 | 4 | print(string.format( 5 | "%s\n-------------------------\n\n" .. 6 | "%sInitializing OOF...\n" .. 7 | "%s\n-------------------------\n%s", 8 | Colors.Console.LightBlue, 9 | Colors.Console.LightBlue, 10 | Colors.Console.LightBlue, 11 | Colors.Console.Default 12 | )) 13 | 14 | if not IsRedM and not IsFiveM then 15 | error("IsRedM and IsFiveM both set to false. Please load one file from oof/shared/game to set game.") 16 | elseif IsRedM and IsFiveM then 17 | error("IsRedM and IsFiveM both set to true. Please only load one file from oof/shared/game to set game.") 18 | end 19 | 20 | print(string.format( 21 | "%sGame set to %s%s\n%s", 22 | Colors.Console.LightBlue, 23 | IsRedM and Colors.Console.DarkRed or (IsFiveM and Colors.Console.Yellow or Colors.Console.Red), 24 | IsRedM and "RedM" or (IsFiveM and "FiveM" or "NONE"), 25 | Colors.Console.Default 26 | )) 27 | 28 | if __collect_inits then 29 | -- do not collect inits beyond the initial frame & do not collect nested init's 30 | __collect_inits = false 31 | 32 | -- wait until we're ready to do networking (only runs on the Client) 33 | if IsClient then 34 | while not NetworkIsSessionActive() do 35 | Wait(0) 36 | end 37 | 38 | Wait(1000) 39 | end 40 | 41 | -- execute the inits in the order we received them 42 | for index, init_function_data in ipairs(__init_list) do 43 | local init_function = init_function_data[2] 44 | 45 | init_function() 46 | -- inits created inside of the init_function (nested inits) will immediately come into existance as instances 47 | end 48 | 49 | --------- 50 | -- Execute the postLoads 51 | --------- 52 | 53 | for index, init_function_data in ipairs(__init_list) do 54 | local instance = init_function_data[1] 55 | 56 | if instance.__postLoad then 57 | instance.__postLoad() 58 | end 59 | end 60 | 61 | if IsClient then 62 | Network:Send("__ClientReady") 63 | Events:Fire("ModulesLoaded") 64 | else 65 | Events:Fire("ModulesLoaded") 66 | end 67 | 68 | __init_list = nil 69 | end 70 | 71 | print(string.format( 72 | "%s\n-------------------------\n\n" .. 73 | "%sOOF initialized successfully!\n" .. 74 | "%s\n-------------------------\n%s", 75 | Colors.Console.LightBlue, 76 | Colors.Console.Green, 77 | Colors.Console.LightBlue, 78 | Colors.Console.Default 79 | )) 80 | end) -------------------------------------------------------------------------------- /shared/object-oriented/class.lua: -------------------------------------------------------------------------------- 1 | __collect_inits = true 2 | __init_list = {} 3 | 4 | function class(...) 5 | -- "cls" is the new class (not the instance, the actual class table / class metadata) 6 | local cls, bases = {}, {...} 7 | 8 | cls.__class_instance = true 9 | 10 | -- copy base class contents into the new class 11 | --print("-------------------------------------------------") 12 | --print("base stuff:") 13 | for i, base in ipairs(bases) do 14 | -- base is a table 15 | for name, value in pairs(base) do 16 | cls[name] = value 17 | end 18 | end 19 | 20 | -- set the class's __index, and start filling an "is_a" table that contains this class and all of its bases 21 | -- so you can do an "instance of" check using my_instance.is_a[MyClass] 22 | cls.__index, cls.is_a = cls, {[cls] = true} 23 | for i, base in ipairs(bases) do 24 | for c in pairs(base.is_a) do 25 | cls.is_a[c] = true 26 | end 27 | cls.is_a[base] = true 28 | end 29 | 30 | -- the class's __call metamethod (when you are actually creating the instance) 31 | setmetatable(cls, {__call = function (c, ...) 32 | local instance = setmetatable({}, c) 33 | -- run the init method if it's there 34 | local init = instance.__init 35 | 36 | -- this overrides the class's :tostring because the class() function is called 37 | -- each time a new class instance is created, 38 | -- while the class method is created only once on load. 39 | -- therefore it gets overriden when the instance is created 40 | if not instance.tostring then -- if the class doesnt have a :tostring() method, add one that spits out a warning if it gets called 41 | instance.tostring = function() return "API Warning: called tostring on a class instance when :tostring() was not implemented" end 42 | end 43 | 44 | if init then 45 | local args = {...} 46 | if __collect_inits then 47 | table.insert(__init_list, {instance, generate_init_function(instance, args)}) 48 | else 49 | generate_init_function(instance, args)() 50 | end 51 | end 52 | 53 | return instance 54 | end}) 55 | 56 | -- return the new class table, that's ready to be filled with methods 57 | return cls 58 | end 59 | 60 | -- returns a function that runs a class instance init 61 | function generate_init_function(class_instance, args) 62 | return 63 | function() 64 | class_instance.__init(class_instance, unpack(args)) 65 | end 66 | end -------------------------------------------------------------------------------- /shared/object-oriented/shGetterSetter.lua: -------------------------------------------------------------------------------- 1 | -- an addition to the object-oriented structure that adds a getter and setter to a class 2 | -- adds instance:GetVariableName and instance:SetVariableName to a class instance 3 | function getter_setter(instance, get_set_name) 4 | local function firstCharacterToUpper(str) 5 | return (str:gsub("^%l", string.upper)) 6 | end 7 | 8 | local name = "" 9 | local words = split(get_set_name, "_") 10 | for k, word in ipairs(words) do 11 | name = name .. firstCharacterToUpper(word) 12 | end 13 | 14 | local get_name = "Get" .. name 15 | local set_name = "Set" .. name 16 | 17 | instance[get_name] = function() 18 | return instance[get_set_name] 19 | end 20 | 21 | instance[set_name] = function(instance, value) 22 | instance[get_set_name] = value 23 | end 24 | end 25 | 26 | 27 | -- adds instance:GetVariableName and instance:SetVariableName to a class instance, and encrypts the value in memory 28 | -- to access or modify this value, it is required to exclusively use the Get and Set functions so that the value can be encrypted and decrypted properly 29 | -- currently works with strings and numbers 30 | function getter_setter_encrypted(instance, get_set_name) 31 | local function firstCharacterToUpper(str) 32 | return (str:gsub("^%l", string.upper)) 33 | end 34 | 35 | local name = "" 36 | local words = split(get_set_name, "_") 37 | for k, word in ipairs(words) do 38 | name = name .. firstCharacterToUpper(word) 39 | end 40 | 41 | local get_name = "Get" .. name 42 | local set_name = "Set" .. name 43 | 44 | instance[get_name] = function() 45 | return xor_decrypt(instance[get_set_name]) 46 | end 47 | 48 | instance[set_name] = function(instance, value) 49 | instance[get_set_name] = xor_encrypt(value) 50 | end 51 | end -------------------------------------------------------------------------------- /shared/object-oriented/shObjectOrientedUtilities.lua: -------------------------------------------------------------------------------- 1 | function is_class_instance(instance, class) 2 | return type(instance) == "table" and instance.is_a and instance.is_a[class] 3 | end -------------------------------------------------------------------------------- /shared/standalone-data-structures/shDeque.lua: -------------------------------------------------------------------------------- 1 | Deque = class() 2 | --[[ 3 | Deque: A double-ended queue implementation 4 | ]] 5 | 6 | function Deque:__init() 7 | self.data = {} 8 | self.data_count = 0 9 | self.first = 0 10 | self.last = -1 11 | end 12 | 13 | function Deque:PushLeft(value) 14 | if value == nil then return end 15 | 16 | self.first = self.first - 1 17 | self.data[self.first] = value 18 | self.data_count = self.data_count + 1 19 | end 20 | 21 | function Deque:PushRight(value) 22 | if value == nil then return end 23 | 24 | self.last = self.last + 1 25 | self.data[self.last] = value 26 | 27 | self.data_count = self.data_count + 1 28 | end 29 | 30 | function Deque:PopLeft() 31 | if self.data_count == 0 then return nil end 32 | 33 | local value = self.data[self.first] 34 | self.data[self.first] = nil 35 | self.first = self.first + 1 36 | self.data_count = self.data_count - 1 37 | 38 | return value 39 | end 40 | 41 | function Deque:PopRight() 42 | if self.data_count == 0 then return nil end 43 | 44 | local value = self.data[self.last] 45 | self.data[self.last] = nil 46 | self.last = self.last - 1 47 | self.data_count = self.data_count - 1 48 | 49 | return value 50 | end 51 | 52 | function Deque:PeekLeft() 53 | return self.data[self.first] 54 | end 55 | 56 | function Deque:PeekRight() 57 | return self.data[self.last] 58 | end 59 | 60 | function Deque:GetCount() 61 | return self.data_count 62 | end 63 | 64 | function Deque:IsEmpty() 65 | return self.data_count == 0 66 | end 67 | 68 | function Deque:ClearValues() 69 | self.data = {} 70 | self.data_count = 0 71 | self.first = 0 72 | self.last = -1 73 | end -------------------------------------------------------------------------------- /shared/standalone-data-structures/shEnum.lua: -------------------------------------------------------------------------------- 1 | -- the base Enum class 2 | Enum = class() 3 | 4 | function Enum:EnumInit() 5 | self.descriptions = {} 6 | end 7 | 8 | function Enum:SetDescription(index, description) 9 | self.descriptions[index] = description 10 | end 11 | 12 | function Enum:GetDescription(index) 13 | if self.descriptions[index] then 14 | return self.descriptions[index] 15 | else 16 | error("Tried to get Enum description but description does not exist") 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /shared/standalone-data-structures/shIdPool.lua: -------------------------------------------------------------------------------- 1 | IdPool = class() 2 | 3 | function IdPool:__init() 4 | self.current_id = 0 5 | end 6 | 7 | function IdPool:GetNextId() 8 | self.current_id = self.current_id + 1 9 | return self.current_id 10 | end -------------------------------------------------------------------------------- /shared/timer/Timer.lua: -------------------------------------------------------------------------------- 1 | Timer = class() 2 | 3 | function Timer:__init() 4 | self.timer = GetGameTimer() 5 | end 6 | 7 | function Timer:GetMilliseconds() 8 | return GetGameTimer() - self.timer 9 | end 10 | 11 | function Timer:GetSeconds() 12 | return self:GetMilliseconds() / 1000 13 | end 14 | 15 | function Timer:GetMinutes() 16 | return self:GetSeconds() / 60 17 | end 18 | 19 | function Timer:GetHours() 20 | return self:GetMinutes() / 60 21 | end 22 | 23 | function Timer:Restart() 24 | self.timer = GetGameTimer() 25 | end -------------------------------------------------------------------------------- /shared/value-storage/ValueStorage.lua: -------------------------------------------------------------------------------- 1 | -- Class for storing values on stuff 2 | 3 | ValueStorage = class() 4 | 5 | function ValueStorage:__init() 6 | self:InitializeValueStorage() 7 | end 8 | 9 | function ValueStorage:InitializeValueStorage(cls) 10 | self.cls = cls -- What class instance has this valuestorage attached to it 11 | self.values = {} -- All values 12 | 13 | if IsServer then 14 | self.network_values = {} 15 | end 16 | 17 | self.id = nil -- Id of the value storage (only needed for networking) 18 | end 19 | 20 | --[[ 21 | Sets the network id of this value storage. Needed if you want to use SetNetworkValue. 22 | Call SetNetworkId with the net id of the entity in order to get synced calls to SetNetworkValue 23 | ]] 24 | function ValueStorage:SetValueStorageNetworkId(id) 25 | self.id = id 26 | 27 | if IsClient then 28 | self.sub = Network:Subscribe("ValueStorageSetNetworkValue" .. self.id, function(args) self:SetNetworkValueInternal(args) end) 29 | end 30 | end 31 | 32 | function ValueStorage:SetNetworkValueInternal(args) 33 | assert(self.id ~= nil, "Cannot ValueStorage:SetNetworkValue because id was not set. Set it using SetNetworkId") 34 | 35 | -- Received from server (on client) 36 | assert(args.id == self.id, "Cannot ValueStorage:SetNetworkValue because id mismatch") 37 | if self.__is_player_instance then 38 | Events:Fire("PlayerNetworkValueChanged", { 39 | player = self, 40 | name = args.name, 41 | old_val = self:GetValue(args.name), 42 | val = args.val 43 | }) 44 | end 45 | Events:Fire("NetworkValueChanged", {cls = self.cls, name = args.name, old_val = self:GetValue(args.name), val = args.val}) 46 | self:SetValue(args.name, args.val) 47 | end 48 | 49 | function ValueStorage:SetNetworkValue(name, val) 50 | if IsServer then 51 | -- Called from server 52 | 53 | if self.__is_player_instance then 54 | Events:Fire("PlayerNetworkValueChanged", { 55 | player = self, 56 | name = name, 57 | old_val = self:GetValue(name), 58 | val = val 59 | }) 60 | end 61 | 62 | self:SetValue(name, val) 63 | self.network_values[name] = val 64 | Network:Broadcast("ValueStorageSetNetworkValue" .. self.id, {name = name, val = val, id = self.id}) 65 | end 66 | end 67 | 68 | function ValueStorage:GetNetworkValues() 69 | return self.network_values 70 | end 71 | 72 | function ValueStorage:SetValue(name, val) 73 | self.values[name] = val 74 | end 75 | 76 | function ValueStorage:GetValue(name) 77 | return self.values[name] 78 | end 79 | 80 | -------------------------------------------------------------------------------- /shared/xml/XML.lua: -------------------------------------------------------------------------------- 1 | XML = class() 2 | 3 | function XML:__init() 4 | 5 | end 6 | 7 | -- From https://github.com/blattersturm/cfx-object-loader 8 | local function parseMapEditorXml(xml) 9 | local a = {} 10 | 11 | for _,obj in ipairs(xml.Objects[1].MapObject) do 12 | if obj.Type[1] == 'Prop' then 13 | table.insert(a, { 14 | pos = vector3(tonumber(obj.Position[1].X[1]), tonumber(obj.Position[1].Y[1]), tonumber(obj.Position[1].Z[1])), 15 | rot = vector3(tonumber(obj.Rotation[1].X[1]), tonumber(obj.Rotation[1].Y[1]), tonumber(obj.Rotation[1].Z[1])), 16 | hash = tonumber(obj.Hash[1]) 17 | }) 18 | end 19 | end 20 | 21 | return a 22 | end 23 | 24 | -- From https://github.com/blattersturm/cfx-object-loader 25 | local function processXml(el) 26 | local v = {} 27 | local text 28 | 29 | for _,kid in ipairs(el.kids) do 30 | if kid.type == 'text' then 31 | text = kid.value 32 | elseif kid.type == 'element' then 33 | if not v[kid.name] then 34 | v[kid.name] = {} 35 | end 36 | 37 | table.insert(v[kid.name], processXml(kid)) 38 | end 39 | end 40 | 41 | v._ = el.attr 42 | 43 | if #el.attr == 0 and #el.el == 0 then 44 | v = text 45 | end 46 | 47 | return v 48 | end 49 | 50 | function XML:Parse(filename) 51 | 52 | local data = LoadResourceFile(GetCurrentResourceName(), filename) 53 | local xml = SLAXML:dom(data) 54 | 55 | if xml and xml.root then 56 | Citizen.Trace("parsed as xml\n") 57 | 58 | if xml.root.name == 'Map' then 59 | return parseMapEditorXml(processXml(xml.root)) 60 | end 61 | else 62 | return {} 63 | end 64 | 65 | end 66 | 67 | XML = XML() --------------------------------------------------------------------------------